2 Copyright (c) 2010, Nicolas Garcia Belmonte
5 > Redistribution and use in source and binary forms, with or without
6 > modification, are permitted provided that the following conditions are met:
7 > * Redistributions of source code must retain the above copyright
8 > notice, this list of conditions and the following disclaimer.
9 > * Redistributions in binary form must reproduce the above copyright
10 > notice, this list of conditions and the following disclaimer in the
11 > documentation and/or other materials provided with the distribution.
12 > * Neither the name of the organization nor the
13 > names of its contributors may be used to endorse or promote products
14 > derived from this software without specific prior written permission.
16 > THIS SOFTWARE IS PROVIDED BY NICOLAS GARCIA BELMONTE ``AS IS'' AND ANY
17 > EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 > WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 > DISCLAIMED. IN NO EVENT SHALL NICOLAS GARCIA BELMONTE BE LIABLE FOR ANY
20 > DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 > (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 > LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 > ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 > (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 > SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 /** Lam Huynh on 10/10/2010 added funnel,guage charts **/
29 /** Lam Huynh on 02/27/2011 added image exporting **/
30 /** Lam Huynh on 02/23/2011 added line charts **/
42 Defines the namespace for all library Classes and Objects.
43 This variable is the *only* global variable defined in the Toolkit.
44 There are also other interesting properties attached to this variable described below.
46 window.$jit = function(w) {
55 $jit.version = '2.0.0b';
59 Works just like *document.getElementById*
63 var element = $jit.id('elementId');
71 Contains utility functions.
73 Some of the utility functions and the Class system were based in the MooTools Framework
74 <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>.
75 MIT license <http://mootools.net/license.txt>.
77 These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
78 I'd suggest you to use the functions from those libraries instead of using these, since their functions
79 are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
83 return document.getElementById(d);
86 $.empty = function() {
89 function pad(number, length) {
91 var str = '' + number;
92 while (str.length < length) {
102 // public method for url encoding
103 encode : function (string) {
104 return escape(this._utf8_encode(string));
107 // public method for url decoding
108 decode : function (string) {
109 return this._utf8_decode(unescape(string));
112 // private method for UTF-8 encoding
113 _utf8_encode : function (string) {
114 string = string.replace(/\r\n/g,"\n");
117 for (var n = 0; n < string.length; n++) {
119 var c = string.charCodeAt(n);
122 utftext += String.fromCharCode(c);
124 else if((c > 127) && (c < 2048)) {
125 utftext += String.fromCharCode((c >> 6) | 192);
126 utftext += String.fromCharCode((c & 63) | 128);
129 utftext += String.fromCharCode((c >> 12) | 224);
130 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
131 utftext += String.fromCharCode((c & 63) | 128);
139 // private method for UTF-8 decoding
140 _utf8_decode : function (utftext) {
145 while ( i < utftext.length ) {
147 c = utftext.charCodeAt(i);
150 string += String.fromCharCode(c);
153 else if((c > 191) && (c < 224)) {
154 c2 = utftext.charCodeAt(i+1);
155 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
159 c2 = utftext.charCodeAt(i+1);
160 c3 = utftext.charCodeAt(i+2);
161 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
172 Array.prototype.sum = function() {
173 return (! this.length) ? 0 : this.slice(1).sum() +
174 ((typeof this[0] == 'number') ? this[0] : 0);
177 function array_match(needle, haystack) {
178 var length = haystack.length;
179 var indexValue = new Array();
180 for(var i = 0, count = 0; i < length; i++) {
181 if(haystack[i] == needle) {
182 indexValue[count] = i;
186 return new Array(count,indexValue);
189 $.roundedRect = function (ctx,x,y,width,height,radius,fillType){
191 ctx.moveTo(x,y+radius);
192 ctx.lineTo(x,y+height-radius);
193 ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
194 ctx.lineTo(x+width-radius,y+height);
195 ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
196 ctx.lineTo(x+width,y+radius);
197 ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
198 ctx.lineTo(x+radius,y);
199 ctx.quadraticCurveTo(x,y,x,y+radius);
200 if(fillType=="fill") {
207 $.saveImageFile = function (id,jsonfilename,imageExt) {
208 var parts = jsonfilename.split("/");
209 var filename = parts[2].replace(".js","."+imageExt);
210 var oCanvas = document.getElementById(id+"-canvas");
213 if(imageExt == "jpg") {
214 var strDataURI = oCanvas.toDataURL("image/jpeg");
216 var strDataURI = oCanvas.toDataURL("image/png");
218 var handleFailure = function(o){
219 //alert('failed to write image' + filename);
220 //remove alert since chrome triggers this function when user navigates away from page before image gets written.
222 var handleSuccess = function(o){
226 success:handleSuccess,
227 failure:handleFailure,
228 argument: { foo:'foo', bar:''}
230 var path = "index.php?action=DynamicAction&DynamicAction=saveImage&module=Charts&to_pdf=1";
231 var postData = "imageStr=" + strDataURI + "&filename=" + filename;
232 var request = YAHOO.util.Connect.asyncRequest('POST', path, callback, postData);
236 $.saveImageTest = function (id,jsonfilename,imageExt) {
237 if(typeof FlashCanvas != "undefined") {
238 setTimeout(function(){$.saveImageFile(id,jsonfilename,imageExt)},10000);
240 $.saveImageFile(id,jsonfilename,imageExt);
246 Augment an object by appending another object's properties.
250 original - (object) The object to be extended.
251 extended - (object) An object which properties are going to be appended to the original object.
255 $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
258 $.extend = function(original, extended) {
259 for ( var key in (extended || {}))
260 original[key] = extended[key];
264 $.lambda = function(value) {
265 return (typeof value == 'function') ? value : function() {
270 $.time = Date.now || function() {
277 Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
281 obj - (mixed) The object to be wrapped in an array.
285 $jit.util.splat(3); //[3]
286 $jit.util.splat([3]); //[3]
289 $.splat = function(obj) {
290 var type = $.type(obj);
291 return type ? ((type != 'array') ? [ obj ] : obj) : [];
294 $.type = function(elem) {
295 var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
296 if(type != 'object') return type;
297 if(elem && elem.$$family) return elem.$$family;
298 return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
300 $.type.s = Object.prototype.toString;
305 Iterates through an iterable applying *f*.
309 iterable - (array) The original array.
310 fn - (function) The function to apply to the array elements.
314 $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
317 $.each = function(iterable, fn) {
318 var type = $.type(iterable);
319 if (type == 'object') {
320 for ( var key in iterable)
321 fn(iterable[key], key);
323 for ( var i = 0, l = iterable.length; i < l; i++)
328 $.indexOf = function(array, item) {
329 if(Array.indexOf) return array.indexOf(item);
330 for(var i=0,l=array.length; i<l; i++) {
331 if(array[i] === item) return i;
339 Maps or collects an array by applying *f*.
343 array - (array) The original array.
344 f - (function) The function to apply to the array elements.
348 $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
351 $.map = function(array, f) {
353 $.each(array, function(elem, i) {
354 ans.push(f(elem, i));
362 Iteratively applies the binary function *f* storing the result in an accumulator.
366 array - (array) The original array.
367 f - (function) The function to apply to the array elements.
368 opt - (optional|mixed) The starting value for the acumulator.
372 $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
375 $.reduce = function(array, f, opt) {
376 var l = array.length;
378 var acum = arguments.length == 3? opt : array[--l];
380 acum = f(acum, array[l]);
388 Merges n-objects and their sub-objects creating a new, fresh object.
392 An arbitrary number of objects.
396 $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
399 $.merge = function() {
401 for ( var i = 0, l = arguments.length; i < l; i++) {
402 var object = arguments[i];
403 if ($.type(object) != 'object')
405 for ( var key in object) {
406 var op = object[key], mp = mix[key];
407 mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
408 .merge(mp, op) : $.unlink(op);
414 $.unlink = function(object) {
416 switch ($.type(object)) {
419 for ( var p in object)
420 unlinked[p] = $.unlink(object[p]);
424 for ( var i = 0, l = object.length; i < l; i++)
425 unlinked[i] = $.unlink(object[i]);
434 if(arguments.length === 0) return [];
435 for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
436 for(var i=0, row=[]; i<l; i++) {
437 row.push(arguments[i][j]);
447 Converts an RGB array into a Hex string.
451 srcArray - (array) An array with R, G and B values
455 $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
458 $.rgbToHex = function(srcArray, array) {
459 if (srcArray.length < 3)
461 if (srcArray.length == 4 && srcArray[3] == 0 && !array)
462 return 'transparent';
464 for ( var i = 0; i < 3; i++) {
465 var bit = (srcArray[i] - 0).toString(16);
466 hex.push(bit.length == 1 ? '0' + bit : bit);
468 return array ? hex : '#' + hex.join('');
474 Converts an Hex color string into an RGB array.
478 hex - (string) A color hex string.
482 $jit.util.hexToRgb('#fff'); //[255, 255, 255]
485 $.hexToRgb = function(hex) {
486 if (hex.length != 7) {
487 hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
492 for ( var i = 0; i < 3; i++) {
494 if (value.length == 1)
496 rgb.push(parseInt(value, 16));
500 hex = parseInt(hex.slice(1), 16);
501 return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
505 $.destroy = function(elem) {
508 elem.parentNode.removeChild(elem);
509 if (elem.clearAttributes)
510 elem.clearAttributes();
513 $.clean = function(elem) {
514 for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
522 Cross-browser add event listener.
526 obj - (obj) The Element to attach the listener to.
527 type - (string) The listener type. For example 'click', or 'mousemove'.
528 fn - (function) The callback function to be used when the event is fired.
532 $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
535 $.addEvent = function(obj, type, fn) {
536 if (obj.addEventListener)
537 obj.addEventListener(type, fn, false);
539 obj.attachEvent('on' + type, fn);
542 $.addEvents = function(obj, typeObj) {
543 for(var type in typeObj) {
544 $.addEvent(obj, type, typeObj[type]);
548 $.hasClass = function(obj, klass) {
549 return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
552 $.addClass = function(obj, klass) {
553 if (!$.hasClass(obj, klass))
554 obj.className = (obj.className + " " + klass);
557 $.removeClass = function(obj, klass) {
558 obj.className = obj.className.replace(new RegExp(
559 '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
562 $.getPos = function(elem) {
563 var offset = getOffsets(elem);
564 var scroll = getScrolls(elem);
566 x: offset.x - scroll.x,
567 y: offset.y - scroll.y
570 function getOffsets(elem) {
575 while (elem && !isBody(elem)) {
576 position.x += elem.offsetLeft;
577 position.y += elem.offsetTop;
578 elem = elem.offsetParent;
583 function getScrolls(elem) {
588 while (elem && !isBody(elem)) {
589 position.x += elem.scrollLeft;
590 position.y += elem.scrollTop;
591 elem = elem.parentNode;
596 function isBody(element) {
597 return (/^(?:body|html)$/i).test(element.tagName);
602 get: function(e, win) {
604 return e || win.event;
606 getWheel: function(e) {
607 return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
609 isRightClick: function(e) {
610 return (e.which == 3 || e.button == 2);
612 getPos: function(e, win) {
613 // get mouse position
616 var doc = win.document;
617 doc = doc.documentElement || doc.body;
618 //TODO(nico): make touch event handling better
619 if(e.touches && e.touches.length) {
623 x: e.pageX || (e.clientX + doc.scrollLeft),
624 y: e.pageY || (e.clientY + doc.scrollTop)
629 if (e.stopPropagation) e.stopPropagation();
630 e.cancelBubble = true;
631 if (e.preventDefault) e.preventDefault();
632 else e.returnValue = false;
636 $jit.util = $jit.id = $;
638 var Class = function(properties) {
639 properties = properties || {};
640 var klass = function() {
641 for ( var key in this) {
642 if (typeof this[key] != 'function')
643 this[key] = $.unlink(this[key]);
645 this.constructor = klass;
646 if (Class.prototyping)
648 var instance = this.initialize ? this.initialize.apply(this, arguments)
651 this.$$family = 'class';
655 for ( var mutator in Class.Mutators) {
656 if (!properties[mutator])
658 properties = Class.Mutators[mutator](properties, properties[mutator]);
659 delete properties[mutator];
662 $.extend(klass, this);
663 klass.constructor = Class;
664 klass.prototype = properties;
670 Implements: function(self, klasses) {
671 $.each($.splat(klasses), function(klass) {
672 Class.prototyping = klass;
673 var instance = (typeof klass == 'function') ? new klass : klass;
674 for ( var prop in instance) {
675 if (!(prop in self)) {
676 self[prop] = instance[prop];
679 delete Class.prototyping;
688 inherit: function(object, properties) {
689 for ( var key in properties) {
690 var override = properties[key];
691 var previous = object[key];
692 var type = $.type(override);
693 if (previous && type == 'function') {
694 if (override != previous) {
695 Class.override(object, key, override);
697 } else if (type == 'object') {
698 object[key] = $.merge(previous, override);
700 object[key] = override;
706 override: function(object, name, method) {
707 var parent = Class.prototyping;
708 if (parent && object[name] != parent[name])
710 var override = function() {
711 var previous = this.parent;
712 this.parent = parent ? parent[name] : object[name];
713 var value = method.apply(this, arguments);
714 this.parent = previous;
717 object[name] = override;
722 Class.prototype.implement = function() {
723 var proto = this.prototype;
724 $.each(Array.prototype.slice.call(arguments || []), function(properties) {
725 Class.inherit(proto, properties);
735 Provides JSON utility functions.
737 Most of these functions are JSON-tree traversal and manipulation functions.
743 Clears all tree nodes having depth greater than maxLevel.
747 tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
748 maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
751 prune: function(tree, maxLevel) {
752 this.each(tree, function(elem, i) {
753 if (i == maxLevel && elem.children) {
754 delete elem.children;
762 Returns the parent node of the node having _id_ as id.
766 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
767 id - (string) The _id_ of the child node whose parent will be returned.
771 A tree JSON node if any, or false otherwise.
774 getParent: function(tree, id) {
777 var ch = tree.children;
778 if (ch && ch.length > 0) {
779 for ( var i = 0; i < ch.length; i++) {
783 var ans = this.getParent(ch[i], id);
794 Returns the subtree that matches the given id.
798 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
799 id - (string) A node *unique* identifier.
803 A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
806 getSubtree: function(tree, id) {
809 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
810 var t = this.getSubtree(ch[i], id);
819 Iterates on tree nodes with relative depth less or equal than a specified level.
823 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
824 initLevel - (number) An integer specifying the initial relative level. Usually zero.
825 toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
826 action - (function) A function that receives a node and an integer specifying the actual level of the node.
830 $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
831 alert(node.name + ' ' + depth);
835 eachLevel: function(tree, initLevel, toLevel, action) {
836 if (initLevel <= toLevel) {
837 action(tree, initLevel);
838 if(!tree.children) return;
839 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
840 this.eachLevel(ch[i], initLevel + 1, toLevel, action);
847 A JSON tree iterator.
851 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
852 action - (function) A function that receives a node.
856 $jit.json.each(tree, function(node) {
862 each: function(tree, action) {
863 this.eachLevel(tree, 0, Number.MAX_VALUE, action);
869 An object containing multiple type of transformations.
880 var Trans = $jit.Trans;
884 var makeTrans = function(transition, params){
885 params = $.splat(params);
886 return $.extend(transition, {
887 easeIn: function(pos){
888 return transition(pos, params);
890 easeOut: function(pos){
891 return 1 - transition(1 - pos, params);
893 easeInOut: function(pos){
894 return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
895 2 * (1 - pos), params)) / 2;
903 return Math.pow(p, x[0] || 6);
907 return Math.pow(2, 8 * (p - 1));
911 return 1 - Math.sin(Math.acos(p));
915 return 1 - Math.sin((1 - p) * Math.PI / 2);
918 Back: function(p, x){
920 return Math.pow(p, 2) * ((x + 1) * p - x);
925 for ( var a = 0, b = 1; 1; a += b, b /= 2) {
926 if (p >= (7 - 4 * a) / 11) {
927 value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
934 Elastic: function(p, x){
935 return Math.pow(2, 10 * --p)
936 * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
941 $.each(transitions, function(val, key){
942 Trans[key] = makeTrans(val);
946 'Quad', 'Cubic', 'Quart', 'Quint'
947 ], function(elem, i){
948 Trans[elem] = makeTrans(function(p){
958 A Class that can perform animations for generic objects.
960 If you are looking for animation transitions please take a look at the <Trans> object.
968 The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
972 var Animation = new Class( {
974 initialize: function(options){
975 this.setOptions(options);
978 setOptions: function(options){
982 transition: Trans.Quart.easeInOut,
987 this.opt = $.merge(opt, options || {});
992 var time = $.time(), opt = this.opt;
993 if (time < this.time + opt.duration) {
994 var delta = opt.transition((time - this.time) / opt.duration);
997 this.timer = clearInterval(this.timer);
1011 startTimer: function(){
1012 var that = this, fps = this.opt.fps;
1015 this.time = $.time() - this.time;
1016 this.timer = setInterval((function(){
1018 }), Math.round(1000 / fps));
1032 stopTimer: function(){
1035 this.time = $.time() - this.time;
1036 this.timer = clearInterval(this.timer);
1043 if (this.opt.link == 'cancel') {
1052 var Options = function() {
1053 var args = arguments;
1054 for(var i=0, l=args.length, ans={}; i<l; i++) {
1055 var opt = Options[args[i]];
1066 * File: Options.AreaChart.js
1071 Object: Options.AreaChart
1073 <AreaChart> options.
1074 Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1080 Options.AreaChart = {
1084 selectOnHover: true,
1085 showAggregates: true,
1087 filterOnClick: false,
1088 restoreOnRightClick: false
1097 var areaChart = new $jit.AreaChart({
1099 type: 'stacked:gradient',
1100 selectOnHover: true,
1101 filterOnClick: true,
1102 restoreOnRightClick: true
1109 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1110 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1111 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1112 selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1113 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1114 showLabels - (boolean) Default's *true*. Display the name of the slots.
1115 filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1116 restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1120 Options.AreaChart = {
1124 labelOffset: 3, // label offset
1125 type: 'stacked', // gradient
1135 selectOnHover: true,
1136 showAggregates: true,
1138 filterOnClick: false,
1139 restoreOnRightClick: false
1143 * File: Options.Margin.js
1148 Object: Options.Margin
1150 Canvas drawing margins.
1169 var viz = new $jit.Viz({
1180 top - (number) Default's *0*. Top margin.
1181 left - (number) Default's *0*. Left margin.
1182 right - (number) Default's *0*. Right margin.
1183 bottom - (number) Default's *0*. Bottom margin.
1197 * File: Options.Canvas.js
1202 Object: Options.Canvas
1204 These are Canvas general options, like where to append it in the DOM, its dimensions, background,
1205 and other more advanced options.
1224 var viz = new $jit.Viz({
1225 injectInto: 'someContainerId',
1233 injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
1234 width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1235 height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1236 useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1237 withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1238 background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1250 colorStop1: 'rgba(255,255,255,1)',
1251 colorStop2: 'rgba(255,255,255,0)'
1255 * File: Options.Tree.js
1260 Object: Options.Tree
1262 Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1268 orientation: "left",
1280 var st = new $jit.ST({
1281 orientation: 'left',
1290 subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1291 siblingOffset - (number) Default's 5. Separation offset between siblings.
1292 orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1293 align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1294 indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1295 multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1301 orientation: "left",
1311 * File: Options.Node.js
1316 Object: Options.Node
1318 Provides Node rendering options for Tree and Graph based visualizations.
1345 var viz = new $jit.Viz({
1357 overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1358 type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
1359 color - (string) Default's *#ccb*. Node color.
1360 alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1361 dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1362 height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1363 width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1364 autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1365 autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1366 lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1367 transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1368 align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
1369 angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1370 span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1371 CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
1391 //Raw canvas styles to be
1392 //applied to the context instance
1393 //before plotting a node
1399 * File: Options.Edge.js
1404 Object: Options.Edge
1406 Provides Edge rendering options for Tree and Graph based visualizations.
1425 var viz = new $jit.Viz({
1431 shadowColor: '#ccc',
1440 overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1441 type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
1442 color - (string) Default's '#ccb'. Edge color.
1443 lineWidth - (number) Default's *1*. Line/Edge width.
1444 alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1445 dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
1446 epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
1447 CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
1451 If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
1464 //Raw canvas styles to be
1465 //applied to the context instance
1466 //before plotting an edge
1472 * File: Options.Fx.js
1479 Provides animation options like duration of the animations, frames per second and animation transitions.
1487 transition: $jit.Trans.Quart.easeInOut,
1495 var viz = new $jit.Viz({
1498 transition: $jit.Trans.linear
1504 clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1505 duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1506 fps - (number) Default's *40*. Frames per second.
1507 transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1511 This object is used for specifying different animation transitions in all visualizations.
1513 There are many different type of animation transitions.
1517 Displays a linear transition
1525 Displays a Quadratic transition.
1529 >Trans.Quad.easeInOut
1535 Displays a Cubic transition.
1538 >Trans.Cubic.easeOut
1539 >Trans.Cubic.easeInOut
1545 Displays a Quartetic transition.
1548 >Trans.Quart.easeOut
1549 >Trans.Quart.easeInOut
1555 Displays a Quintic transition.
1558 >Trans.Quint.easeOut
1559 >Trans.Quint.easeInOut
1565 Displays an Exponential transition.
1569 >Trans.Expo.easeInOut
1575 Displays a Circular transition.
1579 >Trans.Circ.easeInOut
1585 Displays a Sineousidal transition.
1589 >Trans.Sine.easeInOut
1597 >Trans.Back.easeInOut
1605 >Trans.Bounce.easeIn
1606 >Trans.Bounce.easeOut
1607 >Trans.Bounce.easeInOut
1615 >Trans.Elastic.easeIn
1616 >Trans.Elastic.easeOut
1617 >Trans.Elastic.easeInOut
1623 Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
1632 transition: $jit.Trans.Quart.easeInOut,
1637 * File: Options.Label.js
1641 Object: Options.Label
1643 Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.
1650 type: 'HTML', //'SVG', 'Native'
1653 family: 'sans-serif',
1654 textAlign: 'center',
1655 textBaseline: 'alphabetic',
1663 var viz = new $jit.Viz({
1674 overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1675 type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1676 style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1677 size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1678 family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1679 color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1685 type: 'HTML', //'SVG', 'Native'
1688 family: 'sans-serif',
1689 textAlign: 'center',
1690 textBaseline: 'alphabetic',
1696 * File: Options.Tips.js
1701 Object: Options.Tips
1721 var viz = new $jit.Viz({
1727 onShow: function(tip, node) {
1728 tip.innerHTML = node.name;
1736 enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class.
1737 type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
1738 offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
1739 offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
1740 onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
1741 onHide() - This callack is used when hiding a tooltip.
1758 * File: Options.NodeStyles.js
1763 Object: Options.NodeStyles
1765 Apply different styles when a node is hovered or selected.
1770 Options.NodeStyles = {
1781 var viz = new $jit.Viz({
1796 enable - (boolean) Default's *false*. Whether to enable this option.
1797 type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
1798 stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799 stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1802 Options.NodeStyles = {
1813 * File: Options.Events.js
1818 Object: Options.Events
1820 Configuration for adding mouse/touch event handlers to Nodes.
1827 enableForEdges: false,
1830 onRightClick: $.empty,
1831 onMouseMove: $.empty,
1832 onMouseEnter: $.empty,
1833 onMouseLeave: $.empty,
1834 onDragStart: $.empty,
1835 onDragMove: $.empty,
1836 onDragCancel: $.empty,
1838 onTouchStart: $.empty,
1839 onTouchMove: $.empty,
1840 onTouchEnd: $.empty,
1841 onTouchCancel: $.empty,
1842 onMouseWheel: $.empty
1849 var viz = new $jit.Viz({
1852 onClick: function(node, eventInfo, e) {
1855 onMouseEnter: function(node, eventInfo, e) {
1856 viz.canvas.getElement().style.cursor = 'pointer';
1858 onMouseLeave: function(node, eventInfo, e) {
1859 viz.canvas.getElement().style.cursor = '';
1867 enable - (boolean) Default's *false*. Whether to enable the Event system.
1868 enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
1869 type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
1870 onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1871 onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1872 onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1873 onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1874 onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1875 onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1876 onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1877 onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1878 onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1879 onTouchStart(node, eventInfo, e) - Behaves just like onDragStart.
1880 onTouchMove(node, eventInfo, e) - Behaves just like onDragMove.
1881 onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd.
1882 onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1883 onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
1890 enableForEdges: false,
1893 onRightClick: $.empty,
1894 onMouseMove: $.empty,
1895 onMouseEnter: $.empty,
1896 onMouseLeave: $.empty,
1897 onDragStart: $.empty,
1898 onDragMove: $.empty,
1899 onDragCancel: $.empty,
1901 onTouchStart: $.empty,
1902 onTouchMove: $.empty,
1903 onTouchEnd: $.empty,
1904 onMouseWheel: $.empty
1908 * File: Options.Navigation.js
1913 Object: Options.Navigation
1915 Panning and zooming options for Graph/Tree based visualizations. These options are implemented
1916 by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1922 Options.Navigation = {
1925 panning: false, //true, 'avoid nodes'
1934 var viz = new $jit.Viz({
1937 panning: 'avoid nodes',
1945 enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1946 panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
1947 zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
1951 Options.Navigation = {
1956 panning: false, //true | 'avoid nodes'
1961 * File: Options.Controller.js
1966 Object: Options.Controller
1968 Provides controller methods. Controller methods are callback functions that get called at different stages
1969 of the animation, computing or plotting of the visualization.
1973 All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1979 Options.Controller = {
1980 onBeforeCompute: $.empty,
1981 onAfterCompute: $.empty,
1982 onCreateLabel: $.empty,
1983 onPlaceLabel: $.empty,
1984 onComplete: $.empty,
1985 onBeforePlotLine:$.empty,
1986 onAfterPlotLine: $.empty,
1987 onBeforePlotNode:$.empty,
1988 onAfterPlotNode: $.empty,
1997 var viz = new $jit.Viz({
1998 onBeforePlotNode: function(node) {
2000 node.setData('color', '#ffc');
2002 node.removeData('color');
2005 onBeforePlotLine: function(adj) {
2006 if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2007 adj.setData('color', '#ffc');
2009 adj.removeData('color');
2012 onAfterCompute: function() {
2020 onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2021 onAfterCompute() - This method is triggered after all animations or computations ended.
2022 onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
2023 onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
2024 onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
2025 onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2026 onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
2027 onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2029 *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2031 request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.
2034 Options.Controller = {
2037 onBeforeCompute: $.empty,
2038 onAfterCompute: $.empty,
2039 onCreateLabel: $.empty,
2040 onPlaceLabel: $.empty,
2041 onComplete: $.empty,
2042 onBeforePlotLine:$.empty,
2043 onAfterPlotLine: $.empty,
2044 onBeforePlotNode:$.empty,
2045 onAfterPlotNode: $.empty,
2053 * Provides Extras such as Tips and Style Effects.
2057 * Provides the <Tips> and <NodeStyles> classes and functions.
2062 * Manager for mouse events (clicking and mouse moving).
2064 * This class is used for registering objects implementing onClick
2065 * and onMousemove methods. These methods are called when clicking or
2066 * moving the mouse around the Canvas.
2067 * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2070 var ExtrasInitializer = {
2071 initialize: function(className, viz) {
2073 this.canvas = viz.canvas;
2074 this.config = viz.config[className];
2075 this.nodeTypes = viz.fx.nodeTypes;
2076 var type = this.config.type;
2077 this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2078 this.labelContainer = this.dom && viz.labels.getLabelContainer();
2079 this.isEnabled() && this.initializePost();
2081 initializePost: $.empty,
2082 setAsProperty: $.lambda(false),
2083 isEnabled: function() {
2084 return this.config.enable;
2086 isLabel: function(e, win) {
2087 e = $.event.get(e, win);
2088 var labelContainer = this.labelContainer,
2089 target = e.target || e.srcElement;
2090 if(target && target.parentNode == labelContainer)
2096 var EventsInterface = {
2098 onMouseDown: $.empty,
2099 onMouseMove: $.empty,
2100 onMouseOver: $.empty,
2101 onMouseOut: $.empty,
2102 onMouseWheel: $.empty,
2103 onTouchStart: $.empty,
2104 onTouchMove: $.empty,
2105 onTouchEnd: $.empty,
2106 onTouchCancel: $.empty
2109 var MouseEventsManager = new Class({
2110 initialize: function(viz) {
2112 this.canvas = viz.canvas;
2115 this.registeredObjects = [];
2116 this.attachEvents();
2119 attachEvents: function() {
2120 var htmlCanvas = this.canvas.getElement(),
2122 htmlCanvas.oncontextmenu = $.lambda(false);
2123 $.addEvents(htmlCanvas, {
2124 'mouseup': function(e, win) {
2125 var event = $.event.get(e, win);
2126 that.handleEvent('MouseUp', e, win,
2127 that.makeEventObject(e, win),
2128 $.event.isRightClick(event));
2130 'mousedown': function(e, win) {
2131 var event = $.event.get(e, win);
2132 that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win),
2133 $.event.isRightClick(event));
2135 'mousemove': function(e, win) {
2136 that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2138 'mouseover': function(e, win) {
2139 that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2141 'mouseout': function(e, win) {
2142 that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2144 'touchstart': function(e, win) {
2145 that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2147 'touchmove': function(e, win) {
2148 that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2150 'touchend': function(e, win) {
2151 that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2154 //attach mousewheel event
2155 var handleMouseWheel = function(e, win) {
2156 var event = $.event.get(e, win);
2157 var wheel = $.event.getWheel(event);
2158 that.handleEvent('MouseWheel', e, win, wheel);
2160 //TODO(nico): this is a horrible check for non-gecko browsers!
2161 if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2162 $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2164 htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2168 register: function(obj) {
2169 this.registeredObjects.push(obj);
2172 handleEvent: function() {
2173 var args = Array.prototype.slice.call(arguments),
2174 type = args.shift();
2175 for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2176 regs[i]['on' + type].apply(regs[i], args);
2180 makeEventObject: function(e, win) {
2182 graph = this.viz.graph,
2184 ntypes = fx.nodeTypes,
2185 etypes = fx.edgeTypes;
2191 getNodeCalled: false,
2192 getEdgeCalled: false,
2193 getPos: function() {
2194 //TODO(nico): check why this can't be cache anymore when using edge detection
2195 //if(this.pos) return this.pos;
2196 var canvas = that.viz.canvas,
2197 s = canvas.getSize(),
2198 p = canvas.getPos(),
2199 ox = canvas.translateOffsetX,
2200 oy = canvas.translateOffsetY,
2201 sx = canvas.scaleOffsetX,
2202 sy = canvas.scaleOffsetY,
2203 pos = $.event.getPos(e, win);
2205 x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2206 y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2210 getNode: function() {
2211 if(this.getNodeCalled) return this.node;
2212 this.getNodeCalled = true;
2213 for(var id in graph.nodes) {
2214 var n = graph.nodes[id],
2215 geom = n && ntypes[n.getData('type')],
2216 contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2218 this.contains = contains;
2219 return that.node = this.node = n;
2222 return that.node = this.node = false;
2224 getEdge: function() {
2225 if(this.getEdgeCalled) return this.edge;
2226 this.getEdgeCalled = true;
2228 for(var id in graph.edges) {
2229 var edgeFrom = graph.edges[id];
2231 for(var edgeId in edgeFrom) {
2232 if(edgeId in hashset) continue;
2233 var e = edgeFrom[edgeId],
2234 geom = e && etypes[e.getData('type')],
2235 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2237 this.contains = contains;
2238 return that.edge = this.edge = e;
2242 return that.edge = this.edge = false;
2244 getContains: function() {
2245 if(this.getNodeCalled) return this.contains;
2247 return this.contains;
2254 * Provides the initialization function for <NodeStyles> and <Tips> implemented
2255 * by all main visualizations.
2259 initializeExtras: function() {
2260 var mem = new MouseEventsManager(this), that = this;
2261 $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2262 var obj = new Extras.Classes[k](k, that);
2263 if(obj.isEnabled()) {
2266 if(obj.setAsProperty()) {
2267 that[k.toLowerCase()] = obj;
2273 Extras.Classes = {};
2277 This class defines an Event API to be accessed by the user.
2278 The methods implemented are the ones defined in the <Options.Events> object.
2281 Extras.Classes.Events = new Class({
2282 Implements: [ExtrasInitializer, EventsInterface],
2284 initializePost: function() {
2285 this.fx = this.viz.fx;
2286 this.ntypes = this.viz.fx.nodeTypes;
2287 this.etypes = this.viz.fx.edgeTypes;
2289 this.hovered = false;
2290 this.pressed = false;
2291 this.touched = false;
2293 this.touchMoved = false;
2298 setAsProperty: $.lambda(true),
2300 onMouseUp: function(e, win, event, isRightClick) {
2301 var evt = $.event.get(e, win);
2304 this.config.onRightClick(this.hovered, event, evt);
2306 this.config.onClick(this.pressed, event, evt);
2311 this.config.onDragEnd(this.pressed, event, evt);
2313 this.config.onDragCancel(this.pressed, event, evt);
2315 this.pressed = this.moved = false;
2319 onMouseOut: function(e, win, event) {
2321 var evt = $.event.get(e, win), label;
2322 if(this.dom && (label = this.isLabel(e, win))) {
2323 this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2325 this.hovered = false;
2329 var rt = evt.relatedTarget,
2330 canvasWidget = this.canvas.getElement();
2331 while(rt && rt.parentNode) {
2332 if(canvasWidget == rt.parentNode) return;
2336 this.config.onMouseLeave(this.hovered,
2338 this.hovered = false;
2342 onMouseOver: function(e, win, event) {
2344 var evt = $.event.get(e, win), label;
2345 if(this.dom && (label = this.isLabel(e, win))) {
2346 this.hovered = this.viz.graph.getNode(label.id);
2347 this.config.onMouseEnter(this.hovered,
2352 onMouseMove: function(e, win, event) {
2353 var label, evt = $.event.get(e, win);
2356 this.config.onDragMove(this.pressed, event, evt);
2360 this.config.onMouseMove(this.hovered,
2364 var hn = this.hovered;
2365 var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2366 var contains = geom && geom.contains
2367 && geom.contains.call(this.fx, hn, event.getPos());
2369 this.config.onMouseMove(hn, event, evt);
2372 this.config.onMouseLeave(hn, event, evt);
2373 this.hovered = false;
2376 if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2377 this.config.onMouseEnter(this.hovered, event, evt);
2379 this.config.onMouseMove(false, event, evt);
2384 onMouseWheel: function(e, win, delta) {
2385 this.config.onMouseWheel(delta, $.event.get(e, win));
2388 onMouseDown: function(e, win, event) {
2389 var evt = $.event.get(e, win);
2390 this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2391 this.config.onDragStart(this.pressed, event, evt);
2394 onTouchStart: function(e, win, event) {
2395 var evt = $.event.get(e, win);
2396 this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2397 this.config.onTouchStart(this.touched, event, evt);
2400 onTouchMove: function(e, win, event) {
2401 var evt = $.event.get(e, win);
2403 this.touchMoved = true;
2404 this.config.onTouchMove(this.touched, event, evt);
2408 onTouchEnd: function(e, win, event) {
2409 var evt = $.event.get(e, win);
2411 if(this.touchMoved) {
2412 this.config.onTouchEnd(this.touched, event, evt);
2414 this.config.onTouchCancel(this.touched, event, evt);
2416 this.touched = this.touchMoved = false;
2424 A class containing tip related functions. This class is used internally.
2428 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2435 Extras.Classes.Tips = new Class({
2436 Implements: [ExtrasInitializer, EventsInterface],
2438 initializePost: function() {
2441 var tip = $('_tooltip') || document.createElement('div');
2442 tip.id = '_tooltip';
2443 tip.className = 'tip';
2444 $.extend(tip.style, {
2445 position: 'absolute',
2449 document.body.appendChild(tip);
2455 setAsProperty: $.lambda(true),
2457 onMouseOut: function(e, win) {
2459 if(this.dom && this.isLabel(e, win)) {
2464 var rt = e.relatedTarget,
2465 canvasWidget = this.canvas.getElement();
2466 while(rt && rt.parentNode) {
2467 if(canvasWidget == rt.parentNode) return;
2473 onMouseOver: function(e, win) {
2476 if(this.dom && (label = this.isLabel(e, win))) {
2477 this.node = this.viz.graph.getNode(label.id);
2478 this.config.onShow(this.tip, this.node, label);
2482 onMouseMove: function(e, win, opt) {
2483 if(this.dom && this.isLabel(e, win)) {
2484 this.setTooltipPosition($.event.getPos(e, win));
2487 var node = opt.getNode();
2492 if(this.config.force || !this.node || this.node.id != node.id) {
2494 this.config.onShow(this.tip, node, opt.getContains());
2496 this.setTooltipPosition($.event.getPos(e, win));
2500 setTooltipPosition: function(pos) {
2505 //get window dimensions
2507 'height': document.body.clientHeight,
2508 'width': document.body.clientWidth
2510 //get tooltip dimensions
2512 'width': tip.offsetWidth,
2513 'height': tip.offsetHeight
2515 //set tooltip position
2516 var x = cont.offsetX, y = cont.offsetY;
2517 style.top = ((pos.y + y + obj.height > win.height)?
2518 (pos.y - obj.height - y) : pos.y + y) + 'px';
2519 style.left = ((pos.x + obj.width + x > win.width)?
2520 (pos.x - obj.width - x) : pos.x + x) + 'px';
2523 hide: function(triggerCallback) {
2524 if(!SUGAR.util.isTouchScreen()) {
2525 this.tip.style.display = 'none';
2527 triggerCallback && this.config.onHide();
2534 Change node styles when clicking or hovering a node. This class is used internally.
2538 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2542 <Options.NodeStyles>
2544 Extras.Classes.NodeStyles = new Class({
2545 Implements: [ExtrasInitializer, EventsInterface],
2547 initializePost: function() {
2548 this.fx = this.viz.fx;
2549 this.types = this.viz.fx.nodeTypes;
2550 this.nStyles = this.config;
2551 this.nodeStylesOnHover = this.nStyles.stylesHover;
2552 this.nodeStylesOnClick = this.nStyles.stylesClick;
2553 this.hoveredNode = false;
2554 this.fx.nodeFxAnimation = new Animation();
2560 onMouseOut: function(e, win) {
2561 this.down = this.move = false;
2562 if(!this.hoveredNode) return;
2564 if(this.dom && this.isLabel(e, win)) {
2565 this.toggleStylesOnHover(this.hoveredNode, false);
2568 var rt = e.relatedTarget,
2569 canvasWidget = this.canvas.getElement();
2570 while(rt && rt.parentNode) {
2571 if(canvasWidget == rt.parentNode) return;
2574 this.toggleStylesOnHover(this.hoveredNode, false);
2575 this.hoveredNode = false;
2578 onMouseOver: function(e, win) {
2581 if(this.dom && (label = this.isLabel(e, win))) {
2582 var node = this.viz.graph.getNode(label.id);
2583 if(node.selected) return;
2584 this.hoveredNode = node;
2585 this.toggleStylesOnHover(this.hoveredNode, true);
2589 onMouseDown: function(e, win, event, isRightClick) {
2590 if(isRightClick) return;
2592 if(this.dom && (label = this.isLabel(e, win))) {
2593 this.down = this.viz.graph.getNode(label.id);
2594 } else if(!this.dom) {
2595 this.down = event.getNode();
2600 onMouseUp: function(e, win, event, isRightClick) {
2601 if(isRightClick) return;
2603 this.onClick(event.getNode());
2605 this.down = this.move = false;
2608 getRestoredStyles: function(node, type) {
2609 var restoredStyles = {},
2610 nStyles = this['nodeStylesOn' + type];
2611 for(var prop in nStyles) {
2612 restoredStyles[prop] = node.styles['$' + prop];
2614 return restoredStyles;
2617 toggleStylesOnHover: function(node, set) {
2618 if(this.nodeStylesOnHover) {
2619 this.toggleStylesOn('Hover', node, set);
2623 toggleStylesOnClick: function(node, set) {
2624 if(this.nodeStylesOnClick) {
2625 this.toggleStylesOn('Click', node, set);
2629 toggleStylesOn: function(type, node, set) {
2631 var nStyles = this.nStyles;
2635 node.styles = $.merge(node.data, {});
2637 for(var s in this['nodeStylesOn' + type]) {
2639 if(!($s in node.styles)) {
2640 node.styles[$s] = node.getData(s);
2643 viz.fx.nodeFx($.extend({
2646 'properties': that['nodeStylesOn' + type]
2648 transition: Trans.Quart.easeOut,
2653 var restoredStyles = this.getRestoredStyles(node, type);
2654 viz.fx.nodeFx($.extend({
2657 'properties': restoredStyles
2659 transition: Trans.Quart.easeOut,
2666 onClick: function(node) {
2668 var nStyles = this.nodeStylesOnClick;
2669 if(!nStyles) return;
2670 //if the node is selected then unselect it
2672 this.toggleStylesOnClick(node, false);
2673 delete node.selected;
2675 //unselect all selected nodes...
2676 this.viz.graph.eachNode(function(n) {
2678 for(var s in nStyles) {
2679 n.setData(s, n.styles['$' + s], 'end');
2684 //select clicked node
2685 this.toggleStylesOnClick(node, true);
2686 node.selected = true;
2687 delete node.hovered;
2688 this.hoveredNode = false;
2692 onMouseMove: function(e, win, event) {
2693 //if mouse button is down and moving set move=true
2694 if(this.down) this.move = true;
2695 //already handled by mouseover/out
2696 if(this.dom && this.isLabel(e, win)) return;
2697 var nStyles = this.nodeStylesOnHover;
2698 if(!nStyles) return;
2701 if(this.hoveredNode) {
2702 var geom = this.types[this.hoveredNode.getData('type')];
2703 var contains = geom && geom.contains && geom.contains.call(this.fx,
2704 this.hoveredNode, event.getPos());
2705 if(contains) return;
2707 var node = event.getNode();
2708 //if no node is being hovered then just exit
2709 if(!this.hoveredNode && !node) return;
2710 //if the node is hovered then exit
2711 if(node.hovered) return;
2712 //select hovered node
2713 if(node && !node.selected) {
2714 //check if an animation is running and exit it
2715 this.fx.nodeFxAnimation.stopTimer();
2716 //unselect all hovered nodes...
2717 this.viz.graph.eachNode(function(n) {
2718 if(n.hovered && !n.selected) {
2719 for(var s in nStyles) {
2720 n.setData(s, n.styles['$' + s], 'end');
2725 //select hovered node
2726 node.hovered = true;
2727 this.hoveredNode = node;
2728 this.toggleStylesOnHover(node, true);
2729 } else if(this.hoveredNode && !this.hoveredNode.selected) {
2730 //check if an animation is running and exit it
2731 this.fx.nodeFxAnimation.stopTimer();
2732 //unselect hovered node
2733 this.toggleStylesOnHover(this.hoveredNode, false);
2734 delete this.hoveredNode.hovered;
2735 this.hoveredNode = false;
2741 Extras.Classes.Navigation = new Class({
2742 Implements: [ExtrasInitializer, EventsInterface],
2744 initializePost: function() {
2746 this.pressed = false;
2749 onMouseWheel: function(e, win, scroll) {
2750 if(!this.config.zooming) return;
2751 $.event.stop($.event.get(e, win));
2752 var val = this.config.zooming / 1000,
2753 ans = 1 + scroll * val;
2754 this.canvas.scale(ans, ans);
2757 onMouseDown: function(e, win, eventInfo) {
2758 if(!this.config.panning) return;
2759 if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2760 this.pressed = true;
2761 this.pos = eventInfo.getPos();
2762 var canvas = this.canvas,
2763 ox = canvas.translateOffsetX,
2764 oy = canvas.translateOffsetY,
2765 sx = canvas.scaleOffsetX,
2766 sy = canvas.scaleOffsetY;
2773 onMouseMove: function(e, win, eventInfo) {
2774 if(!this.config.panning) return;
2775 if(!this.pressed) return;
2776 if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2777 var thispos = this.pos,
2778 currentPos = eventInfo.getPos(),
2779 canvas = this.canvas,
2780 ox = canvas.translateOffsetX,
2781 oy = canvas.translateOffsetY,
2782 sx = canvas.scaleOffsetX,
2783 sy = canvas.scaleOffsetY;
2788 var x = currentPos.x - thispos.x,
2789 y = currentPos.y - thispos.y;
2790 this.pos = currentPos;
2791 this.canvas.translate(x * 1/sx, y * 1/sy);
2794 onMouseUp: function(e, win, eventInfo, isRightClick) {
2795 if(!this.config.panning) return;
2796 this.pressed = false;
2809 A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to
2810 know more about <Canvas> options take a look at <Options.Canvas>.
2812 A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior
2813 across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2817 Suppose we have this HTML
2820 <div id="infovis"></div>
2823 Now we create a new Visualization
2826 var viz = new $jit.Viz({
2827 //Where to inject the canvas. Any div container will do.
2828 'injectInto':'infovis',
2829 //width and height for canvas.
2830 //Default's to the container offsetWidth and Height.
2836 The generated HTML will look like this
2840 <div id="infovis-canvaswidget" style="position:relative;">
2841 <canvas id="infovis-canvas" width=900 height=500
2842 style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2843 <div id="infovis-label"
2844 style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2850 As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2851 of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2856 //check for native canvas support
2857 var canvasType = typeof HTMLCanvasElement,
2858 supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2859 //create element function
2860 function $E(tag, props) {
2861 var elem = document.createElement(tag);
2862 for(var p in props) {
2863 if(typeof props[p] == "object") {
2864 $.extend(elem[p], props[p]);
2869 if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2870 elem = G_vmlCanvasManager.initElement(elem);
2874 //canvas widget which we will call just Canvas
2875 $jit.Canvas = Canvas = new Class({
2879 labelContainer: false,
2880 translateOffsetX: 0,
2881 translateOffsetY: 0,
2885 initialize: function(viz, opt) {
2888 var id = $.type(opt.injectInto) == 'string'?
2889 opt.injectInto:opt.injectInto.id,
2890 idLabel = id + "-label",
2892 width = opt.width || wrapper.offsetWidth,
2893 height = opt.height || wrapper.offsetHeight;
2896 var canvasOptions = {
2901 //create main wrapper
2902 this.element = $E('div', {
2903 'id': id + '-canvaswidget',
2905 'position': 'relative',
2906 'width': width + 'px',
2907 'height': height + 'px'
2910 //create label container
2911 this.labelContainer = this.createLabelContainer(opt.Label.type,
2912 idLabel, canvasOptions);
2913 //create primary canvas
2914 this.canvases.push(new Canvas.Base({
2915 config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2916 plot: function(base) {
2919 resize: function() {
2923 //create secondary canvas
2924 var back = opt.background;
2926 var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2927 this.canvases.push(new Canvas.Base(backCanvas));
2930 var len = this.canvases.length;
2932 this.element.appendChild(this.canvases[len].canvas);
2934 this.canvases[len].plot();
2937 this.element.appendChild(this.labelContainer);
2938 wrapper.appendChild(this.element);
2939 //Update canvas position when the page is scrolled.
2940 var timer = null, that = this;
2941 $.addEvent(window, 'scroll', function() {
2942 clearTimeout(timer);
2943 timer = setTimeout(function() {
2944 that.getPos(true); //update canvas position
2947 $.addEvent(window, 'click', function() {
2948 clearTimeout(timer);
2949 timer = setTimeout(function() {
2950 that.getPos(true); //update canvas position
2953 sb = document.getElementById('sb'+id);
2954 $.addEvent(sb, 'scroll', function() {
2955 clearTimeout(timer);
2956 timer = setTimeout(function() {
2957 that.getPos(true); //update canvas position
2964 Returns the main canvas context object
2969 var ctx = canvas.getCtx();
2970 //Now I can use the native canvas context
2971 //and for example change some canvas styles
2972 ctx.globalAlpha = 1;
2975 getCtx: function(i) {
2976 return this.canvases[i || 0].getCtx();
2981 Returns the current Configuration for this Canvas Widget.
2986 var config = canvas.getConfig();
2989 getConfig: function() {
2995 Returns the main Canvas DOM wrapper
3000 var wrapper = canvas.getElement();
3001 //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3004 getElement: function() {
3005 return this.element;
3010 Returns canvas dimensions.
3014 An object with *width* and *height* properties.
3018 canvas.getSize(); //returns { width: 900, height: 500 }
3021 getSize: function(i) {
3022 return this.canvases[i || 0].getSize();
3031 width - New canvas width.
3032 height - New canvas height.
3037 canvas.resize(width, height);
3041 resize: function(width, height) {
3043 this.translateOffsetX = this.translateOffsetY = 0;
3044 this.scaleOffsetX = this.scaleOffsetY = 1;
3045 for(var i=0, l=this.canvases.length; i<l; i++) {
3046 this.canvases[i].resize(width, height);
3048 var style = this.element.style;
3049 style.width = width + 'px';
3050 style.height = height + 'px';
3051 if(this.labelContainer)
3052 this.labelContainer.style.width = width + 'px';
3057 Applies a translation to the canvas.
3061 x - (number) x offset.
3062 y - (number) y offset.
3063 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3068 canvas.translate(30, 30);
3072 translate: function(x, y, disablePlot) {
3073 this.translateOffsetX += x*this.scaleOffsetX;
3074 this.translateOffsetY += y*this.scaleOffsetY;
3075 for(var i=0, l=this.canvases.length; i<l; i++) {
3076 this.canvases[i].translate(x, y, disablePlot);
3086 x - (number) scale value.
3087 y - (number) scale value.
3088 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3093 canvas.scale(0.5, 0.5);
3097 scale: function(x, y, disablePlot) {
3099 disablePlot = false;
3101 var px = this.scaleOffsetX * x,
3102 py = this.scaleOffsetY * y;
3103 var dx = this.translateOffsetX * (x -1) / px,
3104 dy = this.translateOffsetY * (y -1) / py;
3105 this.scaleOffsetX = px;
3106 this.scaleOffsetY = py;
3107 for(var i=0, l=this.canvases.length; i<l; i++) {
3108 this.canvases[i].scale(x, y, true);
3110 this.translate(dx, dy, disablePlot);
3115 Returns the canvas position as an *x, y* object.
3119 force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3123 An object with *x* and *y* properties.
3127 canvas.getPos(true); //returns { x: 900, y: 500 }
3130 getPos: function(force){
3131 if(force || !this.pos) {
3132 return this.pos = $.getPos(this.getElement());
3142 this.canvases[i||0].clear();
3145 path: function(type, action){
3146 var ctx = this.canvases[0].getCtx();
3153 createLabelContainer: function(type, idLabel, dim) {
3154 var NS = 'http://www.w3.org/2000/svg';
3155 if(type == 'HTML' || type == 'Native') {
3159 'overflow': 'visible',
3160 'position': 'absolute',
3163 'width': dim.width + 'px',
3167 } else if(type == 'SVG') {
3168 var svgContainer = document.createElementNS(NS, 'svg:svg');
3169 svgContainer.setAttribute("width", dim.width);
3170 svgContainer.setAttribute('height', dim.height);
3171 var style = svgContainer.style;
3172 style.position = 'absolute';
3173 style.left = style.top = '0px';
3174 var labelContainer = document.createElementNS(NS, 'svg:g');
3175 labelContainer.setAttribute('width', dim.width);
3176 labelContainer.setAttribute('height', dim.height);
3177 labelContainer.setAttribute('x', 0);
3178 labelContainer.setAttribute('y', 0);
3179 labelContainer.setAttribute('id', idLabel);
3180 svgContainer.appendChild(labelContainer);
3181 return svgContainer;
3185 //base canvas wrapper
3186 Canvas.Base = new Class({
3187 translateOffsetX: 0,
3188 translateOffsetY: 0,
3192 initialize: function(viz) {
3194 this.opt = viz.config;
3196 this.createCanvas();
3197 this.translateToCenter();
3199 createCanvas: function() {
3202 height = opt.height;
3203 this.canvas = $E('canvas', {
3204 'id': opt.injectInto + opt.idSuffix,
3208 'position': 'absolute',
3211 'width': width + 'px',
3212 'height': height + 'px'
3216 getCtx: function() {
3218 return this.ctx = this.canvas.getContext('2d');
3221 getSize: function() {
3222 if(this.size) return this.size;
3223 var canvas = this.canvas;
3224 return this.size = {
3225 width: canvas.width,
3226 height: canvas.height
3229 translateToCenter: function(ps) {
3230 var size = this.getSize(),
3231 width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3232 height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3233 var ctx = this.getCtx();
3234 ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3235 ctx.translate(width/2, height/2);
3237 resize: function(width, height) {
3238 var size = this.getSize(),
3239 canvas = this.canvas,
3240 styles = canvas.style;
3242 canvas.width = width;
3243 canvas.height = height;
3244 styles.width = width + "px";
3245 styles.height = height + "px";
3246 //small ExCanvas fix
3247 //if(!supportsCanvas) {
3248 //this.translateToCenter(size);
3250 this.translateToCenter();
3252 this.translateOffsetX =
3253 this.translateOffsetY = 0;
3255 this.scaleOffsetY = 1;
3257 this.viz.resize(width, height, this);
3259 translate: function(x, y, disablePlot) {
3260 var sx = this.scaleOffsetX,
3261 sy = this.scaleOffsetY;
3262 this.translateOffsetX += x*sx;
3263 this.translateOffsetY += y*sy;
3264 this.getCtx().translate(x, y);
3265 !disablePlot && this.plot();
3267 scale: function(x, y, disablePlot) {
3268 this.scaleOffsetX *= x;
3269 this.scaleOffsetY *= y;
3270 this.getCtx().scale(x, y);
3271 !disablePlot && this.plot();
3274 var size = this.getSize(),
3275 ox = this.translateOffsetX,
3276 oy = this.translateOffsetY,
3277 sx = this.scaleOffsetX,
3278 sy = this.scaleOffsetY;
3279 this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx,
3280 (-size.height / 2 - oy) * 1/sy,
3281 size.width * 1/sx, size.height * 1/sy);
3285 this.viz.plot(this);
3288 //background canvases
3289 //TODO(nico): document this!
3290 Canvas.Background = {};
3291 Canvas.Background.Circles = new Class({
3292 initialize: function(viz, options) {
3294 this.config = $.merge({
3295 idSuffix: '-bkcanvas',
3302 resize: function(width, height, base) {
3305 plot: function(base) {
3306 var canvas = base.canvas,
3307 ctx = base.getCtx(),
3309 styles = conf.CanvasStyles;
3311 for(var s in styles) ctx[s] = styles[s];
3312 var n = conf.numberOfCircles,
3313 rho = conf.levelDistance;
3314 for(var i=1; i<=n; i++) {
3316 ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3320 //TODO(nico): print labels too!
3323 Canvas.Background.Fade = new Class({
3324 initialize: function(viz, options) {
3326 this.config = $.merge({
3327 idSuffix: '-bkcanvas',
3332 resize: function(width, height, base) {
3335 plot: function(base) {
3336 var canvas = base.canvas,
3337 ctx = base.getCtx(),
3339 styles = conf.CanvasStyles,
3340 size = base.getSize();
3341 ctx.fillStyle = 'rgb(255,255,255)';
3342 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3343 //TODO(nico): print labels too!
3352 * Defines the <Polar> class.
3356 * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3360 * <http://en.wikipedia.org/wiki/Polar_coordinates>
3367 A multi purpose polar representation.
3371 The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3375 <http://en.wikipedia.org/wiki/Polar_coordinates>
3383 var Polar = function(theta, rho) {
3394 Returns a complex number.
3398 simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3404 getc: function(simple) {
3405 return this.toComplex(simple);
3411 Returns a <Polar> representation.
3415 A variable in polar coordinates.
3429 v - A <Complex> or <Polar> instance.
3434 this.theta = v.theta; this.rho = v.rho;
3440 Sets a <Complex> number.
3444 x - A <Complex> number real part.
3445 y - A <Complex> number imaginary part.
3448 setc: function(x, y) {
3449 this.rho = Math.sqrt(x * x + y * y);
3450 this.theta = Math.atan2(y, x);
3451 if(this.theta < 0) this.theta += Math.PI * 2;
3457 Sets a polar number.
3461 theta - A <Polar> number angle property.
3462 rho - A <Polar> number rho property.
3465 setp: function(theta, rho) {
3473 Returns a copy of the current object.
3477 A copy of the real object.
3480 return new Polar(this.theta, this.rho);
3486 Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3490 simple - _optional_ If *true* this method will only return an object with x and y properties (and not the whole <Complex> instance). Default's *false*.
3494 A new <Complex> instance.
3496 toComplex: function(simple) {
3497 var x = Math.cos(this.theta) * this.rho;
3498 var y = Math.sin(this.theta) * this.rho;
3499 if(simple) return { 'x': x, 'y': y};
3500 return new Complex(x, y);
3506 Adds two <Polar> instances.
3510 polar - A <Polar> number.
3514 A new Polar instance.
3516 add: function(polar) {
3517 return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3523 Scales a polar norm.
3527 number - A scale factor.
3531 A new Polar instance.
3533 scale: function(number) {
3534 return new Polar(this.theta, this.rho * number);
3542 Returns *true* if the theta and rho properties are equal.
3546 c - A <Polar> number.
3550 *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3552 equals: function(c) {
3553 return this.theta == c.theta && this.rho == c.rho;
3559 Adds two <Polar> instances affecting the current object.
3563 polar - A <Polar> instance.
3569 $add: function(polar) {
3570 this.theta = this.theta + polar.theta; this.rho += polar.rho;
3577 Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3581 polar - A <Polar> instance.
3587 $madd: function(polar) {
3588 this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3596 Scales a polar instance affecting the object.
3600 number - A scaling factor.
3606 $scale: function(number) {
3614 Calculates a polar interpolation between two points at a given delta moment.
3618 elem - A <Polar> instance.
3619 delta - A delta factor ranging [0, 1].
3623 A new <Polar> instance representing an interpolation between _this_ and _elem_
3625 interpolate: function(elem, delta) {
3626 var pi = Math.PI, pi2 = pi * 2;
3627 var ch = function(t) {
3628 var a = (t < 0)? (t % pi2) + pi2 : t % pi2;
3631 var tt = this.theta, et = elem.theta;
3632 var sum, diff = Math.abs(tt - et);
3635 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3637 sum = ch((et - pi2 + (tt - (et)) * delta));
3639 } else if(diff >= pi) {
3641 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3643 sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3646 sum = ch((et + (tt - et) * delta)) ;
3648 var r = (this.rho - elem.rho) * delta + elem.rho;
3657 var $P = function(a, b) { return new Polar(a, b); };
3659 Polar.KER = $P(0, 0);
3666 * Defines the <Complex> class.
3670 * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3674 * <http://en.wikipedia.org/wiki/Complex_number>
3681 A multi-purpose Complex Class with common methods.
3685 The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3689 <http://en.wikipedia.org/wiki/Complex_number>
3693 x - _optional_ A Complex number real part.
3694 y - _optional_ A Complex number imaginary part.
3698 var Complex = function(x, y) {
3703 $jit.Complex = Complex;
3705 Complex.prototype = {
3709 Returns a complex number.
3722 Returns a <Polar> representation of this number.
3726 simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3730 A variable in <Polar> coordinates.
3732 getp: function(simple) {
3733 return this.toPolar(simple);
3744 c - A <Complex> or <Polar> instance.
3756 Sets a complex number.
3760 x - A <Complex> number Real part.
3761 y - A <Complex> number Imaginary part.
3764 setc: function(x, y) {
3772 Sets a polar number.
3776 theta - A <Polar> number theta property.
3777 rho - A <Polar> number rho property.
3780 setp: function(theta, rho) {
3781 this.x = Math.cos(theta) * rho;
3782 this.y = Math.sin(theta) * rho;
3788 Returns a copy of the current object.
3792 A copy of the real object.
3795 return new Complex(this.x, this.y);
3801 Transforms cartesian to polar coordinates.
3805 simple - _optional_ If *true* this method will only return an object with theta and rho properties (and not the whole <Polar> instance). Default's *false*.
3809 A new <Polar> instance.
3812 toPolar: function(simple) {
3813 var rho = this.norm();
3814 var atan = Math.atan2(this.y, this.x);
3815 if(atan < 0) atan += Math.PI * 2;
3816 if(simple) return { 'theta': atan, 'rho': rho };
3817 return new Polar(atan, rho);
3822 Calculates a <Complex> number norm.
3826 A real number representing the complex norm.
3829 return Math.sqrt(this.squaredNorm());
3835 Calculates a <Complex> number squared norm.
3839 A real number representing the complex squared norm.
3841 squaredNorm: function () {
3842 return this.x*this.x + this.y*this.y;
3848 Returns the result of adding two complex numbers.
3850 Does not alter the original object.
3854 pos - A <Complex> instance.
3858 The result of adding two complex numbers.
3860 add: function(pos) {
3861 return new Complex(this.x + pos.x, this.y + pos.y);
3867 Returns the result of multiplying two <Complex> numbers.
3869 Does not alter the original object.
3873 pos - A <Complex> instance.
3877 The result of multiplying two complex numbers.
3879 prod: function(pos) {
3880 return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3886 Returns the conjugate of this <Complex> number.
3888 Does not alter the original object.
3892 The conjugate of this <Complex> number.
3894 conjugate: function() {
3895 return new Complex(this.x, -this.y);
3902 Returns the result of scaling a <Complex> instance.
3904 Does not alter the original object.
3908 factor - A scale factor.
3912 The result of scaling this complex to a factor.
3914 scale: function(factor) {
3915 return new Complex(this.x * factor, this.y * factor);
3923 Returns *true* if both real and imaginary parts are equal.
3927 c - A <Complex> instance.
3931 A boolean instance indicating if both <Complex> numbers are equal.
3933 equals: function(c) {
3934 return this.x == c.x && this.y == c.y;
3940 Returns the result of adding two <Complex> numbers.
3942 Alters the original object.
3946 pos - A <Complex> instance.
3950 The result of adding two complex numbers.
3952 $add: function(pos) {
3953 this.x += pos.x; this.y += pos.y;
3960 Returns the result of multiplying two <Complex> numbers.
3962 Alters the original object.
3966 pos - A <Complex> instance.
3970 The result of multiplying two complex numbers.
3972 $prod:function(pos) {
3973 var x = this.x, y = this.y;
3974 this.x = x*pos.x - y*pos.y;
3975 this.y = y*pos.x + x*pos.y;
3982 Returns the conjugate for this <Complex>.
3984 Alters the original object.
3988 The conjugate for this complex.
3990 $conjugate: function() {
3998 Returns the result of scaling a <Complex> instance.
4000 Alters the original object.
4004 factor - A scale factor.
4008 The result of scaling this complex to a factor.
4010 $scale: function(factor) {
4011 this.x *= factor; this.y *= factor;
4018 Returns the division of two <Complex> numbers.
4020 Alters the original object.
4024 pos - A <Complex> number.
4028 The result of scaling this complex to a factor.
4030 $div: function(pos) {
4031 var x = this.x, y = this.y;
4032 var sq = pos.squaredNorm();
4033 this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4034 return this.$scale(1 / sq);
4038 var $C = function(a, b) { return new Complex(a, b); };
4040 Complex.KER = $C(0, 0);
4052 A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4054 An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4059 //create new visualization
4060 var viz = new $jit.Viz(options);
4064 viz.graph; //<Graph> instance
4069 The following <Graph.Util> methods are implemented in <Graph>
4071 - <Graph.Util.getNode>
4072 - <Graph.Util.eachNode>
4073 - <Graph.Util.computeLevels>
4074 - <Graph.Util.eachBFS>
4075 - <Graph.Util.clean>
4076 - <Graph.Util.getClosestNodeToPos>
4077 - <Graph.Util.getClosestNodeToOrigin>
4081 $jit.Graph = new Class({
4083 initialize: function(opt, Node, Edge, Label) {
4084 var innerOptions = {
4091 this.opt = $.merge(innerOptions, opt || {});
4095 //add nodeList methods
4098 for(var p in Accessors) {
4099 that.nodeList[p] = (function(p) {
4101 var args = Array.prototype.slice.call(arguments);
4102 that.eachNode(function(n) {
4103 n[p].apply(n, args);
4114 Returns a <Graph.Node> by *id*.
4118 id - (string) A <Graph.Node> id.
4123 var node = graph.getNode('nodeId');
4126 getNode: function(id) {
4127 if(this.hasNode(id)) return this.nodes[id];
4134 Returns a <Graph.Node> by *name*.
4138 name - (string) A <Graph.Node> name.
4143 var node = graph.getByName('someName');
4146 getByName: function(name) {
4147 for(var id in this.nodes) {
4148 var n = this.nodes[id];
4149 if(n.name == name) return n;
4155 Method: getAdjacence
4157 Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4161 id - (string) A <Graph.Node> id.
4162 id2 - (string) A <Graph.Node> id.
4164 getAdjacence: function (id, id2) {
4165 if(id in this.edges) {
4166 return this.edges[id][id2];
4178 obj - An object with the properties described below
4180 id - (string) A node id
4181 name - (string) A node's name
4182 data - (object) A node's data hash
4188 addNode: function(obj) {
4189 if(!this.nodes[obj.id]) {
4190 var edges = this.edges[obj.id] = {};
4191 this.nodes[obj.id] = new Graph.Node($.extend({
4194 'data': $.merge(obj.data || {}, {}),
4195 'adjacencies': edges
4202 return this.nodes[obj.id];
4206 Method: addAdjacence
4208 Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4212 obj - (object) A <Graph.Node> object.
4213 obj2 - (object) Another <Graph.Node> object.
4214 data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4218 <Graph.Node>, <Graph.Adjacence>
4220 addAdjacence: function (obj, obj2, data) {
4221 if(!this.hasNode(obj.id)) { this.addNode(obj); }
4222 if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4223 obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4224 if(!obj.adjacentTo(obj2)) {
4225 var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4226 var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4227 adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4228 return adjsObj[obj2.id];
4230 return this.edges[obj.id][obj2.id];
4236 Removes a <Graph.Node> matching the specified *id*.
4240 id - (string) A node's id.
4243 removeNode: function(id) {
4244 if(this.hasNode(id)) {
4245 delete this.nodes[id];
4246 var adjs = this.edges[id];
4247 for(var to in adjs) {
4248 delete this.edges[to][id];
4250 delete this.edges[id];
4255 Method: removeAdjacence
4257 Removes a <Graph.Adjacence> matching *id1* and *id2*.
4261 id1 - (string) A <Graph.Node> id.
4262 id2 - (string) A <Graph.Node> id.
4264 removeAdjacence: function(id1, id2) {
4265 delete this.edges[id1][id2];
4266 delete this.edges[id2][id1];
4272 Returns a boolean indicating if the node belongs to the <Graph> or not.
4276 id - (string) Node id.
4278 hasNode: function(id) {
4279 return id in this.nodes;
4288 empty: function() { this.nodes = {}; this.edges = {};}
4292 var Graph = $jit.Graph;
4297 Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4303 var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4305 type = type || 'current';
4306 prefix = "$" + (prefix ? prefix + "-" : "");
4308 if(type == 'current') {
4310 } else if(type == 'start') {
4311 data = this.startData;
4312 } else if(type == 'end') {
4313 data = this.endData;
4316 var dollar = prefix + prop;
4319 return data[dollar];
4322 if(!this.Config.overridable)
4323 return prefixConfig[prop] || 0;
4325 return (dollar in data) ?
4326 data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4329 var setDataInternal = function(prefix, prop, value, type) {
4330 type = type || 'current';
4331 prefix = '$' + (prefix ? prefix + '-' : '');
4335 if(type == 'current') {
4337 } else if(type == 'start') {
4338 data = this.startData;
4339 } else if(type == 'end') {
4340 data = this.endData;
4343 data[prefix + prop] = value;
4346 var removeDataInternal = function(prefix, properties) {
4347 prefix = '$' + (prefix ? prefix + '-' : '');
4349 $.each(properties, function(prop) {
4350 var pref = prefix + prop;
4351 delete that.data[pref];
4352 delete that.endData[pref];
4353 delete that.startData[pref];
4361 Returns the specified data value property.
4362 This is useful for querying special/reserved <Graph.Node> data properties
4363 (i.e dollar prefixed properties).
4367 prop - (string) The name of the property. The dollar sign is not needed. For
4368 example *getData(width)* will return *data.$width*.
4369 type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4370 data properties also. These properties are used when making animations.
4371 force - (boolean) Whether to obtain the true value of the property (equivalent to
4372 *data.$prop*) or to check for *node.overridable = true* first.
4376 The value of the dollar prefixed property or the global Node/Edge property
4377 value if *overridable=false*
4381 node.getData('width'); //will return node.data.$width if Node.overridable=true;
4384 getData: function(prop, type, force) {
4385 return getDataInternal.call(this, "", prop, type, force, this.Config);
4392 Sets the current data property with some specific value.
4393 This method is only useful for reserved (dollar prefixed) properties.
4397 prop - (string) The name of the property. The dollar sign is not necessary. For
4398 example *setData(width)* will set *data.$width*.
4399 value - (mixed) The value to store.
4400 type - (string) The type of the data property to store. Default's "current" but
4401 can also be "start" or "end".
4406 node.setData('width', 30);
4409 If we were to make an animation of a node/edge width then we could do
4412 var node = viz.getNode('nodeId');
4413 //set start and end values
4414 node.setData('width', 10, 'start');
4415 node.setData('width', 30, 'end');
4416 //will animate nodes width property
4418 modes: ['node-property:width'],
4423 setData: function(prop, value, type) {
4424 setDataInternal.call(this, "", prop, value, type);
4430 Convenience method to set multiple data values at once.
4434 types - (array|string) A set of 'current', 'end' or 'start' values.
4435 obj - (object) A hash containing the names and values of the properties to be altered.
4439 node.setDataset(['current', 'end'], {
4441 'color': ['#fff', '#ccc']
4444 node.setDataset('end', {
4455 setDataset: function(types, obj) {
4456 types = $.splat(types);
4457 for(var attr in obj) {
4458 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4459 this.setData(attr, val[i], types[i]);
4467 Remove data properties.
4471 One or more property names as arguments. The dollar sign is not needed.
4475 node.removeData('width'); //now the default width value is returned
4478 removeData: function() {
4479 removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4483 Method: getCanvasStyle
4485 Returns the specified canvas style data value property. This is useful for
4486 querying special/reserved <Graph.Node> canvas style data properties (i.e.
4487 dollar prefixed properties that match with $canvas-<name of canvas style>).
4491 prop - (string) The name of the property. The dollar sign is not needed. For
4492 example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4493 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4494 data properties also.
4498 node.getCanvasStyle('shadowBlur');
4505 getCanvasStyle: function(prop, type, force) {
4506 return getDataInternal.call(
4507 this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4511 Method: setCanvasStyle
4513 Sets the canvas style data property with some specific value.
4514 This method is only useful for reserved (dollar prefixed) properties.
4518 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4519 value - (mixed) The value to set to the property.
4520 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4525 node.setCanvasStyle('shadowBlur', 30);
4528 If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4531 var node = viz.getNode('nodeId');
4532 //set start and end values
4533 node.setCanvasStyle('shadowBlur', 10, 'start');
4534 node.setCanvasStyle('shadowBlur', 30, 'end');
4535 //will animate nodes canvas style property for nodes
4537 modes: ['node-style:shadowBlur'],
4544 <Accessors.setData>.
4546 setCanvasStyle: function(prop, value, type) {
4547 setDataInternal.call(this, 'canvas', prop, value, type);
4551 Method: setCanvasStyles
4553 Convenience method to set multiple styles at once.
4557 types - (array|string) A set of 'current', 'end' or 'start' values.
4558 obj - (object) A hash containing the names and values of the properties to be altered.
4562 <Accessors.setDataset>.
4564 setCanvasStyles: function(types, obj) {
4565 types = $.splat(types);
4566 for(var attr in obj) {
4567 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4568 this.setCanvasStyle(attr, val[i], types[i]);
4574 Method: removeCanvasStyle
4576 Remove canvas style properties from data.
4580 A variable number of canvas style strings.
4584 <Accessors.removeData>.
4586 removeCanvasStyle: function() {
4587 removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4591 Method: getLabelData
4593 Returns the specified label data value property. This is useful for
4594 querying special/reserved <Graph.Node> label options (i.e.
4595 dollar prefixed properties that match with $label-<name of label style>).
4599 prop - (string) The name of the property. The dollar sign prefix is not needed. For
4600 example *getLabelData(size)* will return *data[$label-size]*.
4601 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4602 data properties also.
4606 <Accessors.getData>.
4608 getLabelData: function(prop, type, force) {
4609 return getDataInternal.call(
4610 this, 'label', prop, type, force, this.Label);
4614 Method: setLabelData
4616 Sets the current label data with some specific value.
4617 This method is only useful for reserved (dollar prefixed) properties.
4621 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4622 value - (mixed) The value to set to the property.
4623 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4628 node.setLabelData('size', 30);
4631 If we were to make an animation of a node label size then we could do
4634 var node = viz.getNode('nodeId');
4635 //set start and end values
4636 node.setLabelData('size', 10, 'start');
4637 node.setLabelData('size', 30, 'end');
4638 //will animate nodes label size
4640 modes: ['label-property:size'],
4647 <Accessors.setData>.
4649 setLabelData: function(prop, value, type) {
4650 setDataInternal.call(this, 'label', prop, value, type);
4654 Method: setLabelDataset
4656 Convenience function to set multiple label data at once.
4660 types - (array|string) A set of 'current', 'end' or 'start' values.
4661 obj - (object) A hash containing the names and values of the properties to be altered.
4665 <Accessors.setDataset>.
4667 setLabelDataset: function(types, obj) {
4668 types = $.splat(types);
4669 for(var attr in obj) {
4670 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4671 this.setLabelData(attr, val[i], types[i]);
4677 Method: removeLabelData
4679 Remove label properties from data.
4683 A variable number of label property strings.
4687 <Accessors.removeData>.
4689 removeLabelData: function() {
4690 removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4702 <Accessors> methods.
4704 The following <Graph.Util> methods are implemented by <Graph.Node>
4706 - <Graph.Util.eachAdjacency>
4707 - <Graph.Util.eachLevel>
4708 - <Graph.Util.eachSubgraph>
4709 - <Graph.Util.eachSubnode>
4710 - <Graph.Util.anySubnode>
4711 - <Graph.Util.getSubnodes>
4712 - <Graph.Util.getParents>
4713 - <Graph.Util.isDescendantOf>
4715 Graph.Node = new Class({
4717 initialize: function(opt, complex, Node, Edge, Label) {
4718 var innerOptions = {
4735 'pos': (complex && $C(0, 0)) || $P(0, 0),
4736 'startPos': (complex && $C(0, 0)) || $P(0, 0),
4737 'endPos': (complex && $C(0, 0)) || $P(0, 0)
4740 $.extend(this, $.extend(innerOptions, opt));
4741 this.Config = this.Node = Node;
4749 Indicates if the node is adjacent to the node specified by id
4753 id - (string) A node id.
4757 node.adjacentTo('nodeId') == true;
4760 adjacentTo: function(node) {
4761 return node.id in this.adjacencies;
4765 Method: getAdjacency
4767 Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4771 id - (string) A node id.
4773 getAdjacency: function(id) {
4774 return this.adjacencies[id];
4780 Returns the position of the node.
4784 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4788 A <Complex> or <Polar> instance.
4792 var pos = node.getPos('end');
4795 getPos: function(type) {
4796 type = type || "current";
4797 if(type == "current") {
4799 } else if(type == "end") {
4801 } else if(type == "start") {
4802 return this.startPos;
4808 Sets the node's position.
4812 value - (object) A <Complex> or <Polar> instance.
4813 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4817 node.setPos(new $jit.Complex(0, 0), 'end');
4820 setPos: function(value, type) {
4821 type = type || "current";
4823 if(type == "current") {
4825 } else if(type == "end") {
4827 } else if(type == "start") {
4828 pos = this.startPos;
4834 Graph.Node.implement(Accessors);
4837 Class: Graph.Adjacence
4839 A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4843 <Accessors> methods.
4847 <Graph>, <Graph.Node>
4851 nodeFrom - A <Graph.Node> connected by this edge.
4852 nodeTo - Another <Graph.Node> connected by this edge.
4853 data - Node data property containing a hash (i.e {}) with custom options.
4855 Graph.Adjacence = new Class({
4857 initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4858 this.nodeFrom = nodeFrom;
4859 this.nodeTo = nodeTo;
4860 this.data = data || {};
4861 this.startData = {};
4863 this.Config = this.Edge = Edge;
4868 Graph.Adjacence.implement(Accessors);
4873 <Graph> traversal and processing utility object.
4877 For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4883 For internal use only. Provides a filtering function based on flags.
4885 filter: function(param) {
4886 if(!param || !($.type(param) == 'string')) return function() { return true; };
4887 var props = param.split(" ");
4888 return function(elem) {
4889 for(var i=0; i<props.length; i++) {
4890 if(elem[props[i]]) {
4900 Returns a <Graph.Node> by *id*.
4902 Also implemented by:
4908 graph - (object) A <Graph> instance.
4909 id - (string) A <Graph.Node> id.
4914 $jit.Graph.Util.getNode(graph, 'nodeid');
4916 graph.getNode('nodeid');
4919 getNode: function(graph, id) {
4920 return graph.nodes[id];
4926 Iterates over <Graph> nodes performing an *action*.
4928 Also implemented by:
4934 graph - (object) A <Graph> instance.
4935 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4939 $jit.Graph.Util.eachNode(graph, function(node) {
4943 graph.eachNode(function(node) {
4948 eachNode: function(graph, action, flags) {
4949 var filter = this.filter(flags);
4950 for(var i in graph.nodes) {
4951 if(filter(graph.nodes[i])) action(graph.nodes[i]);
4956 Method: eachAdjacency
4958 Iterates over <Graph.Node> adjacencies applying the *action* function.
4960 Also implemented by:
4966 node - (object) A <Graph.Node>.
4967 action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4971 $jit.Graph.Util.eachAdjacency(node, function(adj) {
4972 alert(adj.nodeTo.name);
4975 node.eachAdjacency(function(adj) {
4976 alert(adj.nodeTo.name);
4980 eachAdjacency: function(node, action, flags) {
4981 var adj = node.adjacencies, filter = this.filter(flags);
4982 for(var id in adj) {
4985 if(a.nodeFrom != node) {
4986 var tmp = a.nodeFrom;
4987 a.nodeFrom = a.nodeTo;
4996 Method: computeLevels
4998 Performs a BFS traversal setting the correct depth for each node.
5000 Also implemented by:
5006 The depth of each node can then be accessed by
5011 graph - (object) A <Graph>.
5012 id - (string) A starting node id for the BFS traversal.
5013 startDepth - (optional|number) A minimum depth value. Default's 0.
5016 computeLevels: function(graph, id, startDepth, flags) {
5017 startDepth = startDepth || 0;
5018 var filter = this.filter(flags);
5019 this.eachNode(graph, function(elem) {
5023 var root = graph.getNode(id);
5024 root._depth = startDepth;
5026 while(queue.length != 0) {
5027 var node = queue.pop();
5029 this.eachAdjacency(node, function(adj) {
5031 if(n._flag == false && filter(n)) {
5032 if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5042 Performs a BFS traversal applying *action* to each <Graph.Node>.
5044 Also implemented by:
5050 graph - (object) A <Graph>.
5051 id - (string) A starting node id for the BFS traversal.
5052 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5056 $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5060 graph.eachBFS('mynodeid', function(node) {
5065 eachBFS: function(graph, id, action, flags) {
5066 var filter = this.filter(flags);
5068 var queue = [graph.getNode(id)];
5069 while(queue.length != 0) {
5070 var node = queue.pop();
5072 action(node, node._depth);
5073 this.eachAdjacency(node, function(adj) {
5075 if(n._flag == false && filter(n)) {
5086 Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5088 Also implemented by:
5094 node - (object) A <Graph.Node>.
5095 levelBegin - (number) A relative level value.
5096 levelEnd - (number) A relative level value.
5097 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5100 eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5101 var d = node._depth, filter = this.filter(flags), that = this;
5102 levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5103 (function loopLevel(node, levelBegin, levelEnd) {
5104 var d = node._depth;
5105 if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5107 that.eachAdjacency(node, function(adj) {
5109 if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5112 })(node, levelBegin + d, levelEnd + d);
5116 Method: eachSubgraph
5118 Iterates over a node's children recursively.
5120 Also implemented by:
5125 node - (object) A <Graph.Node>.
5126 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5130 $jit.Graph.Util.eachSubgraph(node, function(node) {
5134 node.eachSubgraph(function(node) {
5139 eachSubgraph: function(node, action, flags) {
5140 this.eachLevel(node, 0, false, action, flags);
5146 Iterates over a node's children (without deeper recursion).
5148 Also implemented by:
5153 node - (object) A <Graph.Node>.
5154 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5158 $jit.Graph.Util.eachSubnode(node, function(node) {
5162 node.eachSubnode(function(node) {
5167 eachSubnode: function(node, action, flags) {
5168 this.eachLevel(node, 1, 1, action, flags);
5174 Returns *true* if any subnode matches the given condition.
5176 Also implemented by:
5181 node - (object) A <Graph.Node>.
5182 cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5186 $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5188 node.anySubnode(function(node) { return node.name == 'mynodename'; });
5191 anySubnode: function(node, cond, flags) {
5193 cond = cond || $.lambda(true);
5194 var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5195 this.eachSubnode(node, function(elem) {
5196 if(c(elem)) flag = true;
5204 Collects all subnodes for a specified node.
5205 The *level* parameter filters nodes having relative depth of *level* from the root node.
5207 Also implemented by:
5212 node - (object) A <Graph.Node>.
5213 level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5219 getSubnodes: function(node, level, flags) {
5220 var ans = [], that = this;
5222 var levelStart, levelEnd;
5223 if($.type(level) == 'array') {
5224 levelStart = level[0];
5225 levelEnd = level[1];
5228 levelEnd = Number.MAX_VALUE - node._depth;
5230 this.eachLevel(node, levelStart, levelEnd, function(n) {
5240 Returns an Array of <Graph.Nodes> which are parents of the given node.
5242 Also implemented by:
5247 node - (object) A <Graph.Node>.
5250 An Array of <Graph.Nodes>.
5254 var pars = $jit.Graph.Util.getParents(node);
5256 var pars = node.getParents();
5258 if(pars.length > 0) {
5259 //do stuff with parents
5263 getParents: function(node) {
5265 this.eachAdjacency(node, function(adj) {
5267 if(n._depth < node._depth) ans.push(n);
5273 Method: isDescendantOf
5275 Returns a boolean indicating if some node is descendant of the node with the given id.
5277 Also implemented by:
5283 node - (object) A <Graph.Node>.
5284 id - (string) A <Graph.Node> id.
5288 $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5290 node.isDescendantOf('nodeid');//true|false
5293 isDescendantOf: function(node, id) {
5294 if(node.id == id) return true;
5295 var pars = this.getParents(node), ans = false;
5296 for ( var i = 0; !ans && i < pars.length; i++) {
5297 ans = ans || this.isDescendantOf(pars[i], id);
5305 Cleans flags from nodes.
5307 Also implemented by:
5312 graph - A <Graph> instance.
5314 clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5317 Method: getClosestNodeToOrigin
5319 Returns the closest node to the center of canvas.
5321 Also implemented by:
5327 graph - (object) A <Graph> instance.
5328 prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5331 getClosestNodeToOrigin: function(graph, prop, flags) {
5332 return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5336 Method: getClosestNodeToPos
5338 Returns the closest node to the given position.
5340 Also implemented by:
5346 graph - (object) A <Graph> instance.
5347 pos - (object) A <Complex> or <Polar> instance.
5348 prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5351 getClosestNodeToPos: function(graph, pos, prop, flags) {
5353 prop = prop || 'current';
5354 pos = pos && pos.getc(true) || Complex.KER;
5355 var distance = function(a, b) {
5356 var d1 = a.x - b.x, d2 = a.y - b.y;
5357 return d1 * d1 + d2 * d2;
5359 this.eachNode(graph, function(elem) {
5360 node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5361 node.getPos(prop).getc(true), pos)) ? elem : node;
5367 //Append graph methods to <Graph>
5368 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5369 Graph.prototype[m] = function() {
5370 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5374 //Append node methods to <Graph.Node>
5375 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5376 Graph.Node.prototype[m] = function() {
5377 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5389 Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5390 morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5402 initialize: function(viz) {
5409 Removes one or more <Graph.Nodes> from the visualization.
5410 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5414 node - (string|array) The node's id. Can also be an array having many ids.
5415 opt - (object) Animation options. It's an object with optional properties described below
5416 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5417 duration - Described in <Options.Fx>.
5418 fps - Described in <Options.Fx>.
5419 transition - Described in <Options.Fx>.
5420 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5424 var viz = new $jit.Viz(options);
5425 viz.op.removeNode('nodeId', {
5429 transition: $jit.Trans.Quart.easeOut
5432 viz.op.removeNode(['someId', 'otherId'], {
5439 removeNode: function(node, opt) {
5441 var options = $.merge(this.options, viz.controller, opt);
5442 var n = $.splat(node);
5443 var i, that, nodeObj;
5444 switch(options.type) {
5446 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5450 this.removeNode(n, { type: 'nothing' });
5451 viz.labels.clearLabels();
5455 case 'fade:seq': case 'fade':
5457 //set alpha to 0 for nodes to remove.
5458 for(i=0; i<n.length; i++) {
5459 nodeObj = viz.graph.getNode(n[i]);
5460 nodeObj.setData('alpha', 0, 'end');
5462 viz.fx.animate($.merge(options, {
5463 modes: ['node-property:alpha'],
5464 onComplete: function() {
5465 that.removeNode(n, { type: 'nothing' });
5466 viz.labels.clearLabels();
5468 viz.fx.animate($.merge(options, {
5477 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5478 for(i=0; i<n.length; i++) {
5479 nodeObj = viz.graph.getNode(n[i]);
5480 nodeObj.setData('alpha', 0, 'end');
5481 nodeObj.ignore = true;
5484 viz.fx.animate($.merge(options, {
5485 modes: ['node-property:alpha', 'linear'],
5486 onComplete: function() {
5487 that.removeNode(n, { type: 'nothing' });
5495 condition: function() { return n.length != 0; },
5496 step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5497 onComplete: function() { options.onComplete(); },
5498 duration: Math.ceil(options.duration / n.length)
5502 default: this.doError();
5509 Removes one or more <Graph.Adjacences> from the visualization.
5510 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5514 vertex - (array) An array having two strings which are the ids of the nodes connected by this edge (i.e ['id1', 'id2']). Can also be a two dimensional array holding many edges (i.e [['id1', 'id2'], ['id3', 'id4'], ...]).
5515 opt - (object) Animation options. It's an object with optional properties described below
5516 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5517 duration - Described in <Options.Fx>.
5518 fps - Described in <Options.Fx>.
5519 transition - Described in <Options.Fx>.
5520 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5524 var viz = new $jit.Viz(options);
5525 viz.op.removeEdge(['nodeId', 'otherId'], {
5529 transition: $jit.Trans.Quart.easeOut
5532 viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5539 removeEdge: function(vertex, opt) {
5541 var options = $.merge(this.options, viz.controller, opt);
5542 var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5544 switch(options.type) {
5546 for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5550 this.removeEdge(v, { type: 'nothing' });
5554 case 'fade:seq': case 'fade':
5556 //set alpha to 0 for edges to remove.
5557 for(i=0; i<v.length; i++) {
5558 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5560 adj.setData('alpha', 0,'end');
5563 viz.fx.animate($.merge(options, {
5564 modes: ['edge-property:alpha'],
5565 onComplete: function() {
5566 that.removeEdge(v, { type: 'nothing' });
5568 viz.fx.animate($.merge(options, {
5577 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5578 for(i=0; i<v.length; i++) {
5579 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5581 adj.setData('alpha',0 ,'end');
5586 viz.fx.animate($.merge(options, {
5587 modes: ['edge-property:alpha', 'linear'],
5588 onComplete: function() {
5589 that.removeEdge(v, { type: 'nothing' });
5597 condition: function() { return v.length != 0; },
5598 step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5599 onComplete: function() { options.onComplete(); },
5600 duration: Math.ceil(options.duration / v.length)
5604 default: this.doError();
5611 Adds a new graph to the visualization.
5612 The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5613 The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5617 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5618 opt - (object) Animation options. It's an object with optional properties described below
5619 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5620 duration - Described in <Options.Fx>.
5621 fps - Described in <Options.Fx>.
5622 transition - Described in <Options.Fx>.
5623 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5627 //...json contains a tree or graph structure...
5629 var viz = new $jit.Viz(options);
5634 transition: $jit.Trans.Quart.easeOut
5644 sum: function(json, opt) {
5646 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5648 viz.root = opt.id || viz.root;
5649 switch(options.type) {
5651 graph = viz.construct(json);
5652 graph.eachNode(function(elem) {
5653 elem.eachAdjacency(function(adj) {
5654 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5661 this.sum(json, { type: 'nothing' });
5665 case 'fade:seq': case 'fade': case 'fade:con':
5667 graph = viz.construct(json);
5669 //set alpha to 0 for nodes to add.
5670 var fadeEdges = this.preprocessSum(graph);
5671 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5673 if(options.type != 'fade:con') {
5674 viz.fx.animate($.merge(options, {
5676 onComplete: function() {
5677 viz.fx.animate($.merge(options, {
5679 onComplete: function() {
5680 options.onComplete();
5686 viz.graph.eachNode(function(elem) {
5687 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5688 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5691 viz.fx.animate($.merge(options, {
5692 modes: ['linear'].concat(modes)
5697 default: this.doError();
5704 This method will transform the current visualized graph into the new JSON representation passed in the method.
5705 The JSON object must at least have the root node in common with the current visualized graph.
5709 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5710 opt - (object) Animation options. It's an object with optional properties described below
5711 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5712 duration - Described in <Options.Fx>.
5713 fps - Described in <Options.Fx>.
5714 transition - Described in <Options.Fx>.
5715 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5716 id - (string) The shared <Graph.Node> id between both graphs.
5718 extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5719 *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5720 For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5721 properties as values, just like specified in <Graph.Plot.animate>.
5725 //...json contains a tree or graph structure...
5727 var viz = new $jit.Viz(options);
5728 viz.op.morph(json, {
5732 transition: $jit.Trans.Quart.easeOut
5735 viz.op.morph(json, {
5739 //if the json data contains dollar prefixed params
5740 //like $width or $height these too can be animated
5741 viz.op.morph(json, {
5745 'node-property': ['width', 'height']
5750 morph: function(json, opt, extraModes) {
5752 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5754 //TODO(nico) this hack makes morphing work with the Hypertree.
5755 //Need to check if it has been solved and this can be removed.
5756 viz.root = opt.id || viz.root;
5757 switch(options.type) {
5759 graph = viz.construct(json);
5760 graph.eachNode(function(elem) {
5761 var nodeExists = viz.graph.hasNode(elem.id);
5762 elem.eachAdjacency(function(adj) {
5763 var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5764 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5765 //Update data properties if the node existed
5767 var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5768 for(var prop in (adj.data || {})) {
5769 addedAdj.data[prop] = adj.data[prop];
5773 //Update data properties if the node existed
5775 var addedNode = viz.graph.getNode(elem.id);
5776 for(var prop in (elem.data || {})) {
5777 addedNode.data[prop] = elem.data[prop];
5781 viz.graph.eachNode(function(elem) {
5782 elem.eachAdjacency(function(adj) {
5783 if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5784 viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5787 if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5793 viz.labels.clearLabels(true);
5794 this.morph(json, { type: 'nothing' });
5799 case 'fade:seq': case 'fade': case 'fade:con':
5801 graph = viz.construct(json);
5802 //preprocessing for nodes to delete.
5803 //get node property modes to interpolate
5804 var nodeModes = extraModes && ('node-property' in extraModes)
5805 && $.map($.splat(extraModes['node-property']),
5806 function(n) { return '$' + n; });
5807 viz.graph.eachNode(function(elem) {
5808 var graphNode = graph.getNode(elem.id);
5810 elem.setData('alpha', 1);
5811 elem.setData('alpha', 1, 'start');
5812 elem.setData('alpha', 0, 'end');
5815 //Update node data information
5816 var graphNodeData = graphNode.data;
5817 for(var prop in graphNodeData) {
5818 if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5819 elem.endData[prop] = graphNodeData[prop];
5821 elem.data[prop] = graphNodeData[prop];
5826 viz.graph.eachNode(function(elem) {
5827 if(elem.ignore) return;
5828 elem.eachAdjacency(function(adj) {
5829 if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5830 var nodeFrom = graph.getNode(adj.nodeFrom.id);
5831 var nodeTo = graph.getNode(adj.nodeTo.id);
5832 if(!nodeFrom.adjacentTo(nodeTo)) {
5833 var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5835 adj.setData('alpha', 1);
5836 adj.setData('alpha', 1, 'start');
5837 adj.setData('alpha', 0, 'end');
5841 //preprocessing for adding nodes.
5842 var fadeEdges = this.preprocessSum(graph);
5844 var modes = !fadeEdges? ['node-property:alpha'] :
5845 ['node-property:alpha',
5846 'edge-property:alpha'];
5847 //Append extra node-property animations (if any)
5848 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))?
5849 (':' + $.splat(extraModes['node-property']).join(':')) : '');
5850 //Append extra edge-property animations (if any)
5851 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))?
5852 (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5853 //Add label-property animations (if any)
5854 if(extraModes && ('label-property' in extraModes)) {
5855 modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5858 viz.graph.eachNode(function(elem) {
5859 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5860 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5863 viz.fx.animate($.merge(options, {
5864 modes: ['polar'].concat(modes),
5865 onComplete: function() {
5866 viz.graph.eachNode(function(elem) {
5867 if(elem.ignore) viz.graph.removeNode(elem.id);
5869 viz.graph.eachNode(function(elem) {
5870 elem.eachAdjacency(function(adj) {
5871 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5874 options.onComplete();
5887 Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5891 node - (object) A <Graph.Node>.
5892 opt - (object) An object containing options described below
5893 type - (string) Whether to 'replot' or 'animate' the contraction.
5895 There are also a number of Animation options. For more information see <Options.Fx>.
5899 var viz = new $jit.Viz(options);
5900 viz.op.contract(node, {
5904 transition: $jit.Trans.Quart.easeOut
5909 contract: function(node, opt) {
5911 if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5912 opt = $.merge(this.options, viz.config, opt || {}, {
5913 'modes': ['node-property:alpha:span', 'linear']
5915 node.collapsed = true;
5917 n.eachSubnode(function(ch) {
5919 ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5923 if(opt.type == 'animate') {
5926 viz.rotate(viz.rotated, 'none', {
5931 n.eachSubnode(function(ch) {
5932 ch.setPos(node.getPos('end'), 'end');
5936 viz.fx.animate(opt);
5937 } else if(opt.type == 'replot'){
5945 Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5949 node - (object) A <Graph.Node>.
5950 opt - (object) An object containing options described below
5951 type - (string) Whether to 'replot' or 'animate'.
5953 There are also a number of Animation options. For more information see <Options.Fx>.
5957 var viz = new $jit.Viz(options);
5958 viz.op.expand(node, {
5962 transition: $jit.Trans.Quart.easeOut
5967 expand: function(node, opt) {
5968 if(!('collapsed' in node)) return;
5970 opt = $.merge(this.options, viz.config, opt || {}, {
5971 'modes': ['node-property:alpha:span', 'linear']
5973 delete node.collapsed;
5975 n.eachSubnode(function(ch) {
5977 ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5981 if(opt.type == 'animate') {
5984 viz.rotate(viz.rotated, 'none', {
5988 viz.fx.animate(opt);
5989 } else if(opt.type == 'replot'){
5994 preprocessSum: function(graph) {
5996 graph.eachNode(function(elem) {
5997 if(!viz.graph.hasNode(elem.id)) {
5998 viz.graph.addNode(elem);
5999 var n = viz.graph.getNode(elem.id);
6000 n.setData('alpha', 0);
6001 n.setData('alpha', 0, 'start');
6002 n.setData('alpha', 1, 'end');
6005 var fadeEdges = false;
6006 graph.eachNode(function(elem) {
6007 elem.eachAdjacency(function(adj) {
6008 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6009 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6010 if(!nodeFrom.adjacentTo(nodeTo)) {
6011 var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6012 if(nodeFrom.startAlpha == nodeFrom.endAlpha
6013 && nodeTo.startAlpha == nodeTo.endAlpha) {
6015 adj.setData('alpha', 0);
6016 adj.setData('alpha', 0, 'start');
6017 adj.setData('alpha', 1, 'end');
6031 Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6032 Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6033 position is over the rendered shape.
6035 Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
6036 *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6040 //implement a new node type
6041 $jit.Viz.Plot.NodeTypes.implement({
6043 'render': function(node, canvas) {
6044 this.nodeHelper.circle.render ...
6046 'contains': function(node, pos) {
6047 this.nodeHelper.circle.contains ...
6051 //implement an edge type
6052 $jit.Viz.Plot.EdgeTypes.implement({
6054 'render': function(node, canvas) {
6055 this.edgeHelper.circle.render ...
6058 'contains': function(node, pos) {
6059 this.edgeHelper.circle.contains ...
6070 Contains rendering and other type of primitives for simple shapes.
6075 'contains': $.lambda(false)
6078 Object: NodeHelper.circle
6084 Renders a circle into the canvas.
6088 type - (string) Possible options are 'fill' or 'stroke'.
6089 pos - (object) An *x*, *y* object with the position of the center of the circle.
6090 radius - (number) The radius of the circle to be rendered.
6091 canvas - (object) A <Canvas> instance.
6095 NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6098 'render': function(type, pos, radius, canvas){
6099 var ctx = canvas.getCtx();
6101 ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6108 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6112 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6113 pos - (object) An *x*, *y* object with the position to check.
6114 radius - (number) The radius of the rendered circle.
6118 NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6121 'contains': function(npos, pos, radius){
6122 var diffx = npos.x - pos.x,
6123 diffy = npos.y - pos.y,
6124 diff = diffx * diffx + diffy * diffy;
6125 return diff <= radius * radius;
6129 Object: NodeHelper.ellipse
6135 Renders an ellipse into the canvas.
6139 type - (string) Possible options are 'fill' or 'stroke'.
6140 pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6141 width - (number) The width of the ellipse.
6142 height - (number) The height of the ellipse.
6143 canvas - (object) A <Canvas> instance.
6147 NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6150 'render': function(type, pos, width, height, canvas){
6151 var ctx = canvas.getCtx();
6155 ctx.scale(width / height, height / width);
6157 ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6166 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6170 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6171 pos - (object) An *x*, *y* object with the position to check.
6172 width - (number) The width of the rendered ellipse.
6173 height - (number) The height of the rendered ellipse.
6177 NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6180 'contains': function(npos, pos, width, height){
6181 // TODO(nico): be more precise...
6184 var dist = (width + height) / 2,
6185 diffx = npos.x - pos.x,
6186 diffy = npos.y - pos.y,
6187 diff = diffx * diffx + diffy * diffy;
6188 return diff <= dist * dist;
6192 Object: NodeHelper.square
6198 Renders a square into the canvas.
6202 type - (string) Possible options are 'fill' or 'stroke'.
6203 pos - (object) An *x*, *y* object with the position of the center of the square.
6204 dim - (number) The radius (or half-diameter) of the square.
6205 canvas - (object) A <Canvas> instance.
6209 NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6212 'render': function(type, pos, dim, canvas){
6213 canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6218 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6222 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6223 pos - (object) An *x*, *y* object with the position to check.
6224 dim - (number) The radius (or half-diameter) of the square.
6228 NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6231 'contains': function(npos, pos, dim){
6232 return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6236 Object: NodeHelper.rectangle
6242 Renders a rectangle into the canvas.
6246 type - (string) Possible options are 'fill' or 'stroke'.
6247 pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6248 width - (number) The width of the rectangle.
6249 height - (number) The height of the rectangle.
6250 canvas - (object) A <Canvas> instance.
6254 NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6257 'render': function(type, pos, width, height, canvas){
6258 canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6264 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6268 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6269 pos - (object) An *x*, *y* object with the position to check.
6270 width - (number) The width of the rendered rectangle.
6271 height - (number) The height of the rendered rectangle.
6275 NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6278 'contains': function(npos, pos, width, height){
6279 return Math.abs(pos.x - npos.x) <= width / 2
6280 && Math.abs(pos.y - npos.y) <= height / 2;
6284 Object: NodeHelper.triangle
6290 Renders a triangle into the canvas.
6294 type - (string) Possible options are 'fill' or 'stroke'.
6295 pos - (object) An *x*, *y* object with the position of the center of the triangle.
6296 dim - (number) The dimension of the triangle.
6297 canvas - (object) A <Canvas> instance.
6301 NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6304 'render': function(type, pos, dim, canvas){
6305 var ctx = canvas.getCtx(),
6313 ctx.moveTo(c1x, c1y);
6314 ctx.lineTo(c2x, c2y);
6315 ctx.lineTo(c3x, c3y);
6322 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6326 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6327 pos - (object) An *x*, *y* object with the position to check.
6328 dim - (number) The dimension of the shape.
6332 NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6335 'contains': function(npos, pos, dim) {
6336 return NodeHelper.circle.contains(npos, pos, dim);
6340 Object: NodeHelper.star
6346 Renders a star into the canvas.
6350 type - (string) Possible options are 'fill' or 'stroke'.
6351 pos - (object) An *x*, *y* object with the position of the center of the star.
6352 dim - (number) The dimension of the star.
6353 canvas - (object) A <Canvas> instance.
6357 NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6360 'render': function(type, pos, dim, canvas){
6361 var ctx = canvas.getCtx(),
6364 ctx.translate(pos.x, pos.y);
6367 for (var i = 0; i < 9; i++) {
6370 ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6382 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6386 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6387 pos - (object) An *x*, *y* object with the position to check.
6388 dim - (number) The dimension of the shape.
6392 NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6395 'contains': function(npos, pos, dim) {
6396 return NodeHelper.circle.contains(npos, pos, dim);
6404 Contains rendering primitives for simple edge shapes.
6408 Object: EdgeHelper.line
6414 Renders a line into the canvas.
6418 from - (object) An *x*, *y* object with the starting position of the line.
6419 to - (object) An *x*, *y* object with the ending position of the line.
6420 canvas - (object) A <Canvas> instance.
6424 EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6427 'render': function(from, to, canvas){
6428 var ctx = canvas.getCtx();
6430 ctx.moveTo(from.x, from.y);
6431 ctx.lineTo(to.x, to.y);
6437 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6441 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6442 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6443 pos - (object) An *x*, *y* object with the position to check.
6444 epsilon - (number) The dimension of the shape.
6448 EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6451 'contains': function(posFrom, posTo, pos, epsilon) {
6454 minPosX = min(posFrom.x, posTo.x),
6455 maxPosX = max(posFrom.x, posTo.x),
6456 minPosY = min(posFrom.y, posTo.y),
6457 maxPosY = max(posFrom.y, posTo.y);
6459 if(pos.x >= minPosX && pos.x <= maxPosX
6460 && pos.y >= minPosY && pos.y <= maxPosY) {
6461 if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6464 var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6465 return Math.abs(dist - pos.y) <= epsilon;
6471 Object: EdgeHelper.arrow
6477 Renders an arrow into the canvas.
6481 from - (object) An *x*, *y* object with the starting position of the arrow.
6482 to - (object) An *x*, *y* object with the ending position of the arrow.
6483 dim - (number) The dimension of the arrow.
6484 swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6485 canvas - (object) A <Canvas> instance.
6489 EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6492 'render': function(from, to, dim, swap, canvas){
6493 var ctx = canvas.getCtx();
6494 // invert edge direction
6500 var vect = new Complex(to.x - from.x, to.y - from.y);
6501 vect.$scale(dim / vect.norm());
6502 var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6503 normal = new Complex(-vect.y / 2, vect.x / 2),
6504 v1 = intermediatePoint.add(normal),
6505 v2 = intermediatePoint.$add(normal.$scale(-1));
6508 ctx.moveTo(from.x, from.y);
6509 ctx.lineTo(to.x, to.y);
6512 ctx.moveTo(v1.x, v1.y);
6513 ctx.lineTo(v2.x, v2.y);
6514 ctx.lineTo(to.x, to.y);
6521 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6525 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6526 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6527 pos - (object) An *x*, *y* object with the position to check.
6528 epsilon - (number) The dimension of the shape.
6532 EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6535 'contains': function(posFrom, posTo, pos, epsilon) {
6536 return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6540 Object: EdgeHelper.hyperline
6546 Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6550 from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6551 to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6552 r - (number) The scaling factor.
6553 canvas - (object) A <Canvas> instance.
6557 EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6560 'render': function(from, to, r, canvas){
6561 var ctx = canvas.getCtx();
6562 var centerOfCircle = computeArcThroughTwoPoints(from, to);
6563 if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6564 || centerOfCircle.ratio < 0) {
6566 ctx.moveTo(from.x * r, from.y * r);
6567 ctx.lineTo(to.x * r, to.y * r);
6570 var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6571 - centerOfCircle.x);
6572 var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6573 - centerOfCircle.x);
6574 var sense = sense(angleBegin, angleEnd);
6576 ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6577 * r, angleBegin, angleEnd, sense);
6581 Calculates the arc parameters through two points.
6583 More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6587 p1 - A <Complex> instance.
6588 p2 - A <Complex> instance.
6589 scale - The Disk's diameter.
6593 An object containing some arc properties.
6595 function computeArcThroughTwoPoints(p1, p2){
6596 var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6597 var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6598 // Fall back to a straight line
6606 var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6607 var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6610 var squaredRatio = (a * a + b * b) / 4 - 1;
6611 // Fall back to a straight line
6612 if (squaredRatio < 0)
6618 var ratio = Math.sqrt(squaredRatio);
6622 ratio: ratio > 1000? -1 : ratio,
6630 Sets angle direction to clockwise (true) or counterclockwise (false).
6634 angleBegin - Starting angle for drawing the arc.
6635 angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6639 A Boolean instance describing the sense for drawing the HyperLine.
6641 function sense(angleBegin, angleEnd){
6642 return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6643 : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6651 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6655 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6656 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6657 pos - (object) An *x*, *y* object with the position to check.
6658 epsilon - (number) The dimension of the shape.
6662 EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6665 'contains': $.lambda(false)
6671 * File: Graph.Plot.js
6677 <Graph> rendering and animation methods.
6681 nodeHelper - <NodeHelper> object.
6682 edgeHelper - <EdgeHelper> object.
6685 //Default intializer
6686 initialize: function(viz, klass){
6688 this.config = viz.config;
6689 this.node = viz.config.Node;
6690 this.edge = viz.config.Edge;
6691 this.animation = new Animation;
6692 this.nodeTypes = new klass.Plot.NodeTypes;
6693 this.edgeTypes = new klass.Plot.EdgeTypes;
6694 this.labels = viz.labels;
6698 nodeHelper: NodeHelper,
6699 edgeHelper: EdgeHelper,
6702 //node/edge property parsers
6710 'lineWidth': 'number',
6711 'angularWidth':'number',
6713 'valueArray':'array-number',
6714 'dimArray':'array-number'
6715 //'colorArray':'array-color'
6718 //canvas specific parsers
6720 'globalAlpha': 'number',
6721 'fillStyle': 'color',
6722 'strokeStyle': 'color',
6723 'lineWidth': 'number',
6724 'shadowBlur': 'number',
6725 'shadowColor': 'color',
6726 'shadowOffsetX': 'number',
6727 'shadowOffsetY': 'number',
6728 'miterLimit': 'number'
6737 //Number interpolator
6738 'compute': function(from, to, delta) {
6739 return from + (to - from) * delta;
6742 //Position interpolators
6743 'moebius': function(elem, props, delta, vector) {
6744 var v = vector.scale(-delta);
6746 var x = v.x, y = v.y;
6747 var ans = elem.startPos
6748 .getc().moebiusTransformation(v);
6749 elem.pos.setc(ans.x, ans.y);
6754 'linear': function(elem, props, delta) {
6755 var from = elem.startPos.getc(true);
6756 var to = elem.endPos.getc(true);
6757 elem.pos.setc(this.compute(from.x, to.x, delta),
6758 this.compute(from.y, to.y, delta));
6761 'polar': function(elem, props, delta) {
6762 var from = elem.startPos.getp(true);
6763 var to = elem.endPos.getp();
6764 var ans = to.interpolate(from, delta);
6765 elem.pos.setp(ans.theta, ans.rho);
6768 //Graph's Node/Edge interpolators
6769 'number': function(elem, prop, delta, getter, setter) {
6770 var from = elem[getter](prop, 'start');
6771 var to = elem[getter](prop, 'end');
6772 elem[setter](prop, this.compute(from, to, delta));
6775 'color': function(elem, prop, delta, getter, setter) {
6776 var from = $.hexToRgb(elem[getter](prop, 'start'));
6777 var to = $.hexToRgb(elem[getter](prop, 'end'));
6778 var comp = this.compute;
6779 var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6780 parseInt(comp(from[1], to[1], delta)),
6781 parseInt(comp(from[2], to[2], delta))]);
6783 elem[setter](prop, val);
6786 'array-number': function(elem, prop, delta, getter, setter) {
6787 var from = elem[getter](prop, 'start'),
6788 to = elem[getter](prop, 'end'),
6790 for(var i=0, l=from.length; i<l; i++) {
6791 var fromi = from[i], toi = to[i];
6793 for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6794 curi.push(this.compute(fromi[j], toi[j], delta));
6798 cur.push(this.compute(fromi, toi, delta));
6801 elem[setter](prop, cur);
6804 'node': function(elem, props, delta, map, getter, setter) {
6807 var len = props.length;
6808 for(var i=0; i<len; i++) {
6810 this[map[pi]](elem, pi, delta, getter, setter);
6813 for(var pi in map) {
6814 this[map[pi]](elem, pi, delta, getter, setter);
6819 'edge': function(elem, props, delta, mapKey, getter, setter) {
6820 var adjs = elem.adjacencies;
6821 for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6824 'node-property': function(elem, props, delta) {
6825 this['node'](elem, props, delta, 'map', 'getData', 'setData');
6828 'edge-property': function(elem, props, delta) {
6829 this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6832 'label-property': function(elem, props, delta) {
6833 this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6836 'node-style': function(elem, props, delta) {
6837 this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6840 'edge-style': function(elem, props, delta) {
6841 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6849 Iteratively performs an action while refreshing the state of the visualization.
6853 options - (object) An object containing some sequence options described below
6854 condition - (function) A function returning a boolean instance in order to stop iterations.
6855 step - (function) A function to execute on each step of the iteration.
6856 onComplete - (function) A function to execute when the sequence finishes.
6857 duration - (number) Duration (in milliseconds) of each step.
6861 var rg = new $jit.RGraph(options);
6864 condition: function() {
6870 onComplete: function() {
6877 sequence: function(options) {
6880 condition: $.lambda(false),
6882 onComplete: $.empty,
6886 var interval = setInterval(function() {
6887 if(options.condition()) {
6890 clearInterval(interval);
6891 options.onComplete();
6893 that.viz.refresh(true);
6894 }, options.duration);
6900 Prepare graph position and other attribute values before performing an Animation.
6901 This method is used internally by the Toolkit.
6905 <Animation>, <Graph.Plot.animate>
6908 prepare: function(modes) {
6909 var graph = this.viz.graph,
6912 'getter': 'getData',
6916 'getter': 'getData',
6920 'getter': 'getCanvasStyle',
6921 'setter': 'setCanvasStyle'
6924 'getter': 'getCanvasStyle',
6925 'setter': 'setCanvasStyle'
6931 if($.type(modes) == 'array') {
6932 for(var i=0, len=modes.length; i < len; i++) {
6933 var elems = modes[i].split(':');
6934 m[elems.shift()] = elems;
6937 for(var p in modes) {
6938 if(p == 'position') {
6939 m[modes.position] = [];
6941 m[p] = $.splat(modes[p]);
6946 graph.eachNode(function(node) {
6947 node.startPos.set(node.pos);
6948 $.each(['node-property', 'node-style'], function(p) {
6951 for(var i=0, l=prop.length; i < l; i++) {
6952 node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6956 $.each(['edge-property', 'edge-style'], function(p) {
6959 node.eachAdjacency(function(adj) {
6960 for(var i=0, l=prop.length; i < l; i++) {
6961 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6973 Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6977 opt - (object) Animation options. The object properties are described below
6978 duration - (optional) Described in <Options.Fx>.
6979 fps - (optional) Described in <Options.Fx>.
6980 hideLabels - (optional|boolean) Whether to hide labels during the animation.
6981 modes - (required|object) An object with animation modes (described below).
6985 Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6986 They are represented by an object that has as keys main categories of properties to animate and as values a list
6987 of these specific properties. The properties are described below
6989 position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6990 node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6991 edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6992 label-property - Describes which Label properties will be interpolated. These properties can be any of the ones defined in <Options.Label> like color or size.
6993 node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6994 edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6998 var viz = new $jit.Viz(options);
6999 //...tweak some Data, CanvasStyles or LabelData properties...
7002 'position': 'linear',
7003 'node-property': ['width', 'height'],
7004 'node-style': 'shadowColor',
7005 'label-property': 'size'
7009 //...can also be written like this...
7012 'node-property:width:height',
7013 'node-style:shadowColor',
7014 'label-property:size'],
7019 animate: function(opt, versor) {
7020 opt = $.merge(this.viz.config, opt || {});
7024 interp = this.Interpolator,
7025 animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7026 //prepare graph values
7027 var m = this.prepare(opt.modes);
7030 if(opt.hideLabels) this.labels.hideLabels(true);
7031 animation.setOptions($.merge(opt, {
7033 compute: function(delta) {
7034 graph.eachNode(function(node) {
7036 interp[p](node, m[p], delta, versor);
7039 that.plot(opt, this.$animating, delta);
7040 this.$animating = true;
7042 complete: function() {
7043 if(opt.hideLabels) that.labels.hideLabels(false);
7046 opt.onAfterCompute();
7054 Apply animation to node properties like color, width, height, dim, etc.
7058 options - Animation options. This object properties is described below
7059 elements - The Elements to be transformed. This is an object that has a properties
7063 //can also be an array of ids
7064 'id': 'id-of-node-to-transform',
7065 //properties to be modified. All properties are optional.
7067 'color': '#ccc', //some color
7068 'width': 10, //some width
7069 'height': 10, //some height
7070 'dim': 20, //some dim
7071 'lineWidth': 10 //some line width
7076 - _reposition_ Whether to recalculate positions and add a motion animation.
7077 This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7079 - _onComplete_ A method that is called when the animation completes.
7081 ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7085 var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7092 'transition': Trans.Quart.easeOut
7097 nodeFx: function(opt) {
7100 animation = this.nodeFxAnimation,
7101 options = $.merge(this.viz.config, {
7108 opt = $.merge(options, opt || {}, {
7109 onBeforeCompute: $.empty,
7110 onAfterCompute: $.empty
7112 //check if an animation is running
7113 animation.stopTimer();
7114 var props = opt.elements.properties;
7115 //set end values for nodes
7116 if(!opt.elements.id) {
7117 graph.eachNode(function(n) {
7118 for(var prop in props) {
7119 n.setData(prop, props[prop], 'end');
7123 var ids = $.splat(opt.elements.id);
7124 $.each(ids, function(id) {
7125 var n = graph.getNode(id);
7127 for(var prop in props) {
7128 n.setData(prop, props[prop], 'end');
7135 for(var prop in props) propnames.push(prop);
7136 //add node properties modes
7137 var modes = ['node-property:' + propnames.join(':')];
7138 //set new node positions
7139 if(opt.reposition) {
7140 modes.push('linear');
7144 this.animate($.merge(opt, {
7158 opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7163 var viz = new $jit.Viz(options);
7168 plot: function(opt, animating) {
7171 canvas = viz.canvas,
7174 ctx = canvas.getCtx(),
7176 opt = opt || this.viz.controller;
7177 opt.clearCanvas && canvas.clear();
7179 var root = aGraph.getNode(id);
7182 var T = !!root.visited;
7183 aGraph.eachNode(function(node) {
7184 var nodeAlpha = node.getData('alpha');
7185 node.eachAdjacency(function(adj) {
7186 var nodeTo = adj.nodeTo;
7187 if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7188 !animating && opt.onBeforePlotLine(adj);
7190 ctx.globalAlpha = min(nodeAlpha,
7191 nodeTo.getData('alpha'),
7192 adj.getData('alpha'));
7193 that.plotLine(adj, canvas, animating);
7195 !animating && opt.onAfterPlotLine(adj);
7200 !animating && opt.onBeforePlotNode(node);
7201 that.plotNode(node, canvas, animating);
7202 !animating && opt.onAfterPlotNode(node);
7204 if(!that.labelsHidden && opt.withLabels) {
7205 if(node.drawn && nodeAlpha >= 0.95) {
7206 that.labels.plotLabel(canvas, node, opt);
7208 that.labels.hideLabel(node, false);
7219 plotTree: function(node, opt, animating) {
7222 canvas = viz.canvas,
7223 config = this.config,
7224 ctx = canvas.getCtx();
7225 var nodeAlpha = node.getData('alpha');
7226 node.eachSubnode(function(elem) {
7227 if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7228 var adj = node.getAdjacency(elem.id);
7229 !animating && opt.onBeforePlotLine(adj);
7230 ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7231 that.plotLine(adj, canvas, animating);
7232 !animating && opt.onAfterPlotLine(adj);
7233 that.plotTree(elem, opt, animating);
7237 !animating && opt.onBeforePlotNode(node);
7238 this.plotNode(node, canvas, animating);
7239 !animating && opt.onAfterPlotNode(node);
7240 if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7241 this.labels.plotLabel(canvas, node, opt);
7243 this.labels.hideLabel(node, false);
7245 this.labels.hideLabel(node, true);
7252 Plots a <Graph.Node>.
7256 node - (object) A <Graph.Node>.
7257 canvas - (object) A <Canvas> element.
7260 plotNode: function(node, canvas, animating) {
7261 var f = node.getData('type'),
7262 ctxObj = this.node.CanvasStyles;
7264 var width = node.getData('lineWidth'),
7265 color = node.getData('color'),
7266 alpha = node.getData('alpha'),
7267 ctx = canvas.getCtx();
7269 ctx.lineWidth = width;
7270 ctx.fillStyle = ctx.strokeStyle = color;
7271 ctx.globalAlpha = alpha;
7273 for(var s in ctxObj) {
7274 ctx[s] = node.getCanvasStyle(s);
7277 this.nodeTypes[f].render.call(this, node, canvas, animating);
7284 Plots a <Graph.Adjacence>.
7288 adj - (object) A <Graph.Adjacence>.
7289 canvas - (object) A <Canvas> instance.
7292 plotLine: function(adj, canvas, animating) {
7293 var f = adj.getData('type'),
7294 ctxObj = this.edge.CanvasStyles;
7296 var width = adj.getData('lineWidth'),
7297 color = adj.getData('color'),
7298 ctx = canvas.getCtx();
7300 ctx.lineWidth = width;
7301 ctx.fillStyle = ctx.strokeStyle = color;
7303 for(var s in ctxObj) {
7304 ctx[s] = adj.getCanvasStyle(s);
7307 this.edgeTypes[f].render.call(this, adj, canvas, animating);
7316 * File: Graph.Label.js
7323 An interface for plotting/hiding/showing labels.
7327 This is a generic interface for plotting/hiding/showing labels.
7328 The <Graph.Label> interface is implemented in multiple ways to provide
7329 different label types.
7331 For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7332 HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7333 The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7335 All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7341 Class: Graph.Label.Native
7343 Implements labels natively, using the Canvas text API.
7345 Graph.Label.Native = new Class({
7349 Plots a label for a given node.
7353 canvas - (object) A <Canvas> instance.
7354 node - (object) A <Graph.Node>.
7355 controller - (object) A configuration object.
7360 var viz = new $jit.Viz(options);
7361 var node = viz.graph.getNode('nodeId');
7362 viz.labels.plotLabel(viz.canvas, node, viz.config);
7365 plotLabel: function(canvas, node, controller) {
7366 var ctx = canvas.getCtx();
7367 var pos = node.pos.getc(true);
7369 ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7370 ctx.textAlign = node.getLabelData('textAlign');
7371 ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7372 ctx.textBaseline = node.getLabelData('textBaseline');
7374 this.renderLabel(canvas, node, controller);
7380 Does the actual rendering of the label in the canvas. The default
7381 implementation renders the label close to the position of the node, this
7382 method should be overriden to position the labels differently.
7386 canvas - A <Canvas> instance.
7387 node - A <Graph.Node>.
7388 controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7390 renderLabel: function(canvas, node, controller) {
7391 var ctx = canvas.getCtx();
7392 var pos = node.pos.getc(true);
7393 ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7401 Class: Graph.Label.DOM
7403 Abstract Class implementing some DOM label methods.
7407 <Graph.Label.HTML> and <Graph.Label.SVG>.
7410 Graph.Label.DOM = new Class({
7411 //A flag value indicating if node labels are being displayed or not.
7412 labelsHidden: false,
7414 labelContainer: false,
7415 //Label elements hash.
7419 Method: getLabelContainer
7421 Lazy fetcher for the label container.
7425 The label container DOM element.
7430 var viz = new $jit.Viz(options);
7431 var labelContainer = viz.labels.getLabelContainer();
7432 alert(labelContainer.innerHTML);
7435 getLabelContainer: function() {
7436 return this.labelContainer ?
7437 this.labelContainer :
7438 this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7444 Lazy fetcher for the label element.
7448 id - (string) The label id (which is also a <Graph.Node> id).
7457 var viz = new $jit.Viz(options);
7458 var label = viz.labels.getLabel('someid');
7459 alert(label.innerHTML);
7463 getLabel: function(id) {
7464 return (id in this.labels && this.labels[id] != null) ?
7466 this.labels[id] = document.getElementById(id);
7472 Hides all labels (by hiding the label container).
7476 hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7480 var viz = new $jit.Viz(options);
7481 rg.labels.hideLabels(true);
7485 hideLabels: function (hide) {
7486 var container = this.getLabelContainer();
7488 container.style.display = 'none';
7490 container.style.display = '';
7491 this.labelsHidden = hide;
7497 Clears the label container.
7499 Useful when using a new visualization with the same canvas element/widget.
7503 force - (boolean) Forces deletion of all labels.
7507 var viz = new $jit.Viz(options);
7508 viz.labels.clearLabels();
7511 clearLabels: function(force) {
7512 for(var id in this.labels) {
7513 if (force || !this.viz.graph.hasNode(id)) {
7514 this.disposeLabel(id);
7515 delete this.labels[id];
7521 Method: disposeLabel
7527 id - (string) A label id (which generally is also a <Graph.Node> id).
7531 var viz = new $jit.Viz(options);
7532 viz.labels.disposeLabel('labelid');
7535 disposeLabel: function(id) {
7536 var elem = this.getLabel(id);
7537 if(elem && elem.parentNode) {
7538 elem.parentNode.removeChild(elem);
7545 Hides the corresponding <Graph.Node> label.
7549 node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7550 show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7554 var rg = new $jit.Viz(options);
7555 viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7558 hideLabel: function(node, show) {
7559 node = $.splat(node);
7560 var st = show ? "" : "none", lab, that = this;
7561 $.each(node, function(n) {
7562 var lab = that.getLabel(n.id);
7564 lab.style.display = st;
7572 Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7576 pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7577 canvas - A <Canvas> instance.
7581 A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7584 fitsInCanvas: function(pos, canvas) {
7585 var size = canvas.getSize();
7586 if(pos.x >= size.width || pos.x < 0
7587 || pos.y >= size.height || pos.y < 0) return false;
7593 Class: Graph.Label.HTML
7595 Implements HTML labels.
7599 All <Graph.Label.DOM> methods.
7602 Graph.Label.HTML = new Class({
7603 Implements: Graph.Label.DOM,
7608 Plots a label for a given node.
7612 canvas - (object) A <Canvas> instance.
7613 node - (object) A <Graph.Node>.
7614 controller - (object) A configuration object.
7619 var viz = new $jit.Viz(options);
7620 var node = viz.graph.getNode('nodeId');
7621 viz.labels.plotLabel(viz.canvas, node, viz.config);
7626 plotLabel: function(canvas, node, controller) {
7627 var id = node.id, tag = this.getLabel(id);
7629 if(!tag && !(tag = document.getElementById(id))) {
7630 tag = document.createElement('div');
7631 var container = this.getLabelContainer();
7633 tag.className = 'node';
7634 tag.style.position = 'absolute';
7635 controller.onCreateLabel(tag, node);
7636 container.appendChild(tag);
7637 this.labels[node.id] = tag;
7640 this.placeLabel(tag, node, controller);
7645 Class: Graph.Label.SVG
7647 Implements SVG labels.
7651 All <Graph.Label.DOM> methods.
7653 Graph.Label.SVG = new Class({
7654 Implements: Graph.Label.DOM,
7659 Plots a label for a given node.
7663 canvas - (object) A <Canvas> instance.
7664 node - (object) A <Graph.Node>.
7665 controller - (object) A configuration object.
7670 var viz = new $jit.Viz(options);
7671 var node = viz.graph.getNode('nodeId');
7672 viz.labels.plotLabel(viz.canvas, node, viz.config);
7677 plotLabel: function(canvas, node, controller) {
7678 var id = node.id, tag = this.getLabel(id);
7679 if(!tag && !(tag = document.getElementById(id))) {
7680 var ns = 'http://www.w3.org/2000/svg';
7681 tag = document.createElementNS(ns, 'svg:text');
7682 var tspan = document.createElementNS(ns, 'svg:tspan');
7683 tag.appendChild(tspan);
7684 var container = this.getLabelContainer();
7685 tag.setAttribute('id', id);
7686 tag.setAttribute('class', 'node');
7687 container.appendChild(tag);
7688 controller.onCreateLabel(tag, node);
7689 this.labels[node.id] = tag;
7691 this.placeLabel(tag, node, controller);
7697 Graph.Geom = new Class({
7699 initialize: function(viz) {
7701 this.config = viz.config;
7702 this.node = viz.config.Node;
7703 this.edge = viz.config.Edge;
7706 Applies a translation to the tree.
7710 pos - A <Complex> number specifying translation vector.
7711 prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7716 st.geom.translate(new Complex(300, 100), 'end');
7719 translate: function(pos, prop) {
7720 prop = $.splat(prop);
7721 this.viz.graph.eachNode(function(elem) {
7722 $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7726 Hides levels of the tree until it properly fits in canvas.
7728 setRightLevelToShow: function(node, canvas, callback) {
7729 var level = this.getRightLevelToShow(node, canvas),
7730 fx = this.viz.labels,
7737 node.eachLevel(0, this.config.levelsToShow, function(n) {
7738 var d = n._depth - node._depth;
7744 fx.hideLabel(n, false);
7756 Returns the right level to show for the current tree in order to fit in canvas.
7758 getRightLevelToShow: function(node, canvas) {
7759 var config = this.config;
7760 var level = config.levelsToShow;
7761 var constrained = config.constrained;
7762 if(!constrained) return level;
7763 while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7776 Provides methods for loading and serving JSON data.
7779 construct: function(json) {
7780 var isGraph = ($.type(json) == 'array');
7781 var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7784 (function (ans, json) {
7787 for(var i=0, ch = json.children; i<ch.length; i++) {
7788 ans.addAdjacence(json, ch[i]);
7789 arguments.callee(ans, ch[i]);
7795 (function (ans, json) {
7796 var getNode = function(id) {
7797 for(var i=0, l=json.length; i<l; i++) {
7798 if(json[i].id == id) {
7802 // The node was not defined in the JSON
7808 return ans.addNode(newNode);
7811 for(var i=0, l=json.length; i<l; i++) {
7812 ans.addNode(json[i]);
7813 var adj = json[i].adjacencies;
7815 for(var j=0, lj=adj.length; j<lj; j++) {
7816 var node = adj[j], data = {};
7817 if(typeof adj[j] != 'string') {
7818 data = $.merge(node.data, {});
7821 ans.addAdjacence(json[i], getNode(node), data);
7833 Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7835 A JSON tree or graph structure consists of nodes, each having as properties
7837 id - (string) A unique identifier for the node
7838 name - (string) A node's name
7839 data - (object) The data optional property contains a hash (i.e {})
7840 where you can store all the information you want about this node.
7842 For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7848 "id": "aUniqueIdentifier",
7849 "name": "usually a nodes name",
7851 "some key": "some value",
7852 "some other key": "some other value"
7854 "children": [ *other nodes or empty* ]
7858 JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7859 For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7861 There are two types of *Graph* structures, *simple* and *extended* graph structures.
7863 For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7864 id of the node connected to the main node.
7871 "id": "aUniqueIdentifier",
7872 "name": "usually a nodes name",
7874 "some key": "some value",
7875 "some other key": "some other value"
7877 "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7880 'other nodes go here...'
7884 For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7886 nodeTo - (string) The other node connected by this adjacency.
7887 data - (object) A data property, where we can store custom key/value information.
7894 "id": "aUniqueIdentifier",
7895 "name": "usually a nodes name",
7897 "some key": "some value",
7898 "some other key": "some other value"
7903 data: {} //put whatever you want here
7905 'other adjacencies go here...'
7908 'other nodes go here...'
7912 About the data property:
7914 As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
7915 You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
7916 have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7918 For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
7919 <Options.Node> will override the general value for that option with that particular value. For this to work
7920 however, you do have to set *overridable = true* in <Options.Node>.
7922 The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
7923 if <Options.Edge> has *overridable = true*.
7925 When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
7926 since this is the value which will be taken into account when creating the layout.
7927 The same thing goes for the *$color* parameter.
7929 In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
7930 *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
7931 canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
7932 to the *shadowBlur* property.
7934 These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
7935 by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7937 Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
7938 information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7940 loadJSON Parameters:
7942 json - A JSON Tree or Graph structure.
7943 i - For Graph structures only. Sets the indexed node as root for the visualization.
7946 loadJSON: function(json, i) {
7948 //if they're canvas labels erase them.
7949 if(this.labels && this.labels.clearLabels) {
7950 this.labels.clearLabels(true);
7952 this.graph = this.construct(json);
7953 if($.type(json) != 'array'){
7954 this.root = json.id;
7956 this.root = json[i? i : 0].id;
7963 Returns a JSON tree/graph structure from the visualization's <Graph>.
7964 See <Loader.loadJSON> for the graph formats available.
7972 type - (string) Default's "tree". The type of the JSON structure to be returned.
7973 Possible options are "tree" or "graph".
7975 toJSON: function(type) {
7976 type = type || "tree";
7977 if(type == 'tree') {
7979 var rootNode = this.graph.getNode(this.root);
7980 var ans = (function recTree(node) {
7983 ans.name = node.name;
7984 ans.data = node.data;
7986 node.eachSubnode(function(n) {
7987 ch.push(recTree(n));
7995 var T = !!this.graph.getNode(this.root).visited;
7996 this.graph.eachNode(function(node) {
7998 ansNode.id = node.id;
7999 ansNode.name = node.name;
8000 ansNode.data = node.data;
8002 node.eachAdjacency(function(adj) {
8003 var nodeTo = adj.nodeTo;
8004 if(!!nodeTo.visited === T) {
8006 ansAdj.nodeTo = nodeTo.id;
8007 ansAdj.data = adj.data;
8011 ansNode.adjacencies = adjs;
8025 * Implements base Tree and Graph layouts.
8029 * Implements base Tree and Graph layouts like Radial, Tree, etc.
8036 * Parent object for common layouts.
8039 var Layouts = $jit.Layouts = {};
8042 //Some util shared layout functions are defined here.
8046 compute: function(graph, prop, opt) {
8047 this.initializeLabel(opt);
8048 var label = this.label, style = label.style;
8049 graph.eachNode(function(n) {
8050 var autoWidth = n.getData('autoWidth'),
8051 autoHeight = n.getData('autoHeight');
8052 if(autoWidth || autoHeight) {
8053 //delete dimensions since these are
8054 //going to be overridden now.
8055 delete n.data.$width;
8056 delete n.data.$height;
8059 var width = n.getData('width'),
8060 height = n.getData('height');
8061 //reset label dimensions
8062 style.width = autoWidth? 'auto' : width + 'px';
8063 style.height = autoHeight? 'auto' : height + 'px';
8065 //TODO(nico) should let the user choose what to insert here.
8066 label.innerHTML = n.name;
8068 var offsetWidth = label.offsetWidth,
8069 offsetHeight = label.offsetHeight;
8070 var type = n.getData('type');
8071 if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8072 n.setData('width', offsetWidth);
8073 n.setData('height', offsetHeight);
8075 var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8076 n.setData('width', dim);
8077 n.setData('height', dim);
8078 n.setData('dim', dim);
8084 initializeLabel: function(opt) {
8086 this.label = document.createElement('div');
8087 document.body.appendChild(this.label);
8089 this.setLabelStyles(opt);
8092 setLabelStyles: function(opt) {
8093 $.extend(this.label.style, {
8094 'visibility': 'hidden',
8095 'position': 'absolute',
8099 this.label.className = 'jit-autoadjust-label';
8105 * Class: Layouts.Tree
8107 * Implements a Tree Layout.
8115 * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8118 Layouts.Tree = (function() {
8120 var slice = Array.prototype.slice;
8123 Calculates the max width and height nodes for a tree level
8125 function getBoundaries(graph, config, level, orn, prop) {
8126 var dim = config.Node;
8127 var multitree = config.multitree;
8128 if (dim.overridable) {
8130 graph.eachNode(function(n) {
8131 if (n._depth == level
8132 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8133 var dw = n.getData('width', prop);
8134 var dh = n.getData('height', prop);
8135 w = (w < dw) ? dw : w;
8136 h = (h < dh) ? dh : h;
8140 'width' : w < 0 ? dim.width : w,
8141 'height' : h < 0 ? dim.height : h
8149 function movetree(node, prop, val, orn) {
8150 var p = (orn == "left" || orn == "right") ? "y" : "x";
8151 node.getPos(prop)[p] += val;
8155 function moveextent(extent, val) {
8157 $.each(extent, function(elem) {
8158 elem = slice.call(elem);
8167 function merge(ps, qs) {
8172 var p = ps.shift(), q = qs.shift();
8173 return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8177 function mergelist(ls, def) {
8182 return mergelist(ls, merge(ps, def));
8186 function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8187 if (ext1.length <= i || ext2.length <= i)
8190 var p = ext1[i][1], q = ext2[i][0];
8191 return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8192 + subtreeOffset, p - q + siblingOffset);
8196 function fitlistl(es, subtreeOffset, siblingOffset) {
8197 function $fitlistl(acc, es, i) {
8200 var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8201 return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8204 return $fitlistl( [], es, 0);
8208 function fitlistr(es, subtreeOffset, siblingOffset) {
8209 function $fitlistr(acc, es, i) {
8212 var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8213 return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8216 es = slice.call(es);
8217 var ans = $fitlistr( [], es.reverse(), 0);
8218 return ans.reverse();
8222 function fitlist(es, subtreeOffset, siblingOffset, align) {
8223 var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8224 subtreeOffset, siblingOffset);
8226 if (align == "left")
8228 else if (align == "right")
8231 for ( var i = 0, ans = []; i < esl.length; i++) {
8232 ans[i] = (esl[i] + esr[i]) / 2;
8238 function design(graph, node, prop, config, orn) {
8239 var multitree = config.multitree;
8240 var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8241 var ind = +(orn == "left" || orn == "right");
8242 var p = auxp[ind], notp = auxp[1 - ind];
8244 var cnode = config.Node;
8245 var s = auxs[ind], nots = auxs[1 - ind];
8247 var siblingOffset = config.siblingOffset;
8248 var subtreeOffset = config.subtreeOffset;
8249 var align = config.align;
8251 function $design(node, maxsize, acum) {
8252 var sval = node.getData(s, prop);
8253 var notsval = maxsize
8254 || (node.getData(nots, prop));
8256 var trees = [], extents = [], chmaxsize = false;
8257 var chacum = notsval + config.levelDistance;
8258 node.eachSubnode(function(n) {
8260 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8263 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8265 var s = $design(n, chmaxsize[nots], acum + chacum);
8267 extents.push(s.extent);
8270 var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8271 for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8272 movetree(trees[i], prop, positions[i], orn);
8273 pextents.push(moveextent(extents[i], positions[i]));
8275 var resultextent = [ [ -sval / 2, sval / 2 ] ]
8276 .concat(mergelist(pextents));
8277 node.getPos(prop)[p] = 0;
8279 if (orn == "top" || orn == "left") {
8280 node.getPos(prop)[notp] = acum;
8282 node.getPos(prop)[notp] = -acum;
8287 extent : resultextent
8291 $design(node, false, 0);
8299 Computes nodes' positions.
8302 compute : function(property, computeLevels) {
8303 var prop = property || 'start';
8304 var node = this.graph.getNode(this.root);
8310 NodeDim.compute(this.graph, prop, this.config);
8311 if (!!computeLevels || !("_depth" in node)) {
8312 this.graph.computeLevels(this.root, 0, "ignore");
8315 this.computePositions(node, prop);
8318 computePositions : function(node, prop) {
8319 var config = this.config;
8320 var multitree = config.multitree;
8321 var align = config.align;
8322 var indent = align !== 'center' && config.indent;
8323 var orn = config.orientation;
8324 var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8326 $.each(orns, function(orn) {
8328 design(that.graph, node, prop, that.config, orn, prop);
8329 var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8331 (function red(node) {
8332 node.eachSubnode(function(n) {
8334 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8336 n.getPos(prop)[i] += node.getPos(prop)[i];
8338 n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8351 * File: Spacetree.js
8357 A Tree layout with advanced contraction and expansion animations.
8361 SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8362 <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8364 Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8368 This visualization was built and engineered from scratch, taking only the papers as inspiration, and only shares some features with the visualization described in those papers.
8372 All <Loader> methods
8374 Constructor Options:
8376 Inherits options from
8379 - <Options.Controller>
8386 - <Options.NodeStyles>
8387 - <Options.Navigation>
8389 Additionally, there are other parameters and some default values changed
8391 constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8392 levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8393 levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8394 Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8395 offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8396 offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8397 duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8399 Instance Properties:
8401 canvas - Access a <Canvas> instance.
8402 graph - Access a <Graph> instance.
8403 op - Access a <ST.Op> instance.
8404 fx - Access a <ST.Plot> instance.
8405 labels - Access a <ST.Label> interface implementation.
8409 $jit.ST= (function() {
8410 // Define some private methods first...
8412 var nodesInPath = [];
8413 // Nodes to contract
8414 function getNodesToHide(node) {
8415 node = node || this.clickedNode;
8416 if(!this.config.constrained) {
8419 var Geom = this.geom;
8420 var graph = this.graph;
8421 var canvas = this.canvas;
8422 var level = node._depth, nodeArray = [];
8423 graph.eachNode(function(n) {
8424 if(n.exist && !n.selected) {
8425 if(n.isDescendantOf(node.id)) {
8426 if(n._depth <= level) nodeArray.push(n);
8432 var leafLevel = Geom.getRightLevelToShow(node, canvas);
8433 node.eachLevel(leafLevel, leafLevel, function(n) {
8434 if(n.exist && !n.selected) nodeArray.push(n);
8437 for (var i = 0; i < nodesInPath.length; i++) {
8438 var n = this.graph.getNode(nodesInPath[i]);
8439 if(!n.isDescendantOf(node.id)) {
8446 function getNodesToShow(node) {
8447 var nodeArray = [], config = this.config;
8448 node = node || this.clickedNode;
8449 this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8450 if(config.multitree && !('$orn' in n.data)
8451 && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8453 } else if(n.drawn && !n.anySubnode("drawn")) {
8459 // Now define the actual class.
8462 Implements: [Loader, Extras, Layouts.Tree],
8464 initialize: function(controller) {
8479 this.controller = this.config = $.merge(
8480 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8481 "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8483 var canvasConfig = this.config;
8484 if(canvasConfig.useCanvas) {
8485 this.canvas = canvasConfig.useCanvas;
8486 this.config.labelContainer = this.canvas.id + '-label';
8488 if(canvasConfig.background) {
8489 canvasConfig.background = $.merge({
8491 colorStop1: this.config.colorStop1,
8492 colorStop2: this.config.colorStop2
8493 }, canvasConfig.background);
8495 this.canvas = new Canvas(this, canvasConfig);
8496 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8499 this.graphOptions = {
8502 this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8503 this.labels = new $ST.Label[canvasConfig.Label.type](this);
8504 this.fx = new $ST.Plot(this, $ST);
8505 this.op = new $ST.Op(this);
8506 this.group = new $ST.Group(this);
8507 this.geom = new $ST.Geom(this);
8508 this.clickedNode= null;
8509 // initialize extras
8510 this.initializeExtras();
8516 Plots the <ST>. This is a shortcut to *fx.plot*.
8519 plot: function() { this.fx.plot(this.controller); },
8523 Method: switchPosition
8525 Switches the tree orientation.
8529 pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8530 method - (string) Set this to "animate" if you want to animate the tree when switching its position. You can also set this parameter to "replot" to just replot the subtree.
8531 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8536 st.switchPosition("right", "animate", {
8537 onComplete: function() {
8538 alert('completed!');
8543 switchPosition: function(pos, method, onComplete) {
8544 var Geom = this.geom, Plot = this.fx, that = this;
8548 onComplete: function() {
8549 Geom.switchOrientation(pos);
8550 that.compute('end', false);
8552 if(method == 'animate') {
8553 that.onClick(that.clickedNode.id, onComplete);
8554 } else if(method == 'replot') {
8555 that.select(that.clickedNode.id, onComplete);
8563 Method: switchAlignment
8565 Switches the tree alignment.
8569 align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8570 method - (string) Set this to "animate" if you want to animate the tree after aligning its position. You can also set this parameter to "replot" to just replot the subtree.
8571 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8576 st.switchAlignment("right", "animate", {
8577 onComplete: function() {
8578 alert('completed!');
8583 switchAlignment: function(align, method, onComplete) {
8584 this.config.align = align;
8585 if(method == 'animate') {
8586 this.select(this.clickedNode.id, onComplete);
8587 } else if(method == 'replot') {
8588 this.onClick(this.clickedNode.id, onComplete);
8593 Method: addNodeInPath
8595 Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8600 id - (string) A <Graph.Node> id.
8605 st.addNodeInPath("nodeId");
8608 addNodeInPath: function(id) {
8609 nodesInPath.push(id);
8610 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8614 Method: clearNodesInPath
8616 Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8625 st.clearNodesInPath();
8628 clearNodesInPath: function(id) {
8629 nodesInPath.length = 0;
8630 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8636 Computes positions and plots the tree.
8639 refresh: function() {
8641 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8644 reposition: function() {
8645 this.graph.computeLevels(this.root, 0, "ignore");
8646 this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8647 this.graph.eachNode(function(n) {
8648 if(n.exist) n.drawn = true;
8650 this.compute('end');
8653 requestNodes: function(node, onComplete) {
8654 var handler = $.merge(this.controller, onComplete),
8655 lev = this.config.levelsToShow;
8656 if(handler.request) {
8657 var leaves = [], d = node._depth;
8658 node.eachLevel(0, lev, function(n) {
8662 n._level = lev - (n._depth - d);
8665 this.group.requestNodes(leaves, handler);
8668 handler.onComplete();
8671 contract: function(onComplete, switched) {
8672 var orn = this.config.orientation;
8673 var Geom = this.geom, Group = this.group;
8674 if(switched) Geom.switchOrientation(switched);
8675 var nodes = getNodesToHide.call(this);
8676 if(switched) Geom.switchOrientation(orn);
8677 Group.contract(nodes, $.merge(this.controller, onComplete));
8680 move: function(node, onComplete) {
8681 this.compute('end', false);
8682 var move = onComplete.Move, offset = {
8687 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8689 this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8692 expand: function (node, onComplete) {
8693 var nodeArray = getNodesToShow.call(this, node);
8694 this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8697 selectPath: function(node) {
8699 this.graph.eachNode(function(n) { n.selected = false; });
8700 function path(node) {
8701 if(node == null || node.selected) return;
8702 node.selected = true;
8703 $.each(that.group.getSiblings([node])[node.id],
8708 var parents = node.getParents();
8709 parents = (parents.length > 0)? parents[0] : null;
8712 for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8713 path(this.graph.getNode(ns[i]));
8720 Switches the current root node. Changes the topology of the Tree.
8723 id - (string) The id of the node to be set as root.
8724 method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8725 onComplete - (optional|object) An action to perform after the animation (if any).
8730 st.setRoot('nodeId', 'animate', {
8731 onComplete: function() {
8737 setRoot: function(id, method, onComplete) {
8738 if(this.busy) return;
8740 var that = this, canvas = this.canvas;
8741 var rootNode = this.graph.getNode(this.root);
8742 var clickedNode = this.graph.getNode(id);
8743 function $setRoot() {
8744 if(this.config.multitree && clickedNode.data.$orn) {
8745 var orn = clickedNode.data.$orn;
8752 rootNode.data.$orn = opp;
8753 (function tag(rootNode) {
8754 rootNode.eachSubnode(function(n) {
8761 delete clickedNode.data.$orn;
8764 this.clickedNode = clickedNode;
8765 this.graph.computeLevels(this.root, 0, "ignore");
8766 this.geom.setRightLevelToShow(clickedNode, canvas, {
8768 onShow: function(node) {
8771 node.setData('alpha', 1, 'end');
8772 node.setData('alpha', 0);
8773 node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8777 this.compute('end');
8780 modes: ['linear', 'node-property:alpha'],
8781 onComplete: function() {
8784 onComplete: function() {
8785 onComplete && onComplete.onComplete();
8792 // delete previous orientations (if any)
8793 delete rootNode.data.$orns;
8795 if(method == 'animate') {
8796 $setRoot.call(this);
8797 that.selectPath(clickedNode);
8798 } else if(method == 'replot') {
8799 $setRoot.call(this);
8800 this.select(this.root);
8810 subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8811 method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8812 onComplete - (optional|object) An action to perform after the animation (if any).
8817 st.addSubtree(json, 'animate', {
8818 onComplete: function() {
8824 addSubtree: function(subtree, method, onComplete) {
8825 if(method == 'replot') {
8826 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8827 } else if (method == 'animate') {
8828 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8833 Method: removeSubtree
8838 id - (string) The _id_ of the subtree to be removed.
8839 removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8840 method - (string) Set this to "animate" if you want to animate the tree after removing the subtree. You can also set this parameter to "replot" to just replot the subtree.
8841 onComplete - (optional|object) An action to perform after the animation (if any).
8846 st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8847 onComplete: function() {
8854 removeSubtree: function(id, removeRoot, method, onComplete) {
8855 var node = this.graph.getNode(id), subids = [];
8856 node.eachLevel(+!removeRoot, false, function(n) {
8859 if(method == 'replot') {
8860 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8861 } else if (method == 'animate') {
8862 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8869 Selects a node in the <ST> without performing an animation. Useful when selecting
8870 nodes which are currently hidden or deep inside the tree.
8873 id - (string) The id of the node to select.
8874 onComplete - (optional|object) an onComplete callback.
8878 st.select('mynodeid', {
8879 onComplete: function() {
8885 select: function(id, onComplete) {
8886 var group = this.group, geom = this.geom;
8887 var node= this.graph.getNode(id), canvas = this.canvas;
8888 var root = this.graph.getNode(this.root);
8889 var complete = $.merge(this.controller, onComplete);
8892 complete.onBeforeCompute(node);
8893 this.selectPath(node);
8894 this.clickedNode= node;
8895 this.requestNodes(node, {
8896 onComplete: function(){
8897 group.hide(group.prepare(getNodesToHide.call(that)), complete);
8898 geom.setRightLevelToShow(node, canvas);
8899 that.compute("current");
8900 that.graph.eachNode(function(n) {
8901 var pos = n.pos.getc(true);
8902 n.startPos.setc(pos.x, pos.y);
8903 n.endPos.setc(pos.x, pos.y);
8906 var offset = { x: complete.offsetX, y: complete.offsetY };
8907 that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8908 group.show(getNodesToShow.call(that));
8910 complete.onAfterCompute(that.clickedNode);
8911 complete.onComplete();
8919 Animates the <ST> to center the node specified by *id*.
8923 id - (string) A node id.
8924 options - (optional|object) A group of options and callbacks described below.
8925 onComplete - (object) An object callback called when the animation finishes.
8926 Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8931 st.onClick('mynodeid', {
8937 onComplete: function() {
8944 onClick: function (id, options) {
8945 var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8946 var innerController = {
8949 offsetX: config.offsetX || 0,
8950 offsetY: config.offsetY || 0
8952 setRightLevelToShowConfig: false,
8953 onBeforeRequest: $.empty,
8954 onBeforeContract: $.empty,
8955 onBeforeMove: $.empty,
8956 onBeforeExpand: $.empty
8958 var complete = $.merge(this.controller, innerController, options);
8962 var node = this.graph.getNode(id);
8963 this.selectPath(node, this.clickedNode);
8964 this.clickedNode = node;
8965 complete.onBeforeCompute(node);
8966 complete.onBeforeRequest(node);
8967 this.requestNodes(node, {
8968 onComplete: function() {
8969 complete.onBeforeContract(node);
8971 onComplete: function() {
8972 Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8973 complete.onBeforeMove(node);
8975 Move: complete.Move,
8976 onComplete: function() {
8977 complete.onBeforeExpand(node);
8979 onComplete: function() {
8981 complete.onAfterCompute(id);
8982 complete.onComplete();
8997 $jit.ST.$extend = true;
9002 Custom extension of <Graph.Op>.
9006 All <Graph.Op> methods
9013 $jit.ST.Op = new Class({
9015 Implements: Graph.Op
9021 Performs operations on group of nodes.
9024 $jit.ST.Group = new Class({
9026 initialize: function(viz) {
9028 this.canvas = viz.canvas;
9029 this.config = viz.config;
9030 this.animation = new Animation;
9036 Calls the request method on the controller to request a subtree for each node.
9038 requestNodes: function(nodes, controller) {
9039 var counter = 0, len = nodes.length, nodeSelected = {};
9040 var complete = function() { controller.onComplete(); };
9042 if(len == 0) complete();
9043 for(var i=0; i<len; i++) {
9044 nodeSelected[nodes[i].id] = nodes[i];
9045 controller.request(nodes[i].id, nodes[i]._level, {
9046 onComplete: function(nodeId, data) {
9047 if(data && data.children) {
9049 viz.op.sum(data, { type: 'nothing' });
9051 if(++counter == len) {
9052 viz.graph.computeLevels(viz.root, 0);
9062 Collapses group of nodes.
9064 contract: function(nodes, controller) {
9068 nodes = this.prepare(nodes);
9069 this.animation.setOptions($.merge(controller, {
9071 compute: function(delta) {
9072 if(delta == 1) delta = 0.99;
9073 that.plotStep(1 - delta, controller, this.$animating);
9074 this.$animating = 'contract';
9077 complete: function() {
9078 that.hide(nodes, controller);
9083 hide: function(nodes, controller) {
9085 for(var i=0; i<nodes.length; i++) {
9086 // TODO nodes are requested on demand, but not
9087 // deleted when hidden. Would that be a good feature?
9088 // Currently that feature is buggy, so I'll turn it off
9089 // Actually this feature is buggy because trimming should take
9090 // place onAfterCompute and not right after collapsing nodes.
9091 if (true || !controller || !controller.request) {
9092 nodes[i].eachLevel(1, false, function(elem){
9102 nodes[i].eachLevel(1, false, function(n) {
9105 viz.op.removeNode(ids, { 'type': 'nothing' });
9106 viz.labels.clearLabels();
9109 controller.onComplete();
9114 Expands group of nodes.
9116 expand: function(nodes, controller) {
9119 this.animation.setOptions($.merge(controller, {
9121 compute: function(delta) {
9122 that.plotStep(delta, controller, this.$animating);
9123 this.$animating = 'expand';
9126 complete: function() {
9127 that.plotStep(undefined, controller, false);
9128 controller.onComplete();
9134 show: function(nodes) {
9135 var config = this.config;
9136 this.prepare(nodes);
9137 $.each(nodes, function(n) {
9138 // check for root nodes if multitree
9139 if(config.multitree && !('$orn' in n.data)) {
9140 delete n.data.$orns;
9142 n.eachSubnode(function(ch) {
9143 if(('$orn' in ch.data)
9144 && orns.indexOf(ch.data.$orn) < 0
9145 && ch.exist && !ch.drawn) {
9146 orns += ch.data.$orn + ' ';
9149 n.data.$orns = orns;
9151 n.eachLevel(0, config.levelsToShow, function(n) {
9152 if(n.exist) n.drawn = true;
9157 prepare: function(nodes) {
9158 this.nodes = this.getNodesWithChildren(nodes);
9163 Filters an array of nodes leaving only nodes with children.
9165 getNodesWithChildren: function(nodes) {
9166 var ans = [], config = this.config, root = this.viz.root;
9167 nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9168 for(var i=0; i<nodes.length; i++) {
9169 if(nodes[i].anySubnode("exist")) {
9170 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9171 if(!config.multitree || '$orn' in nodes[j].data) {
9172 desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9175 if(!desc) ans.push(nodes[i]);
9181 plotStep: function(delta, controller, animating) {
9183 config = this.config,
9184 canvas = viz.canvas,
9185 ctx = canvas.getCtx(),
9188 // hide nodes that are meant to be collapsed/expanded
9190 for(i=0; i<nodes.length; i++) {
9193 var root = config.multitree && !('$orn' in node.data);
9194 var orns = root && node.data.$orns;
9195 node.eachSubgraph(function(n) {
9196 // TODO(nico): Cleanup
9197 // special check for root node subnodes when
9198 // multitree is checked.
9199 if(root && orns && orns.indexOf(n.data.$orn) > 0
9202 nds[node.id].push(n);
9203 } else if((!root || !orns) && n.drawn) {
9205 nds[node.id].push(n);
9210 // plot the whole (non-scaled) tree
9211 if(nodes.length > 0) viz.fx.plot();
9212 // show nodes that were previously hidden
9214 $.each(nds[i], function(n) { n.drawn = true; });
9216 // plot each scaled subtree
9217 for(i=0; i<nodes.length; i++) {
9220 viz.fx.plotSubtree(node, controller, delta, animating);
9225 getSiblings: function(nodes) {
9227 $.each(nodes, function(n) {
9228 var par = n.getParents();
9229 if (par.length == 0) {
9230 siblings[n.id] = [n];
9233 par[0].eachSubnode(function(sn) {
9236 siblings[n.id] = ans;
9246 Performs low level geometrical computations.
9250 This instance can be accessed with the _geom_ parameter of the st instance created.
9255 var st = new ST(canvas, config);
9256 st.geom.translate //or can also call any other <ST.Geom> method
9261 $jit.ST.Geom = new Class({
9262 Implements: Graph.Geom,
9264 Changes the tree current orientation to the one specified.
9266 You should usually use <ST.switchPosition> instead.
9268 switchOrientation: function(orn) {
9269 this.config.orientation = orn;
9273 Makes a value dispatch according to the current layout
9274 Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9276 dispatch: function() {
9277 // TODO(nico) should store Array.prototype.slice.call somewhere.
9278 var args = Array.prototype.slice.call(arguments);
9279 var s = args.shift(), len = args.length;
9280 var val = function(a) { return typeof a == 'function'? a() : a; };
9282 return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9283 } else if(len == 4) {
9285 case "top": return val(args[0]);
9286 case "right": return val(args[1]);
9287 case "bottom": return val(args[2]);
9288 case "left": return val(args[3]);
9295 Returns label height or with, depending on the tree current orientation.
9297 getSize: function(n, invert) {
9298 var data = n.data, config = this.config;
9299 var siblingOffset = config.siblingOffset;
9300 var s = (config.multitree
9302 && data.$orn) || config.orientation;
9303 var w = n.getData('width') + siblingOffset;
9304 var h = n.getData('height') + siblingOffset;
9306 return this.dispatch(s, h, w);
9308 return this.dispatch(s, w, h);
9312 Calculates a subtree base size. This is an utility function used by _getBaseSize_
9314 getTreeBaseSize: function(node, level, leaf) {
9315 var size = this.getSize(node, true), baseHeight = 0, that = this;
9316 if(leaf(level, node)) return size;
9317 if(level === 0) return 0;
9318 node.eachSubnode(function(elem) {
9319 baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9321 return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9328 Returns a Complex instance with the begin or end position of the edge to be plotted.
9332 node - A <Graph.Node> that is connected to this edge.
9333 type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9337 A <Complex> number specifying the begin or end position.
9339 getEdge: function(node, type, s) {
9340 var $C = function(a, b) {
9342 return node.pos.add(new Complex(a, b));
9345 var dim = this.node;
9346 var w = node.getData('width');
9347 var h = node.getData('height');
9349 if(type == 'begin') {
9350 if(dim.align == "center") {
9351 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9352 $C(0, -h/2),$C(w/2, 0));
9353 } else if(dim.align == "left") {
9354 return this.dispatch(s, $C(0, h), $C(0, 0),
9355 $C(0, 0), $C(w, 0));
9356 } else if(dim.align == "right") {
9357 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9358 $C(0, -h),$C(0, 0));
9359 } else throw "align: not implemented";
9362 } else if(type == 'end') {
9363 if(dim.align == "center") {
9364 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9365 $C(0, h/2), $C(-w/2, 0));
9366 } else if(dim.align == "left") {
9367 return this.dispatch(s, $C(0, 0), $C(w, 0),
9368 $C(0, h), $C(0, 0));
9369 } else if(dim.align == "right") {
9370 return this.dispatch(s, $C(0, -h),$C(0, 0),
9371 $C(0, 0), $C(-w, 0));
9372 } else throw "align: not implemented";
9377 Adjusts the tree position due to canvas scaling or translation.
9379 getScaledTreePosition: function(node, scale) {
9380 var dim = this.node;
9381 var w = node.getData('width');
9382 var h = node.getData('height');
9383 var s = (this.config.multitree
9384 && ('$orn' in node.data)
9385 && node.data.$orn) || this.config.orientation;
9387 var $C = function(a, b) {
9389 return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9392 if(dim.align == "left") {
9393 return this.dispatch(s, $C(0, h), $C(0, 0),
9394 $C(0, 0), $C(w, 0));
9395 } else if(dim.align == "center") {
9396 return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9397 $C(0, -h / 2),$C(w / 2, 0));
9398 } else if(dim.align == "right") {
9399 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9400 $C(0, -h),$C(0, 0));
9401 } else throw "align: not implemented";
9407 Returns a Boolean if the current subtree fits in canvas.
9411 node - A <Graph.Node> which is the current root of the subtree.
9412 canvas - The <Canvas> object.
9413 level - The depth of the subtree to be considered.
9415 treeFitsInCanvas: function(node, canvas, level) {
9416 var csize = canvas.getSize();
9417 var s = (this.config.multitree
9418 && ('$orn' in node.data)
9419 && node.data.$orn) || this.config.orientation;
9421 var size = this.dispatch(s, csize.width, csize.height);
9422 var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9423 return level === 0 || !node.anySubnode();
9425 return (baseSize < size);
9432 Custom extension of <Graph.Plot>.
9436 All <Graph.Plot> methods
9443 $jit.ST.Plot = new Class({
9445 Implements: Graph.Plot,
9448 Plots a subtree from the spacetree.
9450 plotSubtree: function(node, opt, scale, animating) {
9451 var viz = this.viz, canvas = viz.canvas, config = viz.config;
9452 scale = Math.min(Math.max(0.001, scale), 1);
9455 var ctx = canvas.getCtx();
9456 var diff = viz.geom.getScaledTreePosition(node, scale);
9457 ctx.translate(diff.x, diff.y);
9458 ctx.scale(scale, scale);
9460 this.plotTree(node, $.merge(opt, {
9462 'hideLabels': !!scale,
9463 'plotSubtree': function(n, ch) {
9464 var root = config.multitree && !('$orn' in node.data);
9465 var orns = root && node.getData('orns');
9466 return !root || orns.indexOf(elem.getData('orn')) > -1;
9469 if(scale >= 0) node.drawn = true;
9473 Method: getAlignedPos
9475 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9479 pos - (object) A <Graph.Node> position.
9480 width - (number) The width of the node.
9481 height - (number) The height of the node.
9484 getAlignedPos: function(pos, width, height) {
9485 var nconfig = this.node;
9487 if(nconfig.align == "center") {
9489 x: pos.x - width / 2,
9490 y: pos.y - height / 2
9492 } else if (nconfig.align == "left") {
9493 orn = this.config.orientation;
9494 if(orn == "bottom" || orn == "top") {
9496 x: pos.x - width / 2,
9502 y: pos.y - height / 2
9505 } else if(nconfig.align == "right") {
9506 orn = this.config.orientation;
9507 if(orn == "bottom" || orn == "top") {
9509 x: pos.x - width / 2,
9515 y: pos.y - height / 2
9518 } else throw "align: not implemented";
9523 getOrientation: function(adj) {
9524 var config = this.config;
9525 var orn = config.orientation;
9527 if(config.multitree) {
9528 var nodeFrom = adj.nodeFrom;
9529 var nodeTo = adj.nodeTo;
9530 orn = (('$orn' in nodeFrom.data)
9531 && nodeFrom.data.$orn)
9532 || (('$orn' in nodeTo.data)
9533 && nodeTo.data.$orn);
9543 Custom extension of <Graph.Label>.
9544 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9548 All <Graph.Label> methods and subclasses.
9552 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9559 Custom extension of <Graph.Label.Native>.
9563 All <Graph.Label.Native> methods
9567 <Graph.Label.Native>
9569 $jit.ST.Label.Native = new Class({
9570 Implements: Graph.Label.Native,
9572 renderLabel: function(canvas, node, controller) {
9573 var ctx = canvas.getCtx();
9574 var coord = node.pos.getc(true);
9575 ctx.fillText(node.name, coord.x, coord.y);
9579 $jit.ST.Label.DOM = new Class({
9580 Implements: Graph.Label.DOM,
9585 Overrides abstract method placeLabel in <Graph.Plot>.
9589 tag - A DOM label element.
9590 node - A <Graph.Node>.
9591 controller - A configuration/controller object passed to the visualization.
9594 placeLabel: function(tag, node, controller) {
9595 var pos = node.pos.getc(true),
9596 config = this.viz.config,
9598 canvas = this.viz.canvas,
9599 w = node.getData('width'),
9600 h = node.getData('height'),
9601 radius = canvas.getSize(),
9604 var ox = canvas.translateOffsetX,
9605 oy = canvas.translateOffsetY,
9606 sx = canvas.scaleOffsetX,
9607 sy = canvas.scaleOffsetY,
9608 posx = pos.x * sx + ox,
9609 posy = pos.y * sy + oy;
9611 if(dim.align == "center") {
9613 x: Math.round(posx - w / 2 + radius.width/2),
9614 y: Math.round(posy - h / 2 + radius.height/2)
9616 } else if (dim.align == "left") {
9617 orn = config.orientation;
9618 if(orn == "bottom" || orn == "top") {
9620 x: Math.round(posx - w / 2 + radius.width/2),
9621 y: Math.round(posy + radius.height/2)
9625 x: Math.round(posx + radius.width/2),
9626 y: Math.round(posy - h / 2 + radius.height/2)
9629 } else if(dim.align == "right") {
9630 orn = config.orientation;
9631 if(orn == "bottom" || orn == "top") {
9633 x: Math.round(posx - w / 2 + radius.width/2),
9634 y: Math.round(posy - h + radius.height/2)
9638 x: Math.round(posx - w + radius.width/2),
9639 y: Math.round(posy - h / 2 + radius.height/2)
9642 } else throw "align: not implemented";
9644 var style = tag.style;
9645 style.left = labelPos.x + 'px';
9646 style.top = labelPos.y + 'px';
9647 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9648 controller.onPlaceLabel(tag, node);
9655 Custom extension of <Graph.Label.SVG>.
9659 All <Graph.Label.SVG> methods
9665 $jit.ST.Label.SVG = new Class({
9666 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9668 initialize: function(viz) {
9676 Custom extension of <Graph.Label.HTML>.
9680 All <Graph.Label.HTML> methods.
9687 $jit.ST.Label.HTML = new Class({
9688 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9690 initialize: function(viz) {
9697 Class: ST.Plot.NodeTypes
9699 This class contains a list of <Graph.Node> built-in types.
9700 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9702 You can add your custom node types, customizing your visualization to the extreme.
9707 ST.Plot.NodeTypes.implement({
9709 'render': function(node, canvas) {
9710 //print your custom node to canvas
9713 'contains': function(node, pos) {
9714 //return true if pos is inside the node or false otherwise
9721 $jit.ST.Plot.NodeTypes = new Class({
9724 'contains': $.lambda(false)
9727 'render': function(node, canvas) {
9728 var dim = node.getData('dim'),
9729 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9731 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9733 'contains': function(node, pos) {
9734 var dim = node.getData('dim'),
9735 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9737 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9741 'render': function(node, canvas) {
9742 var dim = node.getData('dim'),
9744 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9745 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9747 'contains': function(node, pos) {
9748 var dim = node.getData('dim'),
9749 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9751 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9755 'render': function(node, canvas) {
9756 var width = node.getData('width'),
9757 height = node.getData('height'),
9758 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9759 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9761 'contains': function(node, pos) {
9762 var width = node.getData('width'),
9763 height = node.getData('height'),
9764 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9765 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9769 'render': function(node, canvas) {
9770 var width = node.getData('width'),
9771 height = node.getData('height'),
9772 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9773 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9775 'contains': function(node, pos) {
9776 var width = node.getData('width'),
9777 height = node.getData('height'),
9778 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9779 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9785 Class: ST.Plot.EdgeTypes
9787 This class contains a list of <Graph.Adjacence> built-in types.
9788 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9790 You can add your custom edge types, customizing your visualization to the extreme.
9795 ST.Plot.EdgeTypes.implement({
9797 'render': function(adj, canvas) {
9798 //print your custom edge to canvas
9801 'contains': function(adj, pos) {
9802 //return true if pos is inside the arc or false otherwise
9809 $jit.ST.Plot.EdgeTypes = new Class({
9812 'render': function(adj, canvas) {
9813 var orn = this.getOrientation(adj),
9814 nodeFrom = adj.nodeFrom,
9815 nodeTo = adj.nodeTo,
9816 rel = nodeFrom._depth < nodeTo._depth,
9817 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9818 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9819 this.edgeHelper.line.render(from, to, canvas);
9821 'contains': function(adj, pos) {
9822 var orn = this.getOrientation(adj),
9823 nodeFrom = adj.nodeFrom,
9824 nodeTo = adj.nodeTo,
9825 rel = nodeFrom._depth < nodeTo._depth,
9826 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9827 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9828 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9832 'render': function(adj, canvas) {
9833 var orn = this.getOrientation(adj),
9834 node = adj.nodeFrom,
9836 dim = adj.getData('dim'),
9837 from = this.viz.geom.getEdge(node, 'begin', orn),
9838 to = this.viz.geom.getEdge(child, 'end', orn),
9839 direction = adj.data.$direction,
9840 inv = (direction && direction.length>1 && direction[0] != node.id);
9841 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9843 'contains': function(adj, pos) {
9844 var orn = this.getOrientation(adj),
9845 nodeFrom = adj.nodeFrom,
9846 nodeTo = adj.nodeTo,
9847 rel = nodeFrom._depth < nodeTo._depth,
9848 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9849 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9850 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9853 'quadratic:begin': {
9854 'render': function(adj, canvas) {
9855 var orn = this.getOrientation(adj);
9856 var nodeFrom = adj.nodeFrom,
9857 nodeTo = adj.nodeTo,
9858 rel = nodeFrom._depth < nodeTo._depth,
9859 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9860 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9861 dim = adj.getData('dim'),
9862 ctx = canvas.getCtx();
9864 ctx.moveTo(begin.x, begin.y);
9867 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9870 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9873 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9876 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9883 'render': function(adj, canvas) {
9884 var orn = this.getOrientation(adj);
9885 var nodeFrom = adj.nodeFrom,
9886 nodeTo = adj.nodeTo,
9887 rel = nodeFrom._depth < nodeTo._depth,
9888 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9889 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9890 dim = adj.getData('dim'),
9891 ctx = canvas.getCtx();
9893 ctx.moveTo(begin.x, begin.y);
9896 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9899 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9902 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9905 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9912 'render': function(adj, canvas) {
9913 var orn = this.getOrientation(adj),
9914 nodeFrom = adj.nodeFrom,
9915 nodeTo = adj.nodeTo,
9916 rel = nodeFrom._depth < nodeTo._depth,
9917 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9918 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9919 dim = adj.getData('dim'),
9920 ctx = canvas.getCtx();
9922 ctx.moveTo(begin.x, begin.y);
9925 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9928 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9931 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9934 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9943 Options.LineChart = {
9947 labelOffset: 3, // label offset
9948 type: 'basic', // gradient
9964 selectOnHover: true,
9965 showAggregates: true,
9967 filterOnClick: false,
9968 restoreOnRightClick: false
9973 * File: LineChart.js
9977 $jit.ST.Plot.NodeTypes.implement({
9978 'linechart-basic' : {
9979 'render' : function(node, canvas) {
9980 var pos = node.pos.getc(true),
9981 width = node.getData('width'),
9982 height = node.getData('height'),
9983 algnPos = this.getAlignedPos(pos, width, height),
9984 x = algnPos.x + width/2 , y = algnPos.y,
9985 stringArray = node.getData('stringArray'),
9986 lastNode = node.getData('lastNode'),
9987 dimArray = node.getData('dimArray'),
9988 valArray = node.getData('valueArray'),
9989 colorArray = node.getData('colorArray'),
9990 colorLength = colorArray.length,
9991 config = node.getData('config'),
9992 gradient = node.getData('gradient'),
9993 showLabels = config.showLabels,
9994 aggregates = config.showAggregates,
9995 label = config.Label,
9996 prev = node.getData('prev'),
9997 dataPointSize = config.dataPointSize;
9999 var ctx = canvas.getCtx(), border = node.getData('border');
10000 if (colorArray && dimArray && stringArray) {
10002 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10003 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10005 ctx.lineCap = "round";
10009 //render line segment, dimarray[i][0] is the curent datapoint, dimarrya[i][1] is the next datapoint, we need both in the current iteration to draw the line segment
10011 ctx.moveTo(x, y - dimArray[i][0]);
10012 ctx.lineTo(x + width, y - dimArray[i][1]);
10016 //render data point
10017 ctx.fillRect(x - (dataPointSize/2), y - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10021 if(label.type == 'Native' && showLabels) {
10023 ctx.fillStyle = ctx.strokeStyle = label.color;
10024 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10025 ctx.textAlign = 'center';
10026 ctx.textBaseline = 'middle';
10027 ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10033 'contains': function(node, mpos) {
10034 var pos = node.pos.getc(true),
10035 width = node.getData('width'),
10036 height = node.getData('height'),
10037 config = node.getData('config'),
10038 dataPointSize = config.dataPointSize,
10039 dataPointMidPoint = dataPointSize/2,
10040 algnPos = this.getAlignedPos(pos, width, height),
10041 x = algnPos.x + width/2, y = algnPos.y,
10042 dimArray = node.getData('dimArray');
10043 //bounding box check
10044 if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10048 for(var i=0, l=dimArray.length; i<l; i++) {
10049 var dimi = dimArray[i];
10050 var url = Url.decode(node.getData('linkArray')[i]);
10051 if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10052 var valArrayCur = node.getData('valArrayCur');
10053 var results = array_match(valArrayCur[i],valArrayCur);
10054 var matches = results[0];
10055 var indexValues = results[1];
10057 var names = new Array(),
10058 values = new Array(),
10059 percentages = new Array(),
10060 linksArr = new Array();
10061 for(var j=0, il=indexValues.length; j<il; j++) {
10062 names[j] = node.getData('stringArray')[indexValues[j]];
10063 values[j] = valArrayCur[indexValues[j]];
10064 percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10065 linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10070 'color': node.getData('colorArray')[i],
10072 'percentage': percentages,
10079 'name': node.getData('stringArray')[i],
10080 'color': node.getData('colorArray')[i],
10081 'value': node.getData('valueArray')[i][0],
10082 // 'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10083 'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10098 A visualization that displays line charts.
10100 Constructor Options:
10102 See <Options.Line>.
10105 $jit.LineChart = new Class({
10107 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10111 initialize: function(opt) {
10112 this.controller = this.config =
10113 $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10114 Label: { type: 'Native' }
10116 //set functions for showLabels and showAggregates
10117 var showLabels = this.config.showLabels,
10118 typeLabels = $.type(showLabels),
10119 showAggregates = this.config.showAggregates,
10120 typeAggregates = $.type(showAggregates);
10121 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10122 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10123 Options.Fx.clearCanvas = false;
10124 this.initializeViz();
10127 initializeViz: function() {
10128 var config = this.config,
10130 nodeType = config.type.split(":")[0],
10133 var st = new $jit.ST({
10134 injectInto: config.injectInto,
10135 orientation: "bottom",
10136 backgroundColor: config.backgroundColor,
10137 renderBackground: config.renderBackground,
10141 withLabels: config.Label.type != 'Native',
10142 useCanvas: config.useCanvas,
10144 type: config.Label.type
10148 type: 'linechart-' + nodeType,
10157 enable: config.Tips.enable,
10160 onShow: function(tip, node, contains) {
10161 var elem = contains;
10162 config.Tips.onShow(tip, elem, node);
10168 onClick: function(node, eventInfo, evt) {
10169 if(!config.filterOnClick && !config.Events.enable) return;
10170 var elem = eventInfo.getContains();
10171 if(elem) config.filterOnClick && that.filter(elem.name);
10172 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10174 onRightClick: function(node, eventInfo, evt) {
10175 if(!config.restoreOnRightClick) return;
10178 onMouseMove: function(node, eventInfo, evt) {
10179 if(!config.selectOnHover) return;
10181 var elem = eventInfo.getContains();
10182 that.select(node.id, elem.name, elem.index);
10184 that.select(false, false, false);
10188 onCreateLabel: function(domElement, node) {
10189 var labelConf = config.Label,
10190 valueArray = node.getData('valueArray'),
10191 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10192 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10193 if(node.getData('prev')) {
10195 wrapper: document.createElement('div'),
10196 aggregate: document.createElement('div'),
10197 label: document.createElement('div')
10199 var wrapper = nlbs.wrapper,
10200 label = nlbs.label,
10201 aggregate = nlbs.aggregate,
10202 wrapperStyle = wrapper.style,
10203 labelStyle = label.style,
10204 aggregateStyle = aggregate.style;
10205 //store node labels
10206 nodeLabels[node.id] = nlbs;
10208 wrapper.appendChild(label);
10209 wrapper.appendChild(aggregate);
10210 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10211 label.style.display = 'none';
10213 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10214 aggregate.style.display = 'none';
10216 wrapperStyle.position = 'relative';
10217 wrapperStyle.overflow = 'visible';
10218 wrapperStyle.fontSize = labelConf.size + 'px';
10219 wrapperStyle.fontFamily = labelConf.family;
10220 wrapperStyle.color = labelConf.color;
10221 wrapperStyle.textAlign = 'center';
10222 aggregateStyle.position = labelStyle.position = 'absolute';
10224 domElement.style.width = node.getData('width') + 'px';
10225 domElement.style.height = node.getData('height') + 'px';
10226 label.innerHTML = node.name;
10228 domElement.appendChild(wrapper);
10231 onPlaceLabel: function(domElement, node) {
10232 if(!node.getData('prev')) return;
10233 var labels = nodeLabels[node.id],
10234 wrapperStyle = labels.wrapper.style,
10235 labelStyle = labels.label.style,
10236 aggregateStyle = labels.aggregate.style,
10237 width = node.getData('width'),
10238 height = node.getData('height'),
10239 dimArray = node.getData('dimArray'),
10240 valArray = node.getData('valueArray'),
10241 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10242 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10243 font = parseInt(wrapperStyle.fontSize, 10),
10244 domStyle = domElement.style;
10246 if(dimArray && valArray) {
10247 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10248 labelStyle.display = '';
10250 labelStyle.display = 'none';
10252 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10253 aggregateStyle.display = '';
10255 aggregateStyle.display = 'none';
10257 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10258 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10259 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10260 if(dimArray[i][0] > 0) {
10261 acum+= valArray[i][0];
10262 leftAcum+= dimArray[i][0];
10265 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10266 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10267 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10268 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10269 labels.aggregate.innerHTML = acum;
10274 var size = st.canvas.getSize(),
10275 margin = config.Margin;
10276 st.config.offsetY = -size.height/2 + margin.bottom
10277 + (config.showLabels && (config.labelOffset + config.Label.size));
10278 st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10280 this.canvas = this.st.canvas;
10283 renderTitle: function() {
10284 var canvas = this.canvas,
10285 size = canvas.getSize(),
10286 config = this.config,
10287 margin = config.Margin,
10288 label = config.Label,
10289 title = config.Title;
10290 ctx = canvas.getCtx();
10291 ctx.fillStyle = title.color;
10292 ctx.textAlign = 'left';
10293 ctx.textBaseline = 'top';
10294 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10295 if(label.type == 'Native') {
10296 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10300 renderTicks: function() {
10302 var canvas = this.canvas,
10303 size = canvas.getSize(),
10304 config = this.config,
10305 margin = config.Margin,
10306 ticks = config.Ticks,
10307 title = config.Title,
10308 subtitle = config.Subtitle,
10309 label = config.Label,
10310 maxValue = this.maxValue,
10311 maxTickValue = Math.ceil(maxValue*.1)*10;
10312 if(maxTickValue == maxValue) {
10313 var length = maxTickValue.toString().length;
10314 maxTickValue = maxTickValue + parseInt(pad(1,length));
10319 labelIncrement = maxTickValue/ticks.segments,
10320 ctx = canvas.getCtx();
10321 ctx.strokeStyle = ticks.color;
10322 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10323 ctx.textAlign = 'center';
10324 ctx.textBaseline = 'middle';
10326 idLabel = canvas.id + "-label";
10328 container = document.getElementById(idLabel);
10331 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10332 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10333 grid = -size.height+(margin.bottom+config.labelOffset+label.size+margin.top+(title.text? title.size+title.offset:0)+(subtitle.text? subtitle.size+subtitle.offset:0)),
10334 segmentLength = grid/ticks.segments;
10335 ctx.fillStyle = ticks.color;
10336 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size-1, -(size.height/2)+margin.top+(title.text? title.size+title.offset:0),1,size.height-margin.top-margin.bottom-label.size-config.labelOffset-(title.text? title.size+title.offset:0)-(subtitle.text? subtitle.size+subtitle.offset:0));
10338 while(axis>=grid) {
10340 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10341 ctx.rotate(Math.PI / 2);
10342 ctx.fillStyle = label.color;
10343 if(config.showLabels) {
10344 if(label.type == 'Native') {
10345 ctx.fillText(labelValue, 0, 0);
10347 //html labels on y axis
10348 labelDiv = document.createElement('div');
10349 labelDiv.innerHTML = labelValue;
10350 labelDiv.className = "rotatedLabel";
10351 // labelDiv.class = "rotatedLabel";
10352 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10353 labelDiv.style.left = margin.left + "px";
10354 labelDiv.style.width = labelDim + "px";
10355 labelDiv.style.height = labelDim + "px";
10356 labelDiv.style.textAlign = "center";
10357 labelDiv.style.verticalAlign = "middle";
10358 labelDiv.style.position = "absolute";
10359 container.appendChild(labelDiv);
10363 ctx.fillStyle = ticks.color;
10364 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size, Math.round(axis), size.width-margin.right-margin.left-config.labelOffset-label.size,1 );
10365 htmlOrigin += segmentLength;
10366 axis += segmentLength;
10367 labelValue += labelIncrement;
10377 renderBackground: function() {
10378 var canvas = this.canvas,
10379 config = this.config,
10380 backgroundColor = config.backgroundColor,
10381 size = canvas.getSize(),
10382 ctx = canvas.getCtx();
10383 //ctx.globalCompositeOperation = "destination-over";
10384 ctx.fillStyle = backgroundColor;
10385 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10387 clear: function() {
10388 var canvas = this.canvas;
10389 var ctx = canvas.getCtx(),
10390 size = canvas.getSize();
10391 ctx.fillStyle = "rgba(255,255,255,0)";
10392 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10393 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
10395 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
10396 var canvas = this.canvas,
10397 size = canvas.getSize(),
10398 config = this.config,
10399 orgHeight = size.height,
10400 margin = config.Margin,
10402 horz = config.orientation == 'horizontal';
10405 var newWindowWidth = document.body.offsetWidth;
10406 var diff = newWindowWidth - orgWindowWidth;
10407 var newWidth = orgContainerDivWidth + (diff/cols);
10408 canvas.resize(newWidth,orgHeight);
10409 if(typeof FlashCanvas == "undefined") {
10412 this.clear();// hack for flashcanvas bug not properly clearing rectangle
10414 this.loadJSON(json);
10420 Loads JSON data into the visualization.
10424 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
10428 var areaChart = new $jit.AreaChart(options);
10429 areaChart.loadJSON(json);
10432 loadJSON: function(json) {
10433 var prefix = $.time(),
10436 name = $.splat(json.label),
10437 color = $.splat(json.color || this.colors),
10438 config = this.config,
10439 ticks = config.Ticks,
10440 renderBackground = config.renderBackground,
10441 gradient = !!config.type.split(":")[1],
10442 animate = config.animate,
10443 title = config.Title,
10444 groupTotalValue = 0;
10446 var valArrayAll = new Array();
10448 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10449 var val = values[i];
10450 var valArray = $.splat(val.values);
10451 for (var j=0, len=valArray.length; j<len; j++) {
10452 valArrayAll.push(parseInt(valArray[j]));
10454 groupTotalValue += parseInt(valArray.sum());
10457 this.maxValue = Math.max.apply(null, valArrayAll);
10459 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10460 var val = values[i], prev = values[i-1];
10462 var next = (i+1 < l) ? values[i+1] : 0;
10463 var valLeft = $.splat(values[i].values);
10464 var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10465 var valArray = $.zip(valLeft, valRight);
10466 var valArrayCur = $.splat(values[i].values);
10467 var linkArray = $.splat(values[i].links);
10468 var acumLeft = 0, acumRight = 0;
10469 var lastNode = (l-1 == i) ? true : false;
10471 'id': prefix + val.label,
10475 '$valueArray': valArray,
10476 '$valArrayCur': valArrayCur,
10477 '$colorArray': color,
10478 '$linkArray': linkArray,
10479 '$stringArray': name,
10480 '$next': next? next.label:false,
10481 '$prev': prev? prev.label:false,
10483 '$lastNode': lastNode,
10484 '$groupTotalValue': groupTotalValue,
10485 '$gradient': gradient
10491 'id': prefix + '$root',
10502 this.normalizeDims();
10504 if(renderBackground) {
10505 this.renderBackground();
10508 if(!animate && ticks.enable) {
10509 this.renderTicks();
10514 this.renderTitle();
10518 st.select(st.root);
10521 modes: ['node-property:height:dimArray'],
10530 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
10534 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10535 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10540 areaChart.updateJSON(json, {
10541 onComplete: function() {
10542 alert('update complete!');
10547 updateJSON: function(json, onComplete) {
10548 if(this.busy) return;
10553 labels = json.label && $.splat(json.label),
10554 values = json.values,
10555 animate = this.config.animate,
10557 $.each(values, function(v) {
10558 var n = graph.getByName(v.label);
10560 v.values = $.splat(v.values);
10561 var stringArray = n.getData('stringArray'),
10562 valArray = n.getData('valueArray');
10563 $.each(valArray, function(a, i) {
10564 a[0] = v.values[i];
10565 if(labels) stringArray[i] = labels[i];
10567 n.setData('valueArray', valArray);
10568 var prev = n.getData('prev'),
10569 next = n.getData('next'),
10570 nextNode = graph.getByName(next);
10572 var p = graph.getByName(prev);
10574 var valArray = p.getData('valueArray');
10575 $.each(valArray, function(a, i) {
10576 a[1] = v.values[i];
10581 var valArray = n.getData('valueArray');
10582 $.each(valArray, function(a, i) {
10583 a[1] = v.values[i];
10588 this.normalizeDims();
10591 st.select(st.root);
10594 modes: ['node-property:height:dimArray'],
10596 onComplete: function() {
10598 onComplete && onComplete.onComplete();
10607 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10611 Variable strings arguments with the name of the stacks.
10616 areaChart.filter('label A', 'label C');
10621 <AreaChart.restore>.
10623 filter: function() {
10624 if(this.busy) return;
10626 if(this.config.Tips.enable) this.st.tips.hide();
10627 this.select(false, false, false);
10628 var args = Array.prototype.slice.call(arguments);
10629 var rt = this.st.graph.getNode(this.st.root);
10631 rt.eachAdjacency(function(adj) {
10632 var n = adj.nodeTo,
10633 dimArray = n.getData('dimArray'),
10634 stringArray = n.getData('stringArray');
10635 n.setData('dimArray', $.map(dimArray, function(d, i) {
10636 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10639 this.st.fx.animate({
10640 modes: ['node-property:dimArray'],
10642 onComplete: function() {
10651 Sets all stacks that could have been filtered visible.
10656 areaChart.restore();
10661 <AreaChart.filter>.
10663 restore: function() {
10664 if(this.busy) return;
10666 if(this.config.Tips.enable) this.st.tips.hide();
10667 this.select(false, false, false);
10668 this.normalizeDims();
10670 this.st.fx.animate({
10671 modes: ['node-property:height:dimArray'],
10673 onComplete: function() {
10678 //adds the little brown bar when hovering the node
10679 select: function(id, name, index) {
10680 if(!this.config.selectOnHover) return;
10681 var s = this.selected;
10682 if(s.id != id || s.name != name
10683 || s.index != index) {
10687 this.st.graph.eachNode(function(n) {
10688 n.setData('border', false);
10691 var n = this.st.graph.getNode(id);
10692 n.setData('border', s);
10693 var link = index === 0? 'prev':'next';
10694 link = n.getData(link);
10696 n = this.st.graph.getByName(link);
10698 n.setData('border', {
10712 Returns an object containing as keys the legend names and as values hex strings with color values.
10717 var legend = areaChart.getLegend();
10720 getLegend: function() {
10721 var legend = new Array();
10722 var name = new Array();
10723 var color = new Array();
10725 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10728 var colors = n.getData('colorArray'),
10729 len = colors.length;
10730 $.each(n.getData('stringArray'), function(s, i) {
10731 color[i] = colors[i % len];
10734 legend['name'] = name;
10735 legend['color'] = color;
10740 Method: getMaxValue
10742 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10747 var ans = areaChart.getMaxValue();
10750 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10755 //will return 100 for all AreaChart instances,
10756 //displaying all of them with the same scale
10757 $jit.AreaChart.implement({
10758 'getMaxValue': function() {
10766 normalizeDims: function() {
10767 //number of elements
10768 var root = this.st.graph.getNode(this.st.root), l=0;
10769 root.eachAdjacency(function() {
10774 var maxValue = this.maxValue || 1,
10775 size = this.st.canvas.getSize(),
10776 config = this.config,
10777 margin = config.Margin,
10778 labelOffset = config.labelOffset + config.Label.size,
10779 fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10780 animate = config.animate,
10781 ticks = config.Ticks,
10782 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10783 - (config.showLabels && labelOffset);
10786 var maxTickValue = Math.ceil(maxValue*.1)*10;
10787 if(maxTickValue == maxValue) {
10788 var length = maxTickValue.toString().length;
10789 maxTickValue = maxTickValue + parseInt(pad(1,length));
10794 this.st.graph.eachNode(function(n) {
10795 var acumLeft = 0, acumRight = 0, animateValue = [];
10796 $.each(n.getData('valueArray'), function(v) {
10798 acumRight += +v[1];
10799 animateValue.push([0, 0]);
10801 var acum = acumRight>acumLeft? acumRight:acumLeft;
10803 n.setData('width', fixedDim);
10805 n.setData('height', acum * height / maxValue, 'end');
10806 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10807 return [n[0] * height / maxValue, n[1] * height / maxValue];
10809 var dimArray = n.getData('dimArray');
10811 n.setData('dimArray', animateValue);
10816 n.setData('height', acum * height / maxValue);
10817 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10818 return [n[0] * height / maxTickValue, n[1] * height / maxTickValue];
10821 n.setData('height', acum * height / maxValue);
10822 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10823 return [n[0] * height / maxValue, n[1] * height / maxValue];
10838 * File: AreaChart.js
10842 $jit.ST.Plot.NodeTypes.implement({
10843 'areachart-stacked' : {
10844 'render' : function(node, canvas) {
10845 var pos = node.pos.getc(true),
10846 width = node.getData('width'),
10847 height = node.getData('height'),
10848 algnPos = this.getAlignedPos(pos, width, height),
10849 x = algnPos.x, y = algnPos.y,
10850 stringArray = node.getData('stringArray'),
10851 dimArray = node.getData('dimArray'),
10852 valArray = node.getData('valueArray'),
10853 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10854 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10855 colorArray = node.getData('colorArray'),
10856 colorLength = colorArray.length,
10857 config = node.getData('config'),
10858 gradient = node.getData('gradient'),
10859 showLabels = config.showLabels,
10860 aggregates = config.showAggregates,
10861 label = config.Label,
10862 prev = node.getData('prev');
10864 var ctx = canvas.getCtx(), border = node.getData('border');
10865 if (colorArray && dimArray && stringArray) {
10866 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10867 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10869 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10870 var h1 = acumLeft + dimArray[i][0],
10871 h2 = acumRight + dimArray[i][1],
10872 alpha = Math.atan((h2 - h1) / width),
10874 var linear = ctx.createLinearGradient(x + width/2,
10876 x + width/2 + delta * Math.sin(alpha),
10877 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10878 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10879 function(v) { return (v * 0.85) >> 0; }));
10880 linear.addColorStop(0, colorArray[i % colorLength]);
10881 linear.addColorStop(1, color);
10882 ctx.fillStyle = linear;
10885 ctx.moveTo(x, y - acumLeft);
10886 ctx.lineTo(x + width, y - acumRight);
10887 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10888 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10889 ctx.lineTo(x, y - acumLeft);
10893 var strong = border.name == stringArray[i];
10894 var perc = strong? 0.7 : 0.8;
10895 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10896 function(v) { return (v * perc) >> 0; }));
10897 ctx.strokeStyle = color;
10898 ctx.lineWidth = strong? 4 : 1;
10901 if(border.index === 0) {
10902 ctx.moveTo(x, y - acumLeft);
10903 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10905 ctx.moveTo(x + width, y - acumRight);
10906 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10911 acumLeft += (dimArray[i][0] || 0);
10912 acumRight += (dimArray[i][1] || 0);
10914 if(dimArray[i][0] > 0)
10915 valAcum += (valArray[i][0] || 0);
10917 if(prev && label.type == 'Native') {
10920 ctx.fillStyle = ctx.strokeStyle = label.color;
10921 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10922 ctx.textAlign = 'center';
10923 ctx.textBaseline = 'middle';
10924 if(aggregates(node.name, valLeft, valRight, node)) {
10925 ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10927 if(showLabels(node.name, valLeft, valRight, node)) {
10928 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10934 'contains': function(node, mpos) {
10935 var pos = node.pos.getc(true),
10936 width = node.getData('width'),
10937 height = node.getData('height'),
10938 algnPos = this.getAlignedPos(pos, width, height),
10939 x = algnPos.x, y = algnPos.y,
10940 dimArray = node.getData('dimArray'),
10942 //bounding box check
10943 if(mpos.x < x || mpos.x > x + width
10944 || mpos.y > y || mpos.y < y - height) {
10948 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10949 var dimi = dimArray[i];
10952 var intersec = lAcum + (rAcum - lAcum) * rx / width;
10953 if(mpos.y >= intersec) {
10954 var index = +(rx > width/2);
10956 'name': node.getData('stringArray')[i],
10957 'color': node.getData('colorArray')[i],
10958 'value': node.getData('valueArray')[i][index],
10971 A visualization that displays stacked area charts.
10973 Constructor Options:
10975 See <Options.AreaChart>.
10978 $jit.AreaChart = new Class({
10980 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10984 initialize: function(opt) {
10985 this.controller = this.config =
10986 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10987 Label: { type: 'Native' }
10989 //set functions for showLabels and showAggregates
10990 var showLabels = this.config.showLabels,
10991 typeLabels = $.type(showLabels),
10992 showAggregates = this.config.showAggregates,
10993 typeAggregates = $.type(showAggregates);
10994 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10995 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10997 this.initializeViz();
11000 initializeViz: function() {
11001 var config = this.config,
11003 nodeType = config.type.split(":")[0],
11006 var st = new $jit.ST({
11007 injectInto: config.injectInto,
11008 orientation: "bottom",
11012 withLabels: config.Label.type != 'Native',
11013 useCanvas: config.useCanvas,
11015 type: config.Label.type
11019 type: 'areachart-' + nodeType,
11028 enable: config.Tips.enable,
11031 onShow: function(tip, node, contains) {
11032 var elem = contains;
11033 config.Tips.onShow(tip, elem, node);
11039 onClick: function(node, eventInfo, evt) {
11040 if(!config.filterOnClick && !config.Events.enable) return;
11041 var elem = eventInfo.getContains();
11042 if(elem) config.filterOnClick && that.filter(elem.name);
11043 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11045 onRightClick: function(node, eventInfo, evt) {
11046 if(!config.restoreOnRightClick) return;
11049 onMouseMove: function(node, eventInfo, evt) {
11050 if(!config.selectOnHover) return;
11052 var elem = eventInfo.getContains();
11053 that.select(node.id, elem.name, elem.index);
11055 that.select(false, false, false);
11059 onCreateLabel: function(domElement, node) {
11060 var labelConf = config.Label,
11061 valueArray = node.getData('valueArray'),
11062 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11063 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11064 if(node.getData('prev')) {
11066 wrapper: document.createElement('div'),
11067 aggregate: document.createElement('div'),
11068 label: document.createElement('div')
11070 var wrapper = nlbs.wrapper,
11071 label = nlbs.label,
11072 aggregate = nlbs.aggregate,
11073 wrapperStyle = wrapper.style,
11074 labelStyle = label.style,
11075 aggregateStyle = aggregate.style;
11076 //store node labels
11077 nodeLabels[node.id] = nlbs;
11079 wrapper.appendChild(label);
11080 wrapper.appendChild(aggregate);
11081 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11082 label.style.display = 'none';
11084 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11085 aggregate.style.display = 'none';
11087 wrapperStyle.position = 'relative';
11088 wrapperStyle.overflow = 'visible';
11089 wrapperStyle.fontSize = labelConf.size + 'px';
11090 wrapperStyle.fontFamily = labelConf.family;
11091 wrapperStyle.color = labelConf.color;
11092 wrapperStyle.textAlign = 'center';
11093 aggregateStyle.position = labelStyle.position = 'absolute';
11095 domElement.style.width = node.getData('width') + 'px';
11096 domElement.style.height = node.getData('height') + 'px';
11097 label.innerHTML = node.name;
11099 domElement.appendChild(wrapper);
11102 onPlaceLabel: function(domElement, node) {
11103 if(!node.getData('prev')) return;
11104 var labels = nodeLabels[node.id],
11105 wrapperStyle = labels.wrapper.style,
11106 labelStyle = labels.label.style,
11107 aggregateStyle = labels.aggregate.style,
11108 width = node.getData('width'),
11109 height = node.getData('height'),
11110 dimArray = node.getData('dimArray'),
11111 valArray = node.getData('valueArray'),
11112 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11113 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11114 font = parseInt(wrapperStyle.fontSize, 10),
11115 domStyle = domElement.style;
11117 if(dimArray && valArray) {
11118 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11119 labelStyle.display = '';
11121 labelStyle.display = 'none';
11123 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11124 aggregateStyle.display = '';
11126 aggregateStyle.display = 'none';
11128 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11129 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11130 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11131 if(dimArray[i][0] > 0) {
11132 acum+= valArray[i][0];
11133 leftAcum+= dimArray[i][0];
11136 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11137 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11138 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11139 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11140 labels.aggregate.innerHTML = acum;
11145 var size = st.canvas.getSize(),
11146 margin = config.Margin;
11147 st.config.offsetY = -size.height/2 + margin.bottom
11148 + (config.showLabels && (config.labelOffset + config.Label.size));
11149 st.config.offsetX = (margin.right - margin.left)/2;
11151 this.canvas = this.st.canvas;
11157 Loads JSON data into the visualization.
11161 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
11165 var areaChart = new $jit.AreaChart(options);
11166 areaChart.loadJSON(json);
11169 loadJSON: function(json) {
11170 var prefix = $.time(),
11173 name = $.splat(json.label),
11174 color = $.splat(json.color || this.colors),
11175 config = this.config,
11176 gradient = !!config.type.split(":")[1],
11177 animate = config.animate;
11179 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11180 var val = values[i], prev = values[i-1], next = values[i+1];
11181 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11182 var valArray = $.zip(valLeft, valRight);
11183 var acumLeft = 0, acumRight = 0;
11185 'id': prefix + val.label,
11189 '$valueArray': valArray,
11190 '$colorArray': color,
11191 '$stringArray': name,
11192 '$next': next.label,
11193 '$prev': prev? prev.label:false,
11195 '$gradient': gradient
11201 'id': prefix + '$root',
11212 this.normalizeDims();
11214 st.select(st.root);
11217 modes: ['node-property:height:dimArray'],
11226 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
11230 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11231 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11236 areaChart.updateJSON(json, {
11237 onComplete: function() {
11238 alert('update complete!');
11243 updateJSON: function(json, onComplete) {
11244 if(this.busy) return;
11249 labels = json.label && $.splat(json.label),
11250 values = json.values,
11251 animate = this.config.animate,
11253 $.each(values, function(v) {
11254 var n = graph.getByName(v.label);
11256 v.values = $.splat(v.values);
11257 var stringArray = n.getData('stringArray'),
11258 valArray = n.getData('valueArray');
11259 $.each(valArray, function(a, i) {
11260 a[0] = v.values[i];
11261 if(labels) stringArray[i] = labels[i];
11263 n.setData('valueArray', valArray);
11264 var prev = n.getData('prev'),
11265 next = n.getData('next'),
11266 nextNode = graph.getByName(next);
11268 var p = graph.getByName(prev);
11270 var valArray = p.getData('valueArray');
11271 $.each(valArray, function(a, i) {
11272 a[1] = v.values[i];
11277 var valArray = n.getData('valueArray');
11278 $.each(valArray, function(a, i) {
11279 a[1] = v.values[i];
11284 this.normalizeDims();
11286 st.select(st.root);
11289 modes: ['node-property:height:dimArray'],
11291 onComplete: function() {
11293 onComplete && onComplete.onComplete();
11302 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11306 Variable strings arguments with the name of the stacks.
11311 areaChart.filter('label A', 'label C');
11316 <AreaChart.restore>.
11318 filter: function() {
11319 if(this.busy) return;
11321 if(this.config.Tips.enable) this.st.tips.hide();
11322 this.select(false, false, false);
11323 var args = Array.prototype.slice.call(arguments);
11324 var rt = this.st.graph.getNode(this.st.root);
11326 rt.eachAdjacency(function(adj) {
11327 var n = adj.nodeTo,
11328 dimArray = n.getData('dimArray'),
11329 stringArray = n.getData('stringArray');
11330 n.setData('dimArray', $.map(dimArray, function(d, i) {
11331 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11334 this.st.fx.animate({
11335 modes: ['node-property:dimArray'],
11337 onComplete: function() {
11346 Sets all stacks that could have been filtered visible.
11351 areaChart.restore();
11356 <AreaChart.filter>.
11358 restore: function() {
11359 if(this.busy) return;
11361 if(this.config.Tips.enable) this.st.tips.hide();
11362 this.select(false, false, false);
11363 this.normalizeDims();
11365 this.st.fx.animate({
11366 modes: ['node-property:height:dimArray'],
11368 onComplete: function() {
11373 //adds the little brown bar when hovering the node
11374 select: function(id, name, index) {
11375 if(!this.config.selectOnHover) return;
11376 var s = this.selected;
11377 if(s.id != id || s.name != name
11378 || s.index != index) {
11382 this.st.graph.eachNode(function(n) {
11383 n.setData('border', false);
11386 var n = this.st.graph.getNode(id);
11387 n.setData('border', s);
11388 var link = index === 0? 'prev':'next';
11389 link = n.getData(link);
11391 n = this.st.graph.getByName(link);
11393 n.setData('border', {
11407 Returns an object containing as keys the legend names and as values hex strings with color values.
11412 var legend = areaChart.getLegend();
11415 getLegend: function() {
11418 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11421 var colors = n.getData('colorArray'),
11422 len = colors.length;
11423 $.each(n.getData('stringArray'), function(s, i) {
11424 legend[s] = colors[i % len];
11430 Method: getMaxValue
11432 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11437 var ans = areaChart.getMaxValue();
11440 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11445 //will return 100 for all AreaChart instances,
11446 //displaying all of them with the same scale
11447 $jit.AreaChart.implement({
11448 'getMaxValue': function() {
11455 getMaxValue: function() {
11457 this.st.graph.eachNode(function(n) {
11458 var valArray = n.getData('valueArray'),
11459 acumLeft = 0, acumRight = 0;
11460 $.each(valArray, function(v) {
11462 acumRight += +v[1];
11464 var acum = acumRight>acumLeft? acumRight:acumLeft;
11465 maxValue = maxValue>acum? maxValue:acum;
11470 normalizeDims: function() {
11471 //number of elements
11472 var root = this.st.graph.getNode(this.st.root), l=0;
11473 root.eachAdjacency(function() {
11476 var maxValue = this.getMaxValue() || 1,
11477 size = this.st.canvas.getSize(),
11478 config = this.config,
11479 margin = config.Margin,
11480 labelOffset = config.labelOffset + config.Label.size,
11481 fixedDim = (size.width - (margin.left + margin.right)) / l,
11482 animate = config.animate,
11483 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
11484 - (config.showLabels && labelOffset);
11485 this.st.graph.eachNode(function(n) {
11486 var acumLeft = 0, acumRight = 0, animateValue = [];
11487 $.each(n.getData('valueArray'), function(v) {
11489 acumRight += +v[1];
11490 animateValue.push([0, 0]);
11492 var acum = acumRight>acumLeft? acumRight:acumLeft;
11493 n.setData('width', fixedDim);
11495 n.setData('height', acum * height / maxValue, 'end');
11496 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11497 return [n[0] * height / maxValue, n[1] * height / maxValue];
11499 var dimArray = n.getData('dimArray');
11501 n.setData('dimArray', animateValue);
11504 n.setData('height', acum * height / maxValue);
11505 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11506 return [n[0] * height / maxValue, n[1] * height / maxValue];
11514 * File: Options.BarChart.js
11519 Object: Options.BarChart
11521 <BarChart> options.
11522 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11528 Options.BarChart = {
11533 hoveredColor: '#9fd4ff',
11534 orientation: 'horizontal',
11535 showAggregates: true,
11545 var barChart = new $jit.BarChart({
11548 type: 'stacked:gradient'
11555 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11556 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11557 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11558 barsOffset - (number) Default's *0*. Separation between bars.
11559 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11560 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11561 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11562 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11563 showLabels - (boolean) Default's *true*. Display the name of the slots.
11567 Options.BarChart = {
11571 type: 'stacked', //stacked, grouped, : gradient
11572 labelOffset: 3, //label offset
11573 barsOffset: 0, //distance between bars
11574 nodeCount: 0, //number of bars
11575 hoveredColor: '#9fd4ff',
11577 renderBackground: false,
11578 orientation: 'horizontal',
11579 showAggregates: true,
11598 * File: BarChart.js
11602 $jit.ST.Plot.NodeTypes.implement({
11603 'barchart-stacked' : {
11604 'render' : function(node, canvas) {
11605 var pos = node.pos.getc(true),
11606 width = node.getData('width'),
11607 height = node.getData('height'),
11608 algnPos = this.getAlignedPos(pos, width, height),
11609 x = algnPos.x, y = algnPos.y,
11610 dimArray = node.getData('dimArray'),
11611 valueArray = node.getData('valueArray'),
11612 stringArray = node.getData('stringArray'),
11613 linkArray = node.getData('linkArray'),
11614 gvl = node.getData('gvl'),
11615 colorArray = node.getData('colorArray'),
11616 colorLength = colorArray.length,
11617 nodeCount = node.getData('nodeCount');
11618 var ctx = canvas.getCtx(),
11619 canvasSize = canvas.getSize(),
11621 border = node.getData('border'),
11622 gradient = node.getData('gradient'),
11623 config = node.getData('config'),
11624 horz = config.orientation == 'horizontal',
11625 aggregates = config.showAggregates,
11626 showLabels = config.showLabels,
11627 label = config.Label,
11628 margin = config.Margin;
11631 if (colorArray && dimArray && stringArray) {
11632 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11633 acum += (dimArray[i] || 0);
11638 if(config.shadow.enable) {
11639 shadowThickness = config.shadow.size;
11640 ctx.fillStyle = "rgba(0,0,0,.2)";
11642 ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11644 ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11648 if (colorArray && dimArray && stringArray) {
11649 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11650 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11657 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
11658 x + acum + dimArray[i]/2, y + height);
11660 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
11661 x + width, y - acum- dimArray[i]/2);
11663 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11664 function(v) { return (v * 0.8) >> 0; }));
11665 linear.addColorStop(0, color);
11666 linear.addColorStop(0.3, colorArray[i % colorLength]);
11667 linear.addColorStop(0.7, colorArray[i % colorLength]);
11668 linear.addColorStop(1, color);
11669 ctx.fillStyle = linear;
11672 ctx.fillRect(x + acum, y, dimArray[i], height);
11674 ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
11676 if(border && border.name == stringArray[i]) {
11678 opt.dimValue = dimArray[i];
11680 acum += (dimArray[i] || 0);
11681 valAcum += (valueArray[i] || 0);
11686 ctx.strokeStyle = border.color;
11688 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11690 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11694 if(label.type == 'Native') {
11696 ctx.fillStyle = ctx.strokeStyle = label.color;
11697 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11698 ctx.textBaseline = 'middle';
11700 acumValueLabel = gvl;
11702 acumValueLabel = valAcum;
11704 if(aggregates(node.name, valAcum)) {
11706 ctx.textAlign = 'center';
11707 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11710 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11711 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11712 (label ? label.size + config.labelOffset : 0));
11713 mtxt = ctx.measureText(acumValueLabel);
11714 boxWidth = mtxt.width+10;
11716 boxHeight = label.size+6;
11718 if(boxHeight + acum + config.labelOffset > gridHeight) {
11719 bottomPadding = acum - config.labelOffset - boxHeight;
11721 bottomPadding = acum + config.labelOffset + inset;
11725 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11728 boxY = -boxHeight/2;
11730 ctx.rotate(0 * Math.PI / 180);
11731 ctx.fillStyle = "rgba(255,255,255,.8)";
11732 if(boxHeight + acum + config.labelOffset > gridHeight) {
11733 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11735 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11736 ctx.fillStyle = ctx.strokeStyle = label.color;
11737 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11742 if(showLabels(node.name, valAcum, node)) {
11747 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11750 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11751 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11752 boxWidth = mtxt.width+10;
11755 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11756 leftPadding = acum - config.labelOffset - boxWidth - inset;
11758 leftPadding = acum + config.labelOffset;
11762 ctx.textAlign = 'left';
11763 ctx.translate(x + inset + leftPadding, y + height/2);
11764 boxHeight = label.size+6;
11766 boxY = -boxHeight/2;
11767 ctx.fillStyle = "rgba(255,255,255,.8)";
11769 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11770 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11772 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11774 ctx.fillStyle = label.color;
11775 ctx.rotate(0 * Math.PI / 180);
11776 ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11780 //if the number of nodes greater than 8 rotate labels 45 degrees
11781 if(nodeCount > 8) {
11782 ctx.textAlign = 'left';
11783 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11784 ctx.rotate(45* Math.PI / 180);
11785 ctx.fillText(node.name, 0, 0);
11787 ctx.textAlign = 'center';
11788 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11796 'contains': function(node, mpos) {
11797 var pos = node.pos.getc(true),
11798 width = node.getData('width'),
11799 height = node.getData('height'),
11800 algnPos = this.getAlignedPos(pos, width, height),
11801 x = algnPos.x, y = algnPos.y,
11802 dimArray = node.getData('dimArray'),
11803 config = node.getData('config'),
11805 horz = config.orientation == 'horizontal';
11806 //bounding box check
11808 if(mpos.x < x || mpos.x > x + width
11809 || mpos.y > y + height || mpos.y < y) {
11813 if(mpos.x < x || mpos.x > x + width
11814 || mpos.y > y || mpos.y < y - height) {
11819 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11820 var dimi = dimArray[i];
11821 var url = Url.decode(node.getData('linkArray')[i]);
11824 var intersec = acum;
11825 if(mpos.x <= intersec) {
11827 'name': node.getData('stringArray')[i],
11828 'color': node.getData('colorArray')[i],
11829 'value': node.getData('valueArray')[i],
11830 'valuelabel': node.getData('valuelabelArray')[i],
11831 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11838 var intersec = acum;
11839 if(mpos.y >= intersec) {
11841 'name': node.getData('stringArray')[i],
11842 'color': node.getData('colorArray')[i],
11843 'value': node.getData('valueArray')[i],
11844 'valuelabel': node.getData('valuelabelArray')[i],
11845 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11855 'barchart-grouped' : {
11856 'render' : function(node, canvas) {
11857 var pos = node.pos.getc(true),
11858 width = node.getData('width'),
11859 height = node.getData('height'),
11860 algnPos = this.getAlignedPos(pos, width, height),
11861 x = algnPos.x, y = algnPos.y,
11862 dimArray = node.getData('dimArray'),
11863 valueArray = node.getData('valueArray'),
11864 valuelabelArray = node.getData('valuelabelArray'),
11865 linkArray = node.getData('linkArray'),
11866 valueLength = valueArray.length,
11867 colorArray = node.getData('colorArray'),
11868 colorLength = colorArray.length,
11869 stringArray = node.getData('stringArray');
11871 var ctx = canvas.getCtx(),
11872 canvasSize = canvas.getSize(),
11874 border = node.getData('border'),
11875 gradient = node.getData('gradient'),
11876 config = node.getData('config'),
11877 horz = config.orientation == 'horizontal',
11878 aggregates = config.showAggregates,
11879 showLabels = config.showLabels,
11880 label = config.Label,
11881 shadow = config.shadow,
11882 margin = config.Margin,
11883 fixedDim = (horz? height : width) / valueLength;
11887 maxValue = Math.max.apply(null, dimArray);
11891 ctx.fillStyle = "rgba(0,0,0,.2)";
11892 if (colorArray && dimArray && stringArray && shadow.enable) {
11893 shadowThickness = shadow.size;
11895 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11896 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11897 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11900 ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11905 if(nextBar && nextBar > dimArray[i]) {
11906 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11907 } else if (nextBar && nextBar < dimArray[i]){
11908 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11910 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11912 } else if (i> 0 && i<l-1) {
11913 if(nextBar && nextBar > dimArray[i]) {
11914 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11915 } else if (nextBar && nextBar < dimArray[i]){
11916 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11918 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11920 } else if (i == l-1) {
11921 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11931 if (colorArray && dimArray && stringArray) {
11932 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11933 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11937 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
11938 x + dimArray[i]/2, y + fixedDim * (i + 1));
11940 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
11941 x + fixedDim * (i + 1), y - dimArray[i]/2);
11943 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11944 function(v) { return (v * 0.8) >> 0; }));
11945 linear.addColorStop(0, color);
11946 linear.addColorStop(0.3, colorArray[i % colorLength]);
11947 linear.addColorStop(0.7, colorArray[i % colorLength]);
11948 linear.addColorStop(1, color);
11949 ctx.fillStyle = linear;
11952 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11954 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11956 if(border && border.name == stringArray[i]) {
11957 opt.acum = fixedDim * i;
11958 opt.dimValue = dimArray[i];
11960 acum += (dimArray[i] || 0);
11961 valAcum += (valueArray[i] || 0);
11962 ctx.fillStyle = ctx.strokeStyle = label.color;
11963 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11966 if(aggregates(node.name, valAcum) && label.type == 'Native') {
11967 if(valuelabelArray[i]) {
11968 acumValueLabel = valuelabelArray[i];
11970 acumValueLabel = valueArray[i];
11973 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11974 ctx.textAlign = 'left';
11975 ctx.textBaseline = 'top';
11976 ctx.fillStyle = "rgba(255,255,255,.8)";
11978 gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
11979 mtxt = ctx.measureText(acumValueLabel);
11980 boxWidth = mtxt.width+10;
11982 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11983 leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
11985 leftPadding = dimArray[i] + config.labelOffset + inset;
11987 boxHeight = label.size+6;
11988 boxX = x + leftPadding;
11989 boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
11993 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11994 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11996 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11998 ctx.fillStyle = ctx.strokeStyle = label.color;
11999 ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12006 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12008 ctx.textAlign = 'center';
12011 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12012 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12013 (label ? label.size + config.labelOffset : 0));
12015 mtxt = ctx.measureText(acumValueLabel);
12016 boxWidth = mtxt.width+10;
12017 boxHeight = label.size+6;
12018 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12019 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12021 bottomPadding = dimArray[i] + config.labelOffset + inset;
12025 ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12027 boxX = -boxWidth/2;
12028 boxY = -boxHeight/2;
12029 ctx.fillStyle = "rgba(255,255,255,.8)";
12033 //ctx.rotate(270* Math.PI / 180);
12034 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12035 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12037 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12039 ctx.fillStyle = ctx.strokeStyle = label.color;
12040 ctx.fillText(acumValueLabel, 0,0);
12049 ctx.strokeStyle = border.color;
12051 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12053 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12057 if(label.type == 'Native') {
12059 ctx.fillStyle = ctx.strokeStyle = label.color;
12060 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12061 ctx.textBaseline = 'middle';
12063 if(showLabels(node.name, valAcum, node)) {
12065 ctx.textAlign = 'center';
12066 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12067 ctx.rotate(Math.PI / 2);
12068 ctx.fillText(node.name, 0, 0);
12070 ctx.textAlign = 'center';
12071 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12078 'contains': function(node, mpos) {
12079 var pos = node.pos.getc(true),
12080 width = node.getData('width'),
12081 height = node.getData('height'),
12082 algnPos = this.getAlignedPos(pos, width, height),
12083 x = algnPos.x, y = algnPos.y,
12084 dimArray = node.getData('dimArray'),
12085 len = dimArray.length,
12086 config = node.getData('config'),
12088 horz = config.orientation == 'horizontal',
12089 fixedDim = (horz? height : width) / len;
12090 //bounding box check
12092 if(mpos.x < x || mpos.x > x + width
12093 || mpos.y > y + height || mpos.y < y) {
12097 if(mpos.x < x || mpos.x > x + width
12098 || mpos.y > y || mpos.y < y - height) {
12103 for(var i=0, l=dimArray.length; i<l; i++) {
12104 var dimi = dimArray[i];
12105 var url = Url.decode(node.getData('linkArray')[i]);
12107 var limit = y + fixedDim * i;
12108 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12110 'name': node.getData('stringArray')[i],
12111 'color': node.getData('colorArray')[i],
12112 'value': node.getData('valueArray')[i],
12113 'valuelabel': node.getData('valuelabelArray')[i],
12114 'title': node.getData('titleArray')[i],
12115 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12121 var limit = x + fixedDim * i;
12122 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12124 'name': node.getData('stringArray')[i],
12125 'color': node.getData('colorArray')[i],
12126 'value': node.getData('valueArray')[i],
12127 'valuelabel': node.getData('valuelabelArray')[i],
12128 'title': node.getData('titleArray')[i],
12129 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12139 'barchart-basic' : {
12140 'render' : function(node, canvas) {
12141 var pos = node.pos.getc(true),
12142 width = node.getData('width'),
12143 height = node.getData('height'),
12144 algnPos = this.getAlignedPos(pos, width, height),
12145 x = algnPos.x, y = algnPos.y,
12146 dimArray = node.getData('dimArray'),
12147 valueArray = node.getData('valueArray'),
12148 valuelabelArray = node.getData('valuelabelArray'),
12149 linkArray = node.getData('linkArray'),
12150 valueLength = valueArray.length,
12151 colorArray = node.getData('colorMono'),
12152 colorLength = colorArray.length,
12153 stringArray = node.getData('stringArray');
12155 var ctx = canvas.getCtx(),
12156 canvasSize = canvas.getSize(),
12158 border = node.getData('border'),
12159 gradient = node.getData('gradient'),
12160 config = node.getData('config'),
12161 horz = config.orientation == 'horizontal',
12162 aggregates = config.showAggregates,
12163 showLabels = config.showLabels,
12164 label = config.Label,
12165 fixedDim = (horz? height : width) / valueLength,
12166 margin = config.Margin;
12168 if (colorArray && dimArray && stringArray) {
12169 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12170 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12175 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
12176 x + dimArray[i]/2, y + fixedDim * (i + 1));
12178 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
12179 x + fixedDim * (i + 1), y - dimArray[i]/2);
12182 if(config.shadow.size) {
12183 shadowThickness = config.shadow.size;
12184 ctx.fillStyle = "rgba(0,0,0,.2)";
12186 ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12188 ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12192 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
12193 function(v) { return (v * 0.8) >> 0; }));
12194 linear.addColorStop(0, color);
12195 linear.addColorStop(0.3, colorArray[i % colorLength]);
12196 linear.addColorStop(0.7, colorArray[i % colorLength]);
12197 linear.addColorStop(1, color);
12198 ctx.fillStyle = linear;
12201 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12203 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12205 if(border && border.name == stringArray[i]) {
12206 opt.acum = fixedDim * i;
12207 opt.dimValue = dimArray[i];
12209 acum += (dimArray[i] || 0);
12210 valAcum += (valueArray[i] || 0);
12212 if(label.type == 'Native') {
12213 ctx.fillStyle = ctx.strokeStyle = label.color;
12214 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12215 if(aggregates(node.name, valAcum)) {
12216 if(valuelabelArray[i]) {
12217 acumValueLabel = valuelabelArray[i];
12219 acumValueLabel = valueArray[i];
12222 ctx.textAlign = 'center';
12223 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12226 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12227 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12228 (label ? label.size + config.labelOffset : 0));
12229 mtxt = ctx.measureText(acumValueLabel);
12230 boxWidth = mtxt.width+10;
12232 boxHeight = label.size+6;
12234 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12235 bottomPadding = dimArray[i] - config.labelOffset - inset;
12237 bottomPadding = dimArray[i] + config.labelOffset + inset;
12241 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12244 boxY = -boxHeight/2;
12246 //ctx.rotate(270* Math.PI / 180);
12247 ctx.fillStyle = "rgba(255,255,255,.6)";
12248 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12249 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12251 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12252 ctx.fillStyle = ctx.strokeStyle = label.color;
12253 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12262 ctx.strokeStyle = border.color;
12264 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12266 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12270 if(label.type == 'Native') {
12272 ctx.fillStyle = ctx.strokeStyle = label.color;
12273 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12274 ctx.textBaseline = 'middle';
12275 if(showLabels(node.name, valAcum, node)) {
12279 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12280 mtxt = ctx.measureText(node.name + ": " + valAcum);
12281 boxWidth = mtxt.width+10;
12284 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12285 leftPadding = acum - config.labelOffset - boxWidth - inset;
12287 leftPadding = acum + config.labelOffset;
12291 ctx.textAlign = 'left';
12292 ctx.translate(x + inset + leftPadding, y + height/2);
12293 boxHeight = label.size+6;
12295 boxY = -boxHeight/2;
12296 ctx.fillStyle = "rgba(255,255,255,.8)";
12299 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12300 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12302 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12305 ctx.fillStyle = label.color;
12306 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12310 if(stringArray.length > 8) {
12311 ctx.textAlign = 'left';
12312 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12313 ctx.rotate(45* Math.PI / 180);
12314 ctx.fillText(node.name, 0, 0);
12316 ctx.textAlign = 'center';
12317 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12326 'contains': function(node, mpos) {
12327 var pos = node.pos.getc(true),
12328 width = node.getData('width'),
12329 height = node.getData('height'),
12330 config = node.getData('config'),
12331 algnPos = this.getAlignedPos(pos, width, height),
12332 x = algnPos.x, y = algnPos.y ,
12333 dimArray = node.getData('dimArray'),
12334 len = dimArray.length,
12336 horz = config.orientation == 'horizontal',
12337 fixedDim = (horz? height : width) / len;
12339 //bounding box check
12341 if(mpos.x < x || mpos.x > x + width
12342 || mpos.y > y + height || mpos.y < y) {
12346 if(mpos.x < x || mpos.x > x + width
12347 || mpos.y > y || mpos.y < y - height) {
12352 for(var i=0, l=dimArray.length; i<l; i++) {
12353 var dimi = dimArray[i];
12354 var url = Url.decode(node.getData('linkArray')[i]);
12356 var limit = y + fixedDim * i;
12357 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12359 'name': node.getData('stringArray')[i],
12360 'color': node.getData('colorArray')[i],
12361 'value': node.getData('valueArray')[i],
12362 'valuelabel': node.getData('valuelabelArray')[i],
12363 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12369 var limit = x + fixedDim * i;
12370 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12372 'name': node.getData('stringArray')[i],
12373 'color': node.getData('colorArray')[i],
12374 'value': node.getData('valueArray')[i],
12375 'valuelabel': node.getData('valuelabelArray')[i],
12376 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12391 A visualization that displays stacked bar charts.
12393 Constructor Options:
12395 See <Options.BarChart>.
12398 $jit.BarChart = new Class({
12400 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12404 initialize: function(opt) {
12405 this.controller = this.config =
12406 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12407 Label: { type: 'Native' }
12409 //set functions for showLabels and showAggregates
12410 var showLabels = this.config.showLabels,
12411 typeLabels = $.type(showLabels),
12412 showAggregates = this.config.showAggregates,
12413 typeAggregates = $.type(showAggregates);
12414 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12415 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12416 Options.Fx.clearCanvas = false;
12417 this.initializeViz();
12420 initializeViz: function() {
12421 var config = this.config, that = this;
12422 var nodeType = config.type.split(":")[0],
12423 horz = config.orientation == 'horizontal',
12425 var st = new $jit.ST({
12426 injectInto: config.injectInto,
12427 orientation: horz? 'left' : 'bottom',
12428 background: config.background,
12429 renderBackground: config.renderBackground,
12430 backgroundColor: config.backgroundColor,
12431 colorStop1: config.colorStop1,
12432 colorStop2: config.colorStop2,
12434 nodeCount: config.nodeCount,
12435 siblingOffset: config.barsOffset,
12437 withLabels: config.Label.type != 'Native',
12438 useCanvas: config.useCanvas,
12440 type: config.Label.type
12444 type: 'barchart-' + nodeType,
12453 enable: config.Tips.enable,
12456 onShow: function(tip, node, contains) {
12457 var elem = contains;
12458 config.Tips.onShow(tip, elem, node);
12459 if(elem.link != 'undefined' && elem.link != '') {
12460 document.body.style.cursor = 'pointer';
12463 onHide: function(call) {
12464 document.body.style.cursor = 'default';
12471 onClick: function(node, eventInfo, evt) {
12472 if(!config.Events.enable) return;
12473 var elem = eventInfo.getContains();
12474 config.Events.onClick(elem, eventInfo, evt);
12476 onMouseMove: function(node, eventInfo, evt) {
12477 if(!config.hoveredColor) return;
12479 var elem = eventInfo.getContains();
12480 that.select(node.id, elem.name, elem.index);
12482 that.select(false, false, false);
12486 onCreateLabel: function(domElement, node) {
12487 var labelConf = config.Label,
12488 valueArray = node.getData('valueArray'),
12489 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12490 grouped = config.type.split(':')[0] == 'grouped',
12491 horz = config.orientation == 'horizontal';
12493 wrapper: document.createElement('div'),
12494 aggregate: document.createElement('div'),
12495 label: document.createElement('div')
12498 var wrapper = nlbs.wrapper,
12499 label = nlbs.label,
12500 aggregate = nlbs.aggregate,
12501 wrapperStyle = wrapper.style,
12502 labelStyle = label.style,
12503 aggregateStyle = aggregate.style;
12504 //store node labels
12505 nodeLabels[node.id] = nlbs;
12507 wrapper.appendChild(label);
12508 wrapper.appendChild(aggregate);
12509 if(!config.showLabels(node.name, acum, node)) {
12510 labelStyle.display = 'none';
12512 if(!config.showAggregates(node.name, acum, node)) {
12513 aggregateStyle.display = 'none';
12515 wrapperStyle.position = 'relative';
12516 wrapperStyle.overflow = 'visible';
12517 wrapperStyle.fontSize = labelConf.size + 'px';
12518 wrapperStyle.fontFamily = labelConf.family;
12519 wrapperStyle.color = labelConf.color;
12520 wrapperStyle.textAlign = 'center';
12521 aggregateStyle.position = labelStyle.position = 'absolute';
12523 domElement.style.width = node.getData('width') + 'px';
12524 domElement.style.height = node.getData('height') + 'px';
12525 aggregateStyle.left = "0px";
12526 labelStyle.left = config.labelOffset + 'px';
12527 labelStyle.whiteSpace = "nowrap";
12528 label.innerHTML = node.name;
12530 domElement.appendChild(wrapper);
12532 onPlaceLabel: function(domElement, node) {
12533 if(!nodeLabels[node.id]) return;
12534 var labels = nodeLabels[node.id],
12535 wrapperStyle = labels.wrapper.style,
12536 labelStyle = labels.label.style,
12537 aggregateStyle = labels.aggregate.style,
12538 grouped = config.type.split(':')[0] == 'grouped',
12539 horz = config.orientation == 'horizontal',
12540 dimArray = node.getData('dimArray'),
12541 valArray = node.getData('valueArray'),
12542 nodeCount = node.getData('nodeCount'),
12543 valueLength = valArray.length;
12544 valuelabelArray = node.getData('valuelabelArray'),
12545 stringArray = node.getData('stringArray'),
12546 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12547 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12548 font = parseInt(wrapperStyle.fontSize, 10),
12549 domStyle = domElement.style,
12550 fixedDim = (horz? height : width) / valueLength;
12553 if(dimArray && valArray) {
12554 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12556 aggregateStyle.width = width - config.labelOffset + "px";
12557 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12558 if(dimArray[i] > 0) {
12559 acum+= valArray[i];
12562 if(config.showLabels(node.name, acum, node)) {
12563 labelStyle.display = '';
12565 labelStyle.display = 'none';
12567 if(config.showAggregates(node.name, acum, node)) {
12568 aggregateStyle.display = '';
12570 aggregateStyle.display = 'none';
12572 if(config.orientation == 'horizontal') {
12573 aggregateStyle.textAlign = 'right';
12574 labelStyle.textAlign = 'left';
12575 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12576 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12577 domElement.style.height = wrapperStyle.height = height + 'px';
12579 aggregateStyle.top = (-font - config.labelOffset) + 'px';
12580 labelStyle.top = (config.labelOffset + height) + 'px';
12581 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12582 domElement.style.height = wrapperStyle.height = height + 'px';
12583 if(stringArray.length > 8) {
12584 labels.label.className = "rotatedLabelReverse";
12585 labelStyle.textAlign = "left";
12586 labelStyle.top = config.labelOffset + height + width/2 + "px";
12592 labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12593 labels.aggregate.innerHTML = "";
12598 maxValue = Math.max.apply(null,dimArray);
12599 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12600 valueLabelDim = 50;
12601 valueLabel = document.createElement('div');
12602 valueLabel.innerHTML = valuelabelArray[i];
12603 // valueLabel.class = "rotatedLabel";
12604 valueLabel.className = "rotatedLabel";
12605 valueLabel.style.position = "absolute";
12606 valueLabel.style.textAlign = "left";
12607 valueLabel.style.verticalAlign = "middle";
12608 valueLabel.style.height = valueLabelDim + "px";
12609 valueLabel.style.width = valueLabelDim + "px";
12610 valueLabel.style.top = (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12611 valueLabel.style.left = (fixedDim * i) + "px";
12612 labels.wrapper.appendChild(valueLabel);
12615 labels.aggregate.innerHTML = acum;
12622 var size = st.canvas.getSize(),
12623 l = config.nodeCount,
12624 margin = config.Margin;
12625 title = config.Title;
12626 subtitle = config.Subtitle,
12627 grouped = config.type.split(':')[0] == 'grouped',
12628 margin = config.Margin,
12629 ticks = config.Ticks,
12630 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12631 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12632 horz = config.orientation == 'horizontal',
12633 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12634 fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12635 whiteSpace = size.width - (marginWidth + (fixedDim * l));
12636 //bug in IE7 when vertical bar charts load in dashlets where number of bars exceed a certain width, canvas renders with an incorrect width, a hard refresh fixes the problem
12637 if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12639 //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12640 if(!grouped && !horz) {
12641 st.config.siblingOffset = whiteSpace/(l+1);
12648 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12649 if(config.Ticks.enable) {
12650 st.config.offsetY = ((margin.bottom+config.Label.size+config.labelOffset+(subtitle.text? subtitle.size+subtitle.offset:0)) - (margin.top + (title.text? title.size+title.offset:0))) /2;
12652 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12655 st.config.offsetY = -size.height/2 + margin.bottom
12656 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12657 if(config.Ticks.enable) {
12658 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12660 st.config.offsetX = (margin.right - margin.left)/2;
12664 this.canvas = this.st.canvas;
12669 renderTitle: function() {
12670 var canvas = this.canvas,
12671 size = canvas.getSize(),
12672 config = this.config,
12673 margin = config.Margin,
12674 label = config.Label,
12675 title = config.Title;
12676 ctx = canvas.getCtx();
12677 ctx.fillStyle = title.color;
12678 ctx.textAlign = 'left';
12679 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12680 if(label.type == 'Native') {
12681 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12685 renderSubtitle: function() {
12686 var canvas = this.canvas,
12687 size = canvas.getSize(),
12688 config = this.config,
12689 margin = config.Margin,
12690 label = config.Label,
12691 subtitle = config.Subtitle,
12692 nodeCount = config.nodeCount,
12693 horz = config.orientation == 'horizontal' ? true : false,
12694 ctx = canvas.getCtx();
12695 ctx.fillStyle = title.color;
12696 ctx.textAlign = 'left';
12697 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12698 if(label.type == 'Native') {
12699 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12703 renderScrollNote: function() {
12704 var canvas = this.canvas,
12705 size = canvas.getSize(),
12706 config = this.config,
12707 margin = config.Margin,
12708 label = config.Label,
12709 note = config.ScrollNote;
12710 ctx = canvas.getCtx();
12711 ctx.fillStyle = title.color;
12712 title = config.Title;
12713 ctx.textAlign = 'center';
12714 ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12715 if(label.type == 'Native') {
12716 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12720 renderTicks: function() {
12722 var canvas = this.canvas,
12723 size = canvas.getSize(),
12724 config = this.config,
12725 margin = config.Margin,
12726 ticks = config.Ticks,
12727 title = config.Title,
12728 subtitle = config.Subtitle,
12729 label = config.Label,
12730 shadow = config.shadow;
12731 horz = config.orientation == 'horizontal',
12732 maxValue = this.getMaxValue(),
12733 maxTickValue = Math.ceil(maxValue*.1)*10;
12734 if(maxTickValue == maxValue) {
12735 var length = maxTickValue.toString().length;
12736 maxTickValue = maxTickValue + parseInt(pad(1,length));
12738 grouped = config.type.split(':')[0] == 'grouped',
12740 labelIncrement = maxTickValue/ticks.segments,
12741 ctx = canvas.getCtx();
12742 ctx.strokeStyle = ticks.color;
12743 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12745 ctx.textAlign = 'center';
12746 ctx.textBaseline = 'middle';
12748 idLabel = canvas.id + "-label";
12750 container = document.getElementById(idLabel);
12754 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12755 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12756 segmentLength = grid/ticks.segments;
12757 ctx.fillStyle = ticks.color;
12759 (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12760 size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12762 while(axis<=grid) {
12763 ctx.fillStyle = ticks.color;
12764 lineHeight = size.height-margin.bottom-margin.top-config.labelOffset-label.size-(title.text? title.size+title.offset:0)-(subtitle.text? subtitle.size+subtitle.offset:0);
12765 ctx.fillRect(Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0) - (shadow.enable ? shadow.size : 0), 1, lineHeight + (shadow.enable ? shadow.size * 2: 0));
12766 ctx.fillStyle = label.color;
12768 if(label.type == 'Native' && config.showLabels) {
12769 ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12771 axis += segmentLength;
12772 labelValue += labelIncrement;
12777 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12778 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12779 grid = -size.height+(margin.bottom+config.labelOffset+label.size+margin.top+(title.text? title.size+title.offset:0)+(subtitle.text? subtitle.size+subtitle.offset:0)),
12780 segmentLength = grid/ticks.segments;
12781 ctx.fillStyle = ticks.color;
12782 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size-1, -(size.height/2)+margin.top+(title.text? title.size+title.offset:0),1,size.height-margin.top-margin.bottom-label.size-config.labelOffset-(title.text? title.size+title.offset:0)-(subtitle.text? subtitle.size+subtitle.offset:0));
12784 while(axis>=grid) {
12786 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12787 ctx.rotate(0 * Math.PI / 180 );
12788 ctx.fillStyle = label.color;
12789 if(config.showLabels) {
12790 if(label.type == 'Native') {
12791 ctx.fillText(labelValue, 0, 0);
12793 //html labels on y axis
12794 labelDiv = document.createElement('div');
12795 labelDiv.innerHTML = labelValue;
12796 labelDiv.className = "rotatedLabel";
12797 // labelDiv.class = "rotatedLabel";
12798 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12799 labelDiv.style.left = margin.left + "px";
12800 labelDiv.style.width = labelDim + "px";
12801 labelDiv.style.height = labelDim + "px";
12802 labelDiv.style.textAlign = "center";
12803 labelDiv.style.verticalAlign = "middle";
12804 labelDiv.style.position = "absolute";
12805 container.appendChild(labelDiv);
12809 ctx.fillStyle = ticks.color;
12810 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size, Math.round(axis), size.width-margin.right-margin.left-config.labelOffset-label.size,1 );
12811 htmlOrigin += segmentLength;
12812 axis += segmentLength;
12813 labelValue += labelIncrement;
12822 renderBackground: function() {
12823 var canvas = this.canvas,
12824 config = this.config,
12825 backgroundColor = config.backgroundColor,
12826 size = canvas.getSize(),
12827 ctx = canvas.getCtx();
12828 ctx.fillStyle = backgroundColor;
12829 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12832 clear: function() {
12833 var canvas = this.canvas;
12834 var ctx = canvas.getCtx(),
12835 size = canvas.getSize();
12836 ctx.fillStyle = "rgba(255,255,255,0)";
12837 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12838 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
12840 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
12841 var canvas = this.canvas,
12842 size = canvas.getSize(),
12843 config = this.config,
12844 orgHeight = size.height,
12845 margin = config.Margin,
12847 grouped = config.type.split(':')[0] == 'grouped',
12848 horz = config.orientation == 'horizontal',
12849 ctx = canvas.getCtx();
12851 var newWindowWidth = document.body.offsetWidth;
12852 var diff = newWindowWidth - orgWindowWidth;
12853 var newWidth = orgContainerDivWidth + (diff/cols);
12854 var scale = newWidth/orgContainerDivWidth;
12855 canvas.resize(newWidth,orgHeight);
12856 if(typeof FlashCanvas == "undefined") {
12859 this.clear();// hack for flashcanvas bug not properly clearing rectangle
12862 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12865 this.loadJSON(json);
12872 Loads JSON data into the visualization.
12876 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
12880 var barChart = new $jit.BarChart(options);
12881 barChart.loadJSON(json);
12884 loadJSON: function(json) {
12885 if(this.busy) return;
12888 var prefix = $.time(),
12891 name = $.splat(json.label),
12892 color = $.splat(json.color || this.colors),
12893 config = this.config,
12894 gradient = !!config.type.split(":")[1],
12895 renderBackground = config.renderBackground,
12896 animate = config.animate,
12897 ticks = config.Ticks,
12898 title = config.Title,
12899 note = config.ScrollNote,
12900 subtitle = config.Subtitle,
12901 horz = config.orientation == 'horizontal',
12903 colorLength = color.length,
12904 nameLength = name.length;
12905 groupTotalValue = 0;
12906 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12907 var val = values[i];
12908 var valArray = $.splat(val.values);
12909 groupTotalValue += parseFloat(valArray.sum());
12912 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12913 var val = values[i];
12914 var valArray = $.splat(values[i].values);
12915 var valuelabelArray = $.splat(values[i].valuelabels);
12916 var linkArray = $.splat(values[i].links);
12917 var titleArray = $.splat(values[i].titles);
12918 var barTotalValue = valArray.sum();
12921 'id': prefix + val.label,
12926 '$linkArray': linkArray,
12927 '$gvl': val.gvaluelabel,
12928 '$titleArray': titleArray,
12929 '$valueArray': valArray,
12930 '$valuelabelArray': valuelabelArray,
12931 '$colorArray': color,
12932 '$colorMono': $.splat(color[i % colorLength]),
12933 '$stringArray': name,
12934 '$barTotalValue': barTotalValue,
12935 '$groupTotalValue': groupTotalValue,
12936 '$nodeCount': values.length,
12937 '$gradient': gradient,
12944 'id': prefix + '$root',
12955 this.normalizeDims();
12957 if(renderBackground) {
12958 this.renderBackground();
12961 if(!animate && ticks.enable) {
12962 this.renderTicks();
12964 if(!animate && note.text) {
12965 this.renderScrollNote();
12967 if(!animate && title.text) {
12968 this.renderTitle();
12970 if(!animate && subtitle.text) {
12971 this.renderSubtitle();
12975 st.select(st.root);
12979 modes: ['node-property:width:dimArray'],
12981 onComplete: function() {
12987 modes: ['node-property:height:dimArray'],
12989 onComplete: function() {
13002 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
13006 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13007 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13012 barChart.updateJSON(json, {
13013 onComplete: function() {
13014 alert('update complete!');
13019 updateJSON: function(json, onComplete) {
13020 if(this.busy) return;
13024 var graph = st.graph;
13025 var values = json.values;
13026 var animate = this.config.animate;
13028 var horz = this.config.orientation == 'horizontal';
13029 $.each(values, function(v) {
13030 var n = graph.getByName(v.label);
13032 n.setData('valueArray', $.splat(v.values));
13034 n.setData('stringArray', $.splat(json.label));
13038 this.normalizeDims();
13040 st.select(st.root);
13044 modes: ['node-property:width:dimArray'],
13046 onComplete: function() {
13048 onComplete && onComplete.onComplete();
13053 modes: ['node-property:height:dimArray'],
13055 onComplete: function() {
13057 onComplete && onComplete.onComplete();
13064 //adds the little brown bar when hovering the node
13065 select: function(id, name) {
13067 if(!this.config.hoveredColor) return;
13068 var s = this.selected;
13069 if(s.id != id || s.name != name) {
13072 s.color = this.config.hoveredColor;
13073 this.st.graph.eachNode(function(n) {
13075 n.setData('border', s);
13077 n.setData('border', false);
13087 Returns an object containing as keys the legend names and as values hex strings with color values.
13092 var legend = barChart.getLegend();
13095 getLegend: function() {
13096 var legend = new Array();
13097 var name = new Array();
13098 var color = new Array();
13100 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13103 var colors = n.getData('colorArray'),
13104 len = colors.length;
13105 $.each(n.getData('stringArray'), function(s, i) {
13106 color[i] = colors[i % len];
13109 legend['name'] = name;
13110 legend['color'] = color;
13115 Method: getMaxValue
13117 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13122 var ans = barChart.getMaxValue();
13125 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13130 //will return 100 for all BarChart instances,
13131 //displaying all of them with the same scale
13132 $jit.BarChart.implement({
13133 'getMaxValue': function() {
13140 getMaxValue: function() {
13141 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13142 this.st.graph.eachNode(function(n) {
13143 var valArray = n.getData('valueArray'),
13145 if(!valArray) return;
13147 $.each(valArray, function(v) {
13151 acum = Math.max.apply(null, valArray);
13153 maxValue = maxValue>acum? maxValue:acum;
13158 setBarType: function(type) {
13159 this.config.type = type;
13160 this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13163 normalizeDims: function() {
13164 //number of elements
13165 var root = this.st.graph.getNode(this.st.root), l=0;
13166 root.eachAdjacency(function() {
13169 var maxValue = this.getMaxValue() || 1,
13170 size = this.st.canvas.getSize(),
13171 config = this.config,
13172 margin = config.Margin,
13173 ticks = config.Ticks,
13174 title = config.Title,
13175 subtitle = config.Subtitle,
13176 grouped = config.type.split(':')[0] == 'grouped',
13177 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13178 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13179 horz = config.orientation == 'horizontal',
13180 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13181 animate = config.animate,
13182 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13184 - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13185 dim1 = horz? 'height':'width',
13186 dim2 = horz? 'width':'height',
13187 basic = config.type.split(':')[0] == 'basic';
13190 var maxTickValue = Math.ceil(maxValue*.1)*10;
13191 if(maxTickValue == maxValue) {
13192 var length = maxTickValue.toString().length;
13193 maxTickValue = maxTickValue + parseInt(pad(1,length));
13196 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13199 this.st.graph.eachNode(function(n) {
13200 var acum = 0, animateValue = [];
13201 $.each(n.getData('valueArray'), function(v) {
13203 animateValue.push(0);
13207 fixedDim = animateValue.length * 40;
13209 n.setData(dim1, fixedDim);
13213 n.setData(dim2, acum * height / maxValue, 'end');
13214 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13215 return n * height / maxValue;
13217 var dimArray = n.getData('dimArray');
13219 n.setData('dimArray', animateValue);
13225 n.setData(dim2, acum * height / maxTickValue);
13226 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13227 return n * height / maxTickValue;
13230 n.setData(dim2, acum * height / maxValue);
13231 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13232 return n * height / maxValue;
13240 //funnel chart options
13243 Options.FunnelChart = {
13247 type: 'stacked', //stacked, grouped, : gradient
13248 labelOffset: 3, //label offset
13249 barsOffset: 0, //distance between bars
13250 hoveredColor: '#9fd4ff',
13251 orientation: 'vertical',
13252 showAggregates: true,
13265 $jit.ST.Plot.NodeTypes.implement({
13266 'funnelchart-basic' : {
13267 'render' : function(node, canvas) {
13268 var pos = node.pos.getc(true),
13269 width = node.getData('width'),
13270 height = node.getData('height'),
13271 algnPos = this.getAlignedPos(pos, width, height),
13272 x = algnPos.x, y = algnPos.y,
13273 dimArray = node.getData('dimArray'),
13274 valueArray = node.getData('valueArray'),
13275 valuelabelArray = node.getData('valuelabelArray'),
13276 linkArray = node.getData('linkArray'),
13277 colorArray = node.getData('colorArray'),
13278 colorLength = colorArray.length,
13279 stringArray = node.getData('stringArray');
13280 var ctx = canvas.getCtx(),
13282 border = node.getData('border'),
13283 gradient = node.getData('gradient'),
13284 config = node.getData('config'),
13285 horz = config.orientation == 'horizontal',
13286 aggregates = config.showAggregates,
13287 showLabels = config.showLabels,
13288 label = config.Label,
13289 size = canvas.getSize(),
13290 labelOffset = config.labelOffset + 10;
13291 minWidth = width * .25;
13294 if (colorArray && dimArray && stringArray) {
13297 // horizontal lines
13298 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13299 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13301 if(label.type == 'Native') {
13302 if(showLabels(node.name, valAcum, node)) {
13303 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13304 var stringValue = stringArray[i];
13305 var valueLabel = String(valuelabelArray[i]);
13306 var mV = ctx.measureText(stringValue);
13307 var mVL = ctx.measureText(valueLabel);
13308 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13309 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13310 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13311 var bottomWidth = minWidth + ((acum) * ratio);
13312 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13313 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13314 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13315 // ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13319 ctx.moveTo(bottomWidth/2,y - acum); //
13320 ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight); // top right
13321 ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight); // bottom right
13325 ctx.moveTo(-bottomWidth/2,y - acum); //
13326 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight); // top right
13327 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight); // bottom right
13332 acum += (dimArray[i] || 0);
13333 valAcum += (valueArray[i] || 0);
13340 //funnel segments and labels
13341 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13342 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13343 var colori = colorArray[i % colorLength];
13344 if(label.type == 'Native') {
13345 var stringValue = stringArray[i];
13346 var valueLabel = String(valuelabelArray[i]);
13347 var mV = ctx.measureText(stringValue);
13348 var mVL = ctx.measureText(valueLabel);
13353 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13354 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13355 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13356 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13358 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13359 var bottomWidth = minWidth + ((acum) * ratio);
13360 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13365 linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13366 var colorRgb = $.hexToRgb(colori);
13367 var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
13368 function(v) { return (v * .5) >> 0; });
13369 linear.addColorStop(0, 'rgba('+color+',1)');
13370 linear.addColorStop(0.5, 'rgba('+colorRgb+',1)');
13371 linear.addColorStop(1, 'rgba('+color+',1)');
13372 ctx.fillStyle = linear;
13376 ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13377 ctx.lineTo(topWidth/2,y - acum - dimArray[i]); // top right
13378 ctx.lineTo(bottomWidth/2,y - acum); // bottom right
13379 ctx.lineTo(-bottomWidth/2,y - acum); // bottom left
13384 if(border && border.name == stringArray[i]) {
13386 opt.dimValue = dimArray[i];
13393 ctx.strokeStyle = border.color;
13395 //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13399 if(label.type == 'Native') {
13401 ctx.fillStyle = ctx.strokeStyle = label.color;
13402 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13403 ctx.textBaseline = 'middle';
13405 acumValueLabel = valAcum;
13407 if(showLabels(node.name, valAcum, node)) {
13410 ctx.textAlign = 'left';
13411 ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13412 ctx.textAlign = 'right';
13413 ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13418 acum += (dimArray[i] || 0);
13419 valAcum += (valueArray[i] || 0);
13425 'contains': function(node, mpos) {
13426 var pos = node.pos.getc(true),
13427 width = node.getData('width'),
13428 height = node.getData('height'),
13429 algnPos = this.getAlignedPos(pos, width, height),
13430 x = algnPos.x, y = algnPos.y,
13431 dimArray = node.getData('dimArray'),
13432 config = node.getData('config'),
13433 st = node.getData('st'),
13435 horz = config.orientation == 'horizontal',
13436 minWidth = width * .25;
13438 canvas = node.getData('canvas'),
13439 size = canvas.getSize(),
13440 offsetY = st.config.offsetY;
13441 //bounding box check
13443 if(mpos.y > y || mpos.y < y - height) {
13447 var newY = Math.abs(mpos.y + offsetY);
13448 var bound = minWidth + (newY * ratio);
13449 var boundLeft = -bound/2;
13450 var boundRight = bound/2;
13451 if(mpos.x < boundLeft || mpos.x > boundRight ) {
13457 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13458 var dimi = dimArray[i];
13462 var url = Url.decode(node.getData('linkArray')[i]);
13464 var intersec = acum;
13465 if(mpos.y >= intersec) {
13467 'name': node.getData('stringArray')[i],
13468 'color': node.getData('colorArray')[i],
13469 'value': node.getData('valueArray')[i],
13470 'percentage': node.getData('percentageArray')[i],
13471 'valuelabel': node.getData('valuelabelArray')[i],
13486 A visualization that displays funnel charts.
13488 Constructor Options:
13490 See <Options.FunnelChart>.
13493 $jit.FunnelChart = new Class({
13495 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13499 initialize: function(opt) {
13500 this.controller = this.config =
13501 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13502 Label: { type: 'Native' }
13504 //set functions for showLabels and showAggregates
13505 var showLabels = this.config.showLabels,
13506 typeLabels = $.type(showLabels),
13507 showAggregates = this.config.showAggregates,
13508 typeAggregates = $.type(showAggregates);
13509 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13510 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13511 Options.Fx.clearCanvas = false;
13512 this.initializeViz();
13515 initializeViz: function() {
13516 var config = this.config, that = this;
13517 var nodeType = config.type.split(":")[0],
13518 horz = config.orientation == 'horizontal',
13520 var st = new $jit.ST({
13521 injectInto: config.injectInto,
13522 orientation: horz? 'left' : 'bottom',
13524 background: config.background,
13525 renderBackground: config.renderBackground,
13526 backgroundColor: config.backgroundColor,
13527 colorStop1: config.colorStop1,
13528 colorStop2: config.colorStop2,
13529 siblingOffset: config.segmentOffset,
13531 withLabels: config.Label.type != 'Native',
13532 useCanvas: config.useCanvas,
13534 type: config.Label.type
13538 type: 'funnelchart-' + nodeType,
13547 enable: config.Tips.enable,
13550 onShow: function(tip, node, contains) {
13551 var elem = contains;
13552 config.Tips.onShow(tip, elem, node);
13553 if(elem.link != 'undefined' && elem.link != '') {
13554 document.body.style.cursor = 'pointer';
13557 onHide: function(call) {
13558 document.body.style.cursor = 'default';
13565 onClick: function(node, eventInfo, evt) {
13566 if(!config.Events.enable) return;
13567 var elem = eventInfo.getContains();
13568 config.Events.onClick(elem, eventInfo, evt);
13570 onMouseMove: function(node, eventInfo, evt) {
13571 if(!config.hoveredColor) return;
13573 var elem = eventInfo.getContains();
13574 that.select(node.id, elem.name, elem.index);
13576 that.select(false, false, false);
13580 onCreateLabel: function(domElement, node) {
13581 var labelConf = config.Label,
13582 valueArray = node.getData('valueArray'),
13583 idArray = node.getData('idArray'),
13584 valuelabelArray = node.getData('valuelabelArray'),
13585 stringArray = node.getData('stringArray');
13586 size = st.canvas.getSize()
13589 for(var i=0, l=valueArray.length; i<l; i++) {
13591 wrapper: document.createElement('div'),
13592 valueLabel: document.createElement('div'),
13593 label: document.createElement('div')
13595 var wrapper = nlbs.wrapper,
13596 label = nlbs.label,
13597 valueLabel = nlbs.valueLabel,
13598 wrapperStyle = wrapper.style,
13599 labelStyle = label.style,
13600 valueLabelStyle = valueLabel.style;
13601 //store node labels
13602 nodeLabels[idArray[i]] = nlbs;
13604 wrapper.appendChild(label);
13605 wrapper.appendChild(valueLabel);
13607 wrapperStyle.position = 'relative';
13608 wrapperStyle.overflow = 'visible';
13609 wrapperStyle.fontSize = labelConf.size + 'px';
13610 wrapperStyle.fontFamily = labelConf.family;
13611 wrapperStyle.color = labelConf.color;
13612 wrapperStyle.textAlign = 'center';
13613 wrapperStyle.width = size.width + 'px';
13614 valueLabelStyle.position = labelStyle.position = 'absolute';
13615 valueLabelStyle.left = labelStyle.left = '0px';
13616 valueLabelStyle.width = (size.width/3) + 'px';
13617 valueLabelStyle.textAlign = 'right';
13618 label.innerHTML = stringArray[i];
13619 valueLabel.innerHTML = valuelabelArray[i];
13620 domElement.id = prefix+'funnel';
13621 domElement.style.width = size.width + 'px';
13623 domElement.appendChild(wrapper);
13627 onPlaceLabel: function(domElement, node) {
13629 var dimArray = node.getData('dimArray'),
13630 idArray = node.getData('idArray'),
13631 valueArray = node.getData('valueArray'),
13632 valuelabelArray = node.getData('valuelabelArray'),
13633 stringArray = node.getData('stringArray');
13634 size = st.canvas.getSize(),
13635 pos = node.pos.getc(true),
13636 domElement.style.left = "0px",
13637 domElement.style.top = "0px",
13638 minWidth = node.getData('width') * .25,
13640 pos = node.pos.getc(true),
13641 labelConf = config.Label;
13644 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13646 var labels = nodeLabels[idArray[i]],
13647 wrapperStyle = labels.wrapper.style,
13648 labelStyle = labels.label.style,
13649 valueLabelStyle = labels.valueLabel.style;
13650 var bottomWidth = minWidth + (acum * ratio);
13652 font = parseInt(wrapperStyle.fontSize, 10),
13653 domStyle = domElement.style;
13657 wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13658 valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13659 labelStyle.left = (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13661 acum += (dimArray[i] || 0);
13669 var size = st.canvas.getSize(),
13670 margin = config.Margin;
13671 title = config.Title;
13672 subtitle = config.Subtitle;
13675 st.config.offsetY = -size.height/2 + margin.bottom
13676 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13678 st.config.offsetX = (margin.right - margin.left)/2;
13682 this.canvas = this.st.canvas;
13685 renderTitle: function() {
13686 var canvas = this.canvas,
13687 size = canvas.getSize(),
13688 config = this.config,
13689 margin = config.Margin,
13690 label = config.Label,
13691 title = config.Title;
13692 ctx = canvas.getCtx();
13693 ctx.fillStyle = title.color;
13694 ctx.textAlign = 'left';
13695 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13696 if(label.type == 'Native') {
13697 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13701 renderSubtitle: function() {
13702 var canvas = this.canvas,
13703 size = canvas.getSize(),
13704 config = this.config,
13705 margin = config.Margin,
13706 label = config.Label,
13707 subtitle = config.Subtitle;
13708 ctx = canvas.getCtx();
13709 ctx.fillStyle = title.color;
13710 ctx.textAlign = 'left';
13711 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13712 if(label.type == 'Native') {
13713 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13718 renderDropShadow: function() {
13719 var canvas = this.canvas,
13720 size = canvas.getSize(),
13721 config = this.config,
13722 margin = config.Margin,
13723 horz = config.orientation == 'horizontal',
13724 label = config.Label,
13725 title = config.Title,
13726 shadowThickness = 4,
13727 subtitle = config.Subtitle,
13728 ctx = canvas.getCtx(),
13729 minwidth = (size.width/8) * .25,
13730 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13731 topMargin = (title.text? title.size + title.offset : 0) + margin.top,
13732 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13733 - (config.showLabels && (config.Label.size + config.labelOffset)),
13735 topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13736 topY = (-size.height/2) + topMargin - shadowThickness;
13737 bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13738 bottomWidth = minwidth + shadowThickness;
13740 ctx.fillStyle = "rgba(0,0,0,.2)";
13741 ctx.moveTo(0,topY);
13742 ctx.lineTo(-topWidth/2,topY); //top left
13743 ctx.lineTo(-bottomWidth/2,bottomY); // bottom left
13744 ctx.lineTo(bottomWidth/2,bottomY); // bottom right
13745 ctx.lineTo(topWidth/2,topY); // top right
13752 renderBackground: function() {
13753 var canvas = this.canvas,
13754 config = this.config,
13755 backgroundColor = config.backgroundColor,
13756 size = canvas.getSize(),
13757 ctx = canvas.getCtx();
13758 //ctx.globalCompositeOperation = "destination-over";
13759 ctx.fillStyle = backgroundColor;
13760 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13762 clear: function() {
13763 var canvas = this.canvas;
13764 var ctx = canvas.getCtx(),
13765 size = canvas.getSize();
13766 ctx.fillStyle = "rgba(255,255,255,0)";
13767 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13768 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13770 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
13771 var canvas = this.canvas,
13772 size = canvas.getSize(),
13773 config = this.config,
13774 orgHeight = size.height,
13775 margin = config.Margin,
13777 label = config.Label,
13778 horz = config.orientation == 'horizontal',
13779 ctx = canvas.getCtx();
13782 var newWindowWidth = document.body.offsetWidth;
13783 var diff = newWindowWidth - orgWindowWidth;
13784 var newWidth = orgContainerDivWidth + (diff/cols);
13785 canvas.resize(newWidth,orgHeight);
13787 if(typeof FlashCanvas == "undefined") {
13790 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13792 this.loadJSON(json);
13796 loadJSON: function(json) {
13797 if(this.busy) return;
13799 var prefix = $.time(),
13802 name = $.splat(json.label),
13803 color = $.splat(json.color || this.colors),
13804 config = this.config,
13805 canvas = this.canvas,
13806 gradient = !!config.type.split(":")[1],
13807 animate = config.animate,
13808 title = config.Title,
13809 subtitle = config.Subtitle,
13810 renderBackground = config.renderBackground,
13811 horz = config.orientation == 'horizontal',
13813 colorLength = color.length,
13814 nameLength = name.length,
13816 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13817 var val = values[i];
13818 var valArray = $.splat(val.values);
13819 totalValue += parseFloat(valArray.sum());
13823 var nameArray = new Array();
13824 var idArray = new Array();
13825 var valArray = new Array();
13826 var valuelabelArray = new Array();
13827 var linkArray = new Array();
13828 var titleArray = new Array();
13829 var percentageArray = new Array();
13831 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13832 var val = values[i];
13833 nameArray[i] = $.splat(val.label);
13834 idArray[i] = $.splat(prefix + val.label);
13835 valArray[i] = $.splat(val.values);
13836 valuelabelArray[i] = $.splat(val.valuelabels);
13837 linkArray[i] = $.splat(val.links);
13838 titleArray[i] = $.splat(val.titles);
13839 percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13844 nameArray.reverse();
13845 valArray.reverse();
13846 valuelabelArray.reverse();
13847 linkArray.reverse();
13848 titleArray.reverse();
13849 percentageArray.reverse();
13852 'id': prefix + val.label,
13857 '$idArray': idArray,
13858 '$linkArray': linkArray,
13859 '$titleArray': titleArray,
13860 '$valueArray': valArray,
13861 '$valuelabelArray': valuelabelArray,
13862 '$colorArray': color,
13863 '$colorMono': $.splat(color[i % colorLength]),
13864 '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
13865 '$gradient': gradient,
13867 '$percentageArray' : percentageArray,
13875 'id': prefix + '$root',
13886 this.normalizeDims();
13888 if(renderBackground) {
13889 this.renderBackground();
13891 if(!animate && title.text) {
13892 this.renderTitle();
13894 if(!animate && subtitle.text) {
13895 this.renderSubtitle();
13897 if(typeof FlashCanvas == "undefined") {
13898 this.renderDropShadow();
13901 st.select(st.root);
13905 modes: ['node-property:width:dimArray'],
13907 onComplete: function() {
13913 modes: ['node-property:height:dimArray'],
13915 onComplete: function() {
13928 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
13932 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13933 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13938 barChart.updateJSON(json, {
13939 onComplete: function() {
13940 alert('update complete!');
13945 updateJSON: function(json, onComplete) {
13946 if(this.busy) return;
13950 var graph = st.graph;
13951 var values = json.values;
13952 var animate = this.config.animate;
13954 var horz = this.config.orientation == 'horizontal';
13955 $.each(values, function(v) {
13956 var n = graph.getByName(v.label);
13958 n.setData('valueArray', $.splat(v.values));
13960 n.setData('stringArray', $.splat(json.label));
13964 this.normalizeDims();
13966 st.select(st.root);
13970 modes: ['node-property:width:dimArray'],
13972 onComplete: function() {
13974 onComplete && onComplete.onComplete();
13979 modes: ['node-property:height:dimArray'],
13981 onComplete: function() {
13983 onComplete && onComplete.onComplete();
13990 //adds the little brown bar when hovering the node
13991 select: function(id, name) {
13993 if(!this.config.hoveredColor) return;
13994 var s = this.selected;
13995 if(s.id != id || s.name != name) {
13998 s.color = this.config.hoveredColor;
13999 this.st.graph.eachNode(function(n) {
14001 n.setData('border', s);
14003 n.setData('border', false);
14013 Returns an object containing as keys the legend names and as values hex strings with color values.
14018 var legend = barChart.getLegend();
14021 getLegend: function() {
14022 var legend = new Array();
14023 var name = new Array();
14024 var color = new Array();
14026 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14029 var colors = n.getData('colorArray'),
14030 len = colors.length;
14031 $.each(n.getData('stringArray'), function(s, i) {
14032 color[i] = colors[i % len];
14035 legend['name'] = name;
14036 legend['color'] = color;
14041 Method: getMaxValue
14043 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14048 var ans = barChart.getMaxValue();
14051 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14056 //will return 100 for all BarChart instances,
14057 //displaying all of them with the same scale
14058 $jit.BarChart.implement({
14059 'getMaxValue': function() {
14066 getMaxValue: function() {
14067 var maxValue = 0, stacked = true;
14068 this.st.graph.eachNode(function(n) {
14069 var valArray = n.getData('valueArray'),
14071 if(!valArray) return;
14073 $.each(valArray, function(v) {
14077 acum = Math.max.apply(null, valArray);
14079 maxValue = maxValue>acum? maxValue:acum;
14084 setBarType: function(type) {
14085 this.config.type = type;
14086 this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14089 normalizeDims: function() {
14090 //number of elements
14091 var root = this.st.graph.getNode(this.st.root), l=0;
14092 root.eachAdjacency(function() {
14095 var maxValue = this.getMaxValue() || 1,
14096 size = this.st.canvas.getSize(),
14097 config = this.config,
14098 margin = config.Margin,
14099 title = config.Title,
14100 subtitle = config.Subtitle,
14101 marginWidth = margin.left + margin.right,
14102 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14103 horz = config.orientation == 'horizontal',
14104 animate = config.animate,
14105 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14107 - (config.showLabels && (config.Label.size + config.labelOffset)),
14108 dim1 = horz? 'height':'width',
14109 dim2 = horz? 'width':'height';
14112 minWidth = size.width/8;
14116 this.st.graph.eachNode(function(n) {
14117 var acum = 0, animateValue = [];
14118 $.each(n.getData('valueArray'), function(v) {
14120 animateValue.push(0);
14122 n.setData(dim1, minWidth);
14125 n.setData(dim2, acum * height / maxValue, 'end');
14126 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14127 return n * height / maxValue;
14129 var dimArray = n.getData('dimArray');
14131 n.setData('dimArray', animateValue);
14134 n.setData(dim2, acum * height / maxValue);
14135 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14136 return n * height / maxValue;
14147 * File: Options.PieChart.js
14151 Object: Options.PieChart
14153 <PieChart> options.
14154 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14160 Options.PieChart = {
14166 hoveredColor: '#9fd4ff',
14168 resizeLabels: false,
14169 updateHeights: false
14178 var pie = new $jit.PieChart({
14181 type: 'stacked:gradient'
14188 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14189 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14190 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14191 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14192 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14193 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14194 showLabels - (boolean) Default's *true*. Display the name of the slots.
14195 resizeLabels - (boolean|number) Default's *false*. Resize the pie labels according to their stacked values. Set a number for *resizeLabels* to set a font size minimum.
14196 updateHeights - (boolean) Default's *false*. Only for mono-valued (most common) pie charts. Resize the height of the pie slices according to their current values.
14199 Options.PieChart = {
14203 offset: 25, // page offset
14205 labelOffset: 3, // label offset
14206 type: 'stacked', // gradient
14208 hoveredColor: '#9fd4ff',
14219 resizeLabels: false,
14221 //only valid for mono-valued datasets
14222 updateHeights: false
14226 * Class: Layouts.Radial
14228 * Implements a Radial Layout.
14232 * <RGraph>, <Hypertree>
14235 Layouts.Radial = new Class({
14240 * Computes nodes' positions.
14244 * property - _optional_ A <Graph.Node> position property to store the new
14245 * positions. Possible values are 'pos', 'end' or 'start'.
14248 compute : function(property) {
14249 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14250 NodeDim.compute(this.graph, prop, this.config);
14251 this.graph.computeLevels(this.root, 0, "ignore");
14252 var lengthFunc = this.createLevelDistanceFunc();
14253 this.computeAngularWidths(prop);
14254 this.computePositions(prop, lengthFunc);
14260 * Performs the main algorithm for computing node positions.
14262 computePositions : function(property, getLength) {
14263 var propArray = property;
14264 var graph = this.graph;
14265 var root = graph.getNode(this.root);
14266 var parent = this.parent;
14267 var config = this.config;
14269 for ( var i=0, l=propArray.length; i < l; i++) {
14270 var pi = propArray[i];
14271 root.setPos($P(0, 0), pi);
14272 root.setData('span', Math.PI * 2, pi);
14280 graph.eachBFS(this.root, function(elem) {
14281 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14282 var angleInit = elem.angleSpan.begin;
14283 var len = getLength(elem);
14284 //Calculate the sum of all angular widths
14285 var totalAngularWidths = 0, subnodes = [], maxDim = {};
14286 elem.eachSubnode(function(sib) {
14287 totalAngularWidths += sib._treeAngularWidth;
14289 for ( var i=0, l=propArray.length; i < l; i++) {
14290 var pi = propArray[i], dim = sib.getData('dim', pi);
14291 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14293 subnodes.push(sib);
14295 //Maintain children order
14296 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14297 if (parent && parent.id == elem.id && subnodes.length > 0
14298 && subnodes[0].dist) {
14299 subnodes.sort(function(a, b) {
14300 return (a.dist >= b.dist) - (a.dist <= b.dist);
14303 //Calculate nodes positions.
14304 for (var k = 0, ls=subnodes.length; k < ls; k++) {
14305 var child = subnodes[k];
14306 if (!child._flag) {
14307 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14308 var theta = angleInit + angleProportion / 2;
14310 for ( var i=0, l=propArray.length; i < l; i++) {
14311 var pi = propArray[i];
14312 child.setPos($P(theta, len), pi);
14313 child.setData('span', angleProportion, pi);
14314 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14317 child.angleSpan = {
14319 end : angleInit + angleProportion
14321 angleInit += angleProportion;
14328 * Method: setAngularWidthForNodes
14330 * Sets nodes angular widths.
14332 setAngularWidthForNodes : function(prop) {
14333 this.graph.eachBFS(this.root, function(elem, i) {
14334 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14335 elem._angularWidth = diamValue / i;
14340 * Method: setSubtreesAngularWidth
14342 * Sets subtrees angular widths.
14344 setSubtreesAngularWidth : function() {
14346 this.graph.eachNode(function(elem) {
14347 that.setSubtreeAngularWidth(elem);
14352 * Method: setSubtreeAngularWidth
14354 * Sets the angular width for a subtree.
14356 setSubtreeAngularWidth : function(elem) {
14357 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14358 elem.eachSubnode(function(child) {
14359 that.setSubtreeAngularWidth(child);
14360 sumAW += child._treeAngularWidth;
14362 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14366 * Method: computeAngularWidths
14368 * Computes nodes and subtrees angular widths.
14370 computeAngularWidths : function(prop) {
14371 this.setAngularWidthForNodes(prop);
14372 this.setSubtreesAngularWidth();
14379 * File: Sunburst.js
14385 A radial space filling tree visualization.
14389 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14393 This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
14397 All <Loader> methods
14399 Constructor Options:
14401 Inherits options from
14404 - <Options.Controller>
14410 - <Options.NodeStyles>
14411 - <Options.Navigation>
14413 Additionally, there are other parameters and some default values changed
14415 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14416 levelDistance - (number) Default's *100*. The distance between levels of the tree.
14417 Node.type - Described in <Options.Node>. Default's to *multipie*.
14418 Node.height - Described in <Options.Node>. Default's *0*.
14419 Edge.type - Described in <Options.Edge>. Default's *none*.
14420 Label.textAlign - Described in <Options.Label>. Default's *start*.
14421 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14423 Instance Properties:
14425 canvas - Access a <Canvas> instance.
14426 graph - Access a <Graph> instance.
14427 op - Access a <Sunburst.Op> instance.
14428 fx - Access a <Sunburst.Plot> instance.
14429 labels - Access a <Sunburst.Label> interface implementation.
14433 $jit.Sunburst = new Class({
14435 Implements: [ Loader, Extras, Layouts.Radial ],
14437 initialize: function(controller) {
14438 var $Sunburst = $jit.Sunburst;
14441 interpolation: 'linear',
14442 levelDistance: 100,
14444 'type': 'multipie',
14451 textAlign: 'start',
14452 textBaseline: 'middle'
14456 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14457 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14459 var canvasConfig = this.config;
14460 if(canvasConfig.useCanvas) {
14461 this.canvas = canvasConfig.useCanvas;
14462 this.config.labelContainer = this.canvas.id + '-label';
14464 if(canvasConfig.background) {
14465 canvasConfig.background = $.merge({
14467 colorStop1: this.config.colorStop1,
14468 colorStop2: this.config.colorStop2
14469 }, canvasConfig.background);
14471 this.canvas = new Canvas(this, canvasConfig);
14472 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14475 this.graphOptions = {
14483 this.graph = new Graph(this.graphOptions, this.config.Node,
14485 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14486 this.fx = new $Sunburst.Plot(this, $Sunburst);
14487 this.op = new $Sunburst.Op(this);
14490 this.rotated = null;
14492 // initialize extras
14493 this.initializeExtras();
14498 createLevelDistanceFunc
14500 Returns the levelDistance function used for calculating a node distance
14501 to its origin. This function returns a function that is computed
14502 per level and not per node, such that all nodes with the same depth will have the
14503 same distance to the origin. The resulting function gets the
14504 parent node as parameter and returns a float.
14507 createLevelDistanceFunc: function() {
14508 var ld = this.config.levelDistance;
14509 return function(elem) {
14510 return (elem._depth + 1) * ld;
14517 Computes positions and plots the tree.
14520 refresh: function() {
14528 An alias for computing new positions to _endPos_
14535 reposition: function() {
14536 this.compute('end');
14542 Rotates the graph so that the selected node is horizontal on the right.
14546 node - (object) A <Graph.Node>.
14547 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14548 opt - (object) Configuration options merged with this visualization configuration options.
14552 <Sunburst.rotateAngle>
14555 rotate: function(node, method, opt) {
14556 var theta = node.getPos(opt.property || 'current').getp(true).theta;
14557 this.rotated = node;
14558 this.rotateAngle(-theta, method, opt);
14562 Method: rotateAngle
14564 Rotates the graph of an angle theta.
14568 node - (object) A <Graph.Node>.
14569 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14570 opt - (object) Configuration options merged with this visualization configuration options.
14577 rotateAngle: function(theta, method, opt) {
14579 var options = $.merge(this.config, opt || {}, {
14582 var prop = opt.property || (method === "animate" ? 'end' : 'current');
14583 if(method === 'animate') {
14584 this.fx.animation.pause();
14586 this.graph.eachNode(function(n) {
14587 var p = n.getPos(prop);
14590 p.theta += Math.PI * 2;
14593 if (method == 'animate') {
14594 this.fx.animate(options);
14595 } else if (method == 'replot') {
14604 Plots the Sunburst. This is a shortcut to *fx.plot*.
14611 $jit.Sunburst.$extend = true;
14613 (function(Sunburst) {
14618 Custom extension of <Graph.Op>.
14622 All <Graph.Op> methods
14629 Sunburst.Op = new Class( {
14631 Implements: Graph.Op
14636 Class: Sunburst.Plot
14638 Custom extension of <Graph.Plot>.
14642 All <Graph.Plot> methods
14649 Sunburst.Plot = new Class( {
14651 Implements: Graph.Plot
14656 Class: Sunburst.Label
14658 Custom extension of <Graph.Label>.
14659 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14663 All <Graph.Label> methods and subclasses.
14667 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14670 Sunburst.Label = {};
14673 Sunburst.Label.Native
14675 Custom extension of <Graph.Label.Native>.
14679 All <Graph.Label.Native> methods
14683 <Graph.Label.Native>
14685 Sunburst.Label.Native = new Class( {
14686 Implements: Graph.Label.Native,
14688 initialize: function(viz) {
14690 this.label = viz.config.Label;
14691 this.config = viz.config;
14694 renderLabel: function(canvas, node, controller) {
14695 var span = node.getData('span');
14696 if(span < Math.PI /2 && Math.tan(span) *
14697 this.config.levelDistance * node._depth < 10) {
14700 var ctx = canvas.getCtx();
14701 var measure = ctx.measureText(node.name);
14702 if (node.id == this.viz.root) {
14703 var x = -measure.width / 2, y = 0, thetap = 0;
14707 var ld = controller.levelDistance - indent;
14708 var clone = node.pos.clone();
14709 clone.rho += indent;
14710 var p = clone.getp(true);
14711 var ct = clone.getc(true);
14712 var x = ct.x, y = ct.y;
14713 // get angle in degrees
14715 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14716 var thetap = cond ? p.theta + pi : p.theta;
14718 x -= Math.abs(Math.cos(p.theta) * measure.width);
14719 y += Math.sin(p.theta) * measure.width;
14720 } else if (node.id == this.viz.root) {
14721 x -= measure.width / 2;
14725 ctx.translate(x, y);
14726 ctx.rotate(thetap);
14727 ctx.fillText(node.name, 0, 0);
14735 Custom extension of <Graph.Label.SVG>.
14739 All <Graph.Label.SVG> methods
14746 Sunburst.Label.SVG = new Class( {
14747 Implements: Graph.Label.SVG,
14749 initialize: function(viz) {
14756 Overrides abstract method placeLabel in <Graph.Plot>.
14760 tag - A DOM label element.
14761 node - A <Graph.Node>.
14762 controller - A configuration/controller object passed to the visualization.
14765 placeLabel: function(tag, node, controller) {
14766 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14767 var radius = canvas.getSize();
14769 x: Math.round(pos.x + radius.width / 2),
14770 y: Math.round(pos.y + radius.height / 2)
14772 tag.setAttribute('x', labelPos.x);
14773 tag.setAttribute('y', labelPos.y);
14775 var bb = tag.getBBox();
14777 // center the label
14778 var x = tag.getAttribute('x');
14779 var y = tag.getAttribute('y');
14780 // get polar coordinates
14781 var p = node.pos.getp(true);
14782 // get angle in degrees
14784 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14786 tag.setAttribute('x', x - bb.width);
14787 tag.setAttribute('y', y - bb.height);
14788 } else if (node.id == viz.root) {
14789 tag.setAttribute('x', x - bb.width / 2);
14792 var thetap = cond ? p.theta + pi : p.theta;
14794 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14798 controller.onPlaceLabel(tag, node);
14803 Sunburst.Label.HTML
14805 Custom extension of <Graph.Label.HTML>.
14809 All <Graph.Label.HTML> methods.
14816 Sunburst.Label.HTML = new Class( {
14817 Implements: Graph.Label.HTML,
14819 initialize: function(viz) {
14825 Overrides abstract method placeLabel in <Graph.Plot>.
14829 tag - A DOM label element.
14830 node - A <Graph.Node>.
14831 controller - A configuration/controller object passed to the visualization.
14834 placeLabel: function(tag, node, controller) {
14835 var pos = node.pos.clone(),
14836 canvas = this.viz.canvas,
14837 height = node.getData('height'),
14838 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14839 radius = canvas.getSize();
14841 pos = pos.getc(true);
14844 x: Math.round(pos.x + radius.width / 2),
14845 y: Math.round(pos.y + radius.height / 2)
14848 var style = tag.style;
14849 style.left = labelPos.x + 'px';
14850 style.top = labelPos.y + 'px';
14851 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14853 controller.onPlaceLabel(tag, node);
14858 Class: Sunburst.Plot.NodeTypes
14860 This class contains a list of <Graph.Node> built-in types.
14861 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14863 You can add your custom node types, customizing your visualization to the extreme.
14868 Sunburst.Plot.NodeTypes.implement({
14870 'render': function(node, canvas) {
14871 //print your custom node to canvas
14874 'contains': function(node, pos) {
14875 //return true if pos is inside the node or false otherwise
14882 Sunburst.Plot.NodeTypes = new Class( {
14885 'contains': $.lambda(false),
14886 'anglecontains': function(node, pos) {
14887 var span = node.getData('span') / 2, theta = node.pos.theta;
14888 var begin = theta - span, end = theta + span;
14890 begin += Math.PI * 2;
14891 var atan = Math.atan2(pos.y, pos.x);
14893 atan += Math.PI * 2;
14895 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14897 return atan > begin && atan < end;
14900 'anglecontainsgauge': function(node, pos) {
14901 var span = node.getData('span') / 2, theta = node.pos.theta;
14902 var config = node.getData('config');
14903 var ld = this.config.levelDistance;
14904 var yOffset = pos.y-(ld/2);
14905 var begin = ((theta - span)/2)+Math.PI,
14906 end = ((theta + span)/2)+Math.PI;
14909 begin += Math.PI * 2;
14910 var atan = Math.atan2(yOffset, pos.x);
14914 atan += Math.PI * 2;
14918 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14920 return atan > begin && atan < end;
14926 'render': function(node, canvas) {
14927 var span = node.getData('span') / 2, theta = node.pos.theta;
14928 var begin = theta - span, end = theta + span;
14929 var polarNode = node.pos.getp(true);
14930 var polar = new Polar(polarNode.rho, begin);
14931 var p1coord = polar.getc(true);
14933 var p2coord = polar.getc(true);
14935 var ctx = canvas.getCtx();
14938 ctx.lineTo(p1coord.x, p1coord.y);
14940 ctx.lineTo(p2coord.x, p2coord.y);
14942 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14946 'contains': function(node, pos) {
14947 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14948 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14949 var ld = this.config.levelDistance, d = node._depth;
14950 return (rho <= ld * d);
14956 'render': function(node, canvas) {
14957 var height = node.getData('height');
14958 var ldist = height? height : this.config.levelDistance;
14959 var span = node.getData('span') / 2, theta = node.pos.theta;
14960 var begin = theta - span, end = theta + span;
14961 var polarNode = node.pos.getp(true);
14963 var polar = new Polar(polarNode.rho, begin);
14964 var p1coord = polar.getc(true);
14967 var p2coord = polar.getc(true);
14969 polar.rho += ldist;
14970 var p3coord = polar.getc(true);
14972 polar.theta = begin;
14973 var p4coord = polar.getc(true);
14975 var ctx = canvas.getCtx();
14978 ctx.arc(0, 0, polarNode.rho, begin, end, false);
14979 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
14980 ctx.moveTo(p1coord.x, p1coord.y);
14981 ctx.lineTo(p4coord.x, p4coord.y);
14982 ctx.moveTo(p2coord.x, p2coord.y);
14983 ctx.lineTo(p3coord.x, p3coord.y);
14986 if (node.collapsed) {
14991 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
14997 'contains': function(node, pos) {
14998 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14999 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15000 var height = node.getData('height');
15001 var ldist = height? height : this.config.levelDistance;
15002 var ld = this.config.levelDistance, d = node._depth;
15003 return (rho >= ld * d) && (rho <= (ld * d + ldist));
15009 'gradient-multipie': {
15010 'render': function(node, canvas) {
15011 var ctx = canvas.getCtx();
15012 var height = node.getData('height');
15013 var ldist = height? height : this.config.levelDistance;
15014 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15015 0, 0, node.getPos().rho + ldist);
15017 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15018 $.each(colorArray, function(i) {
15019 ans.push(parseInt(i * 0.5, 10));
15021 var endColor = $.rgbToHex(ans);
15022 radialGradient.addColorStop(0, endColor);
15023 radialGradient.addColorStop(1, node.getData('color'));
15024 ctx.fillStyle = radialGradient;
15025 this.nodeTypes['multipie'].render.call(this, node, canvas);
15027 'contains': function(node, pos) {
15028 return this.nodeTypes['multipie'].contains.call(this, node, pos);
15033 'render': function(node, canvas) {
15034 var ctx = canvas.getCtx();
15035 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15038 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15039 $.each(colorArray, function(i) {
15040 ans.push(parseInt(i * 0.5, 10));
15042 var endColor = $.rgbToHex(ans);
15043 radialGradient.addColorStop(1, endColor);
15044 radialGradient.addColorStop(0, node.getData('color'));
15045 ctx.fillStyle = radialGradient;
15046 this.nodeTypes['pie'].render.call(this, node, canvas);
15048 'contains': function(node, pos) {
15049 return this.nodeTypes['pie'].contains.call(this, node, pos);
15055 Class: Sunburst.Plot.EdgeTypes
15057 This class contains a list of <Graph.Adjacence> built-in types.
15058 Edge types implemented are 'none', 'line' and 'arrow'.
15060 You can add your custom edge types, customizing your visualization to the extreme.
15065 Sunburst.Plot.EdgeTypes.implement({
15067 'render': function(adj, canvas) {
15068 //print your custom edge to canvas
15071 'contains': function(adj, pos) {
15072 //return true if pos is inside the arc or false otherwise
15079 Sunburst.Plot.EdgeTypes = new Class({
15082 'render': function(adj, canvas) {
15083 var from = adj.nodeFrom.pos.getc(true),
15084 to = adj.nodeTo.pos.getc(true);
15085 this.edgeHelper.line.render(from, to, canvas);
15087 'contains': function(adj, pos) {
15088 var from = adj.nodeFrom.pos.getc(true),
15089 to = adj.nodeTo.pos.getc(true);
15090 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15094 'render': function(adj, canvas) {
15095 var from = adj.nodeFrom.pos.getc(true),
15096 to = adj.nodeTo.pos.getc(true),
15097 dim = adj.getData('dim'),
15098 direction = adj.data.$direction,
15099 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15100 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15102 'contains': function(adj, pos) {
15103 var from = adj.nodeFrom.pos.getc(true),
15104 to = adj.nodeTo.pos.getc(true);
15105 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15109 'render': function(adj, canvas) {
15110 var from = adj.nodeFrom.pos.getc(),
15111 to = adj.nodeTo.pos.getc(),
15112 dim = Math.max(from.norm(), to.norm());
15113 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15115 'contains': $.lambda(false) //TODO(nico): Implement this!
15123 * File: PieChart.js
15127 $jit.Sunburst.Plot.NodeTypes.implement({
15128 'piechart-stacked' : {
15129 'render' : function(node, canvas) {
15130 var pos = node.pos.getp(true),
15131 dimArray = node.getData('dimArray'),
15132 valueArray = node.getData('valueArray'),
15133 colorArray = node.getData('colorArray'),
15134 colorLength = colorArray.length,
15135 stringArray = node.getData('stringArray'),
15136 span = node.getData('span') / 2,
15137 theta = node.pos.theta,
15138 begin = theta - span,
15139 end = theta + span,
15142 var ctx = canvas.getCtx(),
15144 gradient = node.getData('gradient'),
15145 border = node.getData('border'),
15146 config = node.getData('config'),
15147 showLabels = config.showLabels,
15148 resizeLabels = config.resizeLabels,
15149 label = config.Label;
15151 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15152 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15154 if (colorArray && dimArray && stringArray) {
15155 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15156 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15157 if(dimi <= 0) continue;
15158 ctx.fillStyle = ctx.strokeStyle = colori;
15159 if(gradient && dimi) {
15160 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15161 xpos, ypos, acum + dimi + config.sliceOffset);
15162 var colorRgb = $.hexToRgb(colori),
15163 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15164 endColor = $.rgbToHex(ans);
15166 radialGradient.addColorStop(0, colori);
15167 radialGradient.addColorStop(0.5, colori);
15168 radialGradient.addColorStop(1, endColor);
15169 ctx.fillStyle = radialGradient;
15172 polar.rho = acum + config.sliceOffset;
15173 polar.theta = begin;
15174 var p1coord = polar.getc(true);
15176 var p2coord = polar.getc(true);
15178 var p3coord = polar.getc(true);
15179 polar.theta = begin;
15180 var p4coord = polar.getc(true);
15183 //fixing FF arc method + fill
15184 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15185 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15187 if(border && border.name == stringArray[i]) {
15189 opt.dimValue = dimArray[i];
15193 acum += (dimi || 0);
15194 valAcum += (valueArray[i] || 0);
15198 ctx.globalCompositeOperation = "source-over";
15200 ctx.strokeStyle = border.color;
15201 var s = begin < end? 1 : -1;
15203 //fixing FF arc method + fill
15204 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15205 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15210 if(showLabels && label.type == 'Native') {
15212 ctx.fillStyle = ctx.strokeStyle = label.color;
15213 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15214 fontSize = (label.size * scale) >> 0;
15215 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15217 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15218 ctx.textBaseline = 'middle';
15219 ctx.textAlign = 'center';
15221 polar.rho = acum + config.labelOffset + config.sliceOffset;
15222 polar.theta = node.pos.theta;
15223 var cart = polar.getc(true);
15225 ctx.fillText(node.name, cart.x, cart.y);
15230 'contains': function(node, pos) {
15231 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15232 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15233 var ld = this.config.levelDistance, d = node._depth;
15234 var config = node.getData('config');
15235 if(rho <=ld * d + config.sliceOffset) {
15236 var dimArray = node.getData('dimArray');
15237 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15238 var dimi = dimArray[i];
15239 if(rho >= acum && rho <= acum + dimi) {
15241 name: node.getData('stringArray')[i],
15242 color: node.getData('colorArray')[i],
15243 value: node.getData('valueArray')[i],
15256 'piechart-basic' : {
15257 'render' : function(node, canvas) {
15258 var pos = node.pos.getp(true),
15259 dimArray = node.getData('dimArray'),
15260 valueArray = node.getData('valueArray'),
15261 colorArray = node.getData('colorMono'),
15262 colorLength = colorArray.length,
15263 stringArray = node.getData('stringArray'),
15264 percentage = node.getData('percentage'),
15265 iteration = node.getData('iteration'),
15266 span = node.getData('span') / 2,
15267 theta = node.pos.theta,
15268 begin = theta - span,
15269 end = theta + span,
15272 var ctx = canvas.getCtx(),
15274 gradient = node.getData('gradient'),
15275 border = node.getData('border'),
15276 config = node.getData('config'),
15277 renderSubtitle = node.getData('renderSubtitle'),
15278 renderBackground = config.renderBackground,
15279 showLabels = config.showLabels,
15280 resizeLabels = config.resizeLabels,
15281 label = config.Label;
15283 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15284 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15285 //background rendering for IE
15286 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15287 backgroundColor = config.backgroundColor,
15288 size = canvas.getSize();
15290 ctx.fillStyle = backgroundColor;
15291 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15295 var margin = config.Margin,
15296 title = config.Title,
15297 subtitle = config.Subtitle;
15298 ctx.fillStyle = title.color;
15299 ctx.textAlign = 'left';
15301 if(title.text != "") {
15302 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15304 if(label.type == 'Native') {
15305 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15309 if(subtitle.text != "") {
15310 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15311 if(label.type == 'Native') {
15312 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15317 if (colorArray && dimArray && stringArray) {
15318 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15319 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15320 if(dimi <= 0) continue;
15321 ctx.fillStyle = ctx.strokeStyle = colori;
15323 polar.rho = acum + config.sliceOffset;
15324 polar.theta = begin;
15325 var p1coord = polar.getc(true);
15327 var p2coord = polar.getc(true);
15329 var p3coord = polar.getc(true);
15330 polar.theta = begin;
15331 var p4coord = polar.getc(true);
15333 if(typeof FlashCanvas == "undefined") {
15336 ctx.fillStyle = "rgba(0,0,0,.2)";
15337 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15338 ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);
15341 if(gradient && dimi) {
15342 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15343 xpos, ypos, acum + dimi + config.sliceOffset);
15344 var colorRgb = $.hexToRgb(colori),
15345 endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15346 endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15348 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15349 radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15350 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15351 radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15352 ctx.fillStyle = radialGradient;
15357 //fixing FF arc method + fill
15359 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15360 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15362 if(border && border.name == stringArray[i]) {
15364 opt.dimValue = dimArray[i];
15367 opt.sliceValue = valueArray[i];
15369 acum += (dimi || 0);
15370 valAcum += (valueArray[i] || 0);
15374 ctx.globalCompositeOperation = "source-over";
15376 ctx.strokeStyle = border.color;
15377 var s = begin < end? 1 : -1;
15379 //fixing FF arc method + fill
15380 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15381 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15386 if(showLabels && label.type == 'Native') {
15388 ctx.fillStyle = ctx.strokeStyle = label.color;
15389 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15390 fontSize = (label.size * scale) >> 0;
15391 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15393 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15394 ctx.textBaseline = 'middle';
15395 ctx.textAlign = 'center';
15397 angle = theta * 360 / (2 * pi);
15398 polar.rho = acum + config.labelOffset + config.sliceOffset;
15399 polar.theta = node.pos.theta;
15400 var cart = polar.getc(true);
15401 if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15404 if(config.labelType == 'name') {
15405 ctx.fillText(node.name, cart.x, cart.y);
15407 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15414 'contains': function(node, pos) {
15415 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15416 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15417 var ld = this.config.levelDistance, d = node._depth;
15418 var config = node.getData('config');
15420 if(rho <=ld * d + config.sliceOffset) {
15421 var dimArray = node.getData('dimArray');
15422 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15423 var dimi = dimArray[i];
15424 if(rho >= acum && rho <= acum + dimi) {
15425 var url = Url.decode(node.getData('linkArray')[i]);
15427 name: node.getData('stringArray')[i],
15429 color: node.getData('colorArray')[i],
15430 value: node.getData('valueArray')[i],
15431 percentage: node.getData('percentage'),
15432 valuelabel: node.getData('valuelabelsArray')[i],
15450 A visualization that displays stacked bar charts.
15452 Constructor Options:
15454 See <Options.PieChart>.
15457 $jit.PieChart = new Class({
15459 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15463 initialize: function(opt) {
15464 this.controller = this.config =
15465 $.merge(Options("Canvas", "PieChart", "Label"), {
15466 Label: { type: 'Native' }
15468 this.initializeViz();
15471 initializeViz: function() {
15472 var config = this.config, that = this;
15473 var nodeType = config.type.split(":")[0];
15474 var sb = new $jit.Sunburst({
15475 injectInto: config.injectInto,
15476 useCanvas: config.useCanvas,
15477 withLabels: config.Label.type != 'Native',
15478 background: config.background,
15479 renderBackground: config.renderBackground,
15480 backgroundColor: config.backgroundColor,
15481 colorStop1: config.colorStop1,
15482 colorStop2: config.colorStop2,
15484 type: config.Label.type
15488 type: 'piechart-' + nodeType,
15496 enable: config.Tips.enable,
15499 onShow: function(tip, node, contains) {
15500 var elem = contains;
15501 config.Tips.onShow(tip, elem, node);
15502 if(elem.link != 'undefined' && elem.link != '') {
15503 document.body.style.cursor = 'pointer';
15506 onHide: function() {
15507 document.body.style.cursor = 'default';
15513 onClick: function(node, eventInfo, evt) {
15514 if(!config.Events.enable) return;
15515 var elem = eventInfo.getContains();
15516 config.Events.onClick(elem, eventInfo, evt);
15518 onMouseMove: function(node, eventInfo, evt) {
15519 if(!config.hoveredColor) return;
15521 var elem = eventInfo.getContains();
15522 that.select(node.id, elem.name, elem.index);
15524 that.select(false, false, false);
15528 onCreateLabel: function(domElement, node) {
15529 var labelConf = config.Label;
15530 if(config.showLabels) {
15531 var style = domElement.style;
15532 style.fontSize = labelConf.size + 'px';
15533 style.fontFamily = labelConf.family;
15534 style.color = labelConf.color;
15535 style.textAlign = 'center';
15536 if(config.labelType == 'name') {
15537 domElement.innerHTML = node.name;
15539 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15541 domElement.style.width = '400px';
15544 onPlaceLabel: function(domElement, node) {
15545 if(!config.showLabels) return;
15546 var pos = node.pos.getp(true),
15547 dimArray = node.getData('dimArray'),
15548 span = node.getData('span') / 2,
15549 theta = node.pos.theta,
15550 begin = theta - span,
15551 end = theta + span,
15554 var showLabels = config.showLabels,
15555 resizeLabels = config.resizeLabels,
15556 label = config.Label;
15559 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15560 acum += dimArray[i];
15562 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15563 fontSize = (label.size * scale) >> 0;
15564 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15565 domElement.style.fontSize = fontSize + 'px';
15566 polar.rho = acum + config.labelOffset + config.sliceOffset;
15567 polar.theta = (begin + end) / 2;
15568 var pos = polar.getc(true);
15569 var radius = that.canvas.getSize();
15571 x: Math.round(pos.x + radius.width / 2),
15572 y: Math.round(pos.y + radius.height / 2)
15574 domElement.style.left = (labelPos.x - 200) + 'px';
15575 domElement.style.top = labelPos.y + 'px';
15580 var size = sb.canvas.getSize(),
15582 sb.config.levelDistance = min(size.width, size.height)/2
15583 - config.offset - config.sliceOffset;
15585 this.canvas = this.sb.canvas;
15586 this.canvas.getCtx().globalCompositeOperation = 'lighter';
15588 renderBackground: function() {
15589 var canvas = this.canvas,
15590 config = this.config,
15591 backgroundColor = config.backgroundColor,
15592 size = canvas.getSize(),
15593 ctx = canvas.getCtx();
15594 ctx.globalCompositeOperation = "destination-over";
15595 ctx.fillStyle = backgroundColor;
15596 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15598 renderTitle: function() {
15599 var canvas = this.canvas,
15600 size = canvas.getSize(),
15601 config = this.config,
15602 margin = config.Margin,
15603 radius = this.sb.config.levelDistance,
15604 title = config.Title,
15605 label = config.Label,
15606 subtitle = config.Subtitle;
15607 ctx = canvas.getCtx();
15608 ctx.fillStyle = title.color;
15609 ctx.textAlign = 'left';
15610 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15612 if(label.type == 'Native') {
15613 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15616 renderSubtitle: function() {
15617 var canvas = this.canvas,
15618 size = canvas.getSize(),
15619 config = this.config,
15620 margin = config.Margin,
15621 radius = this.sb.config.levelDistance,
15622 title = config.Title,
15623 label = config.Label,
15624 subtitle = config.Subtitle;
15625 ctx = canvas.getCtx();
15626 ctx.fillStyle = title.color;
15627 ctx.textAlign = 'left';
15628 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15630 if(label.type == 'Native') {
15631 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15634 clear: function() {
15635 var canvas = this.canvas;
15636 var ctx = canvas.getCtx(),
15637 size = canvas.getSize();
15638 ctx.fillStyle = "rgba(255,255,255,0)";
15639 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15640 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15642 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
15643 var canvas = this.canvas,
15644 size = canvas.getSize(),
15645 config = this.config,
15646 orgHeight = size.height,
15647 margin = config.Margin,
15649 horz = config.orientation == 'horizontal';
15652 var newWindowWidth = document.body.offsetWidth;
15653 var diff = newWindowWidth - orgWindowWidth;
15654 var newWidth = orgContainerDivWidth + (diff/cols);
15655 var scale = newWidth/orgContainerDivWidth;
15656 canvas.resize(newWidth,orgHeight);
15657 if(typeof FlashCanvas == "undefined") {
15660 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15662 this.loadJSON(json);
15668 Loads JSON data into the visualization.
15672 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
15676 var pieChart = new $jit.PieChart(options);
15677 pieChart.loadJSON(json);
15680 loadJSON: function(json) {
15681 var prefix = $.time(),
15684 name = $.splat(json.label),
15685 nameLength = name.length,
15686 color = $.splat(json.color || this.colors),
15687 colorLength = color.length,
15688 config = this.config,
15689 renderBackground = config.renderBackground,
15690 title = config.Title,
15691 subtitle = config.Subtitle,
15692 gradient = !!config.type.split(":")[1],
15693 animate = config.animate,
15694 mono = nameLength == 1;
15696 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15697 var val = values[i];
15698 var valArray = $.splat(val.values);
15699 totalValue += parseFloat(valArray.sum());
15702 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15703 var val = values[i];
15704 var valArray = $.splat(val.values);
15705 var percentage = (valArray.sum()/totalValue) * 100;
15707 var linkArray = $.splat(val.links);
15708 var valuelabelsArray = $.splat(val.valuelabels);
15712 'id': prefix + val.label,
15716 'valuelabel': valuelabelsArray,
15717 '$linkArray': linkArray,
15718 '$valuelabelsArray': valuelabelsArray,
15719 '$valueArray': valArray,
15720 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15721 '$colorMono': $.splat(color[i % colorLength]),
15722 '$stringArray': name,
15723 '$gradient': gradient,
15726 '$percentage': percentage.toFixed(1),
15727 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15733 'id': prefix + '$root',
15745 this.normalizeDims();
15749 if(title.text != "") {
15750 this.renderTitle();
15753 if(subtitle.text != "") {
15754 this.renderSubtitle();
15756 if(renderBackground && typeof FlashCanvas == "undefined") {
15757 this.renderBackground();
15762 modes: ['node-property:dimArray'],
15771 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
15775 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15776 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15781 pieChart.updateJSON(json, {
15782 onComplete: function() {
15783 alert('update complete!');
15788 updateJSON: function(json, onComplete) {
15789 if(this.busy) return;
15793 var graph = sb.graph;
15794 var values = json.values;
15795 var animate = this.config.animate;
15797 $.each(values, function(v) {
15798 var n = graph.getByName(v.label),
15799 vals = $.splat(v.values);
15801 n.setData('valueArray', vals);
15802 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15804 n.setData('stringArray', $.splat(json.label));
15808 this.normalizeDims();
15812 modes: ['node-property:dimArray:span', 'linear'],
15814 onComplete: function() {
15816 onComplete && onComplete.onComplete();
15824 //adds the little brown bar when hovering the node
15825 select: function(id, name) {
15826 if(!this.config.hoveredColor) return;
15827 var s = this.selected;
15828 if(s.id != id || s.name != name) {
15831 s.color = this.config.hoveredColor;
15832 this.sb.graph.eachNode(function(n) {
15834 n.setData('border', s);
15836 n.setData('border', false);
15846 Returns an object containing as keys the legend names and as values hex strings with color values.
15851 var legend = pieChart.getLegend();
15854 getLegend: function() {
15855 var legend = new Array();
15856 var name = new Array();
15857 var color = new Array();
15859 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15862 var colors = n.getData('colorArray'),
15863 len = colors.length;
15864 $.each(n.getData('stringArray'), function(s, i) {
15865 color[i] = colors[i % len];
15868 legend['name'] = name;
15869 legend['color'] = color;
15874 Method: getMaxValue
15876 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15881 var ans = pieChart.getMaxValue();
15884 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15889 //will return 100 for all PieChart instances,
15890 //displaying all of them with the same scale
15891 $jit.PieChart.implement({
15892 'getMaxValue': function() {
15899 getMaxValue: function() {
15901 this.sb.graph.eachNode(function(n) {
15902 var valArray = n.getData('valueArray'),
15904 $.each(valArray, function(v) {
15907 maxValue = maxValue>acum? maxValue:acum;
15912 normalizeDims: function() {
15913 //number of elements
15914 var root = this.sb.graph.getNode(this.sb.root), l=0;
15915 root.eachAdjacency(function() {
15918 var maxValue = this.getMaxValue() || 1,
15919 config = this.config,
15920 animate = config.animate,
15921 rho = this.sb.config.levelDistance;
15922 this.sb.graph.eachNode(function(n) {
15923 var acum = 0, animateValue = [];
15924 $.each(n.getData('valueArray'), function(v) {
15926 animateValue.push(1);
15928 var stat = (animateValue.length == 1) && !config.updateHeights;
15930 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15931 return stat? rho: (n * rho / maxValue);
15933 var dimArray = n.getData('dimArray');
15935 n.setData('dimArray', animateValue);
15938 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15939 return stat? rho : (n * rho / maxValue);
15942 n.setData('normalizedDim', acum / maxValue);
15950 Options.GaugeChart = {
15954 offset: 25, // page offset
15956 labelOffset: 3, // label offset
15957 type: 'stacked', // gradient
15959 hoveredColor: '#9fd4ff',
15970 resizeLabels: false,
15972 //only valid for mono-valued datasets
15973 updateHeights: false
15978 $jit.Sunburst.Plot.NodeTypes.implement({
15979 'gaugechart-basic' : {
15980 'render' : function(node, canvas) {
15981 var pos = node.pos.getp(true),
15982 dimArray = node.getData('dimArray'),
15983 valueArray = node.getData('valueArray'),
15984 valuelabelsArray = node.getData('valuelabelsArray'),
15985 gaugeTarget = node.getData('gaugeTarget'),
15986 nodeIteration = node.getData('nodeIteration'),
15987 nodeLength = node.getData('nodeLength'),
15988 colorArray = node.getData('colorMono'),
15989 colorLength = colorArray.length,
15990 stringArray = node.getData('stringArray'),
15991 span = node.getData('span') / 2,
15992 theta = node.pos.theta,
15993 begin = ((theta - span)/2)+Math.PI,
15994 end = ((theta + span)/2)+Math.PI,
15998 var ctx = canvas.getCtx(),
16000 gradient = node.getData('gradient'),
16001 border = node.getData('border'),
16002 config = node.getData('config'),
16003 showLabels = config.showLabels,
16004 resizeLabels = config.resizeLabels,
16005 label = config.Label;
16007 var xpos = Math.cos((begin + end) /2);
16008 var ypos = Math.sin((begin + end) /2);
16010 if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16011 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16012 var dimi = dimArray[i], colori = colorArray[i % colorLength];
16013 if(dimi <= 0) continue;
16014 ctx.fillStyle = ctx.strokeStyle = colori;
16015 if(gradient && dimi) {
16016 var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16017 xpos, (ypos + dimi/2), acum + dimi);
16018 var colorRgb = $.hexToRgb(colori),
16019 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16020 endColor = $.rgbToHex(ans);
16022 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16023 radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16024 radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16025 radialGradient.addColorStop(1, 'rgba('+ans+',1)');
16026 ctx.fillStyle = radialGradient;
16030 polar.theta = begin;
16031 var p1coord = polar.getc(true);
16033 var p2coord = polar.getc(true);
16035 var p3coord = polar.getc(true);
16036 polar.theta = begin;
16037 var p4coord = polar.getc(true);
16041 //fixing FF arc method + fill
16042 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16043 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16047 acum += (dimi || 0);
16048 valAcum += (valueArray[i] || 0);
16051 if(showLabels && label.type == 'Native') {
16053 ctx.fillStyle = ctx.strokeStyle = label.color;
16056 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16057 ctx.textBaseline = 'bottom';
16058 ctx.textAlign = 'center';
16060 polar.rho = acum * .65;
16061 polar.theta = begin;
16062 var cart = polar.getc(true);
16064 //changes y pos of first label
16065 if(nodeIteration == 1) {
16066 textY = cart.y - (label.size/2) + acum /2;
16068 textY = cart.y + acum/2;
16071 if(config.labelType == 'name') {
16072 ctx.fillText(node.name, cart.x, textY);
16074 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16078 if(nodeIteration == nodeLength) {
16080 var cart = polar.getc(true);
16081 if(config.labelType == 'name') {
16082 ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16084 ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16093 'contains': function(node, pos) {
16097 if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16098 var config = node.getData('config');
16099 var ld = this.config.levelDistance , d = node._depth;
16100 var yOffset = pos.y - (ld/2);
16101 var xOffset = pos.x;
16102 var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16103 if(rho <=parseInt(ld * d)) {
16104 var dimArray = node.getData('dimArray');
16105 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16106 var dimi = dimArray[i];
16107 if(rho >= ld * .8 && rho <= acum + dimi) {
16109 var url = Url.decode(node.getData('linkArray')[i]);
16111 name: node.getData('stringArray')[i],
16113 color: node.getData('colorArray')[i],
16114 value: node.getData('valueArray')[i],
16115 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16135 A visualization that displays gauge charts
16137 Constructor Options:
16139 See <Options.Gauge>.
16142 $jit.GaugeChart = new Class({
16144 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16148 initialize: function(opt) {
16149 this.controller = this.config =
16150 $.merge(Options("Canvas", "GaugeChart", "Label"), {
16151 Label: { type: 'Native' }
16153 this.initializeViz();
16156 initializeViz: function() {
16157 var config = this.config, that = this;
16158 var nodeType = config.type.split(":")[0];
16159 var sb = new $jit.Sunburst({
16160 injectInto: config.injectInto,
16161 useCanvas: config.useCanvas,
16162 withLabels: config.Label.type != 'Native',
16163 background: config.background,
16164 renderBackground: config.renderBackground,
16165 backgroundColor: config.backgroundColor,
16166 colorStop1: config.colorStop1,
16167 colorStop2: config.colorStop2,
16169 type: config.Label.type
16173 type: 'gaugechart-' + nodeType,
16181 enable: config.Tips.enable,
16184 onShow: function(tip, node, contains) {
16185 var elem = contains;
16186 config.Tips.onShow(tip, elem, node);
16187 if(elem.link != 'undefined' && elem.link != '') {
16188 document.body.style.cursor = 'pointer';
16191 onHide: function() {
16192 document.body.style.cursor = 'default';
16198 onClick: function(node, eventInfo, evt) {
16199 if(!config.Events.enable) return;
16200 var elem = eventInfo.getContains();
16201 config.Events.onClick(elem, eventInfo, evt);
16204 onCreateLabel: function(domElement, node) {
16205 var labelConf = config.Label;
16206 if(config.showLabels) {
16207 var style = domElement.style;
16208 style.fontSize = labelConf.size + 'px';
16209 style.fontFamily = labelConf.family;
16210 style.color = labelConf.color;
16211 style.textAlign = 'center';
16212 valuelabelsArray = node.getData('valuelabelsArray'),
16213 nodeIteration = node.getData('nodeIteration'),
16214 nodeLength = node.getData('nodeLength'),
16215 canvas = sb.canvas,
16218 if(config.labelType == 'name') {
16219 domElement.innerHTML = node.name;
16221 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16224 domElement.style.width = '400px';
16227 if(nodeIteration == nodeLength && nodeLength != 0) {
16228 idLabel = canvas.id + "-label";
16229 container = document.getElementById(idLabel);
16230 finalLabel = document.createElement('div');
16231 finalLabelStyle = finalLabel.style;
16232 finalLabel.id = prefix + "finalLabel";
16233 finalLabelStyle.position = "absolute";
16234 finalLabelStyle.width = "400px";
16235 finalLabelStyle.left = "0px";
16236 container.appendChild(finalLabel);
16237 if(config.labelType == 'name') {
16238 finalLabel.innerHTML = node.name;
16240 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16246 onPlaceLabel: function(domElement, node) {
16247 if(!config.showLabels) return;
16248 var pos = node.pos.getp(true),
16249 dimArray = node.getData('dimArray'),
16250 nodeIteration = node.getData('nodeIteration'),
16251 nodeLength = node.getData('nodeLength'),
16252 span = node.getData('span') / 2,
16253 theta = node.pos.theta,
16254 begin = ((theta - span)/2)+Math.PI,
16255 end = ((theta + span)/2)+Math.PI,
16258 var showLabels = config.showLabels,
16259 resizeLabels = config.resizeLabels,
16260 label = config.Label,
16261 radiusOffset = sb.config.levelDistance;
16264 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16265 acum += dimArray[i];
16267 var scale = resizeLabels? node.getData('normalizedDim') : 1,
16268 fontSize = (label.size * scale) >> 0;
16269 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16270 domElement.style.fontSize = fontSize + 'px';
16271 polar.rho = acum * .65;
16272 polar.theta = begin;
16273 var pos = polar.getc(true);
16274 var radius = that.canvas.getSize();
16276 x: Math.round(pos.x + radius.width / 2),
16277 y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16282 domElement.style.left = (labelPos.x - 200) + 'px';
16283 domElement.style.top = labelPos.y + 'px';
16285 //reposition first label
16286 if(nodeIteration == 1) {
16287 domElement.style.top = labelPos.y - label.size + 'px';
16291 //position final label
16292 if(nodeIteration == nodeLength && nodeLength != 0) {
16294 var final = polar.getc(true);
16296 x: Math.round(final.x + radius.width / 2),
16297 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16299 finalLabel.style.left = (finalPos.x - 200) + "px";
16300 finalLabel.style.top = finalPos.y - label.size + "px";
16308 this.canvas = this.sb.canvas;
16309 var size = sb.canvas.getSize(),
16311 sb.config.levelDistance = min(size.width, size.height)/2
16312 - config.offset - config.sliceOffset;
16317 renderBackground: function() {
16318 var canvas = this.sb.canvas,
16319 config = this.config,
16320 style = config.gaugeStyle,
16321 ctx = canvas.getCtx(),
16322 size = canvas.getSize(),
16323 radius = this.sb.config.levelDistance,
16324 startAngle = (Math.PI/180)*1,
16325 endAngle = (Math.PI/180)*179;
16328 //background border
16329 ctx.fillStyle = style.borderColor;
16331 ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true);
16335 var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16336 radialGradient.addColorStop(0, '#ffffff');
16337 radialGradient.addColorStop(0.3, style.backgroundColor);
16338 radialGradient.addColorStop(0.6, style.backgroundColor);
16339 radialGradient.addColorStop(1, '#FFFFFF');
16340 ctx.fillStyle = radialGradient;
16343 startAngle = (Math.PI/180)*0;
16344 endAngle = (Math.PI/180)*180;
16346 ctx.arc(0,radius/2,radius,startAngle,endAngle, true);
16354 renderNeedle: function(gaugePosition,target) {
16355 var canvas = this.sb.canvas,
16356 config = this.config,
16357 style = config.gaugeStyle,
16358 ctx = canvas.getCtx(),
16359 size = canvas.getSize(),
16360 radius = this.sb.config.levelDistance;
16361 gaugeCenter = (radius/2);
16363 endAngle = (Math.PI/180)*180;
16367 ctx.fillStyle = style.needleColor;
16368 var segments = 180/target;
16369 needleAngle = gaugePosition * segments;
16370 ctx.translate(0, gaugeCenter);
16372 ctx.rotate(needleAngle * Math.PI / 180);
16376 ctx.lineTo(-radius*.9,-1);
16377 ctx.lineTo(-radius*.9,1);
16387 ctx.strokeStyle = '#aa0000';
16389 ctx.rotate(needleAngle * Math.PI / 180);
16393 ctx.lineTo(-radius*.8,-1);
16394 ctx.lineTo(-radius*.8,1);
16402 ctx.fillStyle = "#000000";
16403 ctx.lineWidth = style.borderSize;
16404 ctx.strokeStyle = style.borderColor;
16405 var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16406 radialGradient.addColorStop(0, '#666666');
16407 radialGradient.addColorStop(0.8, '#444444');
16408 radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
16409 ctx.fillStyle = radialGradient;
16410 ctx.translate(0,5);
16413 ctx.arc(0,0,radius*.2,startAngle,endAngle, true);
16420 renderTicks: function(values) {
16421 var canvas = this.sb.canvas,
16422 config = this.config,
16423 style = config.gaugeStyle,
16424 ctx = canvas.getCtx(),
16425 size = canvas.getSize(),
16426 radius = this.sb.config.levelDistance,
16427 gaugeCenter = (radius/2);
16430 ctx.strokeStyle = style.borderColor;
16432 ctx.lineCap = "round";
16433 for(var i=0, total = 0, l=values.length; i<l; i++) {
16434 var val = values[i];
16435 if(val.label != 'GaugePosition') {
16436 total += (parseInt(val.values) || 0);
16440 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16441 var val = values[i];
16442 if(val.label != 'GaugePosition') {
16443 acum += (parseInt(val.values) || 0);
16445 var segments = 180/total;
16446 angle = acum * segments;
16450 ctx.translate(0, gaugeCenter);
16452 ctx.rotate(angle * (Math.PI/180));
16453 ctx.moveTo(-radius,0);
16454 ctx.lineTo(-radius*.75,0);
16462 renderPositionLabel: function(position) {
16463 var canvas = this.sb.canvas,
16464 config = this.config,
16465 label = config.Label,
16466 style = config.gaugeStyle,
16467 ctx = canvas.getCtx(),
16468 size = canvas.getSize(),
16469 radius = this.sb.config.levelDistance,
16470 gaugeCenter = (radius/2);
16471 ctx.textBaseline = 'middle';
16472 ctx.textAlign = 'center';
16473 ctx.font = style.positionFontSize + 'px ' + label.family;
16474 ctx.fillStyle = "#ffffff";
16476 height = style.positionFontSize + 10,
16478 idLabel = canvas.id + "-label";
16479 container = document.getElementById(idLabel);
16480 if(label.type == 'Native') {
16481 var m = ctx.measureText(position),
16482 width = m.width + 40;
16486 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16487 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16488 if(label.type == 'Native') {
16489 ctx.fillStyle = label.color;
16490 ctx.fillText(position, 0, (height/2) + style.positionOffset);
16492 var labelDiv = document.createElement('div');
16493 labelDivStyle = labelDiv.style;
16494 labelDivStyle.color = label.color;
16495 labelDivStyle.fontSize = style.positionFontSize + "px";
16496 labelDivStyle.position = "absolute";
16497 labelDivStyle.width = width + "px";
16498 labelDivStyle.left = (size.width/2) - (width/2) + "px";
16499 labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16500 labelDiv.innerHTML = position;
16501 container.appendChild(labelDiv);
16506 renderSubtitle: function() {
16507 var canvas = this.canvas,
16508 size = canvas.getSize(),
16509 config = this.config,
16510 margin = config.Margin,
16511 radius = this.sb.config.levelDistance,
16512 title = config.Title,
16513 label = config.Label,
16514 subtitle = config.Subtitle;
16515 ctx = canvas.getCtx();
16516 ctx.fillStyle = title.color;
16517 ctx.textAlign = 'left';
16518 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16520 if(label.type == 'Native') {
16521 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2));
16525 renderChartBackground: function() {
16526 var canvas = this.canvas,
16527 config = this.config,
16528 backgroundColor = config.backgroundColor,
16529 size = canvas.getSize(),
16530 ctx = canvas.getCtx();
16531 //ctx.globalCompositeOperation = "destination-over";
16532 ctx.fillStyle = backgroundColor;
16533 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16535 clear: function() {
16536 var canvas = this.canvas;
16537 var ctx = canvas.getCtx(),
16538 size = canvas.getSize();
16539 ctx.fillStyle = "rgba(255,255,255,0)";
16540 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16541 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16543 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
16544 var canvas = this.canvas,
16545 size = canvas.getSize(),
16546 config = this.config,
16547 orgHeight = size.height,
16548 margin = config.Margin,
16550 horz = config.orientation == 'horizontal';
16553 var newWindowWidth = document.body.offsetWidth;
16554 var diff = newWindowWidth - orgWindowWidth;
16555 var newWidth = orgContainerDivWidth + (diff/cols);
16556 canvas.resize(newWidth,orgHeight);
16557 if(typeof FlashCanvas == "undefined") {
16560 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16562 this.loadJSON(json);
16565 loadJSON: function(json) {
16567 var prefix = $.time(),
16570 name = $.splat(json.label),
16571 nameLength = name.length,
16572 color = $.splat(json.color || this.colors),
16573 colorLength = color.length,
16574 config = this.config,
16575 renderBackground = config.renderBackground,
16576 gradient = !!config.type.split(":")[1],
16577 animate = config.animate,
16578 mono = nameLength == 1;
16579 var props = $.splat(json.properties)[0];
16581 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16583 var val = values[i];
16584 if(val.label != 'GaugePosition') {
16585 var valArray = $.splat(val.values);
16586 var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16587 var valuelabelsArray = $.splat(val.valuelabels);
16590 'id': prefix + val.label,
16594 'valuelabel': valuelabelsArray,
16595 '$linkArray': linkArray,
16596 '$valuelabelsArray': valuelabelsArray,
16597 '$valueArray': valArray,
16598 '$nodeIteration': i,
16599 '$nodeLength': l-1,
16600 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16601 '$colorMono': $.splat(color[i % colorLength]),
16602 '$stringArray': name,
16603 '$gradient': gradient,
16605 '$gaugeTarget': props['gaugeTarget'],
16606 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16611 var gaugePosition = val.gvalue;
16612 var gaugePositionLabel = val.gvaluelabel;
16616 'id': prefix + '$root',
16629 if(renderBackground) {
16630 this.renderChartBackground();
16633 this.renderBackground();
16634 this.renderSubtitle();
16636 this.normalizeDims();
16641 modes: ['node-property:dimArray'],
16647 this.renderPositionLabel(gaugePositionLabel);
16648 if (props['gaugeTarget'] != 0) {
16649 this.renderTicks(json.values);
16650 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16657 updateJSON: function(json, onComplete) {
16658 if(this.busy) return;
16662 var graph = sb.graph;
16663 var values = json.values;
16664 var animate = this.config.animate;
16666 $.each(values, function(v) {
16667 var n = graph.getByName(v.label),
16668 vals = $.splat(v.values);
16670 n.setData('valueArray', vals);
16671 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16673 n.setData('stringArray', $.splat(json.label));
16677 this.normalizeDims();
16681 modes: ['node-property:dimArray:span', 'linear'],
16683 onComplete: function() {
16685 onComplete && onComplete.onComplete();
16693 //adds the little brown bar when hovering the node
16694 select: function(id, name) {
16695 if(!this.config.hoveredColor) return;
16696 var s = this.selected;
16697 if(s.id != id || s.name != name) {
16700 s.color = this.config.hoveredColor;
16701 this.sb.graph.eachNode(function(n) {
16703 n.setData('border', s);
16705 n.setData('border', false);
16715 Returns an object containing as keys the legend names and as values hex strings with color values.
16720 var legend = pieChart.getLegend();
16723 getLegend: function() {
16724 var legend = new Array();
16725 var name = new Array();
16726 var color = new Array();
16728 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16731 var colors = n.getData('colorArray'),
16732 len = colors.length;
16733 $.each(n.getData('stringArray'), function(s, i) {
16734 color[i] = colors[i % len];
16737 legend['name'] = name;
16738 legend['color'] = color;
16743 Method: getMaxValue
16745 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16750 var ans = pieChart.getMaxValue();
16753 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16758 //will return 100 for all PieChart instances,
16759 //displaying all of them with the same scale
16760 $jit.PieChart.implement({
16761 'getMaxValue': function() {
16768 getMaxValue: function() {
16770 this.sb.graph.eachNode(function(n) {
16771 var valArray = n.getData('valueArray'),
16773 $.each(valArray, function(v) {
16776 maxValue = maxValue>acum? maxValue:acum;
16781 normalizeDims: function() {
16782 //number of elements
16783 var root = this.sb.graph.getNode(this.sb.root), l=0;
16784 root.eachAdjacency(function() {
16787 var maxValue = this.getMaxValue() || 1,
16788 config = this.config,
16789 animate = config.animate,
16790 rho = this.sb.config.levelDistance;
16791 this.sb.graph.eachNode(function(n) {
16792 var acum = 0, animateValue = [];
16793 $.each(n.getData('valueArray'), function(v) {
16795 animateValue.push(1);
16797 var stat = (animateValue.length == 1) && !config.updateHeights;
16799 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16800 return stat? rho: (n * rho / maxValue);
16802 var dimArray = n.getData('dimArray');
16804 n.setData('dimArray', animateValue);
16807 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16808 return stat? rho : (n * rho / maxValue);
16811 n.setData('normalizedDim', acum / maxValue);
16818 * Class: Layouts.TM
16820 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16829 Layouts.TM.SliceAndDice = new Class({
16830 compute: function(prop) {
16831 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16832 this.controller.onBeforeCompute(root);
16833 var size = this.canvas.getSize(),
16834 config = this.config,
16835 width = size.width,
16836 height = size.height;
16837 this.graph.computeLevels(this.root, 0, "ignore");
16838 //set root position and dimensions
16839 root.getPos(prop).setc(-width/2, -height/2);
16840 root.setData('width', width, prop);
16841 root.setData('height', height + config.titleHeight, prop);
16842 this.computePositions(root, root, this.layout.orientation, prop);
16843 this.controller.onAfterCompute(root);
16846 computePositions: function(par, ch, orn, prop) {
16847 //compute children areas
16849 par.eachSubnode(function(n) {
16850 totalArea += n.getData('area', prop);
16853 var config = this.config,
16854 offst = config.offset,
16855 width = par.getData('width', prop),
16856 height = par.getData('height', prop) - config.titleHeight,
16857 fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16859 var otherSize, size, dim, pos, pos2, posth, pos2th;
16860 var horizontal = (orn == "h");
16863 otherSize = height;
16864 size = width * fact;
16868 posth = config.titleHeight;
16872 otherSize = height * fact;
16878 pos2th = config.titleHeight;
16880 var cpos = ch.getPos(prop);
16881 ch.setData('width', size, prop);
16882 ch.setData('height', otherSize, prop);
16883 var offsetSize = 0, tm = this;
16884 ch.eachSubnode(function(n) {
16885 var p = n.getPos(prop);
16886 p[pos] = offsetSize + cpos[pos] + posth;
16887 p[pos2] = cpos[pos2] + pos2th;
16888 tm.computePositions(ch, n, orn, prop);
16889 offsetSize += n.getData(dim, prop);
16895 Layouts.TM.Area = {
16899 Called by loadJSON to calculate recursively all node positions and lay out the tree.
16903 json - A JSON tree. See also <Loader.loadJSON>.
16904 coord - A coordinates object specifying width, height, left and top style properties.
16906 compute: function(prop) {
16907 prop = prop || "current";
16908 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16909 this.controller.onBeforeCompute(root);
16910 var config = this.config,
16911 size = this.canvas.getSize(),
16912 width = size.width,
16913 height = size.height,
16914 offst = config.offset,
16915 offwdth = width - offst,
16916 offhght = height - offst;
16917 this.graph.computeLevels(this.root, 0, "ignore");
16918 //set root position and dimensions
16919 root.getPos(prop).setc(-width/2, -height/2);
16920 root.setData('width', width, prop);
16921 root.setData('height', height, prop);
16922 //create a coordinates object
16924 'top': -height/2 + config.titleHeight,
16927 'height': offhght - config.titleHeight
16929 this.computePositions(root, coord, prop);
16930 this.controller.onAfterCompute(root);
16936 Computes dimensions and positions of a group of nodes
16937 according to a custom layout row condition.
16941 tail - An array of nodes.
16942 initElem - An array of nodes (containing the initial node to be laid).
16943 w - A fixed dimension where nodes will be layed out.
16944 coord - A coordinates object specifying width, height, left and top style properties.
16945 comp - A custom comparison function
16947 computeDim: function(tail, initElem, w, coord, comp, prop) {
16948 if(tail.length + initElem.length == 1) {
16949 var l = (tail.length == 1)? tail : initElem;
16950 this.layoutLast(l, w, coord, prop);
16953 if(tail.length >= 2 && initElem.length == 0) {
16954 initElem = [tail.shift()];
16956 if(tail.length == 0) {
16957 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
16961 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
16962 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
16964 var newCoords = this.layoutRow(initElem, w, coord, prop);
16965 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
16971 Method: worstAspectRatio
16973 Calculates the worst aspect ratio of a group of rectangles.
16977 <http://en.wikipedia.org/wiki/Aspect_ratio>
16981 ch - An array of nodes.
16982 w - The fixed dimension where rectangles are being laid out.
16986 The worst aspect ratio.
16990 worstAspectRatio: function(ch, w) {
16991 if(!ch || ch.length == 0) return Number.MAX_VALUE;
16992 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
16993 for(var i=0, l=ch.length; i<l; i++) {
16994 var area = ch[i]._area;
16996 minArea = minArea < area? minArea : area;
16997 maxArea = maxArea > area? maxArea : area;
16999 var sqw = w * w, sqAreaSum = areaSum * areaSum;
17000 return Math.max(sqw * maxArea / sqAreaSum,
17001 sqAreaSum / (sqw * minArea));
17005 Method: avgAspectRatio
17007 Calculates the average aspect ratio of a group of rectangles.
17011 <http://en.wikipedia.org/wiki/Aspect_ratio>
17015 ch - An array of nodes.
17016 w - The fixed dimension where rectangles are being laid out.
17020 The average aspect ratio.
17024 avgAspectRatio: function(ch, w) {
17025 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17027 for(var i=0, l=ch.length; i<l; i++) {
17028 var area = ch[i]._area;
17030 arSum += w > h? w / h : h / w;
17038 Performs the layout of the last computed sibling.
17042 ch - An array of nodes.
17043 w - A fixed dimension where nodes will be layed out.
17044 coord - A coordinates object specifying width, height, left and top style properties.
17046 layoutLast: function(ch, w, coord, prop) {
17048 child.getPos(prop).setc(coord.left, coord.top);
17049 child.setData('width', coord.width, prop);
17050 child.setData('height', coord.height, prop);
17055 Layouts.TM.Squarified = new Class({
17056 Implements: Layouts.TM.Area,
17058 computePositions: function(node, coord, prop) {
17059 var config = this.config;
17061 if (coord.width >= coord.height)
17062 this.layout.orientation = 'h';
17064 this.layout.orientation = 'v';
17066 var ch = node.getSubnodes([1, 1], "ignore");
17067 if(ch.length > 0) {
17068 this.processChildrenLayout(node, ch, coord, prop);
17069 for(var i=0, l=ch.length; i<l; i++) {
17071 var offst = config.offset,
17072 height = chi.getData('height', prop) - offst - config.titleHeight,
17073 width = chi.getData('width', prop) - offst;
17074 var chipos = chi.getPos(prop);
17078 'top': chipos.y + config.titleHeight,
17081 this.computePositions(chi, coord, prop);
17087 Method: processChildrenLayout
17089 Computes children real areas and other useful parameters for performing the Squarified algorithm.
17093 par - The parent node of the json subtree.
17094 ch - An Array of nodes
17095 coord - A coordinates object specifying width, height, left and top style properties.
17097 processChildrenLayout: function(par, ch, coord, prop) {
17098 //compute children real areas
17099 var parentArea = coord.width * coord.height;
17100 var i, l=ch.length, totalChArea=0, chArea = [];
17101 for(i=0; i<l; i++) {
17102 chArea[i] = parseFloat(ch[i].getData('area', prop));
17103 totalChArea += chArea[i];
17105 for(i=0; i<l; i++) {
17106 ch[i]._area = parentArea * chArea[i] / totalChArea;
17108 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17109 ch.sort(function(a, b) {
17110 var diff = b._area - a._area;
17111 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
17113 var initElem = [ch[0]];
17114 var tail = ch.slice(1);
17115 this.squarify(tail, initElem, minimumSideValue, coord, prop);
17121 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17125 tail - An array of nodes.
17126 initElem - An array of nodes, containing the initial node to be laid out.
17127 w - A fixed dimension where nodes will be laid out.
17128 coord - A coordinates object specifying width, height, left and top style properties.
17130 squarify: function(tail, initElem, w, coord, prop) {
17131 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17137 Performs the layout of an array of nodes.
17141 ch - An array of nodes.
17142 w - A fixed dimension where nodes will be laid out.
17143 coord - A coordinates object specifying width, height, left and top style properties.
17145 layoutRow: function(ch, w, coord, prop) {
17146 if(this.layout.horizontal()) {
17147 return this.layoutV(ch, w, coord, prop);
17149 return this.layoutH(ch, w, coord, prop);
17153 layoutV: function(ch, w, coord, prop) {
17154 var totalArea = 0, rnd = function(x) { return x; };
17155 $.each(ch, function(elem) { totalArea += elem._area; });
17156 var width = rnd(totalArea / w), top = 0;
17157 for(var i=0, l=ch.length; i<l; i++) {
17158 var h = rnd(ch[i]._area / width);
17160 chi.getPos(prop).setc(coord.left, coord.top + top);
17161 chi.setData('width', width, prop);
17162 chi.setData('height', h, prop);
17166 'height': coord.height,
17167 'width': coord.width - width,
17169 'left': coord.left + width
17171 //take minimum side value.
17172 ans.dim = Math.min(ans.width, ans.height);
17173 if(ans.dim != ans.height) this.layout.change();
17177 layoutH: function(ch, w, coord, prop) {
17179 $.each(ch, function(elem) { totalArea += elem._area; });
17180 var height = totalArea / w,
17184 for(var i=0, l=ch.length; i<l; i++) {
17186 var w = chi._area / height;
17187 chi.getPos(prop).setc(coord.left + left, top);
17188 chi.setData('width', w, prop);
17189 chi.setData('height', height, prop);
17193 'height': coord.height - height,
17194 'width': coord.width,
17195 'top': coord.top + height,
17198 ans.dim = Math.min(ans.width, ans.height);
17199 if(ans.dim != ans.width) this.layout.change();
17204 Layouts.TM.Strip = new Class({
17205 Implements: Layouts.TM.Area,
17210 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17214 json - A JSON subtree. See also <Loader.loadJSON>.
17215 coord - A coordinates object specifying width, height, left and top style properties.
17217 computePositions: function(node, coord, prop) {
17218 var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17219 if(ch.length > 0) {
17220 this.processChildrenLayout(node, ch, coord, prop);
17221 for(var i=0, l=ch.length; i<l; i++) {
17223 var offst = config.offset,
17224 height = chi.getData('height', prop) - offst - config.titleHeight,
17225 width = chi.getData('width', prop) - offst;
17226 var chipos = chi.getPos(prop);
17230 'top': chipos.y + config.titleHeight,
17233 this.computePositions(chi, coord, prop);
17239 Method: processChildrenLayout
17241 Computes children real areas and other useful parameters for performing the Strip algorithm.
17245 par - The parent node of the json subtree.
17246 ch - An Array of nodes
17247 coord - A coordinates object specifying width, height, left and top style properties.
17249 processChildrenLayout: function(par, ch, coord, prop) {
17250 //compute children real areas
17251 var parentArea = coord.width * coord.height;
17252 var i, l=ch.length, totalChArea=0, chArea = [];
17253 for(i=0; i<l; i++) {
17254 chArea[i] = +ch[i].getData('area', prop);
17255 totalChArea += chArea[i];
17257 for(i=0; i<l; i++) {
17258 ch[i]._area = parentArea * chArea[i] / totalChArea;
17260 var side = this.layout.horizontal()? coord.width : coord.height;
17261 var initElem = [ch[0]];
17262 var tail = ch.slice(1);
17263 this.stripify(tail, initElem, side, coord, prop);
17269 Performs an heuristic method to calculate div elements sizes in order to have
17270 a good compromise between aspect ratio and order.
17274 tail - An array of nodes.
17275 initElem - An array of nodes.
17276 w - A fixed dimension where nodes will be layed out.
17277 coord - A coordinates object specifying width, height, left and top style properties.
17279 stripify: function(tail, initElem, w, coord, prop) {
17280 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17286 Performs the layout of an array of nodes.
17290 ch - An array of nodes.
17291 w - A fixed dimension where nodes will be laid out.
17292 coord - A coordinates object specifying width, height, left and top style properties.
17294 layoutRow: function(ch, w, coord, prop) {
17295 if(this.layout.horizontal()) {
17296 return this.layoutH(ch, w, coord, prop);
17298 return this.layoutV(ch, w, coord, prop);
17302 layoutV: function(ch, w, coord, prop) {
17304 $.each(ch, function(elem) { totalArea += elem._area; });
17305 var width = totalArea / w, top = 0;
17306 for(var i=0, l=ch.length; i<l; i++) {
17308 var h = chi._area / width;
17309 chi.getPos(prop).setc(coord.left,
17310 coord.top + (w - h - top));
17311 chi.setData('width', width, prop);
17312 chi.setData('height', h, prop);
17317 'height': coord.height,
17318 'width': coord.width - width,
17320 'left': coord.left + width,
17325 layoutH: function(ch, w, coord, prop) {
17327 $.each(ch, function(elem) { totalArea += elem._area; });
17328 var height = totalArea / w,
17329 top = coord.height - height,
17332 for(var i=0, l=ch.length; i<l; i++) {
17334 var s = chi._area / height;
17335 chi.getPos(prop).setc(coord.left + left, coord.top + top);
17336 chi.setData('width', s, prop);
17337 chi.setData('height', height, prop);
17341 'height': coord.height - height,
17342 'width': coord.width,
17344 'left': coord.left,
17351 * Class: Layouts.Icicle
17353 * Implements the icicle tree layout.
17361 Layouts.Icicle = new Class({
17365 * Called by loadJSON to calculate all node positions.
17369 * posType - The nodes' position to compute. Either "start", "end" or
17370 * "current". Defaults to "current".
17372 compute: function(posType) {
17373 posType = posType || "current";
17374 var root = this.graph.getNode(this.root),
17375 config = this.config,
17376 size = this.canvas.getSize(),
17377 width = size.width,
17378 height = size.height,
17379 offset = config.offset,
17380 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17382 this.controller.onBeforeCompute(root);
17384 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17388 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17390 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17391 var maxDepth = Math.min(treeDepth, levelsToShow-1);
17392 var initialDepth = startNode._depth;
17393 if(this.layout.horizontal()) {
17394 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17396 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17400 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17401 root.getPos(posType).setc(x, y);
17402 root.setData('width', width, posType);
17403 root.setData('height', height, posType);
17405 var nodeLength, prevNodeLength = 0, totalDim = 0;
17406 var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17408 if(!children.length)
17411 $.each(children, function(e) { totalDim += e.getData('dim'); });
17413 for(var i=0, l=children.length; i < l; i++) {
17414 if(this.layout.horizontal()) {
17415 nodeLength = height * children[i].getData('dim') / totalDim;
17416 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17419 nodeLength = width * children[i].getData('dim') / totalDim;
17420 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17437 Icicle space filling visualization.
17441 All <Loader> methods
17443 Constructor Options:
17445 Inherits options from
17448 - <Options.Controller>
17454 - <Options.NodeStyles>
17455 - <Options.Navigation>
17457 Additionally, there are other parameters and some default values changed
17459 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17460 offset - (number) Default's *2*. Boxes offset.
17461 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17462 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17463 animate - (boolean) Default's *false*. Whether to animate transitions.
17464 Node.type - Described in <Options.Node>. Default's *rectangle*.
17465 Label.type - Described in <Options.Label>. Default's *Native*.
17466 duration - Described in <Options.Fx>. Default's *700*.
17467 fps - Described in <Options.Fx>. Default's *45*.
17469 Instance Properties:
17471 canvas - Access a <Canvas> instance.
17472 graph - Access a <Graph> instance.
17473 op - Access a <Icicle.Op> instance.
17474 fx - Access a <Icicle.Plot> instance.
17475 labels - Access a <Icicle.Label> interface implementation.
17479 $jit.Icicle = new Class({
17480 Implements: [ Loader, Extras, Layouts.Icicle ],
17484 vertical: function(){
17485 return this.orientation == "v";
17487 horizontal: function(){
17488 return this.orientation == "h";
17490 change: function(){
17491 this.orientation = this.vertical()? "h" : "v";
17495 initialize: function(controller) {
17500 levelsToShow: Number.MAX_VALUE,
17501 constrained: false,
17516 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17517 "Events", "Navigation", "Controller", "Label");
17518 this.controller = this.config = $.merge(opts, config, controller);
17519 this.layout.orientation = this.config.orientation;
17521 var canvasConfig = this.config;
17522 if (canvasConfig.useCanvas) {
17523 this.canvas = canvasConfig.useCanvas;
17524 this.config.labelContainer = this.canvas.id + '-label';
17526 this.canvas = new Canvas(this, canvasConfig);
17527 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17530 this.graphOptions = {
17539 this.graph = new Graph(
17540 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17542 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17543 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17544 this.op = new $jit.Icicle.Op(this);
17545 this.group = new $jit.Icicle.Group(this);
17546 this.clickedNode = null;
17548 this.initializeExtras();
17554 Computes positions and plots the tree.
17556 refresh: function(){
17557 var labelType = this.config.Label.type;
17558 if(labelType != 'Native') {
17560 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17569 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
17573 this.fx.plot(this.config);
17579 Sets the node as root.
17583 node - (object) A <Graph.Node>.
17586 enter: function (node) {
17592 config = this.config;
17595 onComplete: function() {
17596 //compute positions of newly inserted nodes
17600 if(config.animate) {
17601 that.graph.nodeList.setDataset(['current', 'end'], {
17602 'alpha': [1, 0] //fade nodes
17605 Graph.Util.eachSubgraph(node, function(n) {
17606 n.setData('alpha', 1, 'end');
17611 modes:['node-property:alpha'],
17612 onComplete: function() {
17613 that.clickedNode = node;
17614 that.compute('end');
17617 modes:['linear', 'node-property:width:height'],
17619 onComplete: function() {
17621 that.clickedNode = node;
17627 that.clickedNode = node;
17634 if(config.request) {
17635 this.requestNodes(clickedNode, callback);
17637 callback.onComplete();
17644 Sets the parent node of the current selected node as root.
17652 GUtil = Graph.Util,
17653 config = this.config,
17654 graph = this.graph,
17655 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17656 parent = parents[0],
17657 clickedNode = parent,
17658 previousClickedNode = this.clickedNode;
17661 this.events.hoveredNode = false;
17668 //final plot callback
17670 onComplete: function() {
17671 that.clickedNode = parent;
17672 if(config.request) {
17673 that.requestNodes(parent, {
17674 onComplete: function() {
17688 //animate node positions
17689 if(config.animate) {
17690 this.clickedNode = clickedNode;
17691 this.compute('end');
17692 //animate the visible subtree only
17693 this.clickedNode = previousClickedNode;
17695 modes:['linear', 'node-property:width:height'],
17697 onComplete: function() {
17698 //animate the parent subtree
17699 that.clickedNode = clickedNode;
17700 //change nodes alpha
17701 graph.nodeList.setDataset(['current', 'end'], {
17704 GUtil.eachSubgraph(previousClickedNode, function(node) {
17705 node.setData('alpha', 1);
17709 modes:['node-property:alpha'],
17710 onComplete: function() {
17711 callback.onComplete();
17717 callback.onComplete();
17720 requestNodes: function(node, onComplete){
17721 var handler = $.merge(this.controller, onComplete),
17722 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17724 if (handler.request) {
17725 var leaves = [], d = node._depth;
17726 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17727 if (n.drawn && !Graph.Util.anySubnode(n)) {
17729 n._level = n._depth - d;
17730 if (this.config.constrained)
17731 n._level = levelsToShow - n._level;
17735 this.group.requestNodes(leaves, handler);
17737 handler.onComplete();
17745 Custom extension of <Graph.Op>.
17749 All <Graph.Op> methods
17756 $jit.Icicle.Op = new Class({
17758 Implements: Graph.Op
17763 * Performs operations on group of nodes.
17765 $jit.Icicle.Group = new Class({
17767 initialize: function(viz){
17769 this.canvas = viz.canvas;
17770 this.config = viz.config;
17774 * Calls the request method on the controller to request a subtree for each node.
17776 requestNodes: function(nodes, controller){
17777 var counter = 0, len = nodes.length, nodeSelected = {};
17778 var complete = function(){
17779 controller.onComplete();
17781 var viz = this.viz;
17784 for(var i = 0; i < len; i++) {
17785 nodeSelected[nodes[i].id] = nodes[i];
17786 controller.request(nodes[i].id, nodes[i]._level, {
17787 onComplete: function(nodeId, data){
17788 if (data && data.children) {
17794 if (++counter == len) {
17795 Graph.Util.computeLevels(viz.graph, viz.root, 0);
17807 Custom extension of <Graph.Plot>.
17811 All <Graph.Plot> methods
17818 $jit.Icicle.Plot = new Class({
17819 Implements: Graph.Plot,
17821 plot: function(opt, animating){
17822 opt = opt || this.viz.controller;
17823 var viz = this.viz,
17825 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17826 initialDepth = root._depth;
17828 viz.canvas.clear();
17829 this.plotTree(root, $.merge(opt, {
17830 'withLabels': true,
17831 'hideLabels': false,
17832 'plotSubtree': function(root, node) {
17833 return !viz.config.constrained ||
17834 (node._depth - initialDepth < viz.config.levelsToShow);
17841 Class: Icicle.Label
17843 Custom extension of <Graph.Label>.
17844 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17848 All <Graph.Label> methods and subclasses.
17852 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17855 $jit.Icicle.Label = {};
17858 Icicle.Label.Native
17860 Custom extension of <Graph.Label.Native>.
17864 All <Graph.Label.Native> methods
17868 <Graph.Label.Native>
17871 $jit.Icicle.Label.Native = new Class({
17872 Implements: Graph.Label.Native,
17874 renderLabel: function(canvas, node, controller) {
17875 var ctx = canvas.getCtx(),
17876 width = node.getData('width'),
17877 height = node.getData('height'),
17878 size = node.getLabelData('size'),
17879 m = ctx.measureText(node.name);
17881 // Guess as much as possible if the label will fit in the node
17882 if(height < (size * 1.5) || width < m.width)
17885 var pos = node.pos.getc(true);
17886 ctx.fillText(node.name,
17888 pos.y + height / 2);
17895 Custom extension of <Graph.Label.SVG>.
17899 All <Graph.Label.SVG> methods
17905 $jit.Icicle.Label.SVG = new Class( {
17906 Implements: Graph.Label.SVG,
17908 initialize: function(viz){
17915 Overrides abstract method placeLabel in <Graph.Plot>.
17919 tag - A DOM label element.
17920 node - A <Graph.Node>.
17921 controller - A configuration/controller object passed to the visualization.
17923 placeLabel: function(tag, node, controller){
17924 var pos = node.pos.getc(true), canvas = this.viz.canvas;
17925 var radius = canvas.getSize();
17927 x: Math.round(pos.x + radius.width / 2),
17928 y: Math.round(pos.y + radius.height / 2)
17930 tag.setAttribute('x', labelPos.x);
17931 tag.setAttribute('y', labelPos.y);
17933 controller.onPlaceLabel(tag, node);
17940 Custom extension of <Graph.Label.HTML>.
17944 All <Graph.Label.HTML> methods.
17951 $jit.Icicle.Label.HTML = new Class( {
17952 Implements: Graph.Label.HTML,
17954 initialize: function(viz){
17961 Overrides abstract method placeLabel in <Graph.Plot>.
17965 tag - A DOM label element.
17966 node - A <Graph.Node>.
17967 controller - A configuration/controller object passed to the visualization.
17969 placeLabel: function(tag, node, controller){
17970 var pos = node.pos.getc(true), canvas = this.viz.canvas;
17971 var radius = canvas.getSize();
17973 x: Math.round(pos.x + radius.width / 2),
17974 y: Math.round(pos.y + radius.height / 2)
17977 var style = tag.style;
17978 style.left = labelPos.x + 'px';
17979 style.top = labelPos.y + 'px';
17980 style.display = '';
17982 controller.onPlaceLabel(tag, node);
17987 Class: Icicle.Plot.NodeTypes
17989 This class contains a list of <Graph.Node> built-in types.
17990 Node types implemented are 'none', 'rectangle'.
17992 You can add your custom node types, customizing your visualization to the extreme.
17997 Icicle.Plot.NodeTypes.implement({
17999 'render': function(node, canvas) {
18000 //print your custom node to canvas
18003 'contains': function(node, pos) {
18004 //return true if pos is inside the node or false otherwise
18011 $jit.Icicle.Plot.NodeTypes = new Class( {
18017 'render': function(node, canvas, animating) {
18018 var config = this.viz.config;
18019 var offset = config.offset;
18020 var width = node.getData('width');
18021 var height = node.getData('height');
18022 var border = node.getData('border');
18023 var pos = node.pos.getc(true);
18024 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18025 var ctx = canvas.getCtx();
18027 if(width - offset < 2 || height - offset < 2) return;
18029 if(config.cushion) {
18030 var color = node.getData('color');
18031 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
18032 posy + (height - offset)/2, 1,
18033 posx + (width-offset)/2, posy + (height-offset)/2,
18034 width < height? height : width);
18035 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
18036 function(r) { return r * 0.3 >> 0; }));
18037 lg.addColorStop(0, color);
18038 lg.addColorStop(1, colorGrad);
18039 ctx.fillStyle = lg;
18043 ctx.strokeStyle = border;
18047 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18048 border && ctx.strokeRect(pos.x, pos.y, width, height);
18051 'contains': function(node, pos) {
18052 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18053 var npos = node.pos.getc(true),
18054 width = node.getData('width'),
18055 height = node.getData('height');
18056 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18061 $jit.Icicle.Plot.EdgeTypes = new Class( {
18068 * File: Layouts.ForceDirected.js
18073 * Class: Layouts.ForceDirected
18075 * Implements a Force Directed Layout.
18083 * Marcus Cobden <http://marcuscobden.co.uk>
18086 Layouts.ForceDirected = new Class({
18088 getOptions: function(random) {
18089 var s = this.canvas.getSize();
18090 var w = s.width, h = s.height;
18093 this.graph.eachNode(function(n) {
18096 var k2 = w * h / count, k = Math.sqrt(k2);
18097 var l = this.config.levelDistance;
18103 nodef: function(x) { return k2 / (x || 1); },
18104 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18108 compute: function(property, incremental) {
18109 var prop = $.splat(property || ['current', 'start', 'end']);
18110 var opt = this.getOptions();
18111 NodeDim.compute(this.graph, prop, this.config);
18112 this.graph.computeLevels(this.root, 0, "ignore");
18113 this.graph.eachNode(function(n) {
18114 $.each(prop, function(p) {
18115 var pos = n.getPos(p);
18116 if(pos.equals(Complex.KER)) {
18117 pos.x = opt.width/5 * (Math.random() - 0.5);
18118 pos.y = opt.height/5 * (Math.random() - 0.5);
18120 //initialize disp vector
18122 $.each(prop, function(p) {
18123 n.disp[p] = $C(0, 0);
18127 this.computePositions(prop, opt, incremental);
18130 computePositions: function(property, opt, incremental) {
18131 var times = this.config.iterations, i = 0, that = this;
18134 for(var total=incremental.iter, j=0; j<total; j++) {
18135 opt.t = opt.tstart * (1 - i++/(times -1));
18136 that.computePositionStep(property, opt);
18138 incremental.onComplete();
18142 incremental.onStep(Math.round(i / (times -1) * 100));
18143 setTimeout(iter, 1);
18146 for(; i < times; i++) {
18147 opt.t = opt.tstart * (1 - i/(times -1));
18148 this.computePositionStep(property, opt);
18153 computePositionStep: function(property, opt) {
18154 var graph = this.graph;
18155 var min = Math.min, max = Math.max;
18156 var dpos = $C(0, 0);
18157 //calculate repulsive forces
18158 graph.eachNode(function(v) {
18160 $.each(property, function(p) {
18161 v.disp[p].x = 0; v.disp[p].y = 0;
18163 graph.eachNode(function(u) {
18165 $.each(property, function(p) {
18166 var vp = v.getPos(p), up = u.getPos(p);
18167 dpos.x = vp.x - up.x;
18168 dpos.y = vp.y - up.y;
18169 var norm = dpos.norm() || 1;
18170 v.disp[p].$add(dpos
18171 .$scale(opt.nodef(norm) / norm));
18176 //calculate attractive forces
18177 var T = !!graph.getNode(this.root).visited;
18178 graph.eachNode(function(node) {
18179 node.eachAdjacency(function(adj) {
18180 var nodeTo = adj.nodeTo;
18181 if(!!nodeTo.visited === T) {
18182 $.each(property, function(p) {
18183 var vp = node.getPos(p), up = nodeTo.getPos(p);
18184 dpos.x = vp.x - up.x;
18185 dpos.y = vp.y - up.y;
18186 var norm = dpos.norm() || 1;
18187 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18188 nodeTo.disp[p].$add(dpos.$scale(-1));
18194 //arrange positions to fit the canvas
18195 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18196 graph.eachNode(function(u) {
18197 $.each(property, function(p) {
18198 var disp = u.disp[p];
18199 var norm = disp.norm() || 1;
18200 var p = u.getPos(p);
18201 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
18202 disp.y * min(Math.abs(disp.y), t) / norm));
18203 p.x = min(w2, max(-w2, p.x));
18204 p.y = min(h2, max(-h2, p.y));
18211 * File: ForceDirected.js
18215 Class: ForceDirected
18217 A visualization that lays graphs using a Force-Directed layout algorithm.
18221 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18225 All <Loader> methods
18227 Constructor Options:
18229 Inherits options from
18232 - <Options.Controller>
18238 - <Options.NodeStyles>
18239 - <Options.Navigation>
18241 Additionally, there are two parameters
18243 levelDistance - (number) Default's *50*. The natural length desired for the edges.
18244 iterations - (number) Default's *50*. The number of iterations for the spring layout simulation. Depending on the browser's speed you could set this to a more 'interesting' number, like *200*.
18246 Instance Properties:
18248 canvas - Access a <Canvas> instance.
18249 graph - Access a <Graph> instance.
18250 op - Access a <ForceDirected.Op> instance.
18251 fx - Access a <ForceDirected.Plot> instance.
18252 labels - Access a <ForceDirected.Label> interface implementation.
18256 $jit.ForceDirected = new Class( {
18258 Implements: [ Loader, Extras, Layouts.ForceDirected ],
18260 initialize: function(controller) {
18261 var $ForceDirected = $jit.ForceDirected;
18268 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18269 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18271 var canvasConfig = this.config;
18272 if(canvasConfig.useCanvas) {
18273 this.canvas = canvasConfig.useCanvas;
18274 this.config.labelContainer = this.canvas.id + '-label';
18276 if(canvasConfig.background) {
18277 canvasConfig.background = $.merge({
18279 }, canvasConfig.background);
18281 this.canvas = new Canvas(this, canvasConfig);
18282 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18285 this.graphOptions = {
18293 this.graph = new Graph(this.graphOptions, this.config.Node,
18295 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18296 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18297 this.op = new $ForceDirected.Op(this);
18300 // initialize extras
18301 this.initializeExtras();
18307 Computes positions and plots the tree.
18309 refresh: function() {
18314 reposition: function() {
18315 this.compute('end');
18319 Method: computeIncremental
18321 Performs the Force Directed algorithm incrementally.
18325 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
18326 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
18327 avoiding browser messages such as "This script is taking too long to complete".
18331 opt - (object) The object properties are described below
18333 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
18334 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18336 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
18337 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
18338 computations for final animation positions then you can just choose 'end'.
18340 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
18341 parameter a percentage value.
18343 onComplete - A callback function called when the algorithm completed.
18347 In this example I calculate the end positions and then animate the graph to those positions
18350 var fd = new $jit.ForceDirected(...);
18351 fd.computeIncremental({
18354 onStep: function(perc) {
18355 Log.write("loading " + perc + "%");
18357 onComplete: function() {
18364 In this example I calculate all positions and (re)plot the graph
18367 var fd = new ForceDirected(...);
18368 fd.computeIncremental({
18370 property: ['end', 'start', 'current'],
18371 onStep: function(perc) {
18372 Log.write("loading " + perc + "%");
18374 onComplete: function() {
18382 computeIncremental: function(opt) {
18387 onComplete: $.empty
18390 this.config.onBeforeCompute(this.graph.getNode(this.root));
18391 this.compute(opt.property, opt);
18397 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18406 Animates the graph from the current positions to the 'end' node positions.
18408 animate: function(opt) {
18409 this.fx.animate($.merge( {
18410 modes: [ 'linear' ]
18415 $jit.ForceDirected.$extend = true;
18417 (function(ForceDirected) {
18420 Class: ForceDirected.Op
18422 Custom extension of <Graph.Op>.
18426 All <Graph.Op> methods
18433 ForceDirected.Op = new Class( {
18435 Implements: Graph.Op
18440 Class: ForceDirected.Plot
18442 Custom extension of <Graph.Plot>.
18446 All <Graph.Plot> methods
18453 ForceDirected.Plot = new Class( {
18455 Implements: Graph.Plot
18460 Class: ForceDirected.Label
18462 Custom extension of <Graph.Label>.
18463 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18467 All <Graph.Label> methods and subclasses.
18471 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18474 ForceDirected.Label = {};
18477 ForceDirected.Label.Native
18479 Custom extension of <Graph.Label.Native>.
18483 All <Graph.Label.Native> methods
18487 <Graph.Label.Native>
18490 ForceDirected.Label.Native = new Class( {
18491 Implements: Graph.Label.Native
18495 ForceDirected.Label.SVG
18497 Custom extension of <Graph.Label.SVG>.
18501 All <Graph.Label.SVG> methods
18508 ForceDirected.Label.SVG = new Class( {
18509 Implements: Graph.Label.SVG,
18511 initialize: function(viz) {
18518 Overrides abstract method placeLabel in <Graph.Label>.
18522 tag - A DOM label element.
18523 node - A <Graph.Node>.
18524 controller - A configuration/controller object passed to the visualization.
18527 placeLabel: function(tag, node, controller) {
18528 var pos = node.pos.getc(true),
18529 canvas = this.viz.canvas,
18530 ox = canvas.translateOffsetX,
18531 oy = canvas.translateOffsetY,
18532 sx = canvas.scaleOffsetX,
18533 sy = canvas.scaleOffsetY,
18534 radius = canvas.getSize();
18536 x: Math.round(pos.x * sx + ox + radius.width / 2),
18537 y: Math.round(pos.y * sy + oy + radius.height / 2)
18539 tag.setAttribute('x', labelPos.x);
18540 tag.setAttribute('y', labelPos.y);
18542 controller.onPlaceLabel(tag, node);
18547 ForceDirected.Label.HTML
18549 Custom extension of <Graph.Label.HTML>.
18553 All <Graph.Label.HTML> methods.
18560 ForceDirected.Label.HTML = new Class( {
18561 Implements: Graph.Label.HTML,
18563 initialize: function(viz) {
18569 Overrides abstract method placeLabel in <Graph.Plot>.
18573 tag - A DOM label element.
18574 node - A <Graph.Node>.
18575 controller - A configuration/controller object passed to the visualization.
18578 placeLabel: function(tag, node, controller) {
18579 var pos = node.pos.getc(true),
18580 canvas = this.viz.canvas,
18581 ox = canvas.translateOffsetX,
18582 oy = canvas.translateOffsetY,
18583 sx = canvas.scaleOffsetX,
18584 sy = canvas.scaleOffsetY,
18585 radius = canvas.getSize();
18587 x: Math.round(pos.x * sx + ox + radius.width / 2),
18588 y: Math.round(pos.y * sy + oy + radius.height / 2)
18590 var style = tag.style;
18591 style.left = labelPos.x + 'px';
18592 style.top = labelPos.y + 'px';
18593 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18595 controller.onPlaceLabel(tag, node);
18600 Class: ForceDirected.Plot.NodeTypes
18602 This class contains a list of <Graph.Node> built-in types.
18603 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18605 You can add your custom node types, customizing your visualization to the extreme.
18610 ForceDirected.Plot.NodeTypes.implement({
18612 'render': function(node, canvas) {
18613 //print your custom node to canvas
18616 'contains': function(node, pos) {
18617 //return true if pos is inside the node or false otherwise
18624 ForceDirected.Plot.NodeTypes = new Class({
18627 'contains': $.lambda(false)
18630 'render': function(node, canvas){
18631 var pos = node.pos.getc(true),
18632 dim = node.getData('dim');
18633 this.nodeHelper.circle.render('fill', pos, dim, canvas);
18635 'contains': function(node, pos){
18636 var npos = node.pos.getc(true),
18637 dim = node.getData('dim');
18638 return this.nodeHelper.circle.contains(npos, pos, dim);
18642 'render': function(node, canvas){
18643 var pos = node.pos.getc(true),
18644 width = node.getData('width'),
18645 height = node.getData('height');
18646 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18648 // TODO(nico): be more precise...
18649 'contains': function(node, pos){
18650 var npos = node.pos.getc(true),
18651 width = node.getData('width'),
18652 height = node.getData('height');
18653 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18657 'render': function(node, canvas){
18658 var pos = node.pos.getc(true),
18659 dim = node.getData('dim');
18660 this.nodeHelper.square.render('fill', pos, dim, canvas);
18662 'contains': function(node, pos){
18663 var npos = node.pos.getc(true),
18664 dim = node.getData('dim');
18665 return this.nodeHelper.square.contains(npos, pos, dim);
18669 'render': function(node, canvas){
18670 var pos = node.pos.getc(true),
18671 width = node.getData('width'),
18672 height = node.getData('height');
18673 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18675 'contains': function(node, pos){
18676 var npos = node.pos.getc(true),
18677 width = node.getData('width'),
18678 height = node.getData('height');
18679 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18683 'render': function(node, canvas){
18684 var pos = node.pos.getc(true),
18685 dim = node.getData('dim');
18686 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18688 'contains': function(node, pos) {
18689 var npos = node.pos.getc(true),
18690 dim = node.getData('dim');
18691 return this.nodeHelper.triangle.contains(npos, pos, dim);
18695 'render': function(node, canvas){
18696 var pos = node.pos.getc(true),
18697 dim = node.getData('dim');
18698 this.nodeHelper.star.render('fill', pos, dim, canvas);
18700 'contains': function(node, pos) {
18701 var npos = node.pos.getc(true),
18702 dim = node.getData('dim');
18703 return this.nodeHelper.star.contains(npos, pos, dim);
18709 Class: ForceDirected.Plot.EdgeTypes
18711 This class contains a list of <Graph.Adjacence> built-in types.
18712 Edge types implemented are 'none', 'line' and 'arrow'.
18714 You can add your custom edge types, customizing your visualization to the extreme.
18719 ForceDirected.Plot.EdgeTypes.implement({
18721 'render': function(adj, canvas) {
18722 //print your custom edge to canvas
18725 'contains': function(adj, pos) {
18726 //return true if pos is inside the arc or false otherwise
18733 ForceDirected.Plot.EdgeTypes = new Class({
18736 'render': function(adj, canvas) {
18737 var from = adj.nodeFrom.pos.getc(true),
18738 to = adj.nodeTo.pos.getc(true);
18739 this.edgeHelper.line.render(from, to, canvas);
18741 'contains': function(adj, pos) {
18742 var from = adj.nodeFrom.pos.getc(true),
18743 to = adj.nodeTo.pos.getc(true);
18744 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18748 'render': function(adj, canvas) {
18749 var from = adj.nodeFrom.pos.getc(true),
18750 to = adj.nodeTo.pos.getc(true),
18751 dim = adj.getData('dim'),
18752 direction = adj.data.$direction,
18753 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18754 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18756 'contains': function(adj, pos) {
18757 var from = adj.nodeFrom.pos.getc(true),
18758 to = adj.nodeTo.pos.getc(true);
18759 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18764 })($jit.ForceDirected);
18776 $jit.TM.$extend = true;
18781 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18785 All <Loader> methods
18787 Constructor Options:
18789 Inherits options from
18792 - <Options.Controller>
18798 - <Options.NodeStyles>
18799 - <Options.Navigation>
18801 Additionally, there are other parameters and some default values changed
18803 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18804 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18805 offset - (number) Default's *2*. Boxes offset.
18806 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18807 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18808 animate - (boolean) Default's *false*. Whether to animate transitions.
18809 Node.type - Described in <Options.Node>. Default's *rectangle*.
18810 duration - Described in <Options.Fx>. Default's *700*.
18811 fps - Described in <Options.Fx>. Default's *45*.
18813 Instance Properties:
18815 canvas - Access a <Canvas> instance.
18816 graph - Access a <Graph> instance.
18817 op - Access a <TM.Op> instance.
18818 fx - Access a <TM.Plot> instance.
18819 labels - Access a <TM.Label> interface implementation.
18823 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18825 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18829 This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
18835 vertical: function(){
18836 return this.orientation == "v";
18838 horizontal: function(){
18839 return this.orientation == "h";
18841 change: function(){
18842 this.orientation = this.vertical()? "h" : "v";
18846 initialize: function(controller){
18852 constrained: false,
18857 //we all know why this is not zero,
18864 textAlign: 'center',
18865 textBaseline: 'top'
18874 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18875 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18876 this.layout.orientation = this.config.orientation;
18878 var canvasConfig = this.config;
18879 if (canvasConfig.useCanvas) {
18880 this.canvas = canvasConfig.useCanvas;
18881 this.config.labelContainer = this.canvas.id + '-label';
18883 if(canvasConfig.background) {
18884 canvasConfig.background = $.merge({
18886 }, canvasConfig.background);
18888 this.canvas = new Canvas(this, canvasConfig);
18889 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18892 this.graphOptions = {
18900 this.graph = new Graph(this.graphOptions, this.config.Node,
18902 this.labels = new TM.Label[canvasConfig.Label.type](this);
18903 this.fx = new TM.Plot(this);
18904 this.op = new TM.Op(this);
18905 this.group = new TM.Group(this);
18906 this.geom = new TM.Geom(this);
18907 this.clickedNode = null;
18909 // initialize extras
18910 this.initializeExtras();
18916 Computes positions and plots the tree.
18918 refresh: function(){
18919 if(this.busy) return;
18922 if(this.config.animate) {
18923 this.compute('end');
18924 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18925 && this.clickedNode.id || this.root));
18926 this.fx.animate($.merge(this.config, {
18927 modes: ['linear', 'node-property:width:height'],
18928 onComplete: function() {
18933 var labelType = this.config.Label.type;
18934 if(labelType != 'Native') {
18936 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18940 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18941 && this.clickedNode.id || this.root));
18949 Plots the TreeMap. This is a shortcut to *fx.plot*.
18959 Returns whether the node is a leaf.
18963 n - (object) A <Graph.Node>.
18967 return n.getSubnodes([
18969 ], "ignore").length == 0;
18975 Sets the node as root.
18979 n - (object) A <Graph.Node>.
18982 enter: function(n){
18983 if(this.busy) return;
18987 config = this.config,
18988 graph = this.graph,
18990 previousClickedNode = this.clickedNode;
18993 onComplete: function() {
18994 //ensure that nodes are shown for that level
18995 if(config.levelsToShow > 0) {
18996 that.geom.setRightLevelToShow(n);
18998 //compute positions of newly inserted nodes
18999 if(config.levelsToShow > 0 || config.request) that.compute();
19000 if(config.animate) {
19002 graph.nodeList.setData('alpha', 0, 'end');
19003 n.eachSubgraph(function(n) {
19004 n.setData('alpha', 1, 'end');
19008 modes:['node-property:alpha'],
19009 onComplete: function() {
19010 //compute end positions
19011 that.clickedNode = clickedNode;
19012 that.compute('end');
19013 //animate positions
19014 //TODO(nico) commenting this line didn't seem to throw errors...
19015 that.clickedNode = previousClickedNode;
19017 modes:['linear', 'node-property:width:height'],
19019 onComplete: function() {
19021 //TODO(nico) check comment above
19022 that.clickedNode = clickedNode;
19029 that.clickedNode = n;
19034 if(config.request) {
19035 this.requestNodes(clickedNode, callback);
19037 callback.onComplete();
19044 Sets the parent node of the current selected node as root.
19048 if(this.busy) return;
19050 this.events.hoveredNode = false;
19052 config = this.config,
19053 graph = this.graph,
19054 parents = graph.getNode(this.clickedNode
19055 && this.clickedNode.id || this.root).getParents(),
19056 parent = parents[0],
19057 clickedNode = parent,
19058 previousClickedNode = this.clickedNode;
19060 //if no parents return
19065 //final plot callback
19067 onComplete: function() {
19068 that.clickedNode = parent;
19069 if(config.request) {
19070 that.requestNodes(parent, {
19071 onComplete: function() {
19085 if (config.levelsToShow > 0)
19086 this.geom.setRightLevelToShow(parent);
19087 //animate node positions
19088 if(config.animate) {
19089 this.clickedNode = clickedNode;
19090 this.compute('end');
19091 //animate the visible subtree only
19092 this.clickedNode = previousClickedNode;
19094 modes:['linear', 'node-property:width:height'],
19096 onComplete: function() {
19097 //animate the parent subtree
19098 that.clickedNode = clickedNode;
19099 //change nodes alpha
19100 graph.eachNode(function(n) {
19101 n.setDataset(['current', 'end'], {
19105 previousClickedNode.eachSubgraph(function(node) {
19106 node.setData('alpha', 1);
19110 modes:['node-property:alpha'],
19111 onComplete: function() {
19112 callback.onComplete();
19118 callback.onComplete();
19122 requestNodes: function(node, onComplete){
19123 var handler = $.merge(this.controller, onComplete),
19124 lev = this.config.levelsToShow;
19125 if (handler.request) {
19126 var leaves = [], d = node._depth;
19127 node.eachLevel(0, lev, function(n){
19128 var nodeLevel = lev - (n._depth - d);
19129 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19131 n._level = nodeLevel;
19134 this.group.requestNodes(leaves, handler);
19136 handler.onComplete();
19144 Custom extension of <Graph.Op>.
19148 All <Graph.Op> methods
19155 TM.Op = new Class({
19156 Implements: Graph.Op,
19158 initialize: function(viz){
19163 //extend level methods of Graph.Geom
19164 TM.Geom = new Class({
19165 Implements: Graph.Geom,
19167 getRightLevelToShow: function() {
19168 return this.viz.config.levelsToShow;
19171 setRightLevelToShow: function(node) {
19172 var level = this.getRightLevelToShow(),
19173 fx = this.viz.labels;
19174 node.eachLevel(0, level+1, function(n) {
19175 var d = n._depth - node._depth;
19180 fx.hideLabel(n, false);
19188 delete node.ignore;
19194 Performs operations on group of nodes.
19197 TM.Group = new Class( {
19199 initialize: function(viz){
19201 this.canvas = viz.canvas;
19202 this.config = viz.config;
19207 Calls the request method on the controller to request a subtree for each node.
19209 requestNodes: function(nodes, controller){
19210 var counter = 0, len = nodes.length, nodeSelected = {};
19211 var complete = function(){
19212 controller.onComplete();
19214 var viz = this.viz;
19217 for ( var i = 0; i < len; i++) {
19218 nodeSelected[nodes[i].id] = nodes[i];
19219 controller.request(nodes[i].id, nodes[i]._level, {
19220 onComplete: function(nodeId, data){
19221 if (data && data.children) {
19227 if (++counter == len) {
19228 viz.graph.computeLevels(viz.root, 0);
19240 Custom extension of <Graph.Plot>.
19244 All <Graph.Plot> methods
19251 TM.Plot = new Class({
19253 Implements: Graph.Plot,
19255 initialize: function(viz){
19257 this.config = viz.config;
19258 this.node = this.config.Node;
19259 this.edge = this.config.Edge;
19260 this.animation = new Animation;
19261 this.nodeTypes = new TM.Plot.NodeTypes;
19262 this.edgeTypes = new TM.Plot.EdgeTypes;
19263 this.labels = viz.labels;
19266 plot: function(opt, animating){
19267 var viz = this.viz,
19269 viz.canvas.clear();
19270 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19271 'withLabels': true,
19272 'hideLabels': false,
19273 'plotSubtree': function(n, ch){
19274 return n.anySubnode("exist");
19283 Custom extension of <Graph.Label>.
19284 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19288 All <Graph.Label> methods and subclasses.
19292 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19300 Custom extension of <Graph.Label.Native>.
19304 All <Graph.Label.Native> methods
19308 <Graph.Label.Native>
19310 TM.Label.Native = new Class({
19311 Implements: Graph.Label.Native,
19313 initialize: function(viz) {
19314 this.config = viz.config;
19315 this.leaf = viz.leaf;
19318 renderLabel: function(canvas, node, controller){
19319 if(!this.leaf(node) && !this.config.titleHeight) return;
19320 var pos = node.pos.getc(true),
19321 ctx = canvas.getCtx(),
19322 width = node.getData('width'),
19323 height = node.getData('height'),
19324 x = pos.x + width/2,
19327 ctx.fillText(node.name, x, y, width);
19334 Custom extension of <Graph.Label.SVG>.
19338 All <Graph.Label.SVG> methods
19344 TM.Label.SVG = new Class( {
19345 Implements: Graph.Label.SVG,
19347 initialize: function(viz){
19349 this.leaf = viz.leaf;
19350 this.config = viz.config;
19356 Overrides abstract method placeLabel in <Graph.Plot>.
19360 tag - A DOM label element.
19361 node - A <Graph.Node>.
19362 controller - A configuration/controller object passed to the visualization.
19365 placeLabel: function(tag, node, controller){
19366 var pos = node.pos.getc(true),
19367 canvas = this.viz.canvas,
19368 ox = canvas.translateOffsetX,
19369 oy = canvas.translateOffsetY,
19370 sx = canvas.scaleOffsetX,
19371 sy = canvas.scaleOffsetY,
19372 radius = canvas.getSize();
19374 x: Math.round(pos.x * sx + ox + radius.width / 2),
19375 y: Math.round(pos.y * sy + oy + radius.height / 2)
19377 tag.setAttribute('x', labelPos.x);
19378 tag.setAttribute('y', labelPos.y);
19380 if(!this.leaf(node) && !this.config.titleHeight) {
19381 tag.style.display = 'none';
19383 controller.onPlaceLabel(tag, node);
19390 Custom extension of <Graph.Label.HTML>.
19394 All <Graph.Label.HTML> methods.
19401 TM.Label.HTML = new Class( {
19402 Implements: Graph.Label.HTML,
19404 initialize: function(viz){
19406 this.leaf = viz.leaf;
19407 this.config = viz.config;
19413 Overrides abstract method placeLabel in <Graph.Plot>.
19417 tag - A DOM label element.
19418 node - A <Graph.Node>.
19419 controller - A configuration/controller object passed to the visualization.
19422 placeLabel: function(tag, node, controller){
19423 var pos = node.pos.getc(true),
19424 canvas = this.viz.canvas,
19425 ox = canvas.translateOffsetX,
19426 oy = canvas.translateOffsetY,
19427 sx = canvas.scaleOffsetX,
19428 sy = canvas.scaleOffsetY,
19429 radius = canvas.getSize();
19431 x: Math.round(pos.x * sx + ox + radius.width / 2),
19432 y: Math.round(pos.y * sy + oy + radius.height / 2)
19435 var style = tag.style;
19436 style.left = labelPos.x + 'px';
19437 style.top = labelPos.y + 'px';
19438 style.width = node.getData('width') * sx + 'px';
19439 style.height = node.getData('height') * sy + 'px';
19440 style.zIndex = node._depth * 100;
19441 style.display = '';
19443 if(!this.leaf(node) && !this.config.titleHeight) {
19444 tag.style.display = 'none';
19446 controller.onPlaceLabel(tag, node);
19451 Class: TM.Plot.NodeTypes
19453 This class contains a list of <Graph.Node> built-in types.
19454 Node types implemented are 'none', 'rectangle'.
19456 You can add your custom node types, customizing your visualization to the extreme.
19461 TM.Plot.NodeTypes.implement({
19463 'render': function(node, canvas) {
19464 //print your custom node to canvas
19467 'contains': function(node, pos) {
19468 //return true if pos is inside the node or false otherwise
19475 TM.Plot.NodeTypes = new Class( {
19481 'render': function(node, canvas, animating){
19482 var leaf = this.viz.leaf(node),
19483 config = this.config,
19484 offst = config.offset,
19485 titleHeight = config.titleHeight,
19486 pos = node.pos.getc(true),
19487 width = node.getData('width'),
19488 height = node.getData('height'),
19489 border = node.getData('border'),
19490 ctx = canvas.getCtx(),
19491 posx = pos.x + offst / 2,
19492 posy = pos.y + offst / 2;
19493 if(width <= offst || height <= offst) return;
19495 if(config.cushion) {
19496 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
19497 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19498 var color = node.getData('color');
19499 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
19500 function(r) { return r * 0.2 >> 0; }));
19501 lg.addColorStop(0, color);
19502 lg.addColorStop(1, colorGrad);
19503 ctx.fillStyle = lg;
19505 ctx.fillRect(posx, posy, width - offst, height - offst);
19508 ctx.strokeStyle = border;
19509 ctx.strokeRect(posx, posy, width - offst, height - offst);
19512 } else if(titleHeight > 0){
19513 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19514 titleHeight - offst);
19517 ctx.strokeStyle = border;
19518 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19524 'contains': function(node, pos) {
19525 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19526 var npos = node.pos.getc(true),
19527 width = node.getData('width'),
19528 leaf = this.viz.leaf(node),
19529 height = leaf? node.getData('height') : this.config.titleHeight;
19530 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19535 TM.Plot.EdgeTypes = new Class( {
19540 Class: TM.SliceAndDice
19542 A slice and dice TreeMap visualization.
19546 All <TM.Base> methods and properties.
19548 TM.SliceAndDice = new Class( {
19550 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19555 Class: TM.Squarified
19557 A squarified TreeMap visualization.
19561 All <TM.Base> methods and properties.
19563 TM.Squarified = new Class( {
19565 Loader, Extras, TM.Base, Layouts.TM.Squarified
19572 A strip TreeMap visualization.
19576 All <TM.Base> methods and properties.
19578 TM.Strip = new Class( {
19580 Loader, Extras, TM.Base, Layouts.TM.Strip
19593 A radial graph visualization with advanced animations.
19597 Animated Exploration of Dynamic Graphs with Radial Layout (Ka-Ping Yee, Danyel Fisher, Rachna Dhamija, Marti Hearst) <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
19601 This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
19605 All <Loader> methods
19607 Constructor Options:
19609 Inherits options from
19612 - <Options.Controller>
19618 - <Options.NodeStyles>
19619 - <Options.Navigation>
19621 Additionally, there are other parameters and some default values changed
19623 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19624 levelDistance - (number) Default's *100*. The distance between levels of the tree.
19626 Instance Properties:
19628 canvas - Access a <Canvas> instance.
19629 graph - Access a <Graph> instance.
19630 op - Access a <RGraph.Op> instance.
19631 fx - Access a <RGraph.Plot> instance.
19632 labels - Access a <RGraph.Label> interface implementation.
19635 $jit.RGraph = new Class( {
19638 Loader, Extras, Layouts.Radial
19641 initialize: function(controller){
19642 var $RGraph = $jit.RGraph;
19645 interpolation: 'linear',
19649 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19650 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19652 var canvasConfig = this.config;
19653 if(canvasConfig.useCanvas) {
19654 this.canvas = canvasConfig.useCanvas;
19655 this.config.labelContainer = this.canvas.id + '-label';
19657 if(canvasConfig.background) {
19658 canvasConfig.background = $.merge({
19660 }, canvasConfig.background);
19662 this.canvas = new Canvas(this, canvasConfig);
19663 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19666 this.graphOptions = {
19674 this.graph = new Graph(this.graphOptions, this.config.Node,
19676 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19677 this.fx = new $RGraph.Plot(this, $RGraph);
19678 this.op = new $RGraph.Op(this);
19682 this.parent = false;
19683 // initialize extras
19684 this.initializeExtras();
19689 createLevelDistanceFunc
19691 Returns the levelDistance function used for calculating a node distance
19692 to its origin. This function returns a function that is computed
19693 per level and not per node, such that all nodes with the same depth will have the
19694 same distance to the origin. The resulting function gets the
19695 parent node as parameter and returns a float.
19698 createLevelDistanceFunc: function(){
19699 var ld = this.config.levelDistance;
19700 return function(elem){
19701 return (elem._depth + 1) * ld;
19708 Computes positions and plots the tree.
19711 refresh: function(){
19716 reposition: function(){
19717 this.compute('end');
19723 Plots the RGraph. This is a shortcut to *fx.plot*.
19729 getNodeAndParentAngle
19731 Returns the _parent_ of the given node, also calculating its angle span.
19733 getNodeAndParentAngle: function(id){
19735 var n = this.graph.getNode(id);
19736 var ps = n.getParents();
19737 var p = (ps.length > 0)? ps[0] : false;
19739 var posParent = p.pos.getc(), posChild = n.pos.getc();
19740 var newPos = posParent.add(posChild.scale(-1));
19741 theta = Math.atan2(newPos.y, newPos.x);
19743 theta += 2 * Math.PI;
19753 Enumerates the children in order to maintain child ordering (second constraint of the paper).
19755 tagChildren: function(par, id){
19756 if (par.angleSpan) {
19758 par.eachAdjacency(function(elem){
19759 adjs.push(elem.nodeTo);
19761 var len = adjs.length;
19762 for ( var i = 0; i < len && id != adjs[i].id; i++)
19764 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19765 adjs[j].dist = k++;
19772 Animates the <RGraph> to center the node specified by *id*.
19776 id - A <Graph.Node> id.
19777 opt - (optional|object) An object containing some extra properties described below
19778 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19783 rgraph.onClick('someid');
19785 rgraph.onClick('someid', {
19791 onClick: function(id, opt){
19792 if (this.root != id && !this.busy) {
19796 this.controller.onBeforeCompute(this.graph.getNode(id));
19797 var obj = this.getNodeAndParentAngle(id);
19799 // second constraint
19800 this.tagChildren(obj.parent, id);
19801 this.parent = obj.parent;
19802 this.compute('end');
19804 // first constraint
19805 var thetaDiff = obj.theta - obj.parent.endPos.theta;
19806 this.graph.eachNode(function(elem){
19807 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19810 var mode = this.config.interpolation;
19812 onComplete: $.empty
19815 this.fx.animate($.merge( {
19821 onComplete: function(){
19830 $jit.RGraph.$extend = true;
19837 Custom extension of <Graph.Op>.
19841 All <Graph.Op> methods
19848 RGraph.Op = new Class( {
19850 Implements: Graph.Op
19857 Custom extension of <Graph.Plot>.
19861 All <Graph.Plot> methods
19868 RGraph.Plot = new Class( {
19870 Implements: Graph.Plot
19875 Object: RGraph.Label
19877 Custom extension of <Graph.Label>.
19878 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19882 All <Graph.Label> methods and subclasses.
19886 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19892 RGraph.Label.Native
19894 Custom extension of <Graph.Label.Native>.
19898 All <Graph.Label.Native> methods
19902 <Graph.Label.Native>
19905 RGraph.Label.Native = new Class( {
19906 Implements: Graph.Label.Native
19912 Custom extension of <Graph.Label.SVG>.
19916 All <Graph.Label.SVG> methods
19923 RGraph.Label.SVG = new Class( {
19924 Implements: Graph.Label.SVG,
19926 initialize: function(viz){
19933 Overrides abstract method placeLabel in <Graph.Plot>.
19937 tag - A DOM label element.
19938 node - A <Graph.Node>.
19939 controller - A configuration/controller object passed to the visualization.
19942 placeLabel: function(tag, node, controller){
19943 var pos = node.pos.getc(true),
19944 canvas = this.viz.canvas,
19945 ox = canvas.translateOffsetX,
19946 oy = canvas.translateOffsetY,
19947 sx = canvas.scaleOffsetX,
19948 sy = canvas.scaleOffsetY,
19949 radius = canvas.getSize();
19951 x: Math.round(pos.x * sx + ox + radius.width / 2),
19952 y: Math.round(pos.y * sy + oy + radius.height / 2)
19954 tag.setAttribute('x', labelPos.x);
19955 tag.setAttribute('y', labelPos.y);
19957 controller.onPlaceLabel(tag, node);
19964 Custom extension of <Graph.Label.HTML>.
19968 All <Graph.Label.HTML> methods.
19975 RGraph.Label.HTML = new Class( {
19976 Implements: Graph.Label.HTML,
19978 initialize: function(viz){
19984 Overrides abstract method placeLabel in <Graph.Plot>.
19988 tag - A DOM label element.
19989 node - A <Graph.Node>.
19990 controller - A configuration/controller object passed to the visualization.
19993 placeLabel: function(tag, node, controller){
19994 var pos = node.pos.getc(true),
19995 canvas = this.viz.canvas,
19996 ox = canvas.translateOffsetX,
19997 oy = canvas.translateOffsetY,
19998 sx = canvas.scaleOffsetX,
19999 sy = canvas.scaleOffsetY,
20000 radius = canvas.getSize();
20002 x: Math.round(pos.x * sx + ox + radius.width / 2),
20003 y: Math.round(pos.y * sy + oy + radius.height / 2)
20006 var style = tag.style;
20007 style.left = labelPos.x + 'px';
20008 style.top = labelPos.y + 'px';
20009 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20011 controller.onPlaceLabel(tag, node);
20016 Class: RGraph.Plot.NodeTypes
20018 This class contains a list of <Graph.Node> built-in types.
20019 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20021 You can add your custom node types, customizing your visualization to the extreme.
20026 RGraph.Plot.NodeTypes.implement({
20028 'render': function(node, canvas) {
20029 //print your custom node to canvas
20032 'contains': function(node, pos) {
20033 //return true if pos is inside the node or false otherwise
20040 RGraph.Plot.NodeTypes = new Class({
20043 'contains': $.lambda(false)
20046 'render': function(node, canvas){
20047 var pos = node.pos.getc(true),
20048 dim = node.getData('dim');
20049 this.nodeHelper.circle.render('fill', pos, dim, canvas);
20051 'contains': function(node, pos){
20052 var npos = node.pos.getc(true),
20053 dim = node.getData('dim');
20054 return this.nodeHelper.circle.contains(npos, pos, dim);
20058 'render': function(node, canvas){
20059 var pos = node.pos.getc(true),
20060 width = node.getData('width'),
20061 height = node.getData('height');
20062 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20064 // TODO(nico): be more precise...
20065 'contains': function(node, pos){
20066 var npos = node.pos.getc(true),
20067 width = node.getData('width'),
20068 height = node.getData('height');
20069 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20073 'render': function(node, canvas){
20074 var pos = node.pos.getc(true),
20075 dim = node.getData('dim');
20076 this.nodeHelper.square.render('fill', pos, dim, canvas);
20078 'contains': function(node, pos){
20079 var npos = node.pos.getc(true),
20080 dim = node.getData('dim');
20081 return this.nodeHelper.square.contains(npos, pos, dim);
20085 'render': function(node, canvas){
20086 var pos = node.pos.getc(true),
20087 width = node.getData('width'),
20088 height = node.getData('height');
20089 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20091 'contains': function(node, pos){
20092 var npos = node.pos.getc(true),
20093 width = node.getData('width'),
20094 height = node.getData('height');
20095 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20099 'render': function(node, canvas){
20100 var pos = node.pos.getc(true),
20101 dim = node.getData('dim');
20102 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20104 'contains': function(node, pos) {
20105 var npos = node.pos.getc(true),
20106 dim = node.getData('dim');
20107 return this.nodeHelper.triangle.contains(npos, pos, dim);
20111 'render': function(node, canvas){
20112 var pos = node.pos.getc(true),
20113 dim = node.getData('dim');
20114 this.nodeHelper.star.render('fill', pos, dim, canvas);
20116 'contains': function(node, pos) {
20117 var npos = node.pos.getc(true),
20118 dim = node.getData('dim');
20119 return this.nodeHelper.star.contains(npos, pos, dim);
20125 Class: RGraph.Plot.EdgeTypes
20127 This class contains a list of <Graph.Adjacence> built-in types.
20128 Edge types implemented are 'none', 'line' and 'arrow'.
20130 You can add your custom edge types, customizing your visualization to the extreme.
20135 RGraph.Plot.EdgeTypes.implement({
20137 'render': function(adj, canvas) {
20138 //print your custom edge to canvas
20141 'contains': function(adj, pos) {
20142 //return true if pos is inside the arc or false otherwise
20149 RGraph.Plot.EdgeTypes = new Class({
20152 'render': function(adj, canvas) {
20153 var from = adj.nodeFrom.pos.getc(true),
20154 to = adj.nodeTo.pos.getc(true);
20155 this.edgeHelper.line.render(from, to, canvas);
20157 'contains': function(adj, pos) {
20158 var from = adj.nodeFrom.pos.getc(true),
20159 to = adj.nodeTo.pos.getc(true);
20160 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20164 'render': function(adj, canvas) {
20165 var from = adj.nodeFrom.pos.getc(true),
20166 to = adj.nodeTo.pos.getc(true),
20167 dim = adj.getData('dim'),
20168 direction = adj.data.$direction,
20169 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20170 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20172 'contains': function(adj, pos) {
20173 var from = adj.nodeFrom.pos.getc(true),
20174 to = adj.nodeTo.pos.getc(true);
20175 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20184 * File: Hypertree.js
20191 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
20195 moebiusTransformation
20197 Calculates a moebius transformation for this point / complex.
20198 For more information go to:
20199 http://en.wikipedia.org/wiki/Moebius_transformation.
20203 c - An initialized Complex instance representing a translation Vector.
20206 Complex.prototype.moebiusTransformation = function(c) {
20207 var num = this.add(c);
20208 var den = c.$conjugate().$prod(this);
20210 return num.$div(den);
20214 moebiusTransformation
20216 Calculates a moebius transformation for the hyperbolic tree.
20218 <http://en.wikipedia.org/wiki/Moebius_transformation>
20222 graph - A <Graph> instance.
20224 prop - A property array.
20225 theta - Rotation angle.
20226 startPos - _optional_ start position.
20228 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20229 this.eachNode(graph, function(elem) {
20230 for ( var i = 0; i < prop.length; i++) {
20231 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20232 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20240 A Hyperbolic Tree/Graph visualization.
20244 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
20245 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20249 This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the Hypertree described in the paper.
20253 All <Loader> methods
20255 Constructor Options:
20257 Inherits options from
20260 - <Options.Controller>
20266 - <Options.NodeStyles>
20267 - <Options.Navigation>
20269 Additionally, there are other parameters and some default values changed
20271 radius - (string|number) Default's *auto*. The radius of the disc to plot the <Hypertree> in. 'auto' will take the smaller value from the width and height canvas dimensions. You can also set this to a custom value, for example *250*.
20272 offset - (number) Default's *0*. A number in the range [0, 1) that will be substracted to each node position to make a more compact <Hypertree>. This will avoid placing nodes too far from each other when a there's a selected node.
20273 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20274 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20275 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
20277 Instance Properties:
20279 canvas - Access a <Canvas> instance.
20280 graph - Access a <Graph> instance.
20281 op - Access a <Hypertree.Op> instance.
20282 fx - Access a <Hypertree.Plot> instance.
20283 labels - Access a <Hypertree.Label> interface implementation.
20287 $jit.Hypertree = new Class( {
20289 Implements: [ Loader, Extras, Layouts.Radial ],
20291 initialize: function(controller) {
20292 var $Hypertree = $jit.Hypertree;
20303 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20304 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20306 var canvasConfig = this.config;
20307 if(canvasConfig.useCanvas) {
20308 this.canvas = canvasConfig.useCanvas;
20309 this.config.labelContainer = this.canvas.id + '-label';
20311 if(canvasConfig.background) {
20312 canvasConfig.background = $.merge({
20314 }, canvasConfig.background);
20316 this.canvas = new Canvas(this, canvasConfig);
20317 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20320 this.graphOptions = {
20328 this.graph = new Graph(this.graphOptions, this.config.Node,
20330 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20331 this.fx = new $Hypertree.Plot(this, $Hypertree);
20332 this.op = new $Hypertree.Op(this);
20336 // initialize extras
20337 this.initializeExtras();
20342 createLevelDistanceFunc
20344 Returns the levelDistance function used for calculating a node distance
20345 to its origin. This function returns a function that is computed
20346 per level and not per node, such that all nodes with the same depth will have the
20347 same distance to the origin. The resulting function gets the
20348 parent node as parameter and returns a float.
20351 createLevelDistanceFunc: function() {
20352 // get max viz. length.
20353 var r = this.getRadius();
20355 var depth = 0, max = Math.max, config = this.config;
20356 this.graph.eachNode(function(node) {
20357 depth = max(node._depth, depth);
20360 // node distance generator
20361 var genDistFunc = function(a) {
20362 return function(node) {
20364 var d = node._depth + 1;
20365 var acum = 0, pow = Math.pow;
20367 acum += pow(a, d--);
20369 return acum - config.offset;
20372 // estimate better edge length.
20373 for ( var i = 0.51; i <= 1; i += 0.01) {
20374 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20375 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20377 return genDistFunc(0.75);
20383 Returns the current radius of the visualization. If *config.radius* is *auto* then it
20384 calculates the radius by taking the smaller size of the <Canvas> widget.
20391 getRadius: function() {
20392 var rad = this.config.radius;
20393 if (rad !== "auto") { return rad; }
20394 var s = this.canvas.getSize();
20395 return Math.min(s.width, s.height) / 2;
20401 Computes positions and plots the tree.
20405 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20408 refresh: function(reposition) {
20411 this.graph.eachNode(function(node) {
20412 node.startPos.rho = node.pos.rho = node.endPos.rho;
20413 node.startPos.theta = node.pos.theta = node.endPos.theta;
20424 Computes nodes' positions and restores the tree to its previous position.
20426 For calculating nodes' positions the root must be placed on its origin. This method does this
20427 and then attemps to restore the hypertree to its previous position.
20430 reposition: function() {
20431 this.compute('end');
20432 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20433 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20435 this.graph.eachNode(function(node) {
20437 node.endPos.rho = node.pos.rho;
20438 node.endPos.theta = node.pos.theta;
20446 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
20456 Animates the <Hypertree> to center the node specified by *id*.
20460 id - A <Graph.Node> id.
20461 opt - (optional|object) An object containing some extra properties described below
20462 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20467 ht.onClick('someid');
20469 ht.onClick('someid', {
20475 onClick: function(id, opt) {
20476 var pos = this.graph.getNode(id).pos.getc(true);
20477 this.move(pos, opt);
20483 Translates the tree to the given position.
20487 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20488 opt - This object has been defined in <Hypertree.onClick>
20493 ht.move({ x: 0, y: 0.7 }, {
20499 move: function(pos, opt) {
20500 var versor = $C(pos.x, pos.y);
20501 if (this.busy === false && versor.norm() < 1) {
20503 var root = this.graph.getClosestNodeToPos(versor), that = this;
20504 this.graph.computeLevels(root.id, 0);
20505 this.controller.onBeforeCompute(root);
20507 onComplete: $.empty
20509 this.fx.animate($.merge( {
20510 modes: [ 'moebius' ],
20513 onComplete: function() {
20522 $jit.Hypertree.$extend = true;
20524 (function(Hypertree) {
20527 Class: Hypertree.Op
20529 Custom extension of <Graph.Op>.
20533 All <Graph.Op> methods
20540 Hypertree.Op = new Class( {
20542 Implements: Graph.Op
20547 Class: Hypertree.Plot
20549 Custom extension of <Graph.Plot>.
20553 All <Graph.Plot> methods
20560 Hypertree.Plot = new Class( {
20562 Implements: Graph.Plot
20567 Object: Hypertree.Label
20569 Custom extension of <Graph.Label>.
20570 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20574 All <Graph.Label> methods and subclasses.
20578 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20581 Hypertree.Label = {};
20584 Hypertree.Label.Native
20586 Custom extension of <Graph.Label.Native>.
20590 All <Graph.Label.Native> methods
20594 <Graph.Label.Native>
20597 Hypertree.Label.Native = new Class( {
20598 Implements: Graph.Label.Native,
20600 initialize: function(viz) {
20604 renderLabel: function(canvas, node, controller) {
20605 var ctx = canvas.getCtx();
20606 var coord = node.pos.getc(true);
20607 var s = this.viz.getRadius();
20608 ctx.fillText(node.name, coord.x * s, coord.y * s);
20613 Hypertree.Label.SVG
20615 Custom extension of <Graph.Label.SVG>.
20619 All <Graph.Label.SVG> methods
20626 Hypertree.Label.SVG = new Class( {
20627 Implements: Graph.Label.SVG,
20629 initialize: function(viz) {
20636 Overrides abstract method placeLabel in <Graph.Plot>.
20640 tag - A DOM label element.
20641 node - A <Graph.Node>.
20642 controller - A configuration/controller object passed to the visualization.
20645 placeLabel: function(tag, node, controller) {
20646 var pos = node.pos.getc(true),
20647 canvas = this.viz.canvas,
20648 ox = canvas.translateOffsetX,
20649 oy = canvas.translateOffsetY,
20650 sx = canvas.scaleOffsetX,
20651 sy = canvas.scaleOffsetY,
20652 radius = canvas.getSize(),
20653 r = this.viz.getRadius();
20655 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20656 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20658 tag.setAttribute('x', labelPos.x);
20659 tag.setAttribute('y', labelPos.y);
20660 controller.onPlaceLabel(tag, node);
20665 Hypertree.Label.HTML
20667 Custom extension of <Graph.Label.HTML>.
20671 All <Graph.Label.HTML> methods.
20678 Hypertree.Label.HTML = new Class( {
20679 Implements: Graph.Label.HTML,
20681 initialize: function(viz) {
20687 Overrides abstract method placeLabel in <Graph.Plot>.
20691 tag - A DOM label element.
20692 node - A <Graph.Node>.
20693 controller - A configuration/controller object passed to the visualization.
20696 placeLabel: function(tag, node, controller) {
20697 var pos = node.pos.getc(true),
20698 canvas = this.viz.canvas,
20699 ox = canvas.translateOffsetX,
20700 oy = canvas.translateOffsetY,
20701 sx = canvas.scaleOffsetX,
20702 sy = canvas.scaleOffsetY,
20703 radius = canvas.getSize(),
20704 r = this.viz.getRadius();
20706 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20707 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20709 var style = tag.style;
20710 style.left = labelPos.x + 'px';
20711 style.top = labelPos.y + 'px';
20712 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20714 controller.onPlaceLabel(tag, node);
20719 Class: Hypertree.Plot.NodeTypes
20721 This class contains a list of <Graph.Node> built-in types.
20722 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20724 You can add your custom node types, customizing your visualization to the extreme.
20729 Hypertree.Plot.NodeTypes.implement({
20731 'render': function(node, canvas) {
20732 //print your custom node to canvas
20735 'contains': function(node, pos) {
20736 //return true if pos is inside the node or false otherwise
20743 Hypertree.Plot.NodeTypes = new Class({
20746 'contains': $.lambda(false)
20749 'render': function(node, canvas) {
20750 var nconfig = this.node,
20751 dim = node.getData('dim'),
20752 p = node.pos.getc();
20753 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20754 p.$scale(node.scale);
20756 this.nodeHelper.circle.render('fill', p, dim, canvas);
20759 'contains': function(node, pos) {
20760 var dim = node.getData('dim'),
20761 npos = node.pos.getc().$scale(node.scale);
20762 return this.nodeHelper.circle.contains(npos, pos, dim);
20766 'render': function(node, canvas) {
20767 var pos = node.pos.getc().$scale(node.scale),
20768 width = node.getData('width'),
20769 height = node.getData('height');
20770 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20772 'contains': function(node, pos) {
20773 var width = node.getData('width'),
20774 height = node.getData('height'),
20775 npos = node.pos.getc().$scale(node.scale);
20776 return this.nodeHelper.circle.contains(npos, pos, width, height);
20780 'render': function(node, canvas) {
20781 var nconfig = this.node,
20782 dim = node.getData('dim'),
20783 p = node.pos.getc();
20784 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20785 p.$scale(node.scale);
20787 this.nodeHelper.square.render('fill', p, dim, canvas);
20790 'contains': function(node, pos) {
20791 var dim = node.getData('dim'),
20792 npos = node.pos.getc().$scale(node.scale);
20793 return this.nodeHelper.square.contains(npos, pos, dim);
20797 'render': function(node, canvas) {
20798 var nconfig = this.node,
20799 width = node.getData('width'),
20800 height = node.getData('height'),
20801 pos = node.pos.getc();
20802 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20803 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20804 pos.$scale(node.scale);
20805 if (width > 0.2 && height > 0.2) {
20806 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20809 'contains': function(node, pos) {
20810 var width = node.getData('width'),
20811 height = node.getData('height'),
20812 npos = node.pos.getc().$scale(node.scale);
20813 return this.nodeHelper.square.contains(npos, pos, width, height);
20817 'render': function(node, canvas) {
20818 var nconfig = this.node,
20819 dim = node.getData('dim'),
20820 p = node.pos.getc();
20821 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20822 p.$scale(node.scale);
20824 this.nodeHelper.triangle.render('fill', p, dim, canvas);
20827 'contains': function(node, pos) {
20828 var dim = node.getData('dim'),
20829 npos = node.pos.getc().$scale(node.scale);
20830 return this.nodeHelper.triangle.contains(npos, pos, dim);
20834 'render': function(node, canvas) {
20835 var nconfig = this.node,
20836 dim = node.getData('dim'),
20837 p = node.pos.getc();
20838 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20839 p.$scale(node.scale);
20841 this.nodeHelper.star.render('fill', p, dim, canvas);
20844 'contains': function(node, pos) {
20845 var dim = node.getData('dim'),
20846 npos = node.pos.getc().$scale(node.scale);
20847 return this.nodeHelper.star.contains(npos, pos, dim);
20853 Class: Hypertree.Plot.EdgeTypes
20855 This class contains a list of <Graph.Adjacence> built-in types.
20856 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20858 You can add your custom edge types, customizing your visualization to the extreme.
20863 Hypertree.Plot.EdgeTypes.implement({
20865 'render': function(adj, canvas) {
20866 //print your custom edge to canvas
20869 'contains': function(adj, pos) {
20870 //return true if pos is inside the arc or false otherwise
20877 Hypertree.Plot.EdgeTypes = new Class({
20880 'render': function(adj, canvas) {
20881 var from = adj.nodeFrom.pos.getc(true),
20882 to = adj.nodeTo.pos.getc(true),
20883 r = adj.nodeFrom.scale;
20884 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20886 'contains': function(adj, pos) {
20887 var from = adj.nodeFrom.pos.getc(true),
20888 to = adj.nodeTo.pos.getc(true),
20889 r = adj.nodeFrom.scale;
20890 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20894 'render': function(adj, canvas) {
20895 var from = adj.nodeFrom.pos.getc(true),
20896 to = adj.nodeTo.pos.getc(true),
20897 r = adj.nodeFrom.scale,
20898 dim = adj.getData('dim'),
20899 direction = adj.data.$direction,
20900 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20901 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20903 'contains': function(adj, pos) {
20904 var from = adj.nodeFrom.pos.getc(true),
20905 to = adj.nodeTo.pos.getc(true),
20906 r = adj.nodeFrom.scale;
20907 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20911 'render': function(adj, canvas) {
20912 var from = adj.nodeFrom.pos.getc(),
20913 to = adj.nodeTo.pos.getc(),
20914 dim = this.viz.getRadius();
20915 this.edgeHelper.hyperline.render(from, to, dim, canvas);
20917 'contains': $.lambda(false)
20921 })($jit.Hypertree);