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) {
3098 var px = this.scaleOffsetX * x,
3099 py = this.scaleOffsetY * y;
3100 var dx = this.translateOffsetX * (x -1) / px,
3101 dy = this.translateOffsetY * (y -1) / py;
3102 this.scaleOffsetX = px;
3103 this.scaleOffsetY = py;
3104 for(var i=0, l=this.canvases.length; i<l; i++) {
3105 this.canvases[i].scale(x, y, true);
3107 this.translate(dx, dy, false);
3112 Returns the canvas position as an *x, y* object.
3116 force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3120 An object with *x* and *y* properties.
3124 canvas.getPos(true); //returns { x: 900, y: 500 }
3127 getPos: function(force){
3128 if(force || !this.pos) {
3129 return this.pos = $.getPos(this.getElement());
3139 this.canvases[i||0].clear();
3142 path: function(type, action){
3143 var ctx = this.canvases[0].getCtx();
3150 createLabelContainer: function(type, idLabel, dim) {
3151 var NS = 'http://www.w3.org/2000/svg';
3152 if(type == 'HTML' || type == 'Native') {
3156 'overflow': 'visible',
3157 'position': 'absolute',
3160 'width': dim.width + 'px',
3164 } else if(type == 'SVG') {
3165 var svgContainer = document.createElementNS(NS, 'svg:svg');
3166 svgContainer.setAttribute("width", dim.width);
3167 svgContainer.setAttribute('height', dim.height);
3168 var style = svgContainer.style;
3169 style.position = 'absolute';
3170 style.left = style.top = '0px';
3171 var labelContainer = document.createElementNS(NS, 'svg:g');
3172 labelContainer.setAttribute('width', dim.width);
3173 labelContainer.setAttribute('height', dim.height);
3174 labelContainer.setAttribute('x', 0);
3175 labelContainer.setAttribute('y', 0);
3176 labelContainer.setAttribute('id', idLabel);
3177 svgContainer.appendChild(labelContainer);
3178 return svgContainer;
3182 //base canvas wrapper
3183 Canvas.Base = new Class({
3184 translateOffsetX: 0,
3185 translateOffsetY: 0,
3189 initialize: function(viz) {
3191 this.opt = viz.config;
3193 this.createCanvas();
3194 this.translateToCenter();
3196 createCanvas: function() {
3199 height = opt.height;
3200 this.canvas = $E('canvas', {
3201 'id': opt.injectInto + opt.idSuffix,
3205 'position': 'absolute',
3208 'width': width + 'px',
3209 'height': height + 'px'
3213 getCtx: function() {
3215 return this.ctx = this.canvas.getContext('2d');
3218 getSize: function() {
3219 if(this.size) return this.size;
3220 var canvas = this.canvas;
3221 return this.size = {
3222 width: canvas.width,
3223 height: canvas.height
3226 translateToCenter: function(ps) {
3227 var size = this.getSize(),
3228 width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3229 height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3230 var ctx = this.getCtx();
3231 ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3232 ctx.translate(width/2, height/2);
3234 resize: function(width, height) {
3235 var size = this.getSize(),
3236 canvas = this.canvas,
3237 styles = canvas.style;
3239 canvas.width = width;
3240 canvas.height = height;
3241 styles.width = width + "px";
3242 styles.height = height + "px";
3243 //small ExCanvas fix
3244 if(!supportsCanvas) {
3245 this.translateToCenter(size);
3247 this.translateToCenter();
3249 this.translateOffsetX =
3250 this.translateOffsetY = 0;
3252 this.scaleOffsetY = 1;
3254 this.viz.resize(width, height, this);
3256 translate: function(x, y, disablePlot) {
3257 var sx = this.scaleOffsetX,
3258 sy = this.scaleOffsetY;
3259 this.translateOffsetX += x*sx;
3260 this.translateOffsetY += y*sy;
3261 this.getCtx().translate(x, y);
3262 !disablePlot && this.plot();
3264 scale: function(x, y, disablePlot) {
3265 this.scaleOffsetX *= x;
3266 this.scaleOffsetY *= y;
3267 this.getCtx().scale(x, y);
3268 !disablePlot && this.plot();
3271 var size = this.getSize(),
3272 ox = this.translateOffsetX,
3273 oy = this.translateOffsetY,
3274 sx = this.scaleOffsetX,
3275 sy = this.scaleOffsetY;
3276 this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx,
3277 (-size.height / 2 - oy) * 1/sy,
3278 size.width * 1/sx, size.height * 1/sy);
3282 this.viz.plot(this);
3285 //background canvases
3286 //TODO(nico): document this!
3287 Canvas.Background = {};
3288 Canvas.Background.Circles = new Class({
3289 initialize: function(viz, options) {
3291 this.config = $.merge({
3292 idSuffix: '-bkcanvas',
3299 resize: function(width, height, base) {
3302 plot: function(base) {
3303 var canvas = base.canvas,
3304 ctx = base.getCtx(),
3306 styles = conf.CanvasStyles;
3308 for(var s in styles) ctx[s] = styles[s];
3309 var n = conf.numberOfCircles,
3310 rho = conf.levelDistance;
3311 for(var i=1; i<=n; i++) {
3313 ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3317 //TODO(nico): print labels too!
3320 Canvas.Background.Fade = new Class({
3321 initialize: function(viz, options) {
3323 this.config = $.merge({
3324 idSuffix: '-bkcanvas',
3329 resize: function(width, height, base) {
3332 plot: function(base) {
3333 var canvas = base.canvas,
3334 ctx = base.getCtx(),
3336 styles = conf.CanvasStyles,
3337 size = base.getSize();
3338 ctx.fillStyle = 'rgb(255,255,255)';
3339 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3340 //TODO(nico): print labels too!
3349 * Defines the <Polar> class.
3353 * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3357 * <http://en.wikipedia.org/wiki/Polar_coordinates>
3364 A multi purpose polar representation.
3368 The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3372 <http://en.wikipedia.org/wiki/Polar_coordinates>
3380 var Polar = function(theta, rho) {
3391 Returns a complex number.
3395 simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3401 getc: function(simple) {
3402 return this.toComplex(simple);
3408 Returns a <Polar> representation.
3412 A variable in polar coordinates.
3426 v - A <Complex> or <Polar> instance.
3431 this.theta = v.theta; this.rho = v.rho;
3437 Sets a <Complex> number.
3441 x - A <Complex> number real part.
3442 y - A <Complex> number imaginary part.
3445 setc: function(x, y) {
3446 this.rho = Math.sqrt(x * x + y * y);
3447 this.theta = Math.atan2(y, x);
3448 if(this.theta < 0) this.theta += Math.PI * 2;
3454 Sets a polar number.
3458 theta - A <Polar> number angle property.
3459 rho - A <Polar> number rho property.
3462 setp: function(theta, rho) {
3470 Returns a copy of the current object.
3474 A copy of the real object.
3477 return new Polar(this.theta, this.rho);
3483 Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3487 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*.
3491 A new <Complex> instance.
3493 toComplex: function(simple) {
3494 var x = Math.cos(this.theta) * this.rho;
3495 var y = Math.sin(this.theta) * this.rho;
3496 if(simple) return { 'x': x, 'y': y};
3497 return new Complex(x, y);
3503 Adds two <Polar> instances.
3507 polar - A <Polar> number.
3511 A new Polar instance.
3513 add: function(polar) {
3514 return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3520 Scales a polar norm.
3524 number - A scale factor.
3528 A new Polar instance.
3530 scale: function(number) {
3531 return new Polar(this.theta, this.rho * number);
3539 Returns *true* if the theta and rho properties are equal.
3543 c - A <Polar> number.
3547 *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3549 equals: function(c) {
3550 return this.theta == c.theta && this.rho == c.rho;
3556 Adds two <Polar> instances affecting the current object.
3560 polar - A <Polar> instance.
3566 $add: function(polar) {
3567 this.theta = this.theta + polar.theta; this.rho += polar.rho;
3574 Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3578 polar - A <Polar> instance.
3584 $madd: function(polar) {
3585 this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3593 Scales a polar instance affecting the object.
3597 number - A scaling factor.
3603 $scale: function(number) {
3611 Calculates a polar interpolation between two points at a given delta moment.
3615 elem - A <Polar> instance.
3616 delta - A delta factor ranging [0, 1].
3620 A new <Polar> instance representing an interpolation between _this_ and _elem_
3622 interpolate: function(elem, delta) {
3623 var pi = Math.PI, pi2 = pi * 2;
3624 var ch = function(t) {
3625 var a = (t < 0)? (t % pi2) + pi2 : t % pi2;
3628 var tt = this.theta, et = elem.theta;
3629 var sum, diff = Math.abs(tt - et);
3632 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3634 sum = ch((et - pi2 + (tt - (et)) * delta));
3636 } else if(diff >= pi) {
3638 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3640 sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3643 sum = ch((et + (tt - et) * delta)) ;
3645 var r = (this.rho - elem.rho) * delta + elem.rho;
3654 var $P = function(a, b) { return new Polar(a, b); };
3656 Polar.KER = $P(0, 0);
3663 * Defines the <Complex> class.
3667 * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3671 * <http://en.wikipedia.org/wiki/Complex_number>
3678 A multi-purpose Complex Class with common methods.
3682 The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3686 <http://en.wikipedia.org/wiki/Complex_number>
3690 x - _optional_ A Complex number real part.
3691 y - _optional_ A Complex number imaginary part.
3695 var Complex = function(x, y) {
3700 $jit.Complex = Complex;
3702 Complex.prototype = {
3706 Returns a complex number.
3719 Returns a <Polar> representation of this number.
3723 simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3727 A variable in <Polar> coordinates.
3729 getp: function(simple) {
3730 return this.toPolar(simple);
3741 c - A <Complex> or <Polar> instance.
3753 Sets a complex number.
3757 x - A <Complex> number Real part.
3758 y - A <Complex> number Imaginary part.
3761 setc: function(x, y) {
3769 Sets a polar number.
3773 theta - A <Polar> number theta property.
3774 rho - A <Polar> number rho property.
3777 setp: function(theta, rho) {
3778 this.x = Math.cos(theta) * rho;
3779 this.y = Math.sin(theta) * rho;
3785 Returns a copy of the current object.
3789 A copy of the real object.
3792 return new Complex(this.x, this.y);
3798 Transforms cartesian to polar coordinates.
3802 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*.
3806 A new <Polar> instance.
3809 toPolar: function(simple) {
3810 var rho = this.norm();
3811 var atan = Math.atan2(this.y, this.x);
3812 if(atan < 0) atan += Math.PI * 2;
3813 if(simple) return { 'theta': atan, 'rho': rho };
3814 return new Polar(atan, rho);
3819 Calculates a <Complex> number norm.
3823 A real number representing the complex norm.
3826 return Math.sqrt(this.squaredNorm());
3832 Calculates a <Complex> number squared norm.
3836 A real number representing the complex squared norm.
3838 squaredNorm: function () {
3839 return this.x*this.x + this.y*this.y;
3845 Returns the result of adding two complex numbers.
3847 Does not alter the original object.
3851 pos - A <Complex> instance.
3855 The result of adding two complex numbers.
3857 add: function(pos) {
3858 return new Complex(this.x + pos.x, this.y + pos.y);
3864 Returns the result of multiplying two <Complex> numbers.
3866 Does not alter the original object.
3870 pos - A <Complex> instance.
3874 The result of multiplying two complex numbers.
3876 prod: function(pos) {
3877 return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3883 Returns the conjugate of this <Complex> number.
3885 Does not alter the original object.
3889 The conjugate of this <Complex> number.
3891 conjugate: function() {
3892 return new Complex(this.x, -this.y);
3899 Returns the result of scaling a <Complex> instance.
3901 Does not alter the original object.
3905 factor - A scale factor.
3909 The result of scaling this complex to a factor.
3911 scale: function(factor) {
3912 return new Complex(this.x * factor, this.y * factor);
3920 Returns *true* if both real and imaginary parts are equal.
3924 c - A <Complex> instance.
3928 A boolean instance indicating if both <Complex> numbers are equal.
3930 equals: function(c) {
3931 return this.x == c.x && this.y == c.y;
3937 Returns the result of adding two <Complex> numbers.
3939 Alters the original object.
3943 pos - A <Complex> instance.
3947 The result of adding two complex numbers.
3949 $add: function(pos) {
3950 this.x += pos.x; this.y += pos.y;
3957 Returns the result of multiplying two <Complex> numbers.
3959 Alters the original object.
3963 pos - A <Complex> instance.
3967 The result of multiplying two complex numbers.
3969 $prod:function(pos) {
3970 var x = this.x, y = this.y;
3971 this.x = x*pos.x - y*pos.y;
3972 this.y = y*pos.x + x*pos.y;
3979 Returns the conjugate for this <Complex>.
3981 Alters the original object.
3985 The conjugate for this complex.
3987 $conjugate: function() {
3995 Returns the result of scaling a <Complex> instance.
3997 Alters the original object.
4001 factor - A scale factor.
4005 The result of scaling this complex to a factor.
4007 $scale: function(factor) {
4008 this.x *= factor; this.y *= factor;
4015 Returns the division of two <Complex> numbers.
4017 Alters the original object.
4021 pos - A <Complex> number.
4025 The result of scaling this complex to a factor.
4027 $div: function(pos) {
4028 var x = this.x, y = this.y;
4029 var sq = pos.squaredNorm();
4030 this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4031 return this.$scale(1 / sq);
4035 var $C = function(a, b) { return new Complex(a, b); };
4037 Complex.KER = $C(0, 0);
4049 A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4051 An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4056 //create new visualization
4057 var viz = new $jit.Viz(options);
4061 viz.graph; //<Graph> instance
4066 The following <Graph.Util> methods are implemented in <Graph>
4068 - <Graph.Util.getNode>
4069 - <Graph.Util.eachNode>
4070 - <Graph.Util.computeLevels>
4071 - <Graph.Util.eachBFS>
4072 - <Graph.Util.clean>
4073 - <Graph.Util.getClosestNodeToPos>
4074 - <Graph.Util.getClosestNodeToOrigin>
4078 $jit.Graph = new Class({
4080 initialize: function(opt, Node, Edge, Label) {
4081 var innerOptions = {
4088 this.opt = $.merge(innerOptions, opt || {});
4092 //add nodeList methods
4095 for(var p in Accessors) {
4096 that.nodeList[p] = (function(p) {
4098 var args = Array.prototype.slice.call(arguments);
4099 that.eachNode(function(n) {
4100 n[p].apply(n, args);
4111 Returns a <Graph.Node> by *id*.
4115 id - (string) A <Graph.Node> id.
4120 var node = graph.getNode('nodeId');
4123 getNode: function(id) {
4124 if(this.hasNode(id)) return this.nodes[id];
4131 Returns a <Graph.Node> by *name*.
4135 name - (string) A <Graph.Node> name.
4140 var node = graph.getByName('someName');
4143 getByName: function(name) {
4144 for(var id in this.nodes) {
4145 var n = this.nodes[id];
4146 if(n.name == name) return n;
4152 Method: getAdjacence
4154 Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4158 id - (string) A <Graph.Node> id.
4159 id2 - (string) A <Graph.Node> id.
4161 getAdjacence: function (id, id2) {
4162 if(id in this.edges) {
4163 return this.edges[id][id2];
4175 obj - An object with the properties described below
4177 id - (string) A node id
4178 name - (string) A node's name
4179 data - (object) A node's data hash
4185 addNode: function(obj) {
4186 if(!this.nodes[obj.id]) {
4187 var edges = this.edges[obj.id] = {};
4188 this.nodes[obj.id] = new Graph.Node($.extend({
4191 'data': $.merge(obj.data || {}, {}),
4192 'adjacencies': edges
4199 return this.nodes[obj.id];
4203 Method: addAdjacence
4205 Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4209 obj - (object) A <Graph.Node> object.
4210 obj2 - (object) Another <Graph.Node> object.
4211 data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4215 <Graph.Node>, <Graph.Adjacence>
4217 addAdjacence: function (obj, obj2, data) {
4218 if(!this.hasNode(obj.id)) { this.addNode(obj); }
4219 if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4220 obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4221 if(!obj.adjacentTo(obj2)) {
4222 var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4223 var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4224 adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4225 return adjsObj[obj2.id];
4227 return this.edges[obj.id][obj2.id];
4233 Removes a <Graph.Node> matching the specified *id*.
4237 id - (string) A node's id.
4240 removeNode: function(id) {
4241 if(this.hasNode(id)) {
4242 delete this.nodes[id];
4243 var adjs = this.edges[id];
4244 for(var to in adjs) {
4245 delete this.edges[to][id];
4247 delete this.edges[id];
4252 Method: removeAdjacence
4254 Removes a <Graph.Adjacence> matching *id1* and *id2*.
4258 id1 - (string) A <Graph.Node> id.
4259 id2 - (string) A <Graph.Node> id.
4261 removeAdjacence: function(id1, id2) {
4262 delete this.edges[id1][id2];
4263 delete this.edges[id2][id1];
4269 Returns a boolean indicating if the node belongs to the <Graph> or not.
4273 id - (string) Node id.
4275 hasNode: function(id) {
4276 return id in this.nodes;
4285 empty: function() { this.nodes = {}; this.edges = {};}
4289 var Graph = $jit.Graph;
4294 Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4300 var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4302 type = type || 'current';
4303 prefix = "$" + (prefix ? prefix + "-" : "");
4305 if(type == 'current') {
4307 } else if(type == 'start') {
4308 data = this.startData;
4309 } else if(type == 'end') {
4310 data = this.endData;
4313 var dollar = prefix + prop;
4316 return data[dollar];
4319 if(!this.Config.overridable)
4320 return prefixConfig[prop] || 0;
4322 return (dollar in data) ?
4323 data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4326 var setDataInternal = function(prefix, prop, value, type) {
4327 type = type || 'current';
4328 prefix = '$' + (prefix ? prefix + '-' : '');
4332 if(type == 'current') {
4334 } else if(type == 'start') {
4335 data = this.startData;
4336 } else if(type == 'end') {
4337 data = this.endData;
4340 data[prefix + prop] = value;
4343 var removeDataInternal = function(prefix, properties) {
4344 prefix = '$' + (prefix ? prefix + '-' : '');
4346 $.each(properties, function(prop) {
4347 var pref = prefix + prop;
4348 delete that.data[pref];
4349 delete that.endData[pref];
4350 delete that.startData[pref];
4358 Returns the specified data value property.
4359 This is useful for querying special/reserved <Graph.Node> data properties
4360 (i.e dollar prefixed properties).
4364 prop - (string) The name of the property. The dollar sign is not needed. For
4365 example *getData(width)* will return *data.$width*.
4366 type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4367 data properties also. These properties are used when making animations.
4368 force - (boolean) Whether to obtain the true value of the property (equivalent to
4369 *data.$prop*) or to check for *node.overridable = true* first.
4373 The value of the dollar prefixed property or the global Node/Edge property
4374 value if *overridable=false*
4378 node.getData('width'); //will return node.data.$width if Node.overridable=true;
4381 getData: function(prop, type, force) {
4382 return getDataInternal.call(this, "", prop, type, force, this.Config);
4389 Sets the current data property with some specific value.
4390 This method is only useful for reserved (dollar prefixed) properties.
4394 prop - (string) The name of the property. The dollar sign is not necessary. For
4395 example *setData(width)* will set *data.$width*.
4396 value - (mixed) The value to store.
4397 type - (string) The type of the data property to store. Default's "current" but
4398 can also be "start" or "end".
4403 node.setData('width', 30);
4406 If we were to make an animation of a node/edge width then we could do
4409 var node = viz.getNode('nodeId');
4410 //set start and end values
4411 node.setData('width', 10, 'start');
4412 node.setData('width', 30, 'end');
4413 //will animate nodes width property
4415 modes: ['node-property:width'],
4420 setData: function(prop, value, type) {
4421 setDataInternal.call(this, "", prop, value, type);
4427 Convenience method to set multiple data values at once.
4431 types - (array|string) A set of 'current', 'end' or 'start' values.
4432 obj - (object) A hash containing the names and values of the properties to be altered.
4436 node.setDataset(['current', 'end'], {
4438 'color': ['#fff', '#ccc']
4441 node.setDataset('end', {
4452 setDataset: function(types, obj) {
4453 types = $.splat(types);
4454 for(var attr in obj) {
4455 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4456 this.setData(attr, val[i], types[i]);
4464 Remove data properties.
4468 One or more property names as arguments. The dollar sign is not needed.
4472 node.removeData('width'); //now the default width value is returned
4475 removeData: function() {
4476 removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4480 Method: getCanvasStyle
4482 Returns the specified canvas style data value property. This is useful for
4483 querying special/reserved <Graph.Node> canvas style data properties (i.e.
4484 dollar prefixed properties that match with $canvas-<name of canvas style>).
4488 prop - (string) The name of the property. The dollar sign is not needed. For
4489 example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4490 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4491 data properties also.
4495 node.getCanvasStyle('shadowBlur');
4502 getCanvasStyle: function(prop, type, force) {
4503 return getDataInternal.call(
4504 this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4508 Method: setCanvasStyle
4510 Sets the canvas style data property with some specific value.
4511 This method is only useful for reserved (dollar prefixed) properties.
4515 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4516 value - (mixed) The value to set to the property.
4517 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4522 node.setCanvasStyle('shadowBlur', 30);
4525 If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4528 var node = viz.getNode('nodeId');
4529 //set start and end values
4530 node.setCanvasStyle('shadowBlur', 10, 'start');
4531 node.setCanvasStyle('shadowBlur', 30, 'end');
4532 //will animate nodes canvas style property for nodes
4534 modes: ['node-style:shadowBlur'],
4541 <Accessors.setData>.
4543 setCanvasStyle: function(prop, value, type) {
4544 setDataInternal.call(this, 'canvas', prop, value, type);
4548 Method: setCanvasStyles
4550 Convenience method to set multiple styles at once.
4554 types - (array|string) A set of 'current', 'end' or 'start' values.
4555 obj - (object) A hash containing the names and values of the properties to be altered.
4559 <Accessors.setDataset>.
4561 setCanvasStyles: function(types, obj) {
4562 types = $.splat(types);
4563 for(var attr in obj) {
4564 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4565 this.setCanvasStyle(attr, val[i], types[i]);
4571 Method: removeCanvasStyle
4573 Remove canvas style properties from data.
4577 A variable number of canvas style strings.
4581 <Accessors.removeData>.
4583 removeCanvasStyle: function() {
4584 removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4588 Method: getLabelData
4590 Returns the specified label data value property. This is useful for
4591 querying special/reserved <Graph.Node> label options (i.e.
4592 dollar prefixed properties that match with $label-<name of label style>).
4596 prop - (string) The name of the property. The dollar sign prefix is not needed. For
4597 example *getLabelData(size)* will return *data[$label-size]*.
4598 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4599 data properties also.
4603 <Accessors.getData>.
4605 getLabelData: function(prop, type, force) {
4606 return getDataInternal.call(
4607 this, 'label', prop, type, force, this.Label);
4611 Method: setLabelData
4613 Sets the current label data with some specific value.
4614 This method is only useful for reserved (dollar prefixed) properties.
4618 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4619 value - (mixed) The value to set to the property.
4620 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4625 node.setLabelData('size', 30);
4628 If we were to make an animation of a node label size then we could do
4631 var node = viz.getNode('nodeId');
4632 //set start and end values
4633 node.setLabelData('size', 10, 'start');
4634 node.setLabelData('size', 30, 'end');
4635 //will animate nodes label size
4637 modes: ['label-property:size'],
4644 <Accessors.setData>.
4646 setLabelData: function(prop, value, type) {
4647 setDataInternal.call(this, 'label', prop, value, type);
4651 Method: setLabelDataset
4653 Convenience function to set multiple label data at once.
4657 types - (array|string) A set of 'current', 'end' or 'start' values.
4658 obj - (object) A hash containing the names and values of the properties to be altered.
4662 <Accessors.setDataset>.
4664 setLabelDataset: function(types, obj) {
4665 types = $.splat(types);
4666 for(var attr in obj) {
4667 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4668 this.setLabelData(attr, val[i], types[i]);
4674 Method: removeLabelData
4676 Remove label properties from data.
4680 A variable number of label property strings.
4684 <Accessors.removeData>.
4686 removeLabelData: function() {
4687 removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4699 <Accessors> methods.
4701 The following <Graph.Util> methods are implemented by <Graph.Node>
4703 - <Graph.Util.eachAdjacency>
4704 - <Graph.Util.eachLevel>
4705 - <Graph.Util.eachSubgraph>
4706 - <Graph.Util.eachSubnode>
4707 - <Graph.Util.anySubnode>
4708 - <Graph.Util.getSubnodes>
4709 - <Graph.Util.getParents>
4710 - <Graph.Util.isDescendantOf>
4712 Graph.Node = new Class({
4714 initialize: function(opt, complex, Node, Edge, Label) {
4715 var innerOptions = {
4732 'pos': (complex && $C(0, 0)) || $P(0, 0),
4733 'startPos': (complex && $C(0, 0)) || $P(0, 0),
4734 'endPos': (complex && $C(0, 0)) || $P(0, 0)
4737 $.extend(this, $.extend(innerOptions, opt));
4738 this.Config = this.Node = Node;
4746 Indicates if the node is adjacent to the node specified by id
4750 id - (string) A node id.
4754 node.adjacentTo('nodeId') == true;
4757 adjacentTo: function(node) {
4758 return node.id in this.adjacencies;
4762 Method: getAdjacency
4764 Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4768 id - (string) A node id.
4770 getAdjacency: function(id) {
4771 return this.adjacencies[id];
4777 Returns the position of the node.
4781 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4785 A <Complex> or <Polar> instance.
4789 var pos = node.getPos('end');
4792 getPos: function(type) {
4793 type = type || "current";
4794 if(type == "current") {
4796 } else if(type == "end") {
4798 } else if(type == "start") {
4799 return this.startPos;
4805 Sets the node's position.
4809 value - (object) A <Complex> or <Polar> instance.
4810 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4814 node.setPos(new $jit.Complex(0, 0), 'end');
4817 setPos: function(value, type) {
4818 type = type || "current";
4820 if(type == "current") {
4822 } else if(type == "end") {
4824 } else if(type == "start") {
4825 pos = this.startPos;
4831 Graph.Node.implement(Accessors);
4834 Class: Graph.Adjacence
4836 A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4840 <Accessors> methods.
4844 <Graph>, <Graph.Node>
4848 nodeFrom - A <Graph.Node> connected by this edge.
4849 nodeTo - Another <Graph.Node> connected by this edge.
4850 data - Node data property containing a hash (i.e {}) with custom options.
4852 Graph.Adjacence = new Class({
4854 initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4855 this.nodeFrom = nodeFrom;
4856 this.nodeTo = nodeTo;
4857 this.data = data || {};
4858 this.startData = {};
4860 this.Config = this.Edge = Edge;
4865 Graph.Adjacence.implement(Accessors);
4870 <Graph> traversal and processing utility object.
4874 For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4880 For internal use only. Provides a filtering function based on flags.
4882 filter: function(param) {
4883 if(!param || !($.type(param) == 'string')) return function() { return true; };
4884 var props = param.split(" ");
4885 return function(elem) {
4886 for(var i=0; i<props.length; i++) {
4887 if(elem[props[i]]) {
4897 Returns a <Graph.Node> by *id*.
4899 Also implemented by:
4905 graph - (object) A <Graph> instance.
4906 id - (string) A <Graph.Node> id.
4911 $jit.Graph.Util.getNode(graph, 'nodeid');
4913 graph.getNode('nodeid');
4916 getNode: function(graph, id) {
4917 return graph.nodes[id];
4923 Iterates over <Graph> nodes performing an *action*.
4925 Also implemented by:
4931 graph - (object) A <Graph> instance.
4932 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4936 $jit.Graph.Util.eachNode(graph, function(node) {
4940 graph.eachNode(function(node) {
4945 eachNode: function(graph, action, flags) {
4946 var filter = this.filter(flags);
4947 for(var i in graph.nodes) {
4948 if(filter(graph.nodes[i])) action(graph.nodes[i]);
4953 Method: eachAdjacency
4955 Iterates over <Graph.Node> adjacencies applying the *action* function.
4957 Also implemented by:
4963 node - (object) A <Graph.Node>.
4964 action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4968 $jit.Graph.Util.eachAdjacency(node, function(adj) {
4969 alert(adj.nodeTo.name);
4972 node.eachAdjacency(function(adj) {
4973 alert(adj.nodeTo.name);
4977 eachAdjacency: function(node, action, flags) {
4978 var adj = node.adjacencies, filter = this.filter(flags);
4979 for(var id in adj) {
4982 if(a.nodeFrom != node) {
4983 var tmp = a.nodeFrom;
4984 a.nodeFrom = a.nodeTo;
4993 Method: computeLevels
4995 Performs a BFS traversal setting the correct depth for each node.
4997 Also implemented by:
5003 The depth of each node can then be accessed by
5008 graph - (object) A <Graph>.
5009 id - (string) A starting node id for the BFS traversal.
5010 startDepth - (optional|number) A minimum depth value. Default's 0.
5013 computeLevels: function(graph, id, startDepth, flags) {
5014 startDepth = startDepth || 0;
5015 var filter = this.filter(flags);
5016 this.eachNode(graph, function(elem) {
5020 var root = graph.getNode(id);
5021 root._depth = startDepth;
5023 while(queue.length != 0) {
5024 var node = queue.pop();
5026 this.eachAdjacency(node, function(adj) {
5028 if(n._flag == false && filter(n)) {
5029 if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5039 Performs a BFS traversal applying *action* to each <Graph.Node>.
5041 Also implemented by:
5047 graph - (object) A <Graph>.
5048 id - (string) A starting node id for the BFS traversal.
5049 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5053 $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5057 graph.eachBFS('mynodeid', function(node) {
5062 eachBFS: function(graph, id, action, flags) {
5063 var filter = this.filter(flags);
5065 var queue = [graph.getNode(id)];
5066 while(queue.length != 0) {
5067 var node = queue.pop();
5069 action(node, node._depth);
5070 this.eachAdjacency(node, function(adj) {
5072 if(n._flag == false && filter(n)) {
5083 Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5085 Also implemented by:
5091 node - (object) A <Graph.Node>.
5092 levelBegin - (number) A relative level value.
5093 levelEnd - (number) A relative level value.
5094 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5097 eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5098 var d = node._depth, filter = this.filter(flags), that = this;
5099 levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5100 (function loopLevel(node, levelBegin, levelEnd) {
5101 var d = node._depth;
5102 if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5104 that.eachAdjacency(node, function(adj) {
5106 if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5109 })(node, levelBegin + d, levelEnd + d);
5113 Method: eachSubgraph
5115 Iterates over a node's children recursively.
5117 Also implemented by:
5122 node - (object) A <Graph.Node>.
5123 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5127 $jit.Graph.Util.eachSubgraph(node, function(node) {
5131 node.eachSubgraph(function(node) {
5136 eachSubgraph: function(node, action, flags) {
5137 this.eachLevel(node, 0, false, action, flags);
5143 Iterates over a node's children (without deeper recursion).
5145 Also implemented by:
5150 node - (object) A <Graph.Node>.
5151 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5155 $jit.Graph.Util.eachSubnode(node, function(node) {
5159 node.eachSubnode(function(node) {
5164 eachSubnode: function(node, action, flags) {
5165 this.eachLevel(node, 1, 1, action, flags);
5171 Returns *true* if any subnode matches the given condition.
5173 Also implemented by:
5178 node - (object) A <Graph.Node>.
5179 cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5183 $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5185 node.anySubnode(function(node) { return node.name == 'mynodename'; });
5188 anySubnode: function(node, cond, flags) {
5190 cond = cond || $.lambda(true);
5191 var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5192 this.eachSubnode(node, function(elem) {
5193 if(c(elem)) flag = true;
5201 Collects all subnodes for a specified node.
5202 The *level* parameter filters nodes having relative depth of *level* from the root node.
5204 Also implemented by:
5209 node - (object) A <Graph.Node>.
5210 level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5216 getSubnodes: function(node, level, flags) {
5217 var ans = [], that = this;
5219 var levelStart, levelEnd;
5220 if($.type(level) == 'array') {
5221 levelStart = level[0];
5222 levelEnd = level[1];
5225 levelEnd = Number.MAX_VALUE - node._depth;
5227 this.eachLevel(node, levelStart, levelEnd, function(n) {
5237 Returns an Array of <Graph.Nodes> which are parents of the given node.
5239 Also implemented by:
5244 node - (object) A <Graph.Node>.
5247 An Array of <Graph.Nodes>.
5251 var pars = $jit.Graph.Util.getParents(node);
5253 var pars = node.getParents();
5255 if(pars.length > 0) {
5256 //do stuff with parents
5260 getParents: function(node) {
5262 this.eachAdjacency(node, function(adj) {
5264 if(n._depth < node._depth) ans.push(n);
5270 Method: isDescendantOf
5272 Returns a boolean indicating if some node is descendant of the node with the given id.
5274 Also implemented by:
5280 node - (object) A <Graph.Node>.
5281 id - (string) A <Graph.Node> id.
5285 $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5287 node.isDescendantOf('nodeid');//true|false
5290 isDescendantOf: function(node, id) {
5291 if(node.id == id) return true;
5292 var pars = this.getParents(node), ans = false;
5293 for ( var i = 0; !ans && i < pars.length; i++) {
5294 ans = ans || this.isDescendantOf(pars[i], id);
5302 Cleans flags from nodes.
5304 Also implemented by:
5309 graph - A <Graph> instance.
5311 clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5314 Method: getClosestNodeToOrigin
5316 Returns the closest node to the center of canvas.
5318 Also implemented by:
5324 graph - (object) A <Graph> instance.
5325 prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5328 getClosestNodeToOrigin: function(graph, prop, flags) {
5329 return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5333 Method: getClosestNodeToPos
5335 Returns the closest node to the given position.
5337 Also implemented by:
5343 graph - (object) A <Graph> instance.
5344 pos - (object) A <Complex> or <Polar> instance.
5345 prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5348 getClosestNodeToPos: function(graph, pos, prop, flags) {
5350 prop = prop || 'current';
5351 pos = pos && pos.getc(true) || Complex.KER;
5352 var distance = function(a, b) {
5353 var d1 = a.x - b.x, d2 = a.y - b.y;
5354 return d1 * d1 + d2 * d2;
5356 this.eachNode(graph, function(elem) {
5357 node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5358 node.getPos(prop).getc(true), pos)) ? elem : node;
5364 //Append graph methods to <Graph>
5365 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5366 Graph.prototype[m] = function() {
5367 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5371 //Append node methods to <Graph.Node>
5372 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5373 Graph.Node.prototype[m] = function() {
5374 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5386 Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5387 morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5399 initialize: function(viz) {
5406 Removes one or more <Graph.Nodes> from the visualization.
5407 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5411 node - (string|array) The node's id. Can also be an array having many ids.
5412 opt - (object) Animation options. It's an object with optional properties described below
5413 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5414 duration - Described in <Options.Fx>.
5415 fps - Described in <Options.Fx>.
5416 transition - Described in <Options.Fx>.
5417 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5421 var viz = new $jit.Viz(options);
5422 viz.op.removeNode('nodeId', {
5426 transition: $jit.Trans.Quart.easeOut
5429 viz.op.removeNode(['someId', 'otherId'], {
5436 removeNode: function(node, opt) {
5438 var options = $.merge(this.options, viz.controller, opt);
5439 var n = $.splat(node);
5440 var i, that, nodeObj;
5441 switch(options.type) {
5443 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5447 this.removeNode(n, { type: 'nothing' });
5448 viz.labels.clearLabels();
5452 case 'fade:seq': case 'fade':
5454 //set alpha to 0 for nodes to remove.
5455 for(i=0; i<n.length; i++) {
5456 nodeObj = viz.graph.getNode(n[i]);
5457 nodeObj.setData('alpha', 0, 'end');
5459 viz.fx.animate($.merge(options, {
5460 modes: ['node-property:alpha'],
5461 onComplete: function() {
5462 that.removeNode(n, { type: 'nothing' });
5463 viz.labels.clearLabels();
5465 viz.fx.animate($.merge(options, {
5474 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5475 for(i=0; i<n.length; i++) {
5476 nodeObj = viz.graph.getNode(n[i]);
5477 nodeObj.setData('alpha', 0, 'end');
5478 nodeObj.ignore = true;
5481 viz.fx.animate($.merge(options, {
5482 modes: ['node-property:alpha', 'linear'],
5483 onComplete: function() {
5484 that.removeNode(n, { type: 'nothing' });
5492 condition: function() { return n.length != 0; },
5493 step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5494 onComplete: function() { options.onComplete(); },
5495 duration: Math.ceil(options.duration / n.length)
5499 default: this.doError();
5506 Removes one or more <Graph.Adjacences> from the visualization.
5507 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5511 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'], ...]).
5512 opt - (object) Animation options. It's an object with optional properties described below
5513 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5514 duration - Described in <Options.Fx>.
5515 fps - Described in <Options.Fx>.
5516 transition - Described in <Options.Fx>.
5517 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5521 var viz = new $jit.Viz(options);
5522 viz.op.removeEdge(['nodeId', 'otherId'], {
5526 transition: $jit.Trans.Quart.easeOut
5529 viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5536 removeEdge: function(vertex, opt) {
5538 var options = $.merge(this.options, viz.controller, opt);
5539 var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5541 switch(options.type) {
5543 for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5547 this.removeEdge(v, { type: 'nothing' });
5551 case 'fade:seq': case 'fade':
5553 //set alpha to 0 for edges to remove.
5554 for(i=0; i<v.length; i++) {
5555 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5557 adj.setData('alpha', 0,'end');
5560 viz.fx.animate($.merge(options, {
5561 modes: ['edge-property:alpha'],
5562 onComplete: function() {
5563 that.removeEdge(v, { type: 'nothing' });
5565 viz.fx.animate($.merge(options, {
5574 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5575 for(i=0; i<v.length; i++) {
5576 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5578 adj.setData('alpha',0 ,'end');
5583 viz.fx.animate($.merge(options, {
5584 modes: ['edge-property:alpha', 'linear'],
5585 onComplete: function() {
5586 that.removeEdge(v, { type: 'nothing' });
5594 condition: function() { return v.length != 0; },
5595 step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5596 onComplete: function() { options.onComplete(); },
5597 duration: Math.ceil(options.duration / v.length)
5601 default: this.doError();
5608 Adds a new graph to the visualization.
5609 The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5610 The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5614 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5615 opt - (object) Animation options. It's an object with optional properties described below
5616 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5617 duration - Described in <Options.Fx>.
5618 fps - Described in <Options.Fx>.
5619 transition - Described in <Options.Fx>.
5620 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5624 //...json contains a tree or graph structure...
5626 var viz = new $jit.Viz(options);
5631 transition: $jit.Trans.Quart.easeOut
5641 sum: function(json, opt) {
5643 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5645 viz.root = opt.id || viz.root;
5646 switch(options.type) {
5648 graph = viz.construct(json);
5649 graph.eachNode(function(elem) {
5650 elem.eachAdjacency(function(adj) {
5651 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5658 this.sum(json, { type: 'nothing' });
5662 case 'fade:seq': case 'fade': case 'fade:con':
5664 graph = viz.construct(json);
5666 //set alpha to 0 for nodes to add.
5667 var fadeEdges = this.preprocessSum(graph);
5668 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5670 if(options.type != 'fade:con') {
5671 viz.fx.animate($.merge(options, {
5673 onComplete: function() {
5674 viz.fx.animate($.merge(options, {
5676 onComplete: function() {
5677 options.onComplete();
5683 viz.graph.eachNode(function(elem) {
5684 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5685 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5688 viz.fx.animate($.merge(options, {
5689 modes: ['linear'].concat(modes)
5694 default: this.doError();
5701 This method will transform the current visualized graph into the new JSON representation passed in the method.
5702 The JSON object must at least have the root node in common with the current visualized graph.
5706 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5707 opt - (object) Animation options. It's an object with optional properties described below
5708 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5709 duration - Described in <Options.Fx>.
5710 fps - Described in <Options.Fx>.
5711 transition - Described in <Options.Fx>.
5712 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5713 id - (string) The shared <Graph.Node> id between both graphs.
5715 extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5716 *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5717 For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5718 properties as values, just like specified in <Graph.Plot.animate>.
5722 //...json contains a tree or graph structure...
5724 var viz = new $jit.Viz(options);
5725 viz.op.morph(json, {
5729 transition: $jit.Trans.Quart.easeOut
5732 viz.op.morph(json, {
5736 //if the json data contains dollar prefixed params
5737 //like $width or $height these too can be animated
5738 viz.op.morph(json, {
5742 'node-property': ['width', 'height']
5747 morph: function(json, opt, extraModes) {
5749 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5751 //TODO(nico) this hack makes morphing work with the Hypertree.
5752 //Need to check if it has been solved and this can be removed.
5753 viz.root = opt.id || viz.root;
5754 switch(options.type) {
5756 graph = viz.construct(json);
5757 graph.eachNode(function(elem) {
5758 var nodeExists = viz.graph.hasNode(elem.id);
5759 elem.eachAdjacency(function(adj) {
5760 var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5761 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5762 //Update data properties if the node existed
5764 var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5765 for(var prop in (adj.data || {})) {
5766 addedAdj.data[prop] = adj.data[prop];
5770 //Update data properties if the node existed
5772 var addedNode = viz.graph.getNode(elem.id);
5773 for(var prop in (elem.data || {})) {
5774 addedNode.data[prop] = elem.data[prop];
5778 viz.graph.eachNode(function(elem) {
5779 elem.eachAdjacency(function(adj) {
5780 if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5781 viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5784 if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5790 viz.labels.clearLabels(true);
5791 this.morph(json, { type: 'nothing' });
5796 case 'fade:seq': case 'fade': case 'fade:con':
5798 graph = viz.construct(json);
5799 //preprocessing for nodes to delete.
5800 //get node property modes to interpolate
5801 var nodeModes = extraModes && ('node-property' in extraModes)
5802 && $.map($.splat(extraModes['node-property']),
5803 function(n) { return '$' + n; });
5804 viz.graph.eachNode(function(elem) {
5805 var graphNode = graph.getNode(elem.id);
5807 elem.setData('alpha', 1);
5808 elem.setData('alpha', 1, 'start');
5809 elem.setData('alpha', 0, 'end');
5812 //Update node data information
5813 var graphNodeData = graphNode.data;
5814 for(var prop in graphNodeData) {
5815 if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5816 elem.endData[prop] = graphNodeData[prop];
5818 elem.data[prop] = graphNodeData[prop];
5823 viz.graph.eachNode(function(elem) {
5824 if(elem.ignore) return;
5825 elem.eachAdjacency(function(adj) {
5826 if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5827 var nodeFrom = graph.getNode(adj.nodeFrom.id);
5828 var nodeTo = graph.getNode(adj.nodeTo.id);
5829 if(!nodeFrom.adjacentTo(nodeTo)) {
5830 var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5832 adj.setData('alpha', 1);
5833 adj.setData('alpha', 1, 'start');
5834 adj.setData('alpha', 0, 'end');
5838 //preprocessing for adding nodes.
5839 var fadeEdges = this.preprocessSum(graph);
5841 var modes = !fadeEdges? ['node-property:alpha'] :
5842 ['node-property:alpha',
5843 'edge-property:alpha'];
5844 //Append extra node-property animations (if any)
5845 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))?
5846 (':' + $.splat(extraModes['node-property']).join(':')) : '');
5847 //Append extra edge-property animations (if any)
5848 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))?
5849 (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5850 //Add label-property animations (if any)
5851 if(extraModes && ('label-property' in extraModes)) {
5852 modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5855 viz.graph.eachNode(function(elem) {
5856 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5857 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5860 viz.fx.animate($.merge(options, {
5861 modes: ['polar'].concat(modes),
5862 onComplete: function() {
5863 viz.graph.eachNode(function(elem) {
5864 if(elem.ignore) viz.graph.removeNode(elem.id);
5866 viz.graph.eachNode(function(elem) {
5867 elem.eachAdjacency(function(adj) {
5868 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5871 options.onComplete();
5884 Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5888 node - (object) A <Graph.Node>.
5889 opt - (object) An object containing options described below
5890 type - (string) Whether to 'replot' or 'animate' the contraction.
5892 There are also a number of Animation options. For more information see <Options.Fx>.
5896 var viz = new $jit.Viz(options);
5897 viz.op.contract(node, {
5901 transition: $jit.Trans.Quart.easeOut
5906 contract: function(node, opt) {
5908 if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5909 opt = $.merge(this.options, viz.config, opt || {}, {
5910 'modes': ['node-property:alpha:span', 'linear']
5912 node.collapsed = true;
5914 n.eachSubnode(function(ch) {
5916 ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5920 if(opt.type == 'animate') {
5923 viz.rotate(viz.rotated, 'none', {
5928 n.eachSubnode(function(ch) {
5929 ch.setPos(node.getPos('end'), 'end');
5933 viz.fx.animate(opt);
5934 } else if(opt.type == 'replot'){
5942 Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5946 node - (object) A <Graph.Node>.
5947 opt - (object) An object containing options described below
5948 type - (string) Whether to 'replot' or 'animate'.
5950 There are also a number of Animation options. For more information see <Options.Fx>.
5954 var viz = new $jit.Viz(options);
5955 viz.op.expand(node, {
5959 transition: $jit.Trans.Quart.easeOut
5964 expand: function(node, opt) {
5965 if(!('collapsed' in node)) return;
5967 opt = $.merge(this.options, viz.config, opt || {}, {
5968 'modes': ['node-property:alpha:span', 'linear']
5970 delete node.collapsed;
5972 n.eachSubnode(function(ch) {
5974 ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5978 if(opt.type == 'animate') {
5981 viz.rotate(viz.rotated, 'none', {
5985 viz.fx.animate(opt);
5986 } else if(opt.type == 'replot'){
5991 preprocessSum: function(graph) {
5993 graph.eachNode(function(elem) {
5994 if(!viz.graph.hasNode(elem.id)) {
5995 viz.graph.addNode(elem);
5996 var n = viz.graph.getNode(elem.id);
5997 n.setData('alpha', 0);
5998 n.setData('alpha', 0, 'start');
5999 n.setData('alpha', 1, 'end');
6002 var fadeEdges = false;
6003 graph.eachNode(function(elem) {
6004 elem.eachAdjacency(function(adj) {
6005 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6006 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6007 if(!nodeFrom.adjacentTo(nodeTo)) {
6008 var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6009 if(nodeFrom.startAlpha == nodeFrom.endAlpha
6010 && nodeTo.startAlpha == nodeTo.endAlpha) {
6012 adj.setData('alpha', 0);
6013 adj.setData('alpha', 0, 'start');
6014 adj.setData('alpha', 1, 'end');
6028 Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6029 Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6030 position is over the rendered shape.
6032 Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
6033 *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6037 //implement a new node type
6038 $jit.Viz.Plot.NodeTypes.implement({
6040 'render': function(node, canvas) {
6041 this.nodeHelper.circle.render ...
6043 'contains': function(node, pos) {
6044 this.nodeHelper.circle.contains ...
6048 //implement an edge type
6049 $jit.Viz.Plot.EdgeTypes.implement({
6051 'render': function(node, canvas) {
6052 this.edgeHelper.circle.render ...
6055 'contains': function(node, pos) {
6056 this.edgeHelper.circle.contains ...
6067 Contains rendering and other type of primitives for simple shapes.
6072 'contains': $.lambda(false)
6075 Object: NodeHelper.circle
6081 Renders a circle into the canvas.
6085 type - (string) Possible options are 'fill' or 'stroke'.
6086 pos - (object) An *x*, *y* object with the position of the center of the circle.
6087 radius - (number) The radius of the circle to be rendered.
6088 canvas - (object) A <Canvas> instance.
6092 NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6095 'render': function(type, pos, radius, canvas){
6096 var ctx = canvas.getCtx();
6098 ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6105 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6109 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6110 pos - (object) An *x*, *y* object with the position to check.
6111 radius - (number) The radius of the rendered circle.
6115 NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6118 'contains': function(npos, pos, radius){
6119 var diffx = npos.x - pos.x,
6120 diffy = npos.y - pos.y,
6121 diff = diffx * diffx + diffy * diffy;
6122 return diff <= radius * radius;
6126 Object: NodeHelper.ellipse
6132 Renders an ellipse into the canvas.
6136 type - (string) Possible options are 'fill' or 'stroke'.
6137 pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6138 width - (number) The width of the ellipse.
6139 height - (number) The height of the ellipse.
6140 canvas - (object) A <Canvas> instance.
6144 NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6147 'render': function(type, pos, width, height, canvas){
6148 var ctx = canvas.getCtx();
6152 ctx.scale(width / height, height / width);
6154 ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6163 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6167 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6168 pos - (object) An *x*, *y* object with the position to check.
6169 width - (number) The width of the rendered ellipse.
6170 height - (number) The height of the rendered ellipse.
6174 NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6177 'contains': function(npos, pos, width, height){
6178 // TODO(nico): be more precise...
6181 var dist = (width + height) / 2,
6182 diffx = npos.x - pos.x,
6183 diffy = npos.y - pos.y,
6184 diff = diffx * diffx + diffy * diffy;
6185 return diff <= dist * dist;
6189 Object: NodeHelper.square
6195 Renders a square into the canvas.
6199 type - (string) Possible options are 'fill' or 'stroke'.
6200 pos - (object) An *x*, *y* object with the position of the center of the square.
6201 dim - (number) The radius (or half-diameter) of the square.
6202 canvas - (object) A <Canvas> instance.
6206 NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6209 'render': function(type, pos, dim, canvas){
6210 canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6215 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6219 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6220 pos - (object) An *x*, *y* object with the position to check.
6221 dim - (number) The radius (or half-diameter) of the square.
6225 NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6228 'contains': function(npos, pos, dim){
6229 return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6233 Object: NodeHelper.rectangle
6239 Renders a rectangle into the canvas.
6243 type - (string) Possible options are 'fill' or 'stroke'.
6244 pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6245 width - (number) The width of the rectangle.
6246 height - (number) The height of the rectangle.
6247 canvas - (object) A <Canvas> instance.
6251 NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6254 'render': function(type, pos, width, height, canvas){
6255 canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6261 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6265 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6266 pos - (object) An *x*, *y* object with the position to check.
6267 width - (number) The width of the rendered rectangle.
6268 height - (number) The height of the rendered rectangle.
6272 NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6275 'contains': function(npos, pos, width, height){
6276 return Math.abs(pos.x - npos.x) <= width / 2
6277 && Math.abs(pos.y - npos.y) <= height / 2;
6281 Object: NodeHelper.triangle
6287 Renders a triangle into the canvas.
6291 type - (string) Possible options are 'fill' or 'stroke'.
6292 pos - (object) An *x*, *y* object with the position of the center of the triangle.
6293 dim - (number) The dimension of the triangle.
6294 canvas - (object) A <Canvas> instance.
6298 NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6301 'render': function(type, pos, dim, canvas){
6302 var ctx = canvas.getCtx(),
6310 ctx.moveTo(c1x, c1y);
6311 ctx.lineTo(c2x, c2y);
6312 ctx.lineTo(c3x, c3y);
6319 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6323 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6324 pos - (object) An *x*, *y* object with the position to check.
6325 dim - (number) The dimension of the shape.
6329 NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6332 'contains': function(npos, pos, dim) {
6333 return NodeHelper.circle.contains(npos, pos, dim);
6337 Object: NodeHelper.star
6343 Renders a star into the canvas.
6347 type - (string) Possible options are 'fill' or 'stroke'.
6348 pos - (object) An *x*, *y* object with the position of the center of the star.
6349 dim - (number) The dimension of the star.
6350 canvas - (object) A <Canvas> instance.
6354 NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6357 'render': function(type, pos, dim, canvas){
6358 var ctx = canvas.getCtx(),
6361 ctx.translate(pos.x, pos.y);
6364 for (var i = 0; i < 9; i++) {
6367 ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6379 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6383 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6384 pos - (object) An *x*, *y* object with the position to check.
6385 dim - (number) The dimension of the shape.
6389 NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6392 'contains': function(npos, pos, dim) {
6393 return NodeHelper.circle.contains(npos, pos, dim);
6401 Contains rendering primitives for simple edge shapes.
6405 Object: EdgeHelper.line
6411 Renders a line into the canvas.
6415 from - (object) An *x*, *y* object with the starting position of the line.
6416 to - (object) An *x*, *y* object with the ending position of the line.
6417 canvas - (object) A <Canvas> instance.
6421 EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6424 'render': function(from, to, canvas){
6425 var ctx = canvas.getCtx();
6427 ctx.moveTo(from.x, from.y);
6428 ctx.lineTo(to.x, to.y);
6434 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6438 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6439 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6440 pos - (object) An *x*, *y* object with the position to check.
6441 epsilon - (number) The dimension of the shape.
6445 EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6448 'contains': function(posFrom, posTo, pos, epsilon) {
6451 minPosX = min(posFrom.x, posTo.x),
6452 maxPosX = max(posFrom.x, posTo.x),
6453 minPosY = min(posFrom.y, posTo.y),
6454 maxPosY = max(posFrom.y, posTo.y);
6456 if(pos.x >= minPosX && pos.x <= maxPosX
6457 && pos.y >= minPosY && pos.y <= maxPosY) {
6458 if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6461 var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6462 return Math.abs(dist - pos.y) <= epsilon;
6468 Object: EdgeHelper.arrow
6474 Renders an arrow into the canvas.
6478 from - (object) An *x*, *y* object with the starting position of the arrow.
6479 to - (object) An *x*, *y* object with the ending position of the arrow.
6480 dim - (number) The dimension of the arrow.
6481 swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6482 canvas - (object) A <Canvas> instance.
6486 EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6489 'render': function(from, to, dim, swap, canvas){
6490 var ctx = canvas.getCtx();
6491 // invert edge direction
6497 var vect = new Complex(to.x - from.x, to.y - from.y);
6498 vect.$scale(dim / vect.norm());
6499 var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6500 normal = new Complex(-vect.y / 2, vect.x / 2),
6501 v1 = intermediatePoint.add(normal),
6502 v2 = intermediatePoint.$add(normal.$scale(-1));
6505 ctx.moveTo(from.x, from.y);
6506 ctx.lineTo(to.x, to.y);
6509 ctx.moveTo(v1.x, v1.y);
6510 ctx.lineTo(v2.x, v2.y);
6511 ctx.lineTo(to.x, to.y);
6518 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6522 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6523 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6524 pos - (object) An *x*, *y* object with the position to check.
6525 epsilon - (number) The dimension of the shape.
6529 EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6532 'contains': function(posFrom, posTo, pos, epsilon) {
6533 return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6537 Object: EdgeHelper.hyperline
6543 Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6547 from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6548 to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6549 r - (number) The scaling factor.
6550 canvas - (object) A <Canvas> instance.
6554 EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6557 'render': function(from, to, r, canvas){
6558 var ctx = canvas.getCtx();
6559 var centerOfCircle = computeArcThroughTwoPoints(from, to);
6560 if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6561 || centerOfCircle.ratio < 0) {
6563 ctx.moveTo(from.x * r, from.y * r);
6564 ctx.lineTo(to.x * r, to.y * r);
6567 var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6568 - centerOfCircle.x);
6569 var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6570 - centerOfCircle.x);
6571 var sense = sense(angleBegin, angleEnd);
6573 ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6574 * r, angleBegin, angleEnd, sense);
6578 Calculates the arc parameters through two points.
6580 More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6584 p1 - A <Complex> instance.
6585 p2 - A <Complex> instance.
6586 scale - The Disk's diameter.
6590 An object containing some arc properties.
6592 function computeArcThroughTwoPoints(p1, p2){
6593 var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6594 var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6595 // Fall back to a straight line
6603 var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6604 var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6607 var squaredRatio = (a * a + b * b) / 4 - 1;
6608 // Fall back to a straight line
6609 if (squaredRatio < 0)
6615 var ratio = Math.sqrt(squaredRatio);
6619 ratio: ratio > 1000? -1 : ratio,
6627 Sets angle direction to clockwise (true) or counterclockwise (false).
6631 angleBegin - Starting angle for drawing the arc.
6632 angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6636 A Boolean instance describing the sense for drawing the HyperLine.
6638 function sense(angleBegin, angleEnd){
6639 return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6640 : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6648 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6652 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6653 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6654 pos - (object) An *x*, *y* object with the position to check.
6655 epsilon - (number) The dimension of the shape.
6659 EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6662 'contains': $.lambda(false)
6668 * File: Graph.Plot.js
6674 <Graph> rendering and animation methods.
6678 nodeHelper - <NodeHelper> object.
6679 edgeHelper - <EdgeHelper> object.
6682 //Default intializer
6683 initialize: function(viz, klass){
6685 this.config = viz.config;
6686 this.node = viz.config.Node;
6687 this.edge = viz.config.Edge;
6688 this.animation = new Animation;
6689 this.nodeTypes = new klass.Plot.NodeTypes;
6690 this.edgeTypes = new klass.Plot.EdgeTypes;
6691 this.labels = viz.labels;
6695 nodeHelper: NodeHelper,
6696 edgeHelper: EdgeHelper,
6699 //node/edge property parsers
6707 'lineWidth': 'number',
6708 'angularWidth':'number',
6710 'valueArray':'array-number',
6711 'dimArray':'array-number'
6712 //'colorArray':'array-color'
6715 //canvas specific parsers
6717 'globalAlpha': 'number',
6718 'fillStyle': 'color',
6719 'strokeStyle': 'color',
6720 'lineWidth': 'number',
6721 'shadowBlur': 'number',
6722 'shadowColor': 'color',
6723 'shadowOffsetX': 'number',
6724 'shadowOffsetY': 'number',
6725 'miterLimit': 'number'
6734 //Number interpolator
6735 'compute': function(from, to, delta) {
6736 return from + (to - from) * delta;
6739 //Position interpolators
6740 'moebius': function(elem, props, delta, vector) {
6741 var v = vector.scale(-delta);
6743 var x = v.x, y = v.y;
6744 var ans = elem.startPos
6745 .getc().moebiusTransformation(v);
6746 elem.pos.setc(ans.x, ans.y);
6751 'linear': function(elem, props, delta) {
6752 var from = elem.startPos.getc(true);
6753 var to = elem.endPos.getc(true);
6754 elem.pos.setc(this.compute(from.x, to.x, delta),
6755 this.compute(from.y, to.y, delta));
6758 'polar': function(elem, props, delta) {
6759 var from = elem.startPos.getp(true);
6760 var to = elem.endPos.getp();
6761 var ans = to.interpolate(from, delta);
6762 elem.pos.setp(ans.theta, ans.rho);
6765 //Graph's Node/Edge interpolators
6766 'number': function(elem, prop, delta, getter, setter) {
6767 var from = elem[getter](prop, 'start');
6768 var to = elem[getter](prop, 'end');
6769 elem[setter](prop, this.compute(from, to, delta));
6772 'color': function(elem, prop, delta, getter, setter) {
6773 var from = $.hexToRgb(elem[getter](prop, 'start'));
6774 var to = $.hexToRgb(elem[getter](prop, 'end'));
6775 var comp = this.compute;
6776 var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6777 parseInt(comp(from[1], to[1], delta)),
6778 parseInt(comp(from[2], to[2], delta))]);
6780 elem[setter](prop, val);
6783 'array-number': function(elem, prop, delta, getter, setter) {
6784 var from = elem[getter](prop, 'start'),
6785 to = elem[getter](prop, 'end'),
6787 for(var i=0, l=from.length; i<l; i++) {
6788 var fromi = from[i], toi = to[i];
6790 for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6791 curi.push(this.compute(fromi[j], toi[j], delta));
6795 cur.push(this.compute(fromi, toi, delta));
6798 elem[setter](prop, cur);
6801 'node': function(elem, props, delta, map, getter, setter) {
6804 var len = props.length;
6805 for(var i=0; i<len; i++) {
6807 this[map[pi]](elem, pi, delta, getter, setter);
6810 for(var pi in map) {
6811 this[map[pi]](elem, pi, delta, getter, setter);
6816 'edge': function(elem, props, delta, mapKey, getter, setter) {
6817 var adjs = elem.adjacencies;
6818 for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6821 'node-property': function(elem, props, delta) {
6822 this['node'](elem, props, delta, 'map', 'getData', 'setData');
6825 'edge-property': function(elem, props, delta) {
6826 this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6829 'label-property': function(elem, props, delta) {
6830 this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6833 'node-style': function(elem, props, delta) {
6834 this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6837 'edge-style': function(elem, props, delta) {
6838 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6846 Iteratively performs an action while refreshing the state of the visualization.
6850 options - (object) An object containing some sequence options described below
6851 condition - (function) A function returning a boolean instance in order to stop iterations.
6852 step - (function) A function to execute on each step of the iteration.
6853 onComplete - (function) A function to execute when the sequence finishes.
6854 duration - (number) Duration (in milliseconds) of each step.
6858 var rg = new $jit.RGraph(options);
6861 condition: function() {
6867 onComplete: function() {
6874 sequence: function(options) {
6877 condition: $.lambda(false),
6879 onComplete: $.empty,
6883 var interval = setInterval(function() {
6884 if(options.condition()) {
6887 clearInterval(interval);
6888 options.onComplete();
6890 that.viz.refresh(true);
6891 }, options.duration);
6897 Prepare graph position and other attribute values before performing an Animation.
6898 This method is used internally by the Toolkit.
6902 <Animation>, <Graph.Plot.animate>
6905 prepare: function(modes) {
6906 var graph = this.viz.graph,
6909 'getter': 'getData',
6913 'getter': 'getData',
6917 'getter': 'getCanvasStyle',
6918 'setter': 'setCanvasStyle'
6921 'getter': 'getCanvasStyle',
6922 'setter': 'setCanvasStyle'
6928 if($.type(modes) == 'array') {
6929 for(var i=0, len=modes.length; i < len; i++) {
6930 var elems = modes[i].split(':');
6931 m[elems.shift()] = elems;
6934 for(var p in modes) {
6935 if(p == 'position') {
6936 m[modes.position] = [];
6938 m[p] = $.splat(modes[p]);
6943 graph.eachNode(function(node) {
6944 node.startPos.set(node.pos);
6945 $.each(['node-property', 'node-style'], function(p) {
6948 for(var i=0, l=prop.length; i < l; i++) {
6949 node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6953 $.each(['edge-property', 'edge-style'], function(p) {
6956 node.eachAdjacency(function(adj) {
6957 for(var i=0, l=prop.length; i < l; i++) {
6958 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6970 Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6974 opt - (object) Animation options. The object properties are described below
6975 duration - (optional) Described in <Options.Fx>.
6976 fps - (optional) Described in <Options.Fx>.
6977 hideLabels - (optional|boolean) Whether to hide labels during the animation.
6978 modes - (required|object) An object with animation modes (described below).
6982 Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6983 They are represented by an object that has as keys main categories of properties to animate and as values a list
6984 of these specific properties. The properties are described below
6986 position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6987 node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6988 edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6989 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.
6990 node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6991 edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6995 var viz = new $jit.Viz(options);
6996 //...tweak some Data, CanvasStyles or LabelData properties...
6999 'position': 'linear',
7000 'node-property': ['width', 'height'],
7001 'node-style': 'shadowColor',
7002 'label-property': 'size'
7006 //...can also be written like this...
7009 'node-property:width:height',
7010 'node-style:shadowColor',
7011 'label-property:size'],
7016 animate: function(opt, versor) {
7017 opt = $.merge(this.viz.config, opt || {});
7021 interp = this.Interpolator,
7022 animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7023 //prepare graph values
7024 var m = this.prepare(opt.modes);
7027 if(opt.hideLabels) this.labels.hideLabels(true);
7028 animation.setOptions($.merge(opt, {
7030 compute: function(delta) {
7031 graph.eachNode(function(node) {
7033 interp[p](node, m[p], delta, versor);
7036 that.plot(opt, this.$animating, delta);
7037 this.$animating = true;
7039 complete: function() {
7040 if(opt.hideLabels) that.labels.hideLabels(false);
7043 opt.onAfterCompute();
7051 Apply animation to node properties like color, width, height, dim, etc.
7055 options - Animation options. This object properties is described below
7056 elements - The Elements to be transformed. This is an object that has a properties
7060 //can also be an array of ids
7061 'id': 'id-of-node-to-transform',
7062 //properties to be modified. All properties are optional.
7064 'color': '#ccc', //some color
7065 'width': 10, //some width
7066 'height': 10, //some height
7067 'dim': 20, //some dim
7068 'lineWidth': 10 //some line width
7073 - _reposition_ Whether to recalculate positions and add a motion animation.
7074 This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7076 - _onComplete_ A method that is called when the animation completes.
7078 ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7082 var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7089 'transition': Trans.Quart.easeOut
7094 nodeFx: function(opt) {
7097 animation = this.nodeFxAnimation,
7098 options = $.merge(this.viz.config, {
7105 opt = $.merge(options, opt || {}, {
7106 onBeforeCompute: $.empty,
7107 onAfterCompute: $.empty
7109 //check if an animation is running
7110 animation.stopTimer();
7111 var props = opt.elements.properties;
7112 //set end values for nodes
7113 if(!opt.elements.id) {
7114 graph.eachNode(function(n) {
7115 for(var prop in props) {
7116 n.setData(prop, props[prop], 'end');
7120 var ids = $.splat(opt.elements.id);
7121 $.each(ids, function(id) {
7122 var n = graph.getNode(id);
7124 for(var prop in props) {
7125 n.setData(prop, props[prop], 'end');
7132 for(var prop in props) propnames.push(prop);
7133 //add node properties modes
7134 var modes = ['node-property:' + propnames.join(':')];
7135 //set new node positions
7136 if(opt.reposition) {
7137 modes.push('linear');
7141 this.animate($.merge(opt, {
7155 opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7160 var viz = new $jit.Viz(options);
7165 plot: function(opt, animating) {
7168 canvas = viz.canvas,
7171 ctx = canvas.getCtx(),
7173 opt = opt || this.viz.controller;
7174 opt.clearCanvas && canvas.clear();
7176 var root = aGraph.getNode(id);
7179 var T = !!root.visited;
7180 aGraph.eachNode(function(node) {
7181 var nodeAlpha = node.getData('alpha');
7182 node.eachAdjacency(function(adj) {
7183 var nodeTo = adj.nodeTo;
7184 if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7185 !animating && opt.onBeforePlotLine(adj);
7187 ctx.globalAlpha = min(nodeAlpha,
7188 nodeTo.getData('alpha'),
7189 adj.getData('alpha'));
7190 that.plotLine(adj, canvas, animating);
7192 !animating && opt.onAfterPlotLine(adj);
7197 !animating && opt.onBeforePlotNode(node);
7198 that.plotNode(node, canvas, animating);
7199 !animating && opt.onAfterPlotNode(node);
7201 if(!that.labelsHidden && opt.withLabels) {
7202 if(node.drawn && nodeAlpha >= 0.95) {
7203 that.labels.plotLabel(canvas, node, opt);
7205 that.labels.hideLabel(node, false);
7216 plotTree: function(node, opt, animating) {
7219 canvas = viz.canvas,
7220 config = this.config,
7221 ctx = canvas.getCtx();
7222 var nodeAlpha = node.getData('alpha');
7223 node.eachSubnode(function(elem) {
7224 if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7225 var adj = node.getAdjacency(elem.id);
7226 !animating && opt.onBeforePlotLine(adj);
7227 ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7228 that.plotLine(adj, canvas, animating);
7229 !animating && opt.onAfterPlotLine(adj);
7230 that.plotTree(elem, opt, animating);
7234 !animating && opt.onBeforePlotNode(node);
7235 this.plotNode(node, canvas, animating);
7236 !animating && opt.onAfterPlotNode(node);
7237 if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7238 this.labels.plotLabel(canvas, node, opt);
7240 this.labels.hideLabel(node, false);
7242 this.labels.hideLabel(node, true);
7249 Plots a <Graph.Node>.
7253 node - (object) A <Graph.Node>.
7254 canvas - (object) A <Canvas> element.
7257 plotNode: function(node, canvas, animating) {
7258 var f = node.getData('type'),
7259 ctxObj = this.node.CanvasStyles;
7261 var width = node.getData('lineWidth'),
7262 color = node.getData('color'),
7263 alpha = node.getData('alpha'),
7264 ctx = canvas.getCtx();
7266 ctx.lineWidth = width;
7267 ctx.fillStyle = ctx.strokeStyle = color;
7268 ctx.globalAlpha = alpha;
7270 for(var s in ctxObj) {
7271 ctx[s] = node.getCanvasStyle(s);
7274 this.nodeTypes[f].render.call(this, node, canvas, animating);
7281 Plots a <Graph.Adjacence>.
7285 adj - (object) A <Graph.Adjacence>.
7286 canvas - (object) A <Canvas> instance.
7289 plotLine: function(adj, canvas, animating) {
7290 var f = adj.getData('type'),
7291 ctxObj = this.edge.CanvasStyles;
7293 var width = adj.getData('lineWidth'),
7294 color = adj.getData('color'),
7295 ctx = canvas.getCtx();
7297 ctx.lineWidth = width;
7298 ctx.fillStyle = ctx.strokeStyle = color;
7300 for(var s in ctxObj) {
7301 ctx[s] = adj.getCanvasStyle(s);
7304 this.edgeTypes[f].render.call(this, adj, canvas, animating);
7313 * File: Graph.Label.js
7320 An interface for plotting/hiding/showing labels.
7324 This is a generic interface for plotting/hiding/showing labels.
7325 The <Graph.Label> interface is implemented in multiple ways to provide
7326 different label types.
7328 For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7329 HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7330 The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7332 All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7338 Class: Graph.Label.Native
7340 Implements labels natively, using the Canvas text API.
7342 Graph.Label.Native = new Class({
7346 Plots a label for a given node.
7350 canvas - (object) A <Canvas> instance.
7351 node - (object) A <Graph.Node>.
7352 controller - (object) A configuration object.
7357 var viz = new $jit.Viz(options);
7358 var node = viz.graph.getNode('nodeId');
7359 viz.labels.plotLabel(viz.canvas, node, viz.config);
7362 plotLabel: function(canvas, node, controller) {
7363 var ctx = canvas.getCtx();
7364 var pos = node.pos.getc(true);
7366 ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7367 ctx.textAlign = node.getLabelData('textAlign');
7368 ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7369 ctx.textBaseline = node.getLabelData('textBaseline');
7371 this.renderLabel(canvas, node, controller);
7377 Does the actual rendering of the label in the canvas. The default
7378 implementation renders the label close to the position of the node, this
7379 method should be overriden to position the labels differently.
7383 canvas - A <Canvas> instance.
7384 node - A <Graph.Node>.
7385 controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7387 renderLabel: function(canvas, node, controller) {
7388 var ctx = canvas.getCtx();
7389 var pos = node.pos.getc(true);
7390 ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7398 Class: Graph.Label.DOM
7400 Abstract Class implementing some DOM label methods.
7404 <Graph.Label.HTML> and <Graph.Label.SVG>.
7407 Graph.Label.DOM = new Class({
7408 //A flag value indicating if node labels are being displayed or not.
7409 labelsHidden: false,
7411 labelContainer: false,
7412 //Label elements hash.
7416 Method: getLabelContainer
7418 Lazy fetcher for the label container.
7422 The label container DOM element.
7427 var viz = new $jit.Viz(options);
7428 var labelContainer = viz.labels.getLabelContainer();
7429 alert(labelContainer.innerHTML);
7432 getLabelContainer: function() {
7433 return this.labelContainer ?
7434 this.labelContainer :
7435 this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7441 Lazy fetcher for the label element.
7445 id - (string) The label id (which is also a <Graph.Node> id).
7454 var viz = new $jit.Viz(options);
7455 var label = viz.labels.getLabel('someid');
7456 alert(label.innerHTML);
7460 getLabel: function(id) {
7461 return (id in this.labels && this.labels[id] != null) ?
7463 this.labels[id] = document.getElementById(id);
7469 Hides all labels (by hiding the label container).
7473 hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7477 var viz = new $jit.Viz(options);
7478 rg.labels.hideLabels(true);
7482 hideLabels: function (hide) {
7483 var container = this.getLabelContainer();
7485 container.style.display = 'none';
7487 container.style.display = '';
7488 this.labelsHidden = hide;
7494 Clears the label container.
7496 Useful when using a new visualization with the same canvas element/widget.
7500 force - (boolean) Forces deletion of all labels.
7504 var viz = new $jit.Viz(options);
7505 viz.labels.clearLabels();
7508 clearLabels: function(force) {
7509 for(var id in this.labels) {
7510 if (force || !this.viz.graph.hasNode(id)) {
7511 this.disposeLabel(id);
7512 delete this.labels[id];
7518 Method: disposeLabel
7524 id - (string) A label id (which generally is also a <Graph.Node> id).
7528 var viz = new $jit.Viz(options);
7529 viz.labels.disposeLabel('labelid');
7532 disposeLabel: function(id) {
7533 var elem = this.getLabel(id);
7534 if(elem && elem.parentNode) {
7535 elem.parentNode.removeChild(elem);
7542 Hides the corresponding <Graph.Node> label.
7546 node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7547 show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7551 var rg = new $jit.Viz(options);
7552 viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7555 hideLabel: function(node, show) {
7556 node = $.splat(node);
7557 var st = show ? "" : "none", lab, that = this;
7558 $.each(node, function(n) {
7559 var lab = that.getLabel(n.id);
7561 lab.style.display = st;
7569 Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7573 pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7574 canvas - A <Canvas> instance.
7578 A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7581 fitsInCanvas: function(pos, canvas) {
7582 var size = canvas.getSize();
7583 if(pos.x >= size.width || pos.x < 0
7584 || pos.y >= size.height || pos.y < 0) return false;
7590 Class: Graph.Label.HTML
7592 Implements HTML labels.
7596 All <Graph.Label.DOM> methods.
7599 Graph.Label.HTML = new Class({
7600 Implements: Graph.Label.DOM,
7605 Plots a label for a given node.
7609 canvas - (object) A <Canvas> instance.
7610 node - (object) A <Graph.Node>.
7611 controller - (object) A configuration object.
7616 var viz = new $jit.Viz(options);
7617 var node = viz.graph.getNode('nodeId');
7618 viz.labels.plotLabel(viz.canvas, node, viz.config);
7623 plotLabel: function(canvas, node, controller) {
7624 var id = node.id, tag = this.getLabel(id);
7626 if(!tag && !(tag = document.getElementById(id))) {
7627 tag = document.createElement('div');
7628 var container = this.getLabelContainer();
7630 tag.className = 'node';
7631 tag.style.position = 'absolute';
7632 controller.onCreateLabel(tag, node);
7633 container.appendChild(tag);
7634 this.labels[node.id] = tag;
7637 this.placeLabel(tag, node, controller);
7642 Class: Graph.Label.SVG
7644 Implements SVG labels.
7648 All <Graph.Label.DOM> methods.
7650 Graph.Label.SVG = new Class({
7651 Implements: Graph.Label.DOM,
7656 Plots a label for a given node.
7660 canvas - (object) A <Canvas> instance.
7661 node - (object) A <Graph.Node>.
7662 controller - (object) A configuration object.
7667 var viz = new $jit.Viz(options);
7668 var node = viz.graph.getNode('nodeId');
7669 viz.labels.plotLabel(viz.canvas, node, viz.config);
7674 plotLabel: function(canvas, node, controller) {
7675 var id = node.id, tag = this.getLabel(id);
7676 if(!tag && !(tag = document.getElementById(id))) {
7677 var ns = 'http://www.w3.org/2000/svg';
7678 tag = document.createElementNS(ns, 'svg:text');
7679 var tspan = document.createElementNS(ns, 'svg:tspan');
7680 tag.appendChild(tspan);
7681 var container = this.getLabelContainer();
7682 tag.setAttribute('id', id);
7683 tag.setAttribute('class', 'node');
7684 container.appendChild(tag);
7685 controller.onCreateLabel(tag, node);
7686 this.labels[node.id] = tag;
7688 this.placeLabel(tag, node, controller);
7694 Graph.Geom = new Class({
7696 initialize: function(viz) {
7698 this.config = viz.config;
7699 this.node = viz.config.Node;
7700 this.edge = viz.config.Edge;
7703 Applies a translation to the tree.
7707 pos - A <Complex> number specifying translation vector.
7708 prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7713 st.geom.translate(new Complex(300, 100), 'end');
7716 translate: function(pos, prop) {
7717 prop = $.splat(prop);
7718 this.viz.graph.eachNode(function(elem) {
7719 $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7723 Hides levels of the tree until it properly fits in canvas.
7725 setRightLevelToShow: function(node, canvas, callback) {
7726 var level = this.getRightLevelToShow(node, canvas),
7727 fx = this.viz.labels,
7734 node.eachLevel(0, this.config.levelsToShow, function(n) {
7735 var d = n._depth - node._depth;
7741 fx.hideLabel(n, false);
7753 Returns the right level to show for the current tree in order to fit in canvas.
7755 getRightLevelToShow: function(node, canvas) {
7756 var config = this.config;
7757 var level = config.levelsToShow;
7758 var constrained = config.constrained;
7759 if(!constrained) return level;
7760 while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7773 Provides methods for loading and serving JSON data.
7776 construct: function(json) {
7777 var isGraph = ($.type(json) == 'array');
7778 var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7781 (function (ans, json) {
7784 for(var i=0, ch = json.children; i<ch.length; i++) {
7785 ans.addAdjacence(json, ch[i]);
7786 arguments.callee(ans, ch[i]);
7792 (function (ans, json) {
7793 var getNode = function(id) {
7794 for(var i=0, l=json.length; i<l; i++) {
7795 if(json[i].id == id) {
7799 // The node was not defined in the JSON
7805 return ans.addNode(newNode);
7808 for(var i=0, l=json.length; i<l; i++) {
7809 ans.addNode(json[i]);
7810 var adj = json[i].adjacencies;
7812 for(var j=0, lj=adj.length; j<lj; j++) {
7813 var node = adj[j], data = {};
7814 if(typeof adj[j] != 'string') {
7815 data = $.merge(node.data, {});
7818 ans.addAdjacence(json[i], getNode(node), data);
7830 Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7832 A JSON tree or graph structure consists of nodes, each having as properties
7834 id - (string) A unique identifier for the node
7835 name - (string) A node's name
7836 data - (object) The data optional property contains a hash (i.e {})
7837 where you can store all the information you want about this node.
7839 For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7845 "id": "aUniqueIdentifier",
7846 "name": "usually a nodes name",
7848 "some key": "some value",
7849 "some other key": "some other value"
7851 "children": [ *other nodes or empty* ]
7855 JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7856 For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7858 There are two types of *Graph* structures, *simple* and *extended* graph structures.
7860 For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7861 id of the node connected to the main node.
7868 "id": "aUniqueIdentifier",
7869 "name": "usually a nodes name",
7871 "some key": "some value",
7872 "some other key": "some other value"
7874 "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7877 'other nodes go here...'
7881 For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7883 nodeTo - (string) The other node connected by this adjacency.
7884 data - (object) A data property, where we can store custom key/value information.
7891 "id": "aUniqueIdentifier",
7892 "name": "usually a nodes name",
7894 "some key": "some value",
7895 "some other key": "some other value"
7900 data: {} //put whatever you want here
7902 'other adjacencies go here...'
7905 'other nodes go here...'
7909 About the data property:
7911 As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
7912 You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
7913 have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7915 For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
7916 <Options.Node> will override the general value for that option with that particular value. For this to work
7917 however, you do have to set *overridable = true* in <Options.Node>.
7919 The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
7920 if <Options.Edge> has *overridable = true*.
7922 When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
7923 since this is the value which will be taken into account when creating the layout.
7924 The same thing goes for the *$color* parameter.
7926 In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
7927 *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
7928 canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
7929 to the *shadowBlur* property.
7931 These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
7932 by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7934 Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
7935 information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7937 loadJSON Parameters:
7939 json - A JSON Tree or Graph structure.
7940 i - For Graph structures only. Sets the indexed node as root for the visualization.
7943 loadJSON: function(json, i) {
7945 //if they're canvas labels erase them.
7946 if(this.labels && this.labels.clearLabels) {
7947 this.labels.clearLabels(true);
7949 this.graph = this.construct(json);
7950 if($.type(json) != 'array'){
7951 this.root = json.id;
7953 this.root = json[i? i : 0].id;
7960 Returns a JSON tree/graph structure from the visualization's <Graph>.
7961 See <Loader.loadJSON> for the graph formats available.
7969 type - (string) Default's "tree". The type of the JSON structure to be returned.
7970 Possible options are "tree" or "graph".
7972 toJSON: function(type) {
7973 type = type || "tree";
7974 if(type == 'tree') {
7976 var rootNode = this.graph.getNode(this.root);
7977 var ans = (function recTree(node) {
7980 ans.name = node.name;
7981 ans.data = node.data;
7983 node.eachSubnode(function(n) {
7984 ch.push(recTree(n));
7992 var T = !!this.graph.getNode(this.root).visited;
7993 this.graph.eachNode(function(node) {
7995 ansNode.id = node.id;
7996 ansNode.name = node.name;
7997 ansNode.data = node.data;
7999 node.eachAdjacency(function(adj) {
8000 var nodeTo = adj.nodeTo;
8001 if(!!nodeTo.visited === T) {
8003 ansAdj.nodeTo = nodeTo.id;
8004 ansAdj.data = adj.data;
8008 ansNode.adjacencies = adjs;
8022 * Implements base Tree and Graph layouts.
8026 * Implements base Tree and Graph layouts like Radial, Tree, etc.
8033 * Parent object for common layouts.
8036 var Layouts = $jit.Layouts = {};
8039 //Some util shared layout functions are defined here.
8043 compute: function(graph, prop, opt) {
8044 this.initializeLabel(opt);
8045 var label = this.label, style = label.style;
8046 graph.eachNode(function(n) {
8047 var autoWidth = n.getData('autoWidth'),
8048 autoHeight = n.getData('autoHeight');
8049 if(autoWidth || autoHeight) {
8050 //delete dimensions since these are
8051 //going to be overridden now.
8052 delete n.data.$width;
8053 delete n.data.$height;
8056 var width = n.getData('width'),
8057 height = n.getData('height');
8058 //reset label dimensions
8059 style.width = autoWidth? 'auto' : width + 'px';
8060 style.height = autoHeight? 'auto' : height + 'px';
8062 //TODO(nico) should let the user choose what to insert here.
8063 label.innerHTML = n.name;
8065 var offsetWidth = label.offsetWidth,
8066 offsetHeight = label.offsetHeight;
8067 var type = n.getData('type');
8068 if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8069 n.setData('width', offsetWidth);
8070 n.setData('height', offsetHeight);
8072 var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8073 n.setData('width', dim);
8074 n.setData('height', dim);
8075 n.setData('dim', dim);
8081 initializeLabel: function(opt) {
8083 this.label = document.createElement('div');
8084 document.body.appendChild(this.label);
8086 this.setLabelStyles(opt);
8089 setLabelStyles: function(opt) {
8090 $.extend(this.label.style, {
8091 'visibility': 'hidden',
8092 'position': 'absolute',
8096 this.label.className = 'jit-autoadjust-label';
8102 * Class: Layouts.Tree
8104 * Implements a Tree Layout.
8112 * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8115 Layouts.Tree = (function() {
8117 var slice = Array.prototype.slice;
8120 Calculates the max width and height nodes for a tree level
8122 function getBoundaries(graph, config, level, orn, prop) {
8123 var dim = config.Node;
8124 var multitree = config.multitree;
8125 if (dim.overridable) {
8127 graph.eachNode(function(n) {
8128 if (n._depth == level
8129 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8130 var dw = n.getData('width', prop);
8131 var dh = n.getData('height', prop);
8132 w = (w < dw) ? dw : w;
8133 h = (h < dh) ? dh : h;
8137 'width' : w < 0 ? dim.width : w,
8138 'height' : h < 0 ? dim.height : h
8146 function movetree(node, prop, val, orn) {
8147 var p = (orn == "left" || orn == "right") ? "y" : "x";
8148 node.getPos(prop)[p] += val;
8152 function moveextent(extent, val) {
8154 $.each(extent, function(elem) {
8155 elem = slice.call(elem);
8164 function merge(ps, qs) {
8169 var p = ps.shift(), q = qs.shift();
8170 return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8174 function mergelist(ls, def) {
8179 return mergelist(ls, merge(ps, def));
8183 function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8184 if (ext1.length <= i || ext2.length <= i)
8187 var p = ext1[i][1], q = ext2[i][0];
8188 return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8189 + subtreeOffset, p - q + siblingOffset);
8193 function fitlistl(es, subtreeOffset, siblingOffset) {
8194 function $fitlistl(acc, es, i) {
8197 var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8198 return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8201 return $fitlistl( [], es, 0);
8205 function fitlistr(es, subtreeOffset, siblingOffset) {
8206 function $fitlistr(acc, es, i) {
8209 var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8210 return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8213 es = slice.call(es);
8214 var ans = $fitlistr( [], es.reverse(), 0);
8215 return ans.reverse();
8219 function fitlist(es, subtreeOffset, siblingOffset, align) {
8220 var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8221 subtreeOffset, siblingOffset);
8223 if (align == "left")
8225 else if (align == "right")
8228 for ( var i = 0, ans = []; i < esl.length; i++) {
8229 ans[i] = (esl[i] + esr[i]) / 2;
8235 function design(graph, node, prop, config, orn) {
8236 var multitree = config.multitree;
8237 var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8238 var ind = +(orn == "left" || orn == "right");
8239 var p = auxp[ind], notp = auxp[1 - ind];
8241 var cnode = config.Node;
8242 var s = auxs[ind], nots = auxs[1 - ind];
8244 var siblingOffset = config.siblingOffset;
8245 var subtreeOffset = config.subtreeOffset;
8246 var align = config.align;
8248 function $design(node, maxsize, acum) {
8249 var sval = node.getData(s, prop);
8250 var notsval = maxsize
8251 || (node.getData(nots, prop));
8253 var trees = [], extents = [], chmaxsize = false;
8254 var chacum = notsval + config.levelDistance;
8255 node.eachSubnode(function(n) {
8257 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8260 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8262 var s = $design(n, chmaxsize[nots], acum + chacum);
8264 extents.push(s.extent);
8267 var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8268 for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8269 movetree(trees[i], prop, positions[i], orn);
8270 pextents.push(moveextent(extents[i], positions[i]));
8272 var resultextent = [ [ -sval / 2, sval / 2 ] ]
8273 .concat(mergelist(pextents));
8274 node.getPos(prop)[p] = 0;
8276 if (orn == "top" || orn == "left") {
8277 node.getPos(prop)[notp] = acum;
8279 node.getPos(prop)[notp] = -acum;
8284 extent : resultextent
8288 $design(node, false, 0);
8296 Computes nodes' positions.
8299 compute : function(property, computeLevels) {
8300 var prop = property || 'start';
8301 var node = this.graph.getNode(this.root);
8307 NodeDim.compute(this.graph, prop, this.config);
8308 if (!!computeLevels || !("_depth" in node)) {
8309 this.graph.computeLevels(this.root, 0, "ignore");
8312 this.computePositions(node, prop);
8315 computePositions : function(node, prop) {
8316 var config = this.config;
8317 var multitree = config.multitree;
8318 var align = config.align;
8319 var indent = align !== 'center' && config.indent;
8320 var orn = config.orientation;
8321 var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8323 $.each(orns, function(orn) {
8325 design(that.graph, node, prop, that.config, orn, prop);
8326 var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8328 (function red(node) {
8329 node.eachSubnode(function(n) {
8331 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8333 n.getPos(prop)[i] += node.getPos(prop)[i];
8335 n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8348 * File: Spacetree.js
8354 A Tree layout with advanced contraction and expansion animations.
8358 SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8359 <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8361 Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8365 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.
8369 All <Loader> methods
8371 Constructor Options:
8373 Inherits options from
8376 - <Options.Controller>
8383 - <Options.NodeStyles>
8384 - <Options.Navigation>
8386 Additionally, there are other parameters and some default values changed
8388 constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8389 levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8390 levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8391 Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8392 offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8393 offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8394 duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8396 Instance Properties:
8398 canvas - Access a <Canvas> instance.
8399 graph - Access a <Graph> instance.
8400 op - Access a <ST.Op> instance.
8401 fx - Access a <ST.Plot> instance.
8402 labels - Access a <ST.Label> interface implementation.
8406 $jit.ST= (function() {
8407 // Define some private methods first...
8409 var nodesInPath = [];
8410 // Nodes to contract
8411 function getNodesToHide(node) {
8412 node = node || this.clickedNode;
8413 if(!this.config.constrained) {
8416 var Geom = this.geom;
8417 var graph = this.graph;
8418 var canvas = this.canvas;
8419 var level = node._depth, nodeArray = [];
8420 graph.eachNode(function(n) {
8421 if(n.exist && !n.selected) {
8422 if(n.isDescendantOf(node.id)) {
8423 if(n._depth <= level) nodeArray.push(n);
8429 var leafLevel = Geom.getRightLevelToShow(node, canvas);
8430 node.eachLevel(leafLevel, leafLevel, function(n) {
8431 if(n.exist && !n.selected) nodeArray.push(n);
8434 for (var i = 0; i < nodesInPath.length; i++) {
8435 var n = this.graph.getNode(nodesInPath[i]);
8436 if(!n.isDescendantOf(node.id)) {
8443 function getNodesToShow(node) {
8444 var nodeArray = [], config = this.config;
8445 node = node || this.clickedNode;
8446 this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8447 if(config.multitree && !('$orn' in n.data)
8448 && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8450 } else if(n.drawn && !n.anySubnode("drawn")) {
8456 // Now define the actual class.
8459 Implements: [Loader, Extras, Layouts.Tree],
8461 initialize: function(controller) {
8476 this.controller = this.config = $.merge(
8477 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8478 "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8480 var canvasConfig = this.config;
8481 if(canvasConfig.useCanvas) {
8482 this.canvas = canvasConfig.useCanvas;
8483 this.config.labelContainer = this.canvas.id + '-label';
8485 if(canvasConfig.background) {
8486 canvasConfig.background = $.merge({
8488 colorStop1: this.config.colorStop1,
8489 colorStop2: this.config.colorStop2
8490 }, canvasConfig.background);
8492 this.canvas = new Canvas(this, canvasConfig);
8493 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8496 this.graphOptions = {
8499 this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8500 this.labels = new $ST.Label[canvasConfig.Label.type](this);
8501 this.fx = new $ST.Plot(this, $ST);
8502 this.op = new $ST.Op(this);
8503 this.group = new $ST.Group(this);
8504 this.geom = new $ST.Geom(this);
8505 this.clickedNode= null;
8506 // initialize extras
8507 this.initializeExtras();
8513 Plots the <ST>. This is a shortcut to *fx.plot*.
8516 plot: function() { this.fx.plot(this.controller); },
8520 Method: switchPosition
8522 Switches the tree orientation.
8526 pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8527 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.
8528 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8533 st.switchPosition("right", "animate", {
8534 onComplete: function() {
8535 alert('completed!');
8540 switchPosition: function(pos, method, onComplete) {
8541 var Geom = this.geom, Plot = this.fx, that = this;
8545 onComplete: function() {
8546 Geom.switchOrientation(pos);
8547 that.compute('end', false);
8549 if(method == 'animate') {
8550 that.onClick(that.clickedNode.id, onComplete);
8551 } else if(method == 'replot') {
8552 that.select(that.clickedNode.id, onComplete);
8560 Method: switchAlignment
8562 Switches the tree alignment.
8566 align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8567 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.
8568 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8573 st.switchAlignment("right", "animate", {
8574 onComplete: function() {
8575 alert('completed!');
8580 switchAlignment: function(align, method, onComplete) {
8581 this.config.align = align;
8582 if(method == 'animate') {
8583 this.select(this.clickedNode.id, onComplete);
8584 } else if(method == 'replot') {
8585 this.onClick(this.clickedNode.id, onComplete);
8590 Method: addNodeInPath
8592 Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8597 id - (string) A <Graph.Node> id.
8602 st.addNodeInPath("nodeId");
8605 addNodeInPath: function(id) {
8606 nodesInPath.push(id);
8607 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8611 Method: clearNodesInPath
8613 Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8622 st.clearNodesInPath();
8625 clearNodesInPath: function(id) {
8626 nodesInPath.length = 0;
8627 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8633 Computes positions and plots the tree.
8636 refresh: function() {
8638 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8641 reposition: function() {
8642 this.graph.computeLevels(this.root, 0, "ignore");
8643 this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8644 this.graph.eachNode(function(n) {
8645 if(n.exist) n.drawn = true;
8647 this.compute('end');
8650 requestNodes: function(node, onComplete) {
8651 var handler = $.merge(this.controller, onComplete),
8652 lev = this.config.levelsToShow;
8653 if(handler.request) {
8654 var leaves = [], d = node._depth;
8655 node.eachLevel(0, lev, function(n) {
8659 n._level = lev - (n._depth - d);
8662 this.group.requestNodes(leaves, handler);
8665 handler.onComplete();
8668 contract: function(onComplete, switched) {
8669 var orn = this.config.orientation;
8670 var Geom = this.geom, Group = this.group;
8671 if(switched) Geom.switchOrientation(switched);
8672 var nodes = getNodesToHide.call(this);
8673 if(switched) Geom.switchOrientation(orn);
8674 Group.contract(nodes, $.merge(this.controller, onComplete));
8677 move: function(node, onComplete) {
8678 this.compute('end', false);
8679 var move = onComplete.Move, offset = {
8684 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8686 this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8689 expand: function (node, onComplete) {
8690 var nodeArray = getNodesToShow.call(this, node);
8691 this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8694 selectPath: function(node) {
8696 this.graph.eachNode(function(n) { n.selected = false; });
8697 function path(node) {
8698 if(node == null || node.selected) return;
8699 node.selected = true;
8700 $.each(that.group.getSiblings([node])[node.id],
8705 var parents = node.getParents();
8706 parents = (parents.length > 0)? parents[0] : null;
8709 for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8710 path(this.graph.getNode(ns[i]));
8717 Switches the current root node. Changes the topology of the Tree.
8720 id - (string) The id of the node to be set as root.
8721 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.
8722 onComplete - (optional|object) An action to perform after the animation (if any).
8727 st.setRoot('nodeId', 'animate', {
8728 onComplete: function() {
8734 setRoot: function(id, method, onComplete) {
8735 if(this.busy) return;
8737 var that = this, canvas = this.canvas;
8738 var rootNode = this.graph.getNode(this.root);
8739 var clickedNode = this.graph.getNode(id);
8740 function $setRoot() {
8741 if(this.config.multitree && clickedNode.data.$orn) {
8742 var orn = clickedNode.data.$orn;
8749 rootNode.data.$orn = opp;
8750 (function tag(rootNode) {
8751 rootNode.eachSubnode(function(n) {
8758 delete clickedNode.data.$orn;
8761 this.clickedNode = clickedNode;
8762 this.graph.computeLevels(this.root, 0, "ignore");
8763 this.geom.setRightLevelToShow(clickedNode, canvas, {
8765 onShow: function(node) {
8768 node.setData('alpha', 1, 'end');
8769 node.setData('alpha', 0);
8770 node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8774 this.compute('end');
8777 modes: ['linear', 'node-property:alpha'],
8778 onComplete: function() {
8781 onComplete: function() {
8782 onComplete && onComplete.onComplete();
8789 // delete previous orientations (if any)
8790 delete rootNode.data.$orns;
8792 if(method == 'animate') {
8793 $setRoot.call(this);
8794 that.selectPath(clickedNode);
8795 } else if(method == 'replot') {
8796 $setRoot.call(this);
8797 this.select(this.root);
8807 subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8808 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.
8809 onComplete - (optional|object) An action to perform after the animation (if any).
8814 st.addSubtree(json, 'animate', {
8815 onComplete: function() {
8821 addSubtree: function(subtree, method, onComplete) {
8822 if(method == 'replot') {
8823 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8824 } else if (method == 'animate') {
8825 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8830 Method: removeSubtree
8835 id - (string) The _id_ of the subtree to be removed.
8836 removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8837 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.
8838 onComplete - (optional|object) An action to perform after the animation (if any).
8843 st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8844 onComplete: function() {
8851 removeSubtree: function(id, removeRoot, method, onComplete) {
8852 var node = this.graph.getNode(id), subids = [];
8853 node.eachLevel(+!removeRoot, false, function(n) {
8856 if(method == 'replot') {
8857 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8858 } else if (method == 'animate') {
8859 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8866 Selects a node in the <ST> without performing an animation. Useful when selecting
8867 nodes which are currently hidden or deep inside the tree.
8870 id - (string) The id of the node to select.
8871 onComplete - (optional|object) an onComplete callback.
8875 st.select('mynodeid', {
8876 onComplete: function() {
8882 select: function(id, onComplete) {
8883 var group = this.group, geom = this.geom;
8884 var node= this.graph.getNode(id), canvas = this.canvas;
8885 var root = this.graph.getNode(this.root);
8886 var complete = $.merge(this.controller, onComplete);
8889 complete.onBeforeCompute(node);
8890 this.selectPath(node);
8891 this.clickedNode= node;
8892 this.requestNodes(node, {
8893 onComplete: function(){
8894 group.hide(group.prepare(getNodesToHide.call(that)), complete);
8895 geom.setRightLevelToShow(node, canvas);
8896 that.compute("current");
8897 that.graph.eachNode(function(n) {
8898 var pos = n.pos.getc(true);
8899 n.startPos.setc(pos.x, pos.y);
8900 n.endPos.setc(pos.x, pos.y);
8903 var offset = { x: complete.offsetX, y: complete.offsetY };
8904 that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8905 group.show(getNodesToShow.call(that));
8907 complete.onAfterCompute(that.clickedNode);
8908 complete.onComplete();
8916 Animates the <ST> to center the node specified by *id*.
8920 id - (string) A node id.
8921 options - (optional|object) A group of options and callbacks described below.
8922 onComplete - (object) An object callback called when the animation finishes.
8923 Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8928 st.onClick('mynodeid', {
8934 onComplete: function() {
8941 onClick: function (id, options) {
8942 var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8943 var innerController = {
8946 offsetX: config.offsetX || 0,
8947 offsetY: config.offsetY || 0
8949 setRightLevelToShowConfig: false,
8950 onBeforeRequest: $.empty,
8951 onBeforeContract: $.empty,
8952 onBeforeMove: $.empty,
8953 onBeforeExpand: $.empty
8955 var complete = $.merge(this.controller, innerController, options);
8959 var node = this.graph.getNode(id);
8960 this.selectPath(node, this.clickedNode);
8961 this.clickedNode = node;
8962 complete.onBeforeCompute(node);
8963 complete.onBeforeRequest(node);
8964 this.requestNodes(node, {
8965 onComplete: function() {
8966 complete.onBeforeContract(node);
8968 onComplete: function() {
8969 Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8970 complete.onBeforeMove(node);
8972 Move: complete.Move,
8973 onComplete: function() {
8974 complete.onBeforeExpand(node);
8976 onComplete: function() {
8978 complete.onAfterCompute(id);
8979 complete.onComplete();
8994 $jit.ST.$extend = true;
8999 Custom extension of <Graph.Op>.
9003 All <Graph.Op> methods
9010 $jit.ST.Op = new Class({
9012 Implements: Graph.Op
9018 Performs operations on group of nodes.
9021 $jit.ST.Group = new Class({
9023 initialize: function(viz) {
9025 this.canvas = viz.canvas;
9026 this.config = viz.config;
9027 this.animation = new Animation;
9033 Calls the request method on the controller to request a subtree for each node.
9035 requestNodes: function(nodes, controller) {
9036 var counter = 0, len = nodes.length, nodeSelected = {};
9037 var complete = function() { controller.onComplete(); };
9039 if(len == 0) complete();
9040 for(var i=0; i<len; i++) {
9041 nodeSelected[nodes[i].id] = nodes[i];
9042 controller.request(nodes[i].id, nodes[i]._level, {
9043 onComplete: function(nodeId, data) {
9044 if(data && data.children) {
9046 viz.op.sum(data, { type: 'nothing' });
9048 if(++counter == len) {
9049 viz.graph.computeLevels(viz.root, 0);
9059 Collapses group of nodes.
9061 contract: function(nodes, controller) {
9065 nodes = this.prepare(nodes);
9066 this.animation.setOptions($.merge(controller, {
9068 compute: function(delta) {
9069 if(delta == 1) delta = 0.99;
9070 that.plotStep(1 - delta, controller, this.$animating);
9071 this.$animating = 'contract';
9074 complete: function() {
9075 that.hide(nodes, controller);
9080 hide: function(nodes, controller) {
9082 for(var i=0; i<nodes.length; i++) {
9083 // TODO nodes are requested on demand, but not
9084 // deleted when hidden. Would that be a good feature?
9085 // Currently that feature is buggy, so I'll turn it off
9086 // Actually this feature is buggy because trimming should take
9087 // place onAfterCompute and not right after collapsing nodes.
9088 if (true || !controller || !controller.request) {
9089 nodes[i].eachLevel(1, false, function(elem){
9099 nodes[i].eachLevel(1, false, function(n) {
9102 viz.op.removeNode(ids, { 'type': 'nothing' });
9103 viz.labels.clearLabels();
9106 controller.onComplete();
9111 Expands group of nodes.
9113 expand: function(nodes, controller) {
9116 this.animation.setOptions($.merge(controller, {
9118 compute: function(delta) {
9119 that.plotStep(delta, controller, this.$animating);
9120 this.$animating = 'expand';
9123 complete: function() {
9124 that.plotStep(undefined, controller, false);
9125 controller.onComplete();
9131 show: function(nodes) {
9132 var config = this.config;
9133 this.prepare(nodes);
9134 $.each(nodes, function(n) {
9135 // check for root nodes if multitree
9136 if(config.multitree && !('$orn' in n.data)) {
9137 delete n.data.$orns;
9139 n.eachSubnode(function(ch) {
9140 if(('$orn' in ch.data)
9141 && orns.indexOf(ch.data.$orn) < 0
9142 && ch.exist && !ch.drawn) {
9143 orns += ch.data.$orn + ' ';
9146 n.data.$orns = orns;
9148 n.eachLevel(0, config.levelsToShow, function(n) {
9149 if(n.exist) n.drawn = true;
9154 prepare: function(nodes) {
9155 this.nodes = this.getNodesWithChildren(nodes);
9160 Filters an array of nodes leaving only nodes with children.
9162 getNodesWithChildren: function(nodes) {
9163 var ans = [], config = this.config, root = this.viz.root;
9164 nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9165 for(var i=0; i<nodes.length; i++) {
9166 if(nodes[i].anySubnode("exist")) {
9167 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9168 if(!config.multitree || '$orn' in nodes[j].data) {
9169 desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9172 if(!desc) ans.push(nodes[i]);
9178 plotStep: function(delta, controller, animating) {
9180 config = this.config,
9181 canvas = viz.canvas,
9182 ctx = canvas.getCtx(),
9185 // hide nodes that are meant to be collapsed/expanded
9187 for(i=0; i<nodes.length; i++) {
9190 var root = config.multitree && !('$orn' in node.data);
9191 var orns = root && node.data.$orns;
9192 node.eachSubgraph(function(n) {
9193 // TODO(nico): Cleanup
9194 // special check for root node subnodes when
9195 // multitree is checked.
9196 if(root && orns && orns.indexOf(n.data.$orn) > 0
9199 nds[node.id].push(n);
9200 } else if((!root || !orns) && n.drawn) {
9202 nds[node.id].push(n);
9207 // plot the whole (non-scaled) tree
9208 if(nodes.length > 0) viz.fx.plot();
9209 // show nodes that were previously hidden
9211 $.each(nds[i], function(n) { n.drawn = true; });
9213 // plot each scaled subtree
9214 for(i=0; i<nodes.length; i++) {
9217 viz.fx.plotSubtree(node, controller, delta, animating);
9222 getSiblings: function(nodes) {
9224 $.each(nodes, function(n) {
9225 var par = n.getParents();
9226 if (par.length == 0) {
9227 siblings[n.id] = [n];
9230 par[0].eachSubnode(function(sn) {
9233 siblings[n.id] = ans;
9243 Performs low level geometrical computations.
9247 This instance can be accessed with the _geom_ parameter of the st instance created.
9252 var st = new ST(canvas, config);
9253 st.geom.translate //or can also call any other <ST.Geom> method
9258 $jit.ST.Geom = new Class({
9259 Implements: Graph.Geom,
9261 Changes the tree current orientation to the one specified.
9263 You should usually use <ST.switchPosition> instead.
9265 switchOrientation: function(orn) {
9266 this.config.orientation = orn;
9270 Makes a value dispatch according to the current layout
9271 Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9273 dispatch: function() {
9274 // TODO(nico) should store Array.prototype.slice.call somewhere.
9275 var args = Array.prototype.slice.call(arguments);
9276 var s = args.shift(), len = args.length;
9277 var val = function(a) { return typeof a == 'function'? a() : a; };
9279 return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9280 } else if(len == 4) {
9282 case "top": return val(args[0]);
9283 case "right": return val(args[1]);
9284 case "bottom": return val(args[2]);
9285 case "left": return val(args[3]);
9292 Returns label height or with, depending on the tree current orientation.
9294 getSize: function(n, invert) {
9295 var data = n.data, config = this.config;
9296 var siblingOffset = config.siblingOffset;
9297 var s = (config.multitree
9299 && data.$orn) || config.orientation;
9300 var w = n.getData('width') + siblingOffset;
9301 var h = n.getData('height') + siblingOffset;
9303 return this.dispatch(s, h, w);
9305 return this.dispatch(s, w, h);
9309 Calculates a subtree base size. This is an utility function used by _getBaseSize_
9311 getTreeBaseSize: function(node, level, leaf) {
9312 var size = this.getSize(node, true), baseHeight = 0, that = this;
9313 if(leaf(level, node)) return size;
9314 if(level === 0) return 0;
9315 node.eachSubnode(function(elem) {
9316 baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9318 return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9325 Returns a Complex instance with the begin or end position of the edge to be plotted.
9329 node - A <Graph.Node> that is connected to this edge.
9330 type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9334 A <Complex> number specifying the begin or end position.
9336 getEdge: function(node, type, s) {
9337 var $C = function(a, b) {
9339 return node.pos.add(new Complex(a, b));
9342 var dim = this.node;
9343 var w = node.getData('width');
9344 var h = node.getData('height');
9346 if(type == 'begin') {
9347 if(dim.align == "center") {
9348 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9349 $C(0, -h/2),$C(w/2, 0));
9350 } else if(dim.align == "left") {
9351 return this.dispatch(s, $C(0, h), $C(0, 0),
9352 $C(0, 0), $C(w, 0));
9353 } else if(dim.align == "right") {
9354 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9355 $C(0, -h),$C(0, 0));
9356 } else throw "align: not implemented";
9359 } else if(type == 'end') {
9360 if(dim.align == "center") {
9361 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9362 $C(0, h/2), $C(-w/2, 0));
9363 } else if(dim.align == "left") {
9364 return this.dispatch(s, $C(0, 0), $C(w, 0),
9365 $C(0, h), $C(0, 0));
9366 } else if(dim.align == "right") {
9367 return this.dispatch(s, $C(0, -h),$C(0, 0),
9368 $C(0, 0), $C(-w, 0));
9369 } else throw "align: not implemented";
9374 Adjusts the tree position due to canvas scaling or translation.
9376 getScaledTreePosition: function(node, scale) {
9377 var dim = this.node;
9378 var w = node.getData('width');
9379 var h = node.getData('height');
9380 var s = (this.config.multitree
9381 && ('$orn' in node.data)
9382 && node.data.$orn) || this.config.orientation;
9384 var $C = function(a, b) {
9386 return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9389 if(dim.align == "left") {
9390 return this.dispatch(s, $C(0, h), $C(0, 0),
9391 $C(0, 0), $C(w, 0));
9392 } else if(dim.align == "center") {
9393 return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9394 $C(0, -h / 2),$C(w / 2, 0));
9395 } else if(dim.align == "right") {
9396 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9397 $C(0, -h),$C(0, 0));
9398 } else throw "align: not implemented";
9404 Returns a Boolean if the current subtree fits in canvas.
9408 node - A <Graph.Node> which is the current root of the subtree.
9409 canvas - The <Canvas> object.
9410 level - The depth of the subtree to be considered.
9412 treeFitsInCanvas: function(node, canvas, level) {
9413 var csize = canvas.getSize();
9414 var s = (this.config.multitree
9415 && ('$orn' in node.data)
9416 && node.data.$orn) || this.config.orientation;
9418 var size = this.dispatch(s, csize.width, csize.height);
9419 var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9420 return level === 0 || !node.anySubnode();
9422 return (baseSize < size);
9429 Custom extension of <Graph.Plot>.
9433 All <Graph.Plot> methods
9440 $jit.ST.Plot = new Class({
9442 Implements: Graph.Plot,
9445 Plots a subtree from the spacetree.
9447 plotSubtree: function(node, opt, scale, animating) {
9448 var viz = this.viz, canvas = viz.canvas, config = viz.config;
9449 scale = Math.min(Math.max(0.001, scale), 1);
9452 var ctx = canvas.getCtx();
9453 var diff = viz.geom.getScaledTreePosition(node, scale);
9454 ctx.translate(diff.x, diff.y);
9455 ctx.scale(scale, scale);
9457 this.plotTree(node, $.merge(opt, {
9459 'hideLabels': !!scale,
9460 'plotSubtree': function(n, ch) {
9461 var root = config.multitree && !('$orn' in node.data);
9462 var orns = root && node.getData('orns');
9463 return !root || orns.indexOf(elem.getData('orn')) > -1;
9466 if(scale >= 0) node.drawn = true;
9470 Method: getAlignedPos
9472 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9476 pos - (object) A <Graph.Node> position.
9477 width - (number) The width of the node.
9478 height - (number) The height of the node.
9481 getAlignedPos: function(pos, width, height) {
9482 var nconfig = this.node;
9484 if(nconfig.align == "center") {
9486 x: pos.x - width / 2,
9487 y: pos.y - height / 2
9489 } else if (nconfig.align == "left") {
9490 orn = this.config.orientation;
9491 if(orn == "bottom" || orn == "top") {
9493 x: pos.x - width / 2,
9499 y: pos.y - height / 2
9502 } else if(nconfig.align == "right") {
9503 orn = this.config.orientation;
9504 if(orn == "bottom" || orn == "top") {
9506 x: pos.x - width / 2,
9512 y: pos.y - height / 2
9515 } else throw "align: not implemented";
9520 getOrientation: function(adj) {
9521 var config = this.config;
9522 var orn = config.orientation;
9524 if(config.multitree) {
9525 var nodeFrom = adj.nodeFrom;
9526 var nodeTo = adj.nodeTo;
9527 orn = (('$orn' in nodeFrom.data)
9528 && nodeFrom.data.$orn)
9529 || (('$orn' in nodeTo.data)
9530 && nodeTo.data.$orn);
9540 Custom extension of <Graph.Label>.
9541 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9545 All <Graph.Label> methods and subclasses.
9549 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9556 Custom extension of <Graph.Label.Native>.
9560 All <Graph.Label.Native> methods
9564 <Graph.Label.Native>
9566 $jit.ST.Label.Native = new Class({
9567 Implements: Graph.Label.Native,
9569 renderLabel: function(canvas, node, controller) {
9570 var ctx = canvas.getCtx();
9571 var coord = node.pos.getc(true);
9572 ctx.fillText(node.name, coord.x, coord.y);
9576 $jit.ST.Label.DOM = new Class({
9577 Implements: Graph.Label.DOM,
9582 Overrides abstract method placeLabel in <Graph.Plot>.
9586 tag - A DOM label element.
9587 node - A <Graph.Node>.
9588 controller - A configuration/controller object passed to the visualization.
9591 placeLabel: function(tag, node, controller) {
9592 var pos = node.pos.getc(true),
9593 config = this.viz.config,
9595 canvas = this.viz.canvas,
9596 w = node.getData('width'),
9597 h = node.getData('height'),
9598 radius = canvas.getSize(),
9601 var ox = canvas.translateOffsetX,
9602 oy = canvas.translateOffsetY,
9603 sx = canvas.scaleOffsetX,
9604 sy = canvas.scaleOffsetY,
9605 posx = pos.x * sx + ox,
9606 posy = pos.y * sy + oy;
9608 if(dim.align == "center") {
9610 x: Math.round(posx - w / 2 + radius.width/2),
9611 y: Math.round(posy - h / 2 + radius.height/2)
9613 } else if (dim.align == "left") {
9614 orn = config.orientation;
9615 if(orn == "bottom" || orn == "top") {
9617 x: Math.round(posx - w / 2 + radius.width/2),
9618 y: Math.round(posy + radius.height/2)
9622 x: Math.round(posx + radius.width/2),
9623 y: Math.round(posy - h / 2 + radius.height/2)
9626 } else if(dim.align == "right") {
9627 orn = config.orientation;
9628 if(orn == "bottom" || orn == "top") {
9630 x: Math.round(posx - w / 2 + radius.width/2),
9631 y: Math.round(posy - h + radius.height/2)
9635 x: Math.round(posx - w + radius.width/2),
9636 y: Math.round(posy - h / 2 + radius.height/2)
9639 } else throw "align: not implemented";
9641 var style = tag.style;
9642 style.left = labelPos.x + 'px';
9643 style.top = labelPos.y + 'px';
9644 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9645 controller.onPlaceLabel(tag, node);
9652 Custom extension of <Graph.Label.SVG>.
9656 All <Graph.Label.SVG> methods
9662 $jit.ST.Label.SVG = new Class({
9663 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9665 initialize: function(viz) {
9673 Custom extension of <Graph.Label.HTML>.
9677 All <Graph.Label.HTML> methods.
9684 $jit.ST.Label.HTML = new Class({
9685 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9687 initialize: function(viz) {
9694 Class: ST.Plot.NodeTypes
9696 This class contains a list of <Graph.Node> built-in types.
9697 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9699 You can add your custom node types, customizing your visualization to the extreme.
9704 ST.Plot.NodeTypes.implement({
9706 'render': function(node, canvas) {
9707 //print your custom node to canvas
9710 'contains': function(node, pos) {
9711 //return true if pos is inside the node or false otherwise
9718 $jit.ST.Plot.NodeTypes = new Class({
9721 'contains': $.lambda(false)
9724 'render': function(node, canvas) {
9725 var dim = node.getData('dim'),
9726 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9728 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9730 'contains': function(node, pos) {
9731 var dim = node.getData('dim'),
9732 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9734 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9738 'render': function(node, canvas) {
9739 var dim = node.getData('dim'),
9741 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9742 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9744 'contains': function(node, pos) {
9745 var dim = node.getData('dim'),
9746 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9748 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9752 'render': function(node, canvas) {
9753 var width = node.getData('width'),
9754 height = node.getData('height'),
9755 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9756 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9758 'contains': function(node, pos) {
9759 var width = node.getData('width'),
9760 height = node.getData('height'),
9761 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9762 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9766 'render': function(node, canvas) {
9767 var width = node.getData('width'),
9768 height = node.getData('height'),
9769 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9770 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9772 'contains': function(node, pos) {
9773 var width = node.getData('width'),
9774 height = node.getData('height'),
9775 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9776 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9782 Class: ST.Plot.EdgeTypes
9784 This class contains a list of <Graph.Adjacence> built-in types.
9785 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9787 You can add your custom edge types, customizing your visualization to the extreme.
9792 ST.Plot.EdgeTypes.implement({
9794 'render': function(adj, canvas) {
9795 //print your custom edge to canvas
9798 'contains': function(adj, pos) {
9799 //return true if pos is inside the arc or false otherwise
9806 $jit.ST.Plot.EdgeTypes = new Class({
9809 'render': function(adj, canvas) {
9810 var orn = this.getOrientation(adj),
9811 nodeFrom = adj.nodeFrom,
9812 nodeTo = adj.nodeTo,
9813 rel = nodeFrom._depth < nodeTo._depth,
9814 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9815 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9816 this.edgeHelper.line.render(from, to, canvas);
9818 'contains': function(adj, pos) {
9819 var orn = this.getOrientation(adj),
9820 nodeFrom = adj.nodeFrom,
9821 nodeTo = adj.nodeTo,
9822 rel = nodeFrom._depth < nodeTo._depth,
9823 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9824 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9825 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9829 'render': function(adj, canvas) {
9830 var orn = this.getOrientation(adj),
9831 node = adj.nodeFrom,
9833 dim = adj.getData('dim'),
9834 from = this.viz.geom.getEdge(node, 'begin', orn),
9835 to = this.viz.geom.getEdge(child, 'end', orn),
9836 direction = adj.data.$direction,
9837 inv = (direction && direction.length>1 && direction[0] != node.id);
9838 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9840 'contains': function(adj, pos) {
9841 var orn = this.getOrientation(adj),
9842 nodeFrom = adj.nodeFrom,
9843 nodeTo = adj.nodeTo,
9844 rel = nodeFrom._depth < nodeTo._depth,
9845 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9846 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9847 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9850 'quadratic:begin': {
9851 'render': function(adj, canvas) {
9852 var orn = this.getOrientation(adj);
9853 var nodeFrom = adj.nodeFrom,
9854 nodeTo = adj.nodeTo,
9855 rel = nodeFrom._depth < nodeTo._depth,
9856 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9857 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9858 dim = adj.getData('dim'),
9859 ctx = canvas.getCtx();
9861 ctx.moveTo(begin.x, begin.y);
9864 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9867 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9870 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9873 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9880 'render': function(adj, canvas) {
9881 var orn = this.getOrientation(adj);
9882 var nodeFrom = adj.nodeFrom,
9883 nodeTo = adj.nodeTo,
9884 rel = nodeFrom._depth < nodeTo._depth,
9885 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9886 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9887 dim = adj.getData('dim'),
9888 ctx = canvas.getCtx();
9890 ctx.moveTo(begin.x, begin.y);
9893 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9896 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9899 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9902 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9909 'render': function(adj, canvas) {
9910 var orn = this.getOrientation(adj),
9911 nodeFrom = adj.nodeFrom,
9912 nodeTo = adj.nodeTo,
9913 rel = nodeFrom._depth < nodeTo._depth,
9914 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9915 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9916 dim = adj.getData('dim'),
9917 ctx = canvas.getCtx();
9919 ctx.moveTo(begin.x, begin.y);
9922 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9925 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9928 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9931 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9940 Options.LineChart = {
9944 labelOffset: 3, // label offset
9945 type: 'basic', // gradient
9961 selectOnHover: true,
9962 showAggregates: true,
9964 filterOnClick: false,
9965 restoreOnRightClick: false
9970 * File: LineChart.js
9974 $jit.ST.Plot.NodeTypes.implement({
9975 'linechart-basic' : {
9976 'render' : function(node, canvas) {
9977 var pos = node.pos.getc(true),
9978 width = node.getData('width'),
9979 height = node.getData('height'),
9980 algnPos = this.getAlignedPos(pos, width, height),
9981 x = algnPos.x + width/2 , y = algnPos.y,
9982 stringArray = node.getData('stringArray'),
9983 lastNode = node.getData('lastNode'),
9984 dimArray = node.getData('dimArray'),
9985 valArray = node.getData('valueArray'),
9986 colorArray = node.getData('colorArray'),
9987 colorLength = colorArray.length,
9988 config = node.getData('config'),
9989 gradient = node.getData('gradient'),
9990 showLabels = config.showLabels,
9991 aggregates = config.showAggregates,
9992 label = config.Label,
9993 prev = node.getData('prev'),
9994 dataPointSize = config.dataPointSize;
9996 var ctx = canvas.getCtx(), border = node.getData('border');
9997 if (colorArray && dimArray && stringArray) {
9999 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10000 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10002 ctx.lineCap = "round";
10006 //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
10008 ctx.moveTo(x, y - dimArray[i][0]);
10009 ctx.lineTo(x + width, y - dimArray[i][1]);
10013 //render data point
10014 ctx.fillRect(x - (dataPointSize/2), y - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10018 if(label.type == 'Native' && showLabels) {
10020 ctx.fillStyle = ctx.strokeStyle = label.color;
10021 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10022 ctx.textAlign = 'center';
10023 ctx.textBaseline = 'middle';
10024 ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10030 'contains': function(node, mpos) {
10031 var pos = node.pos.getc(true),
10032 width = node.getData('width'),
10033 height = node.getData('height'),
10034 config = node.getData('config'),
10035 dataPointSize = config.dataPointSize,
10036 dataPointMidPoint = dataPointSize/2,
10037 algnPos = this.getAlignedPos(pos, width, height),
10038 x = algnPos.x + width/2, y = algnPos.y,
10039 dimArray = node.getData('dimArray');
10040 //bounding box check
10041 if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10045 for(var i=0, l=dimArray.length; i<l; i++) {
10046 var dimi = dimArray[i];
10047 var url = Url.decode(node.getData('linkArray')[i]);
10048 if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10049 var valArrayCur = node.getData('valArrayCur');
10050 var results = array_match(valArrayCur[i],valArrayCur);
10051 var matches = results[0];
10052 var indexValues = results[1];
10054 var names = new Array(),
10055 values = new Array(),
10056 percentages = new Array(),
10057 linksArr = new Array();
10058 for(var j=0, il=indexValues.length; j<il; j++) {
10059 names[j] = node.getData('stringArray')[indexValues[j]];
10060 values[j] = valArrayCur[indexValues[j]];
10061 percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10062 linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10067 'color': node.getData('colorArray')[i],
10069 'percentage': percentages,
10076 'name': node.getData('stringArray')[i],
10077 'color': node.getData('colorArray')[i],
10078 'value': node.getData('valueArray')[i][0],
10079 // 'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10080 'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10095 A visualization that displays line charts.
10097 Constructor Options:
10099 See <Options.Line>.
10102 $jit.LineChart = new Class({
10104 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10108 initialize: function(opt) {
10109 this.controller = this.config =
10110 $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10111 Label: { type: 'Native' }
10113 //set functions for showLabels and showAggregates
10114 var showLabels = this.config.showLabels,
10115 typeLabels = $.type(showLabels),
10116 showAggregates = this.config.showAggregates,
10117 typeAggregates = $.type(showAggregates);
10118 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10119 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10120 Options.Fx.clearCanvas = false;
10121 this.initializeViz();
10124 initializeViz: function() {
10125 var config = this.config,
10127 nodeType = config.type.split(":")[0],
10130 var st = new $jit.ST({
10131 injectInto: config.injectInto,
10132 orientation: "bottom",
10133 backgroundColor: config.backgroundColor,
10134 renderBackground: config.renderBackground,
10138 withLabels: config.Label.type != 'Native',
10139 useCanvas: config.useCanvas,
10141 type: config.Label.type
10145 type: 'linechart-' + nodeType,
10154 enable: config.Tips.enable,
10157 onShow: function(tip, node, contains) {
10158 var elem = contains;
10159 config.Tips.onShow(tip, elem, node);
10165 onClick: function(node, eventInfo, evt) {
10166 if(!config.filterOnClick && !config.Events.enable) return;
10167 var elem = eventInfo.getContains();
10168 if(elem) config.filterOnClick && that.filter(elem.name);
10169 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10171 onRightClick: function(node, eventInfo, evt) {
10172 if(!config.restoreOnRightClick) return;
10175 onMouseMove: function(node, eventInfo, evt) {
10176 if(!config.selectOnHover) return;
10178 var elem = eventInfo.getContains();
10179 that.select(node.id, elem.name, elem.index);
10181 that.select(false, false, false);
10185 onCreateLabel: function(domElement, node) {
10186 var labelConf = config.Label,
10187 valueArray = node.getData('valueArray'),
10188 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10189 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10190 if(node.getData('prev')) {
10192 wrapper: document.createElement('div'),
10193 aggregate: document.createElement('div'),
10194 label: document.createElement('div')
10196 var wrapper = nlbs.wrapper,
10197 label = nlbs.label,
10198 aggregate = nlbs.aggregate,
10199 wrapperStyle = wrapper.style,
10200 labelStyle = label.style,
10201 aggregateStyle = aggregate.style;
10202 //store node labels
10203 nodeLabels[node.id] = nlbs;
10205 wrapper.appendChild(label);
10206 wrapper.appendChild(aggregate);
10207 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10208 label.style.display = 'none';
10210 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10211 aggregate.style.display = 'none';
10213 wrapperStyle.position = 'relative';
10214 wrapperStyle.overflow = 'visible';
10215 wrapperStyle.fontSize = labelConf.size + 'px';
10216 wrapperStyle.fontFamily = labelConf.family;
10217 wrapperStyle.color = labelConf.color;
10218 wrapperStyle.textAlign = 'center';
10219 aggregateStyle.position = labelStyle.position = 'absolute';
10221 domElement.style.width = node.getData('width') + 'px';
10222 domElement.style.height = node.getData('height') + 'px';
10223 label.innerHTML = node.name;
10225 domElement.appendChild(wrapper);
10228 onPlaceLabel: function(domElement, node) {
10229 if(!node.getData('prev')) return;
10230 var labels = nodeLabels[node.id],
10231 wrapperStyle = labels.wrapper.style,
10232 labelStyle = labels.label.style,
10233 aggregateStyle = labels.aggregate.style,
10234 width = node.getData('width'),
10235 height = node.getData('height'),
10236 dimArray = node.getData('dimArray'),
10237 valArray = node.getData('valueArray'),
10238 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10239 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10240 font = parseInt(wrapperStyle.fontSize, 10),
10241 domStyle = domElement.style;
10243 if(dimArray && valArray) {
10244 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10245 labelStyle.display = '';
10247 labelStyle.display = 'none';
10249 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10250 aggregateStyle.display = '';
10252 aggregateStyle.display = 'none';
10254 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10255 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10256 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10257 if(dimArray[i][0] > 0) {
10258 acum+= valArray[i][0];
10259 leftAcum+= dimArray[i][0];
10262 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10263 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10264 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10265 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10266 labels.aggregate.innerHTML = acum;
10271 var size = st.canvas.getSize(),
10272 margin = config.Margin;
10273 st.config.offsetY = -size.height/2 + margin.bottom
10274 + (config.showLabels && (config.labelOffset + config.Label.size));
10275 st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10277 this.canvas = this.st.canvas;
10280 renderTitle: function() {
10281 var canvas = this.canvas,
10282 size = canvas.getSize(),
10283 config = this.config,
10284 margin = config.Margin,
10285 label = config.Label,
10286 title = config.Title;
10287 ctx = canvas.getCtx();
10288 ctx.fillStyle = title.color;
10289 ctx.textAlign = 'left';
10290 ctx.textBaseline = 'top';
10291 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10292 if(label.type == 'Native') {
10293 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10297 renderTicks: function() {
10299 var canvas = this.canvas,
10300 size = canvas.getSize(),
10301 config = this.config,
10302 margin = config.Margin,
10303 ticks = config.Ticks,
10304 title = config.Title,
10305 subtitle = config.Subtitle,
10306 label = config.Label,
10307 maxValue = this.maxValue,
10308 maxTickValue = Math.ceil(maxValue*.1)*10;
10309 if(maxTickValue == maxValue) {
10310 var length = maxTickValue.toString().length;
10311 maxTickValue = maxTickValue + parseInt(pad(1,length));
10316 labelIncrement = maxTickValue/ticks.segments,
10317 ctx = canvas.getCtx();
10318 ctx.strokeStyle = ticks.color;
10319 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10320 ctx.textAlign = 'center';
10321 ctx.textBaseline = 'middle';
10323 idLabel = canvas.id + "-label";
10325 container = document.getElementById(idLabel);
10328 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10329 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10330 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)),
10331 segmentLength = grid/ticks.segments;
10332 ctx.fillStyle = ticks.color;
10333 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));
10335 while(axis>=grid) {
10337 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10338 ctx.rotate(Math.PI / 2);
10339 ctx.fillStyle = label.color;
10340 if(config.showLabels) {
10341 if(label.type == 'Native') {
10342 ctx.fillText(labelValue, 0, 0);
10344 //html labels on y axis
10345 labelDiv = document.createElement('div');
10346 labelDiv.innerHTML = labelValue;
10347 labelDiv.className = "rotatedLabel";
10348 // labelDiv.class = "rotatedLabel";
10349 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10350 labelDiv.style.left = margin.left + "px";
10351 labelDiv.style.width = labelDim + "px";
10352 labelDiv.style.height = labelDim + "px";
10353 labelDiv.style.textAlign = "center";
10354 labelDiv.style.verticalAlign = "middle";
10355 labelDiv.style.position = "absolute";
10356 container.appendChild(labelDiv);
10360 ctx.fillStyle = ticks.color;
10361 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 );
10362 htmlOrigin += segmentLength;
10363 axis += segmentLength;
10364 labelValue += labelIncrement;
10374 renderBackground: function() {
10375 var canvas = this.canvas,
10376 config = this.config,
10377 backgroundColor = config.backgroundColor,
10378 size = canvas.getSize(),
10379 ctx = canvas.getCtx();
10380 //ctx.globalCompositeOperation = "destination-over";
10381 ctx.fillStyle = backgroundColor;
10382 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10389 Loads JSON data into the visualization.
10393 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>.
10397 var areaChart = new $jit.AreaChart(options);
10398 areaChart.loadJSON(json);
10401 loadJSON: function(json) {
10402 var prefix = $.time(),
10405 name = $.splat(json.label),
10406 color = $.splat(json.color || this.colors),
10407 config = this.config,
10408 ticks = config.Ticks,
10409 renderBackground = config.renderBackground,
10410 gradient = !!config.type.split(":")[1],
10411 animate = config.animate,
10412 title = config.Title,
10413 groupTotalValue = 0;
10415 var valArrayAll = new Array();
10417 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10418 var val = values[i];
10419 var valArray = $.splat(val.values);
10420 for (var j=0, len=valArray.length; j<len; j++) {
10421 valArrayAll.push(parseInt(valArray[j]));
10423 groupTotalValue += parseInt(valArray.sum());
10426 this.maxValue = Math.max.apply(null, valArrayAll);
10428 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10429 var val = values[i], prev = values[i-1];
10431 var next = (i+1 < l) ? values[i+1] : 0;
10432 var valLeft = $.splat(values[i].values);
10433 var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10434 var valArray = $.zip(valLeft, valRight);
10435 var valArrayCur = $.splat(values[i].values);
10436 var linkArray = $.splat(values[i].links);
10437 var acumLeft = 0, acumRight = 0;
10438 var lastNode = (l-1 == i) ? true : false;
10440 'id': prefix + val.label,
10444 '$valueArray': valArray,
10445 '$valArrayCur': valArrayCur,
10446 '$colorArray': color,
10447 '$linkArray': linkArray,
10448 '$stringArray': name,
10449 '$next': next? next.label:false,
10450 '$prev': prev? prev.label:false,
10452 '$lastNode': lastNode,
10453 '$groupTotalValue': groupTotalValue,
10454 '$gradient': gradient
10460 'id': prefix + '$root',
10471 this.normalizeDims();
10473 if(renderBackground) {
10474 this.renderBackground();
10477 if(!animate && ticks.enable) {
10478 this.renderTicks();
10483 this.renderTitle();
10487 st.select(st.root);
10490 modes: ['node-property:height:dimArray'],
10499 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.
10503 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10504 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10509 areaChart.updateJSON(json, {
10510 onComplete: function() {
10511 alert('update complete!');
10516 updateJSON: function(json, onComplete) {
10517 if(this.busy) return;
10522 labels = json.label && $.splat(json.label),
10523 values = json.values,
10524 animate = this.config.animate,
10526 $.each(values, function(v) {
10527 var n = graph.getByName(v.label);
10529 v.values = $.splat(v.values);
10530 var stringArray = n.getData('stringArray'),
10531 valArray = n.getData('valueArray');
10532 $.each(valArray, function(a, i) {
10533 a[0] = v.values[i];
10534 if(labels) stringArray[i] = labels[i];
10536 n.setData('valueArray', valArray);
10537 var prev = n.getData('prev'),
10538 next = n.getData('next'),
10539 nextNode = graph.getByName(next);
10541 var p = graph.getByName(prev);
10543 var valArray = p.getData('valueArray');
10544 $.each(valArray, function(a, i) {
10545 a[1] = v.values[i];
10550 var valArray = n.getData('valueArray');
10551 $.each(valArray, function(a, i) {
10552 a[1] = v.values[i];
10557 this.normalizeDims();
10560 st.select(st.root);
10563 modes: ['node-property:height:dimArray'],
10565 onComplete: function() {
10567 onComplete && onComplete.onComplete();
10576 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10580 Variable strings arguments with the name of the stacks.
10585 areaChart.filter('label A', 'label C');
10590 <AreaChart.restore>.
10592 filter: function() {
10593 if(this.busy) return;
10595 if(this.config.Tips.enable) this.st.tips.hide();
10596 this.select(false, false, false);
10597 var args = Array.prototype.slice.call(arguments);
10598 var rt = this.st.graph.getNode(this.st.root);
10600 rt.eachAdjacency(function(adj) {
10601 var n = adj.nodeTo,
10602 dimArray = n.getData('dimArray'),
10603 stringArray = n.getData('stringArray');
10604 n.setData('dimArray', $.map(dimArray, function(d, i) {
10605 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10608 this.st.fx.animate({
10609 modes: ['node-property:dimArray'],
10611 onComplete: function() {
10620 Sets all stacks that could have been filtered visible.
10625 areaChart.restore();
10630 <AreaChart.filter>.
10632 restore: function() {
10633 if(this.busy) return;
10635 if(this.config.Tips.enable) this.st.tips.hide();
10636 this.select(false, false, false);
10637 this.normalizeDims();
10639 this.st.fx.animate({
10640 modes: ['node-property:height:dimArray'],
10642 onComplete: function() {
10647 //adds the little brown bar when hovering the node
10648 select: function(id, name, index) {
10649 if(!this.config.selectOnHover) return;
10650 var s = this.selected;
10651 if(s.id != id || s.name != name
10652 || s.index != index) {
10656 this.st.graph.eachNode(function(n) {
10657 n.setData('border', false);
10660 var n = this.st.graph.getNode(id);
10661 n.setData('border', s);
10662 var link = index === 0? 'prev':'next';
10663 link = n.getData(link);
10665 n = this.st.graph.getByName(link);
10667 n.setData('border', {
10681 Returns an object containing as keys the legend names and as values hex strings with color values.
10686 var legend = areaChart.getLegend();
10689 getLegend: function() {
10690 var legend = new Array();
10691 var name = new Array();
10692 var color = new Array();
10694 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10697 var colors = n.getData('colorArray'),
10698 len = colors.length;
10699 $.each(n.getData('stringArray'), function(s, i) {
10700 color[i] = colors[i % len];
10703 legend['name'] = name;
10704 legend['color'] = color;
10709 Method: getMaxValue
10711 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10716 var ans = areaChart.getMaxValue();
10719 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10724 //will return 100 for all AreaChart instances,
10725 //displaying all of them with the same scale
10726 $jit.AreaChart.implement({
10727 'getMaxValue': function() {
10735 normalizeDims: function() {
10736 //number of elements
10737 var root = this.st.graph.getNode(this.st.root), l=0;
10738 root.eachAdjacency(function() {
10743 var maxValue = this.maxValue || 1,
10744 size = this.st.canvas.getSize(),
10745 config = this.config,
10746 margin = config.Margin,
10747 labelOffset = config.labelOffset + config.Label.size,
10748 fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10749 animate = config.animate,
10750 ticks = config.Ticks,
10751 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10752 - (config.showLabels && labelOffset);
10755 var maxTickValue = Math.ceil(maxValue*.1)*10;
10756 if(maxTickValue == maxValue) {
10757 var length = maxTickValue.toString().length;
10758 maxTickValue = maxTickValue + parseInt(pad(1,length));
10763 this.st.graph.eachNode(function(n) {
10764 var acumLeft = 0, acumRight = 0, animateValue = [];
10765 $.each(n.getData('valueArray'), function(v) {
10767 acumRight += +v[1];
10768 animateValue.push([0, 0]);
10770 var acum = acumRight>acumLeft? acumRight:acumLeft;
10772 n.setData('width', fixedDim);
10774 n.setData('height', acum * height / maxValue, 'end');
10775 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10776 return [n[0] * height / maxValue, n[1] * height / maxValue];
10778 var dimArray = n.getData('dimArray');
10780 n.setData('dimArray', animateValue);
10785 n.setData('height', acum * height / maxValue);
10786 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10787 return [n[0] * height / maxTickValue, n[1] * height / maxTickValue];
10790 n.setData('height', acum * height / maxValue);
10791 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10792 return [n[0] * height / maxValue, n[1] * height / maxValue];
10807 * File: AreaChart.js
10811 $jit.ST.Plot.NodeTypes.implement({
10812 'areachart-stacked' : {
10813 'render' : function(node, canvas) {
10814 var pos = node.pos.getc(true),
10815 width = node.getData('width'),
10816 height = node.getData('height'),
10817 algnPos = this.getAlignedPos(pos, width, height),
10818 x = algnPos.x, y = algnPos.y,
10819 stringArray = node.getData('stringArray'),
10820 dimArray = node.getData('dimArray'),
10821 valArray = node.getData('valueArray'),
10822 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10823 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10824 colorArray = node.getData('colorArray'),
10825 colorLength = colorArray.length,
10826 config = node.getData('config'),
10827 gradient = node.getData('gradient'),
10828 showLabels = config.showLabels,
10829 aggregates = config.showAggregates,
10830 label = config.Label,
10831 prev = node.getData('prev');
10833 var ctx = canvas.getCtx(), border = node.getData('border');
10834 if (colorArray && dimArray && stringArray) {
10835 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10836 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10838 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10839 var h1 = acumLeft + dimArray[i][0],
10840 h2 = acumRight + dimArray[i][1],
10841 alpha = Math.atan((h2 - h1) / width),
10843 var linear = ctx.createLinearGradient(x + width/2,
10845 x + width/2 + delta * Math.sin(alpha),
10846 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10847 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10848 function(v) { return (v * 0.85) >> 0; }));
10849 linear.addColorStop(0, colorArray[i % colorLength]);
10850 linear.addColorStop(1, color);
10851 ctx.fillStyle = linear;
10854 ctx.moveTo(x, y - acumLeft);
10855 ctx.lineTo(x + width, y - acumRight);
10856 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10857 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10858 ctx.lineTo(x, y - acumLeft);
10862 var strong = border.name == stringArray[i];
10863 var perc = strong? 0.7 : 0.8;
10864 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10865 function(v) { return (v * perc) >> 0; }));
10866 ctx.strokeStyle = color;
10867 ctx.lineWidth = strong? 4 : 1;
10870 if(border.index === 0) {
10871 ctx.moveTo(x, y - acumLeft);
10872 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10874 ctx.moveTo(x + width, y - acumRight);
10875 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10880 acumLeft += (dimArray[i][0] || 0);
10881 acumRight += (dimArray[i][1] || 0);
10883 if(dimArray[i][0] > 0)
10884 valAcum += (valArray[i][0] || 0);
10886 if(prev && label.type == 'Native') {
10889 ctx.fillStyle = ctx.strokeStyle = label.color;
10890 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10891 ctx.textAlign = 'center';
10892 ctx.textBaseline = 'middle';
10893 if(aggregates(node.name, valLeft, valRight, node)) {
10894 ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10896 if(showLabels(node.name, valLeft, valRight, node)) {
10897 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10903 'contains': function(node, mpos) {
10904 var pos = node.pos.getc(true),
10905 width = node.getData('width'),
10906 height = node.getData('height'),
10907 algnPos = this.getAlignedPos(pos, width, height),
10908 x = algnPos.x, y = algnPos.y,
10909 dimArray = node.getData('dimArray'),
10911 //bounding box check
10912 if(mpos.x < x || mpos.x > x + width
10913 || mpos.y > y || mpos.y < y - height) {
10917 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10918 var dimi = dimArray[i];
10921 var intersec = lAcum + (rAcum - lAcum) * rx / width;
10922 if(mpos.y >= intersec) {
10923 var index = +(rx > width/2);
10925 'name': node.getData('stringArray')[i],
10926 'color': node.getData('colorArray')[i],
10927 'value': node.getData('valueArray')[i][index],
10940 A visualization that displays stacked area charts.
10942 Constructor Options:
10944 See <Options.AreaChart>.
10947 $jit.AreaChart = new Class({
10949 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10953 initialize: function(opt) {
10954 this.controller = this.config =
10955 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10956 Label: { type: 'Native' }
10958 //set functions for showLabels and showAggregates
10959 var showLabels = this.config.showLabels,
10960 typeLabels = $.type(showLabels),
10961 showAggregates = this.config.showAggregates,
10962 typeAggregates = $.type(showAggregates);
10963 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10964 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10966 this.initializeViz();
10969 initializeViz: function() {
10970 var config = this.config,
10972 nodeType = config.type.split(":")[0],
10975 var st = new $jit.ST({
10976 injectInto: config.injectInto,
10977 orientation: "bottom",
10981 withLabels: config.Label.type != 'Native',
10982 useCanvas: config.useCanvas,
10984 type: config.Label.type
10988 type: 'areachart-' + nodeType,
10997 enable: config.Tips.enable,
11000 onShow: function(tip, node, contains) {
11001 var elem = contains;
11002 config.Tips.onShow(tip, elem, node);
11008 onClick: function(node, eventInfo, evt) {
11009 if(!config.filterOnClick && !config.Events.enable) return;
11010 var elem = eventInfo.getContains();
11011 if(elem) config.filterOnClick && that.filter(elem.name);
11012 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11014 onRightClick: function(node, eventInfo, evt) {
11015 if(!config.restoreOnRightClick) return;
11018 onMouseMove: function(node, eventInfo, evt) {
11019 if(!config.selectOnHover) return;
11021 var elem = eventInfo.getContains();
11022 that.select(node.id, elem.name, elem.index);
11024 that.select(false, false, false);
11028 onCreateLabel: function(domElement, node) {
11029 var labelConf = config.Label,
11030 valueArray = node.getData('valueArray'),
11031 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11032 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11033 if(node.getData('prev')) {
11035 wrapper: document.createElement('div'),
11036 aggregate: document.createElement('div'),
11037 label: document.createElement('div')
11039 var wrapper = nlbs.wrapper,
11040 label = nlbs.label,
11041 aggregate = nlbs.aggregate,
11042 wrapperStyle = wrapper.style,
11043 labelStyle = label.style,
11044 aggregateStyle = aggregate.style;
11045 //store node labels
11046 nodeLabels[node.id] = nlbs;
11048 wrapper.appendChild(label);
11049 wrapper.appendChild(aggregate);
11050 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11051 label.style.display = 'none';
11053 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11054 aggregate.style.display = 'none';
11056 wrapperStyle.position = 'relative';
11057 wrapperStyle.overflow = 'visible';
11058 wrapperStyle.fontSize = labelConf.size + 'px';
11059 wrapperStyle.fontFamily = labelConf.family;
11060 wrapperStyle.color = labelConf.color;
11061 wrapperStyle.textAlign = 'center';
11062 aggregateStyle.position = labelStyle.position = 'absolute';
11064 domElement.style.width = node.getData('width') + 'px';
11065 domElement.style.height = node.getData('height') + 'px';
11066 label.innerHTML = node.name;
11068 domElement.appendChild(wrapper);
11071 onPlaceLabel: function(domElement, node) {
11072 if(!node.getData('prev')) return;
11073 var labels = nodeLabels[node.id],
11074 wrapperStyle = labels.wrapper.style,
11075 labelStyle = labels.label.style,
11076 aggregateStyle = labels.aggregate.style,
11077 width = node.getData('width'),
11078 height = node.getData('height'),
11079 dimArray = node.getData('dimArray'),
11080 valArray = node.getData('valueArray'),
11081 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11082 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11083 font = parseInt(wrapperStyle.fontSize, 10),
11084 domStyle = domElement.style;
11086 if(dimArray && valArray) {
11087 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11088 labelStyle.display = '';
11090 labelStyle.display = 'none';
11092 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11093 aggregateStyle.display = '';
11095 aggregateStyle.display = 'none';
11097 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11098 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11099 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11100 if(dimArray[i][0] > 0) {
11101 acum+= valArray[i][0];
11102 leftAcum+= dimArray[i][0];
11105 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11106 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11107 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11108 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11109 labels.aggregate.innerHTML = acum;
11114 var size = st.canvas.getSize(),
11115 margin = config.Margin;
11116 st.config.offsetY = -size.height/2 + margin.bottom
11117 + (config.showLabels && (config.labelOffset + config.Label.size));
11118 st.config.offsetX = (margin.right - margin.left)/2;
11120 this.canvas = this.st.canvas;
11126 Loads JSON data into the visualization.
11130 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>.
11134 var areaChart = new $jit.AreaChart(options);
11135 areaChart.loadJSON(json);
11138 loadJSON: function(json) {
11139 var prefix = $.time(),
11142 name = $.splat(json.label),
11143 color = $.splat(json.color || this.colors),
11144 config = this.config,
11145 gradient = !!config.type.split(":")[1],
11146 animate = config.animate;
11148 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11149 var val = values[i], prev = values[i-1], next = values[i+1];
11150 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11151 var valArray = $.zip(valLeft, valRight);
11152 var acumLeft = 0, acumRight = 0;
11154 'id': prefix + val.label,
11158 '$valueArray': valArray,
11159 '$colorArray': color,
11160 '$stringArray': name,
11161 '$next': next.label,
11162 '$prev': prev? prev.label:false,
11164 '$gradient': gradient
11170 'id': prefix + '$root',
11181 this.normalizeDims();
11183 st.select(st.root);
11186 modes: ['node-property:height:dimArray'],
11195 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.
11199 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11200 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11205 areaChart.updateJSON(json, {
11206 onComplete: function() {
11207 alert('update complete!');
11212 updateJSON: function(json, onComplete) {
11213 if(this.busy) return;
11218 labels = json.label && $.splat(json.label),
11219 values = json.values,
11220 animate = this.config.animate,
11222 $.each(values, function(v) {
11223 var n = graph.getByName(v.label);
11225 v.values = $.splat(v.values);
11226 var stringArray = n.getData('stringArray'),
11227 valArray = n.getData('valueArray');
11228 $.each(valArray, function(a, i) {
11229 a[0] = v.values[i];
11230 if(labels) stringArray[i] = labels[i];
11232 n.setData('valueArray', valArray);
11233 var prev = n.getData('prev'),
11234 next = n.getData('next'),
11235 nextNode = graph.getByName(next);
11237 var p = graph.getByName(prev);
11239 var valArray = p.getData('valueArray');
11240 $.each(valArray, function(a, i) {
11241 a[1] = v.values[i];
11246 var valArray = n.getData('valueArray');
11247 $.each(valArray, function(a, i) {
11248 a[1] = v.values[i];
11253 this.normalizeDims();
11255 st.select(st.root);
11258 modes: ['node-property:height:dimArray'],
11260 onComplete: function() {
11262 onComplete && onComplete.onComplete();
11271 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11275 Variable strings arguments with the name of the stacks.
11280 areaChart.filter('label A', 'label C');
11285 <AreaChart.restore>.
11287 filter: function() {
11288 if(this.busy) return;
11290 if(this.config.Tips.enable) this.st.tips.hide();
11291 this.select(false, false, false);
11292 var args = Array.prototype.slice.call(arguments);
11293 var rt = this.st.graph.getNode(this.st.root);
11295 rt.eachAdjacency(function(adj) {
11296 var n = adj.nodeTo,
11297 dimArray = n.getData('dimArray'),
11298 stringArray = n.getData('stringArray');
11299 n.setData('dimArray', $.map(dimArray, function(d, i) {
11300 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11303 this.st.fx.animate({
11304 modes: ['node-property:dimArray'],
11306 onComplete: function() {
11315 Sets all stacks that could have been filtered visible.
11320 areaChart.restore();
11325 <AreaChart.filter>.
11327 restore: function() {
11328 if(this.busy) return;
11330 if(this.config.Tips.enable) this.st.tips.hide();
11331 this.select(false, false, false);
11332 this.normalizeDims();
11334 this.st.fx.animate({
11335 modes: ['node-property:height:dimArray'],
11337 onComplete: function() {
11342 //adds the little brown bar when hovering the node
11343 select: function(id, name, index) {
11344 if(!this.config.selectOnHover) return;
11345 var s = this.selected;
11346 if(s.id != id || s.name != name
11347 || s.index != index) {
11351 this.st.graph.eachNode(function(n) {
11352 n.setData('border', false);
11355 var n = this.st.graph.getNode(id);
11356 n.setData('border', s);
11357 var link = index === 0? 'prev':'next';
11358 link = n.getData(link);
11360 n = this.st.graph.getByName(link);
11362 n.setData('border', {
11376 Returns an object containing as keys the legend names and as values hex strings with color values.
11381 var legend = areaChart.getLegend();
11384 getLegend: function() {
11387 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11390 var colors = n.getData('colorArray'),
11391 len = colors.length;
11392 $.each(n.getData('stringArray'), function(s, i) {
11393 legend[s] = colors[i % len];
11399 Method: getMaxValue
11401 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11406 var ans = areaChart.getMaxValue();
11409 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11414 //will return 100 for all AreaChart instances,
11415 //displaying all of them with the same scale
11416 $jit.AreaChart.implement({
11417 'getMaxValue': function() {
11424 getMaxValue: function() {
11426 this.st.graph.eachNode(function(n) {
11427 var valArray = n.getData('valueArray'),
11428 acumLeft = 0, acumRight = 0;
11429 $.each(valArray, function(v) {
11431 acumRight += +v[1];
11433 var acum = acumRight>acumLeft? acumRight:acumLeft;
11434 maxValue = maxValue>acum? maxValue:acum;
11439 normalizeDims: function() {
11440 //number of elements
11441 var root = this.st.graph.getNode(this.st.root), l=0;
11442 root.eachAdjacency(function() {
11445 var maxValue = this.getMaxValue() || 1,
11446 size = this.st.canvas.getSize(),
11447 config = this.config,
11448 margin = config.Margin,
11449 labelOffset = config.labelOffset + config.Label.size,
11450 fixedDim = (size.width - (margin.left + margin.right)) / l,
11451 animate = config.animate,
11452 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
11453 - (config.showLabels && labelOffset);
11454 this.st.graph.eachNode(function(n) {
11455 var acumLeft = 0, acumRight = 0, animateValue = [];
11456 $.each(n.getData('valueArray'), function(v) {
11458 acumRight += +v[1];
11459 animateValue.push([0, 0]);
11461 var acum = acumRight>acumLeft? acumRight:acumLeft;
11462 n.setData('width', fixedDim);
11464 n.setData('height', acum * height / maxValue, 'end');
11465 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11466 return [n[0] * height / maxValue, n[1] * height / maxValue];
11468 var dimArray = n.getData('dimArray');
11470 n.setData('dimArray', animateValue);
11473 n.setData('height', acum * height / maxValue);
11474 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11475 return [n[0] * height / maxValue, n[1] * height / maxValue];
11483 * File: Options.BarChart.js
11488 Object: Options.BarChart
11490 <BarChart> options.
11491 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11497 Options.BarChart = {
11502 hoveredColor: '#9fd4ff',
11503 orientation: 'horizontal',
11504 showAggregates: true,
11514 var barChart = new $jit.BarChart({
11517 type: 'stacked:gradient'
11524 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11525 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11526 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11527 barsOffset - (number) Default's *0*. Separation between bars.
11528 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11529 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11530 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11531 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11532 showLabels - (boolean) Default's *true*. Display the name of the slots.
11536 Options.BarChart = {
11540 type: 'stacked', //stacked, grouped, : gradient
11541 labelOffset: 3, //label offset
11542 barsOffset: 0, //distance between bars
11543 nodeCount: 0, //number of bars
11544 hoveredColor: '#9fd4ff',
11546 renderBackground: false,
11547 orientation: 'horizontal',
11548 showAggregates: true,
11567 * File: BarChart.js
11571 $jit.ST.Plot.NodeTypes.implement({
11572 'barchart-stacked' : {
11573 'render' : function(node, canvas) {
11574 var pos = node.pos.getc(true),
11575 width = node.getData('width'),
11576 height = node.getData('height'),
11577 algnPos = this.getAlignedPos(pos, width, height),
11578 x = algnPos.x, y = algnPos.y,
11579 dimArray = node.getData('dimArray'),
11580 valueArray = node.getData('valueArray'),
11581 stringArray = node.getData('stringArray'),
11582 linkArray = node.getData('linkArray'),
11583 gvl = node.getData('gvl'),
11584 colorArray = node.getData('colorArray'),
11585 colorLength = colorArray.length,
11586 nodeCount = node.getData('nodeCount');
11587 var ctx = canvas.getCtx(),
11588 canvasSize = canvas.getSize(),
11590 border = node.getData('border'),
11591 gradient = node.getData('gradient'),
11592 config = node.getData('config'),
11593 horz = config.orientation == 'horizontal',
11594 aggregates = config.showAggregates,
11595 showLabels = config.showLabels,
11596 label = config.Label,
11597 margin = config.Margin;
11600 if (colorArray && dimArray && stringArray) {
11601 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11602 acum += (dimArray[i] || 0);
11607 if(config.shadow.enable) {
11608 shadowThickness = config.shadow.size;
11609 ctx.fillStyle = "rgba(0,0,0,.2)";
11611 ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11613 ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11617 if (colorArray && dimArray && stringArray) {
11618 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11619 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11626 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
11627 x + acum + dimArray[i]/2, y + height);
11629 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
11630 x + width, y - acum- dimArray[i]/2);
11632 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11633 function(v) { return (v * 0.8) >> 0; }));
11634 linear.addColorStop(0, color);
11635 linear.addColorStop(0.3, colorArray[i % colorLength]);
11636 linear.addColorStop(0.7, colorArray[i % colorLength]);
11637 linear.addColorStop(1, color);
11638 ctx.fillStyle = linear;
11641 ctx.fillRect(x + acum, y, dimArray[i], height);
11643 ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
11645 if(border && border.name == stringArray[i]) {
11647 opt.dimValue = dimArray[i];
11649 acum += (dimArray[i] || 0);
11650 valAcum += (valueArray[i] || 0);
11655 ctx.strokeStyle = border.color;
11657 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11659 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11663 if(label.type == 'Native') {
11665 ctx.fillStyle = ctx.strokeStyle = label.color;
11666 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11667 ctx.textBaseline = 'middle';
11669 acumValueLabel = gvl;
11671 acumValueLabel = valAcum;
11673 if(aggregates(node.name, valAcum)) {
11675 ctx.textAlign = 'center';
11676 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11679 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11680 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11681 (label ? label.size + config.labelOffset : 0));
11682 mtxt = ctx.measureText(acumValueLabel);
11683 boxWidth = mtxt.width+10;
11685 boxHeight = label.size+6;
11687 if(boxHeight + acum + config.labelOffset > gridHeight) {
11688 bottomPadding = acum - config.labelOffset - boxHeight;
11690 bottomPadding = acum + config.labelOffset + inset;
11694 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11697 boxY = -boxHeight/2;
11699 ctx.rotate(0 * Math.PI / 180);
11700 ctx.fillStyle = "rgba(255,255,255,.8)";
11701 if(boxHeight + acum + config.labelOffset > gridHeight) {
11702 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11704 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11705 ctx.fillStyle = ctx.strokeStyle = label.color;
11706 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11711 if(showLabels(node.name, valAcum, node)) {
11716 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11719 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11720 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11721 boxWidth = mtxt.width+10;
11724 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11725 leftPadding = acum - config.labelOffset - boxWidth - inset;
11727 leftPadding = acum + config.labelOffset;
11731 ctx.textAlign = 'left';
11732 ctx.translate(x + inset + leftPadding, y + height/2);
11733 boxHeight = label.size+6;
11735 boxY = -boxHeight/2;
11736 ctx.fillStyle = "rgba(255,255,255,.8)";
11738 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11739 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11741 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11743 ctx.fillStyle = label.color;
11744 ctx.rotate(0 * Math.PI / 180);
11745 ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11749 //if the number of nodes greater than 8 rotate labels 45 degrees
11750 if(nodeCount > 8) {
11751 ctx.textAlign = 'left';
11752 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11753 ctx.rotate(45* Math.PI / 180);
11754 ctx.fillText(node.name, 0, 0);
11756 ctx.textAlign = 'center';
11757 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11765 'contains': function(node, mpos) {
11766 var pos = node.pos.getc(true),
11767 width = node.getData('width'),
11768 height = node.getData('height'),
11769 algnPos = this.getAlignedPos(pos, width, height),
11770 x = algnPos.x, y = algnPos.y,
11771 dimArray = node.getData('dimArray'),
11772 config = node.getData('config'),
11774 horz = config.orientation == 'horizontal';
11775 //bounding box check
11777 if(mpos.x < x || mpos.x > x + width
11778 || mpos.y > y + height || mpos.y < y) {
11782 if(mpos.x < x || mpos.x > x + width
11783 || mpos.y > y || mpos.y < y - height) {
11788 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11789 var dimi = dimArray[i];
11790 var url = Url.decode(node.getData('linkArray')[i]);
11793 var intersec = acum;
11794 if(mpos.x <= intersec) {
11796 'name': node.getData('stringArray')[i],
11797 'color': node.getData('colorArray')[i],
11798 'value': node.getData('valueArray')[i],
11799 'valuelabel': node.getData('valuelabelArray')[i],
11800 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11807 var intersec = acum;
11808 if(mpos.y >= intersec) {
11810 'name': node.getData('stringArray')[i],
11811 'color': node.getData('colorArray')[i],
11812 'value': node.getData('valueArray')[i],
11813 'valuelabel': node.getData('valuelabelArray')[i],
11814 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11824 'barchart-grouped' : {
11825 'render' : function(node, canvas) {
11826 var pos = node.pos.getc(true),
11827 width = node.getData('width'),
11828 height = node.getData('height'),
11829 algnPos = this.getAlignedPos(pos, width, height),
11830 x = algnPos.x, y = algnPos.y,
11831 dimArray = node.getData('dimArray'),
11832 valueArray = node.getData('valueArray'),
11833 valuelabelArray = node.getData('valuelabelArray'),
11834 linkArray = node.getData('linkArray'),
11835 valueLength = valueArray.length,
11836 colorArray = node.getData('colorArray'),
11837 colorLength = colorArray.length,
11838 stringArray = node.getData('stringArray');
11840 var ctx = canvas.getCtx(),
11841 canvasSize = canvas.getSize(),
11843 border = node.getData('border'),
11844 gradient = node.getData('gradient'),
11845 config = node.getData('config'),
11846 horz = config.orientation == 'horizontal',
11847 aggregates = config.showAggregates,
11848 showLabels = config.showLabels,
11849 label = config.Label,
11850 shadow = config.shadow,
11851 margin = config.Margin,
11852 fixedDim = (horz? height : width) / valueLength;
11856 maxValue = Math.max.apply(null, dimArray);
11860 ctx.fillStyle = "rgba(0,0,0,.2)";
11861 if (colorArray && dimArray && stringArray && shadow.enable) {
11862 shadowThickness = shadow.size;
11864 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11865 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11866 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11869 ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11874 if(nextBar && nextBar > dimArray[i]) {
11875 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11876 } else if (nextBar && nextBar < dimArray[i]){
11877 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11879 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11881 } else if (i> 0 && i<l-1) {
11882 if(nextBar && nextBar > dimArray[i]) {
11883 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11884 } else if (nextBar && nextBar < dimArray[i]){
11885 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11887 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11889 } else if (i == l-1) {
11890 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11900 if (colorArray && dimArray && stringArray) {
11901 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11902 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11906 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
11907 x + dimArray[i]/2, y + fixedDim * (i + 1));
11909 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
11910 x + fixedDim * (i + 1), y - dimArray[i]/2);
11912 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11913 function(v) { return (v * 0.8) >> 0; }));
11914 linear.addColorStop(0, color);
11915 linear.addColorStop(0.3, colorArray[i % colorLength]);
11916 linear.addColorStop(0.7, colorArray[i % colorLength]);
11917 linear.addColorStop(1, color);
11918 ctx.fillStyle = linear;
11921 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11923 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11925 if(border && border.name == stringArray[i]) {
11926 opt.acum = fixedDim * i;
11927 opt.dimValue = dimArray[i];
11929 acum += (dimArray[i] || 0);
11930 valAcum += (valueArray[i] || 0);
11931 ctx.fillStyle = ctx.strokeStyle = label.color;
11932 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11935 if(aggregates(node.name, valAcum) && label.type == 'Native') {
11936 if(valuelabelArray[i]) {
11937 acumValueLabel = valuelabelArray[i];
11939 acumValueLabel = valueArray[i];
11942 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11943 ctx.textAlign = 'left';
11944 ctx.textBaseline = 'top';
11945 ctx.fillStyle = "rgba(255,255,255,.8)";
11947 gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
11948 mtxt = ctx.measureText(acumValueLabel);
11949 boxWidth = mtxt.width+10;
11951 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11952 leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
11954 leftPadding = dimArray[i] + config.labelOffset + inset;
11956 boxHeight = label.size+6;
11957 boxX = x + leftPadding;
11958 boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
11962 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11963 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11965 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11967 ctx.fillStyle = ctx.strokeStyle = label.color;
11968 ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
11975 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11977 ctx.textAlign = 'center';
11980 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11981 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11982 (label ? label.size + config.labelOffset : 0));
11984 mtxt = ctx.measureText(acumValueLabel);
11985 boxWidth = mtxt.width+10;
11986 boxHeight = label.size+6;
11987 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
11988 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
11990 bottomPadding = dimArray[i] + config.labelOffset + inset;
11994 ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
11996 boxX = -boxWidth/2;
11997 boxY = -boxHeight/2;
11998 ctx.fillStyle = "rgba(255,255,255,.8)";
12002 //ctx.rotate(270* Math.PI / 180);
12003 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12004 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12006 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12008 ctx.fillStyle = ctx.strokeStyle = label.color;
12009 ctx.fillText(acumValueLabel, 0,0);
12018 ctx.strokeStyle = border.color;
12020 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12022 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12026 if(label.type == 'Native') {
12028 ctx.fillStyle = ctx.strokeStyle = label.color;
12029 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12030 ctx.textBaseline = 'middle';
12032 if(showLabels(node.name, valAcum, node)) {
12034 ctx.textAlign = 'center';
12035 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12036 ctx.rotate(Math.PI / 2);
12037 ctx.fillText(node.name, 0, 0);
12039 ctx.textAlign = 'center';
12040 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12047 'contains': function(node, mpos) {
12048 var pos = node.pos.getc(true),
12049 width = node.getData('width'),
12050 height = node.getData('height'),
12051 algnPos = this.getAlignedPos(pos, width, height),
12052 x = algnPos.x, y = algnPos.y,
12053 dimArray = node.getData('dimArray'),
12054 len = dimArray.length,
12055 config = node.getData('config'),
12057 horz = config.orientation == 'horizontal',
12058 fixedDim = (horz? height : width) / len;
12059 //bounding box check
12061 if(mpos.x < x || mpos.x > x + width
12062 || mpos.y > y + height || mpos.y < y) {
12066 if(mpos.x < x || mpos.x > x + width
12067 || mpos.y > y || mpos.y < y - height) {
12072 for(var i=0, l=dimArray.length; i<l; i++) {
12073 var dimi = dimArray[i];
12074 var url = Url.decode(node.getData('linkArray')[i]);
12076 var limit = y + fixedDim * i;
12077 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12079 'name': node.getData('stringArray')[i],
12080 'color': node.getData('colorArray')[i],
12081 'value': node.getData('valueArray')[i],
12082 'valuelabel': node.getData('valuelabelArray')[i],
12083 'title': node.getData('titleArray')[i],
12084 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12090 var limit = x + fixedDim * i;
12091 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12093 'name': node.getData('stringArray')[i],
12094 'color': node.getData('colorArray')[i],
12095 'value': node.getData('valueArray')[i],
12096 'valuelabel': node.getData('valuelabelArray')[i],
12097 'title': node.getData('titleArray')[i],
12098 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12108 'barchart-basic' : {
12109 'render' : function(node, canvas) {
12110 var pos = node.pos.getc(true),
12111 width = node.getData('width'),
12112 height = node.getData('height'),
12113 algnPos = this.getAlignedPos(pos, width, height),
12114 x = algnPos.x, y = algnPos.y,
12115 dimArray = node.getData('dimArray'),
12116 valueArray = node.getData('valueArray'),
12117 valuelabelArray = node.getData('valuelabelArray'),
12118 linkArray = node.getData('linkArray'),
12119 valueLength = valueArray.length,
12120 colorArray = node.getData('colorMono'),
12121 colorLength = colorArray.length,
12122 stringArray = node.getData('stringArray');
12124 var ctx = canvas.getCtx(),
12125 canvasSize = canvas.getSize(),
12127 border = node.getData('border'),
12128 gradient = node.getData('gradient'),
12129 config = node.getData('config'),
12130 horz = config.orientation == 'horizontal',
12131 aggregates = config.showAggregates,
12132 showLabels = config.showLabels,
12133 label = config.Label,
12134 fixedDim = (horz? height : width) / valueLength,
12135 margin = config.Margin;
12137 if (colorArray && dimArray && stringArray) {
12138 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12139 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12144 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
12145 x + dimArray[i]/2, y + fixedDim * (i + 1));
12147 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
12148 x + fixedDim * (i + 1), y - dimArray[i]/2);
12151 if(config.shadow.size) {
12152 shadowThickness = config.shadow.size;
12153 ctx.fillStyle = "rgba(0,0,0,.2)";
12155 ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12157 ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12161 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
12162 function(v) { return (v * 0.8) >> 0; }));
12163 linear.addColorStop(0, color);
12164 linear.addColorStop(0.3, colorArray[i % colorLength]);
12165 linear.addColorStop(0.7, colorArray[i % colorLength]);
12166 linear.addColorStop(1, color);
12167 ctx.fillStyle = linear;
12170 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12172 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12174 if(border && border.name == stringArray[i]) {
12175 opt.acum = fixedDim * i;
12176 opt.dimValue = dimArray[i];
12178 acum += (dimArray[i] || 0);
12179 valAcum += (valueArray[i] || 0);
12181 if(label.type == 'Native') {
12182 ctx.fillStyle = ctx.strokeStyle = label.color;
12183 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12184 if(aggregates(node.name, valAcum)) {
12185 if(valuelabelArray[i]) {
12186 acumValueLabel = valuelabelArray[i];
12188 acumValueLabel = valueArray[i];
12191 ctx.textAlign = 'center';
12192 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12195 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12196 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12197 (label ? label.size + config.labelOffset : 0));
12198 mtxt = ctx.measureText(acumValueLabel);
12199 boxWidth = mtxt.width+10;
12201 boxHeight = label.size+6;
12203 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12204 bottomPadding = dimArray[i] - config.labelOffset - inset;
12206 bottomPadding = dimArray[i] + config.labelOffset + inset;
12210 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12213 boxY = -boxHeight/2;
12215 //ctx.rotate(270* Math.PI / 180);
12216 ctx.fillStyle = "rgba(255,255,255,.6)";
12217 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12218 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12220 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12221 ctx.fillStyle = ctx.strokeStyle = label.color;
12222 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12231 ctx.strokeStyle = border.color;
12233 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12235 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12239 if(label.type == 'Native') {
12241 ctx.fillStyle = ctx.strokeStyle = label.color;
12242 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12243 ctx.textBaseline = 'middle';
12244 if(showLabels(node.name, valAcum, node)) {
12248 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12249 mtxt = ctx.measureText(node.name + ": " + valAcum);
12250 boxWidth = mtxt.width+10;
12253 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12254 leftPadding = acum - config.labelOffset - boxWidth - inset;
12256 leftPadding = acum + config.labelOffset;
12260 ctx.textAlign = 'left';
12261 ctx.translate(x + inset + leftPadding, y + height/2);
12262 boxHeight = label.size+6;
12264 boxY = -boxHeight/2;
12265 ctx.fillStyle = "rgba(255,255,255,.8)";
12268 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12269 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12271 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12274 ctx.fillStyle = label.color;
12275 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12279 if(stringArray.length > 8) {
12280 ctx.textAlign = 'left';
12281 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12282 ctx.rotate(45* Math.PI / 180);
12283 ctx.fillText(node.name, 0, 0);
12285 ctx.textAlign = 'center';
12286 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12295 'contains': function(node, mpos) {
12296 var pos = node.pos.getc(true),
12297 width = node.getData('width'),
12298 height = node.getData('height'),
12299 config = node.getData('config'),
12300 algnPos = this.getAlignedPos(pos, width, height),
12301 x = algnPos.x, y = algnPos.y ,
12302 dimArray = node.getData('dimArray'),
12303 len = dimArray.length,
12305 horz = config.orientation == 'horizontal',
12306 fixedDim = (horz? height : width) / len;
12308 //bounding box check
12310 if(mpos.x < x || mpos.x > x + width
12311 || mpos.y > y + height || mpos.y < y) {
12315 if(mpos.x < x || mpos.x > x + width
12316 || mpos.y > y || mpos.y < y - height) {
12321 for(var i=0, l=dimArray.length; i<l; i++) {
12322 var dimi = dimArray[i];
12323 var url = Url.decode(node.getData('linkArray')[i]);
12325 var limit = y + fixedDim * i;
12326 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12328 'name': node.getData('stringArray')[i],
12329 'color': node.getData('colorArray')[i],
12330 'value': node.getData('valueArray')[i],
12331 'valuelabel': node.getData('valuelabelArray')[i],
12332 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12338 var limit = x + fixedDim * i;
12339 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12341 'name': node.getData('stringArray')[i],
12342 'color': node.getData('colorArray')[i],
12343 'value': node.getData('valueArray')[i],
12344 'valuelabel': node.getData('valuelabelArray')[i],
12345 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12360 A visualization that displays stacked bar charts.
12362 Constructor Options:
12364 See <Options.BarChart>.
12367 $jit.BarChart = new Class({
12369 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12373 initialize: function(opt) {
12374 this.controller = this.config =
12375 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12376 Label: { type: 'Native' }
12378 //set functions for showLabels and showAggregates
12379 var showLabels = this.config.showLabels,
12380 typeLabels = $.type(showLabels),
12381 showAggregates = this.config.showAggregates,
12382 typeAggregates = $.type(showAggregates);
12383 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12384 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12385 Options.Fx.clearCanvas = false;
12386 this.initializeViz();
12389 initializeViz: function() {
12390 var config = this.config, that = this;
12391 var nodeType = config.type.split(":")[0],
12392 horz = config.orientation == 'horizontal',
12394 var st = new $jit.ST({
12395 injectInto: config.injectInto,
12396 orientation: horz? 'left' : 'bottom',
12397 background: config.background,
12398 renderBackground: config.renderBackground,
12399 backgroundColor: config.backgroundColor,
12400 colorStop1: config.colorStop1,
12401 colorStop2: config.colorStop2,
12403 nodeCount: config.nodeCount,
12404 siblingOffset: config.barsOffset,
12406 withLabels: config.Label.type != 'Native',
12407 useCanvas: config.useCanvas,
12409 type: config.Label.type
12413 type: 'barchart-' + nodeType,
12422 enable: config.Tips.enable,
12425 onShow: function(tip, node, contains) {
12426 var elem = contains;
12427 config.Tips.onShow(tip, elem, node);
12428 if(elem.link != 'undefined' && elem.link != '') {
12429 document.body.style.cursor = 'pointer';
12432 onHide: function(call) {
12433 document.body.style.cursor = 'default';
12440 onClick: function(node, eventInfo, evt) {
12441 if(!config.Events.enable) return;
12442 var elem = eventInfo.getContains();
12443 config.Events.onClick(elem, eventInfo, evt);
12445 onMouseMove: function(node, eventInfo, evt) {
12446 if(!config.hoveredColor) return;
12448 var elem = eventInfo.getContains();
12449 that.select(node.id, elem.name, elem.index);
12451 that.select(false, false, false);
12455 onCreateLabel: function(domElement, node) {
12456 var labelConf = config.Label,
12457 valueArray = node.getData('valueArray'),
12458 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12459 grouped = config.type.split(':')[0] == 'grouped',
12460 horz = config.orientation == 'horizontal';
12462 wrapper: document.createElement('div'),
12463 aggregate: document.createElement('div'),
12464 label: document.createElement('div')
12467 var wrapper = nlbs.wrapper,
12468 label = nlbs.label,
12469 aggregate = nlbs.aggregate,
12470 wrapperStyle = wrapper.style,
12471 labelStyle = label.style,
12472 aggregateStyle = aggregate.style;
12473 //store node labels
12474 nodeLabels[node.id] = nlbs;
12476 wrapper.appendChild(label);
12477 wrapper.appendChild(aggregate);
12478 if(!config.showLabels(node.name, acum, node)) {
12479 labelStyle.display = 'none';
12481 if(!config.showAggregates(node.name, acum, node)) {
12482 aggregateStyle.display = 'none';
12484 wrapperStyle.position = 'relative';
12485 wrapperStyle.overflow = 'visible';
12486 wrapperStyle.fontSize = labelConf.size + 'px';
12487 wrapperStyle.fontFamily = labelConf.family;
12488 wrapperStyle.color = labelConf.color;
12489 wrapperStyle.textAlign = 'center';
12490 aggregateStyle.position = labelStyle.position = 'absolute';
12492 domElement.style.width = node.getData('width') + 'px';
12493 domElement.style.height = node.getData('height') + 'px';
12494 aggregateStyle.left = "0px";
12495 labelStyle.left = config.labelOffset + 'px';
12496 labelStyle.whiteSpace = "nowrap";
12497 label.innerHTML = node.name;
12499 domElement.appendChild(wrapper);
12501 onPlaceLabel: function(domElement, node) {
12502 if(!nodeLabels[node.id]) return;
12503 var labels = nodeLabels[node.id],
12504 wrapperStyle = labels.wrapper.style,
12505 labelStyle = labels.label.style,
12506 aggregateStyle = labels.aggregate.style,
12507 grouped = config.type.split(':')[0] == 'grouped',
12508 horz = config.orientation == 'horizontal',
12509 dimArray = node.getData('dimArray'),
12510 valArray = node.getData('valueArray'),
12511 nodeCount = node.getData('nodeCount'),
12512 valueLength = valArray.length;
12513 valuelabelArray = node.getData('valuelabelArray'),
12514 stringArray = node.getData('stringArray'),
12515 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12516 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12517 font = parseInt(wrapperStyle.fontSize, 10),
12518 domStyle = domElement.style,
12519 fixedDim = (horz? height : width) / valueLength;
12522 if(dimArray && valArray) {
12523 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12525 aggregateStyle.width = width - config.labelOffset + "px";
12526 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12527 if(dimArray[i] > 0) {
12528 acum+= valArray[i];
12531 if(config.showLabels(node.name, acum, node)) {
12532 labelStyle.display = '';
12534 labelStyle.display = 'none';
12536 if(config.showAggregates(node.name, acum, node)) {
12537 aggregateStyle.display = '';
12539 aggregateStyle.display = 'none';
12541 if(config.orientation == 'horizontal') {
12542 aggregateStyle.textAlign = 'right';
12543 labelStyle.textAlign = 'left';
12544 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12545 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12546 domElement.style.height = wrapperStyle.height = height + 'px';
12548 aggregateStyle.top = (-font - config.labelOffset) + 'px';
12549 labelStyle.top = (config.labelOffset + height) + 'px';
12550 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12551 domElement.style.height = wrapperStyle.height = height + 'px';
12552 if(stringArray.length > 8) {
12553 labels.label.className = "rotatedLabelReverse";
12554 labelStyle.textAlign = "left";
12555 labelStyle.top = config.labelOffset + height + width/2 + "px";
12561 labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12562 labels.aggregate.innerHTML = "";
12567 maxValue = Math.max.apply(null,dimArray);
12568 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12569 valueLabelDim = 50;
12570 valueLabel = document.createElement('div');
12571 valueLabel.innerHTML = valuelabelArray[i];
12572 // valueLabel.class = "rotatedLabel";
12573 valueLabel.className = "rotatedLabel";
12574 valueLabel.style.position = "absolute";
12575 valueLabel.style.textAlign = "left";
12576 valueLabel.style.verticalAlign = "middle";
12577 valueLabel.style.height = valueLabelDim + "px";
12578 valueLabel.style.width = valueLabelDim + "px";
12579 valueLabel.style.top = (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12580 valueLabel.style.left = (fixedDim * i) + "px";
12581 labels.wrapper.appendChild(valueLabel);
12584 labels.aggregate.innerHTML = acum;
12591 var size = st.canvas.getSize(),
12592 l = config.nodeCount,
12593 margin = config.Margin;
12594 title = config.Title;
12595 subtitle = config.Subtitle,
12596 grouped = config.type.split(':')[0] == 'grouped',
12597 margin = config.Margin,
12598 ticks = config.Ticks,
12599 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12600 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12601 horz = config.orientation == 'horizontal',
12602 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12603 fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12604 whiteSpace = size.width - (marginWidth + (fixedDim * l));
12605 //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
12606 if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12608 //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12609 if(!grouped && !horz) {
12610 st.config.siblingOffset = whiteSpace/(l+1);
12617 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12618 if(config.Ticks.enable) {
12619 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;
12621 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12624 st.config.offsetY = -size.height/2 + margin.bottom
12625 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12626 if(config.Ticks.enable) {
12627 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12629 st.config.offsetX = (margin.right - margin.left)/2;
12633 this.canvas = this.st.canvas;
12636 renderTitle: function() {
12637 var canvas = this.canvas,
12638 size = canvas.getSize(),
12639 config = this.config,
12640 margin = config.Margin,
12641 label = config.Label,
12642 title = config.Title;
12643 ctx = canvas.getCtx();
12644 ctx.fillStyle = title.color;
12645 ctx.textAlign = 'left';
12646 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12647 if(label.type == 'Native') {
12648 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12652 renderSubtitle: function() {
12653 var canvas = this.canvas,
12654 size = canvas.getSize(),
12655 config = this.config,
12656 margin = config.Margin,
12657 label = config.Label,
12658 subtitle = config.Subtitle,
12659 nodeCount = config.nodeCount,
12660 horz = config.orientation == 'horizontal' ? true : false,
12661 ctx = canvas.getCtx();
12662 ctx.fillStyle = title.color;
12663 ctx.textAlign = 'left';
12664 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12665 if(label.type == 'Native') {
12666 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12670 renderScrollNote: function() {
12671 var canvas = this.canvas,
12672 size = canvas.getSize(),
12673 config = this.config,
12674 margin = config.Margin,
12675 label = config.Label,
12676 note = config.ScrollNote;
12677 ctx = canvas.getCtx();
12678 ctx.fillStyle = title.color;
12679 title = config.Title;
12680 ctx.textAlign = 'center';
12681 ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12682 if(label.type == 'Native') {
12683 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12687 renderTicks: function() {
12689 var canvas = this.canvas,
12690 size = canvas.getSize(),
12691 config = this.config,
12692 margin = config.Margin,
12693 ticks = config.Ticks,
12694 title = config.Title,
12695 subtitle = config.Subtitle,
12696 label = config.Label,
12697 shadow = config.shadow;
12698 horz = config.orientation == 'horizontal',
12699 maxValue = this.getMaxValue(),
12700 maxTickValue = Math.ceil(maxValue*.1)*10;
12701 if(maxTickValue == maxValue) {
12702 var length = maxTickValue.toString().length;
12703 maxTickValue = maxTickValue + parseInt(pad(1,length));
12705 grouped = config.type.split(':')[0] == 'grouped',
12707 labelIncrement = maxTickValue/ticks.segments,
12708 ctx = canvas.getCtx();
12709 ctx.strokeStyle = ticks.color;
12710 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12712 ctx.textAlign = 'center';
12713 ctx.textBaseline = 'middle';
12715 idLabel = canvas.id + "-label";
12717 container = document.getElementById(idLabel);
12721 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12722 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12723 segmentLength = grid/ticks.segments;
12724 ctx.fillStyle = ticks.color;
12726 (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12727 size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12729 while(axis<=grid) {
12730 ctx.fillStyle = ticks.color;
12731 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);
12732 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));
12733 ctx.fillStyle = label.color;
12735 if(label.type == 'Native' && config.showLabels) {
12736 ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12738 axis += segmentLength;
12739 labelValue += labelIncrement;
12744 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12745 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12746 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)),
12747 segmentLength = grid/ticks.segments;
12748 ctx.fillStyle = ticks.color;
12749 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));
12751 while(axis>=grid) {
12753 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12754 ctx.rotate(0 * Math.PI / 180 );
12755 ctx.fillStyle = label.color;
12756 if(config.showLabels) {
12757 if(label.type == 'Native') {
12758 ctx.fillText(labelValue, 0, 0);
12760 //html labels on y axis
12761 labelDiv = document.createElement('div');
12762 labelDiv.innerHTML = labelValue;
12763 labelDiv.className = "rotatedLabel";
12764 // labelDiv.class = "rotatedLabel";
12765 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12766 labelDiv.style.left = margin.left + "px";
12767 labelDiv.style.width = labelDim + "px";
12768 labelDiv.style.height = labelDim + "px";
12769 labelDiv.style.textAlign = "center";
12770 labelDiv.style.verticalAlign = "middle";
12771 labelDiv.style.position = "absolute";
12772 container.appendChild(labelDiv);
12776 ctx.fillStyle = ticks.color;
12777 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 );
12778 htmlOrigin += segmentLength;
12779 axis += segmentLength;
12780 labelValue += labelIncrement;
12789 renderBackground: function() {
12790 var canvas = this.canvas,
12791 config = this.config,
12792 backgroundColor = config.backgroundColor,
12793 size = canvas.getSize(),
12794 ctx = canvas.getCtx();
12795 ctx.fillStyle = backgroundColor;
12796 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12801 Loads JSON data into the visualization.
12805 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>.
12809 var barChart = new $jit.BarChart(options);
12810 barChart.loadJSON(json);
12813 loadJSON: function(json) {
12814 if(this.busy) return;
12817 var prefix = $.time(),
12820 name = $.splat(json.label),
12821 color = $.splat(json.color || this.colors),
12822 config = this.config,
12823 gradient = !!config.type.split(":")[1],
12824 renderBackground = config.renderBackground,
12825 animate = config.animate,
12826 ticks = config.Ticks,
12827 title = config.Title,
12828 note = config.ScrollNote,
12829 subtitle = config.Subtitle,
12830 horz = config.orientation == 'horizontal',
12832 colorLength = color.length,
12833 nameLength = name.length;
12834 groupTotalValue = 0;
12835 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12836 var val = values[i];
12837 var valArray = $.splat(val.values);
12838 groupTotalValue += parseInt(valArray.sum());
12841 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12842 var val = values[i];
12843 var valArray = $.splat(values[i].values);
12844 var valuelabelArray = $.splat(values[i].valuelabels);
12845 var linkArray = $.splat(values[i].links);
12846 var titleArray = $.splat(values[i].titles);
12847 var barTotalValue = valArray.sum();
12850 'id': prefix + val.label,
12855 '$linkArray': linkArray,
12856 '$gvl': val.gvaluelabel,
12857 '$titleArray': titleArray,
12858 '$valueArray': valArray,
12859 '$valuelabelArray': valuelabelArray,
12860 '$colorArray': color,
12861 '$colorMono': $.splat(color[i % colorLength]),
12862 '$stringArray': name,
12863 '$barTotalValue': barTotalValue,
12864 '$groupTotalValue': groupTotalValue,
12865 '$nodeCount': values.length,
12866 '$gradient': gradient,
12873 'id': prefix + '$root',
12884 this.normalizeDims();
12886 if(renderBackground) {
12887 this.renderBackground();
12890 if(!animate && ticks.enable) {
12891 this.renderTicks();
12893 if(!animate && note.text) {
12894 this.renderScrollNote();
12896 if(!animate && title.text) {
12897 this.renderTitle();
12899 if(!animate && subtitle.text) {
12900 this.renderSubtitle();
12903 st.select(st.root);
12907 modes: ['node-property:width:dimArray'],
12909 onComplete: function() {
12915 modes: ['node-property:height:dimArray'],
12917 onComplete: function() {
12930 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.
12934 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
12935 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
12940 barChart.updateJSON(json, {
12941 onComplete: function() {
12942 alert('update complete!');
12947 updateJSON: function(json, onComplete) {
12948 if(this.busy) return;
12952 var graph = st.graph;
12953 var values = json.values;
12954 var animate = this.config.animate;
12956 var horz = this.config.orientation == 'horizontal';
12957 $.each(values, function(v) {
12958 var n = graph.getByName(v.label);
12960 n.setData('valueArray', $.splat(v.values));
12962 n.setData('stringArray', $.splat(json.label));
12966 this.normalizeDims();
12968 st.select(st.root);
12972 modes: ['node-property:width:dimArray'],
12974 onComplete: function() {
12976 onComplete && onComplete.onComplete();
12981 modes: ['node-property:height:dimArray'],
12983 onComplete: function() {
12985 onComplete && onComplete.onComplete();
12992 //adds the little brown bar when hovering the node
12993 select: function(id, name) {
12995 if(!this.config.hoveredColor) return;
12996 var s = this.selected;
12997 if(s.id != id || s.name != name) {
13000 s.color = this.config.hoveredColor;
13001 this.st.graph.eachNode(function(n) {
13003 n.setData('border', s);
13005 n.setData('border', false);
13015 Returns an object containing as keys the legend names and as values hex strings with color values.
13020 var legend = barChart.getLegend();
13023 getLegend: function() {
13024 var legend = new Array();
13025 var name = new Array();
13026 var color = new Array();
13028 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13031 var colors = n.getData('colorArray'),
13032 len = colors.length;
13033 $.each(n.getData('stringArray'), function(s, i) {
13034 color[i] = colors[i % len];
13037 legend['name'] = name;
13038 legend['color'] = color;
13043 Method: getMaxValue
13045 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13050 var ans = barChart.getMaxValue();
13053 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13058 //will return 100 for all BarChart instances,
13059 //displaying all of them with the same scale
13060 $jit.BarChart.implement({
13061 'getMaxValue': function() {
13068 getMaxValue: function() {
13069 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13070 this.st.graph.eachNode(function(n) {
13071 var valArray = n.getData('valueArray'),
13073 if(!valArray) return;
13075 $.each(valArray, function(v) {
13079 acum = Math.max.apply(null, valArray);
13081 maxValue = maxValue>acum? maxValue:acum;
13086 setBarType: function(type) {
13087 this.config.type = type;
13088 this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13091 normalizeDims: function() {
13092 //number of elements
13093 var root = this.st.graph.getNode(this.st.root), l=0;
13094 root.eachAdjacency(function() {
13097 var maxValue = this.getMaxValue() || 1,
13098 size = this.st.canvas.getSize(),
13099 config = this.config,
13100 margin = config.Margin,
13101 ticks = config.Ticks,
13102 title = config.Title,
13103 subtitle = config.Subtitle,
13104 grouped = config.type.split(':')[0] == 'grouped',
13105 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13106 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13107 horz = config.orientation == 'horizontal',
13108 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13109 animate = config.animate,
13110 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13112 - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13113 dim1 = horz? 'height':'width',
13114 dim2 = horz? 'width':'height',
13115 basic = config.type.split(':')[0] == 'basic';
13118 var maxTickValue = Math.ceil(maxValue*.1)*10;
13119 if(maxTickValue == maxValue) {
13120 var length = maxTickValue.toString().length;
13121 maxTickValue = maxTickValue + parseInt(pad(1,length));
13124 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13127 this.st.graph.eachNode(function(n) {
13128 var acum = 0, animateValue = [];
13129 $.each(n.getData('valueArray'), function(v) {
13131 animateValue.push(0);
13135 fixedDim = animateValue.length * 40;
13137 n.setData(dim1, fixedDim);
13141 n.setData(dim2, acum * height / maxValue, 'end');
13142 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13143 return n * height / maxValue;
13145 var dimArray = n.getData('dimArray');
13147 n.setData('dimArray', animateValue);
13153 n.setData(dim2, acum * height / maxTickValue);
13154 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13155 return n * height / maxTickValue;
13158 n.setData(dim2, acum * height / maxValue);
13159 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13160 return n * height / maxValue;
13168 //funnel chart options
13171 Options.FunnelChart = {
13175 type: 'stacked', //stacked, grouped, : gradient
13176 labelOffset: 3, //label offset
13177 barsOffset: 0, //distance between bars
13178 hoveredColor: '#9fd4ff',
13179 orientation: 'vertical',
13180 showAggregates: true,
13193 $jit.ST.Plot.NodeTypes.implement({
13194 'funnelchart-basic' : {
13195 'render' : function(node, canvas) {
13196 var pos = node.pos.getc(true),
13197 width = node.getData('width'),
13198 height = node.getData('height'),
13199 algnPos = this.getAlignedPos(pos, width, height),
13200 x = algnPos.x, y = algnPos.y,
13201 dimArray = node.getData('dimArray'),
13202 valueArray = node.getData('valueArray'),
13203 valuelabelArray = node.getData('valuelabelArray'),
13204 linkArray = node.getData('linkArray'),
13205 colorArray = node.getData('colorArray'),
13206 colorLength = colorArray.length,
13207 stringArray = node.getData('stringArray');
13208 var ctx = canvas.getCtx(),
13210 border = node.getData('border'),
13211 gradient = node.getData('gradient'),
13212 config = node.getData('config'),
13213 horz = config.orientation == 'horizontal',
13214 aggregates = config.showAggregates,
13215 showLabels = config.showLabels,
13216 label = config.Label,
13217 size = canvas.getSize(),
13218 labelOffset = config.labelOffset + 10;
13219 minWidth = width * .25;
13222 if (colorArray && dimArray && stringArray) {
13225 // horizontal lines
13226 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13227 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13229 if(label.type == 'Native') {
13230 if(showLabels(node.name, valAcum, node)) {
13231 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13232 var stringValue = stringArray[i];
13233 var valueLabel = String(valuelabelArray[i]);
13234 var mV = ctx.measureText(stringValue);
13235 var mVL = ctx.measureText(valueLabel);
13236 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13237 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13238 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13239 var bottomWidth = minWidth + ((acum) * ratio);
13240 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13241 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13242 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13243 // ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13247 ctx.moveTo(bottomWidth/2,y - acum); //
13248 ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight); // top right
13249 ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight); // bottom right
13253 ctx.moveTo(-bottomWidth/2,y - acum); //
13254 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight); // top right
13255 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight); // bottom right
13260 acum += (dimArray[i] || 0);
13261 valAcum += (valueArray[i] || 0);
13268 //funnel segments and labels
13269 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13270 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13271 var colori = colorArray[i % colorLength];
13272 if(label.type == 'Native') {
13273 var stringValue = stringArray[i];
13274 var valueLabel = String(valuelabelArray[i]);
13275 var mV = ctx.measureText(stringValue);
13276 var mVL = ctx.measureText(valueLabel);
13281 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13282 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13283 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13284 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13286 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13287 var bottomWidth = minWidth + ((acum) * ratio);
13288 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13293 linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13294 var colorRgb = $.hexToRgb(colori);
13295 var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
13296 function(v) { return (v * .5) >> 0; });
13297 linear.addColorStop(0, 'rgba('+color+',1)');
13298 linear.addColorStop(0.5, 'rgba('+colorRgb+',1)');
13299 linear.addColorStop(1, 'rgba('+color+',1)');
13300 ctx.fillStyle = linear;
13304 ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13305 ctx.lineTo(topWidth/2,y - acum - dimArray[i]); // top right
13306 ctx.lineTo(bottomWidth/2,y - acum); // bottom right
13307 ctx.lineTo(-bottomWidth/2,y - acum); // bottom left
13312 if(border && border.name == stringArray[i]) {
13314 opt.dimValue = dimArray[i];
13321 ctx.strokeStyle = border.color;
13323 //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13327 if(label.type == 'Native') {
13329 ctx.fillStyle = ctx.strokeStyle = label.color;
13330 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13331 ctx.textBaseline = 'middle';
13333 acumValueLabel = valAcum;
13335 if(showLabels(node.name, valAcum, node)) {
13338 ctx.textAlign = 'left';
13339 ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13340 ctx.textAlign = 'right';
13341 ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13346 acum += (dimArray[i] || 0);
13347 valAcum += (valueArray[i] || 0);
13353 'contains': function(node, mpos) {
13354 var pos = node.pos.getc(true),
13355 width = node.getData('width'),
13356 height = node.getData('height'),
13357 algnPos = this.getAlignedPos(pos, width, height),
13358 x = algnPos.x, y = algnPos.y,
13359 dimArray = node.getData('dimArray'),
13360 config = node.getData('config'),
13361 st = node.getData('st'),
13363 horz = config.orientation == 'horizontal',
13364 minWidth = width * .25;
13366 canvas = node.getData('canvas'),
13367 size = canvas.getSize(),
13368 offsetY = st.config.offsetY;
13369 //bounding box check
13371 if(mpos.y > y || mpos.y < y - height) {
13375 var newY = Math.abs(mpos.y + offsetY);
13376 var bound = minWidth + (newY * ratio);
13377 var boundLeft = -bound/2;
13378 var boundRight = bound/2;
13379 if(mpos.x < boundLeft || mpos.x > boundRight ) {
13385 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13386 var dimi = dimArray[i];
13390 var url = Url.decode(node.getData('linkArray')[i]);
13392 var intersec = acum;
13393 if(mpos.y >= intersec) {
13395 'name': node.getData('stringArray')[i],
13396 'color': node.getData('colorArray')[i],
13397 'value': node.getData('valueArray')[i],
13398 'percentage': node.getData('percentageArray')[i],
13399 'valuelabel': node.getData('valuelabelArray')[i],
13414 A visualization that displays funnel charts.
13416 Constructor Options:
13418 See <Options.FunnelChart>.
13421 $jit.FunnelChart = new Class({
13423 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13427 initialize: function(opt) {
13428 this.controller = this.config =
13429 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13430 Label: { type: 'Native' }
13432 //set functions for showLabels and showAggregates
13433 var showLabels = this.config.showLabels,
13434 typeLabels = $.type(showLabels),
13435 showAggregates = this.config.showAggregates,
13436 typeAggregates = $.type(showAggregates);
13437 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13438 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13439 Options.Fx.clearCanvas = false;
13440 this.initializeViz();
13443 initializeViz: function() {
13444 var config = this.config, that = this;
13445 var nodeType = config.type.split(":")[0],
13446 horz = config.orientation == 'horizontal',
13448 var st = new $jit.ST({
13449 injectInto: config.injectInto,
13450 orientation: horz? 'left' : 'bottom',
13452 background: config.background,
13453 renderBackground: config.renderBackground,
13454 backgroundColor: config.backgroundColor,
13455 colorStop1: config.colorStop1,
13456 colorStop2: config.colorStop2,
13457 siblingOffset: config.segmentOffset,
13459 withLabels: config.Label.type != 'Native',
13460 useCanvas: config.useCanvas,
13462 type: config.Label.type
13466 type: 'funnelchart-' + nodeType,
13475 enable: config.Tips.enable,
13478 onShow: function(tip, node, contains) {
13479 var elem = contains;
13480 config.Tips.onShow(tip, elem, node);
13481 if(elem.link != 'undefined' && elem.link != '') {
13482 document.body.style.cursor = 'pointer';
13485 onHide: function(call) {
13486 document.body.style.cursor = 'default';
13493 onClick: function(node, eventInfo, evt) {
13494 if(!config.Events.enable) return;
13495 var elem = eventInfo.getContains();
13496 config.Events.onClick(elem, eventInfo, evt);
13498 onMouseMove: function(node, eventInfo, evt) {
13499 if(!config.hoveredColor) return;
13501 var elem = eventInfo.getContains();
13502 that.select(node.id, elem.name, elem.index);
13504 that.select(false, false, false);
13508 onCreateLabel: function(domElement, node) {
13509 var labelConf = config.Label,
13510 valueArray = node.getData('valueArray'),
13511 idArray = node.getData('idArray'),
13512 valuelabelArray = node.getData('valuelabelArray'),
13513 stringArray = node.getData('stringArray');
13514 size = st.canvas.getSize()
13517 for(var i=0, l=valueArray.length; i<l; i++) {
13519 wrapper: document.createElement('div'),
13520 valueLabel: document.createElement('div'),
13521 label: document.createElement('div')
13523 var wrapper = nlbs.wrapper,
13524 label = nlbs.label,
13525 valueLabel = nlbs.valueLabel,
13526 wrapperStyle = wrapper.style,
13527 labelStyle = label.style,
13528 valueLabelStyle = valueLabel.style;
13529 //store node labels
13530 nodeLabels[idArray[i]] = nlbs;
13532 wrapper.appendChild(label);
13533 wrapper.appendChild(valueLabel);
13535 wrapperStyle.position = 'relative';
13536 wrapperStyle.overflow = 'visible';
13537 wrapperStyle.fontSize = labelConf.size + 'px';
13538 wrapperStyle.fontFamily = labelConf.family;
13539 wrapperStyle.color = labelConf.color;
13540 wrapperStyle.textAlign = 'center';
13541 wrapperStyle.width = size.width + 'px';
13542 valueLabelStyle.position = labelStyle.position = 'absolute';
13543 valueLabelStyle.left = labelStyle.left = '0px';
13544 valueLabelStyle.width = (size.width/3) + 'px';
13545 valueLabelStyle.textAlign = 'right';
13546 label.innerHTML = stringArray[i];
13547 valueLabel.innerHTML = valuelabelArray[i];
13548 domElement.id = prefix+'funnel';
13549 domElement.style.width = size.width + 'px';
13551 domElement.appendChild(wrapper);
13555 onPlaceLabel: function(domElement, node) {
13557 var dimArray = node.getData('dimArray'),
13558 idArray = node.getData('idArray'),
13559 valueArray = node.getData('valueArray'),
13560 valuelabelArray = node.getData('valuelabelArray'),
13561 stringArray = node.getData('stringArray');
13562 size = st.canvas.getSize(),
13563 pos = node.pos.getc(true),
13564 domElement.style.left = "0px",
13565 domElement.style.top = "0px",
13566 minWidth = node.getData('width') * .25,
13568 pos = node.pos.getc(true),
13569 labelConf = config.Label;
13572 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13574 var labels = nodeLabels[idArray[i]],
13575 wrapperStyle = labels.wrapper.style,
13576 labelStyle = labels.label.style,
13577 valueLabelStyle = labels.valueLabel.style;
13578 var bottomWidth = minWidth + (acum * ratio);
13580 font = parseInt(wrapperStyle.fontSize, 10),
13581 domStyle = domElement.style;
13585 wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13586 valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13587 labelStyle.left = (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13589 acum += (dimArray[i] || 0);
13597 var size = st.canvas.getSize(),
13598 margin = config.Margin;
13599 title = config.Title;
13600 subtitle = config.Subtitle;
13603 st.config.offsetY = -size.height/2 + margin.bottom
13604 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13606 st.config.offsetX = (margin.right - margin.left)/2;
13610 this.canvas = this.st.canvas;
13613 renderTitle: function() {
13614 var canvas = this.canvas,
13615 size = canvas.getSize(),
13616 config = this.config,
13617 margin = config.Margin,
13618 label = config.Label,
13619 title = config.Title;
13620 ctx = canvas.getCtx();
13621 ctx.fillStyle = title.color;
13622 ctx.textAlign = 'left';
13623 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13624 if(label.type == 'Native') {
13625 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13629 renderSubtitle: function() {
13630 var canvas = this.canvas,
13631 size = canvas.getSize(),
13632 config = this.config,
13633 margin = config.Margin,
13634 label = config.Label,
13635 subtitle = config.Subtitle;
13636 ctx = canvas.getCtx();
13637 ctx.fillStyle = title.color;
13638 ctx.textAlign = 'left';
13639 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13640 if(label.type == 'Native') {
13641 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13646 renderDropShadow: function() {
13647 var canvas = this.canvas,
13648 size = canvas.getSize(),
13649 config = this.config,
13650 margin = config.Margin,
13651 horz = config.orientation == 'horizontal',
13652 label = config.Label,
13653 title = config.Title,
13654 shadowThickness = 4,
13655 subtitle = config.Subtitle,
13656 ctx = canvas.getCtx(),
13657 minwidth = (size.width/8) * .25,
13658 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13659 topMargin = (title.text? title.size + title.offset : 0) + margin.top,
13660 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13661 - (config.showLabels && (config.Label.size + config.labelOffset)),
13663 topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13664 topY = (-size.height/2) + topMargin - shadowThickness;
13665 bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13666 bottomWidth = minwidth + shadowThickness;
13668 ctx.fillStyle = "rgba(0,0,0,.2)";
13669 ctx.moveTo(0,topY);
13670 ctx.lineTo(-topWidth/2,topY); //top left
13671 ctx.lineTo(-bottomWidth/2,bottomY); // bottom left
13672 ctx.lineTo(bottomWidth/2,bottomY); // bottom right
13673 ctx.lineTo(topWidth/2,topY); // top right
13680 renderBackground: function() {
13681 var canvas = this.canvas,
13682 config = this.config,
13683 backgroundColor = config.backgroundColor,
13684 size = canvas.getSize(),
13685 ctx = canvas.getCtx();
13686 //ctx.globalCompositeOperation = "destination-over";
13687 ctx.fillStyle = backgroundColor;
13688 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13692 loadJSON: function(json) {
13693 if(this.busy) return;
13695 var prefix = $.time(),
13698 name = $.splat(json.label),
13699 color = $.splat(json.color || this.colors),
13700 config = this.config,
13701 canvas = this.canvas,
13702 gradient = !!config.type.split(":")[1],
13703 animate = config.animate,
13704 title = config.Title,
13705 subtitle = config.Subtitle,
13706 renderBackground = config.renderBackground,
13707 horz = config.orientation == 'horizontal',
13709 colorLength = color.length,
13710 nameLength = name.length,
13713 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13714 var val = values[i];
13715 var valArray = $.splat(val.values);
13716 totalValue += parseInt(valArray.sum());
13720 var idArray = new Array();
13721 var valArray = new Array();
13722 var valuelabelArray = new Array();
13723 var linkArray = new Array();
13724 var titleArray = new Array();
13725 var percentageArray = new Array();
13727 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13728 var val = values[i];
13729 idArray[i] = $.splat(prefix + val.label);
13730 valArray[i] = $.splat(val.values);
13731 valuelabelArray[i] = $.splat(val.valuelabels);
13732 linkArray[i] = $.splat(val.links);
13733 titleArray[i] = $.splat(val.titles);
13734 percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13740 valArray.reverse();
13741 valuelabelArray.reverse();
13742 linkArray.reverse();
13743 titleArray.reverse();
13744 percentageArray.reverse();
13749 'id': prefix + val.label,
13754 '$idArray': idArray,
13755 '$linkArray': linkArray,
13756 '$titleArray': titleArray,
13757 '$valueArray': valArray,
13758 '$valuelabelArray': valuelabelArray,
13759 '$colorArray': color,
13760 '$colorMono': $.splat(color[i % colorLength]),
13761 '$stringArray': name.reverse(),
13762 '$gradient': gradient,
13764 '$percentageArray' : percentageArray,
13772 'id': prefix + '$root',
13783 this.normalizeDims();
13785 if(renderBackground) {
13786 this.renderBackground();
13788 if(!animate && title.text) {
13789 this.renderTitle();
13791 if(!animate && subtitle.text) {
13792 this.renderSubtitle();
13794 if(typeof FlashCanvas == "undefined") {
13795 this.renderDropShadow();
13798 st.select(st.root);
13802 modes: ['node-property:width:dimArray'],
13804 onComplete: function() {
13810 modes: ['node-property:height:dimArray'],
13812 onComplete: function() {
13825 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.
13829 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13830 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13835 barChart.updateJSON(json, {
13836 onComplete: function() {
13837 alert('update complete!');
13842 updateJSON: function(json, onComplete) {
13843 if(this.busy) return;
13847 var graph = st.graph;
13848 var values = json.values;
13849 var animate = this.config.animate;
13851 var horz = this.config.orientation == 'horizontal';
13852 $.each(values, function(v) {
13853 var n = graph.getByName(v.label);
13855 n.setData('valueArray', $.splat(v.values));
13857 n.setData('stringArray', $.splat(json.label));
13861 this.normalizeDims();
13863 st.select(st.root);
13867 modes: ['node-property:width:dimArray'],
13869 onComplete: function() {
13871 onComplete && onComplete.onComplete();
13876 modes: ['node-property:height:dimArray'],
13878 onComplete: function() {
13880 onComplete && onComplete.onComplete();
13887 //adds the little brown bar when hovering the node
13888 select: function(id, name) {
13890 if(!this.config.hoveredColor) return;
13891 var s = this.selected;
13892 if(s.id != id || s.name != name) {
13895 s.color = this.config.hoveredColor;
13896 this.st.graph.eachNode(function(n) {
13898 n.setData('border', s);
13900 n.setData('border', false);
13910 Returns an object containing as keys the legend names and as values hex strings with color values.
13915 var legend = barChart.getLegend();
13918 getLegend: function() {
13919 var legend = new Array();
13920 var name = new Array();
13921 var color = new Array();
13923 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13926 var colors = n.getData('colorArray'),
13927 len = colors.length;
13928 $.each(n.getData('stringArray'), function(s, i) {
13929 color[i] = colors[i % len];
13932 legend['name'] = name;
13933 legend['color'] = color;
13938 Method: getMaxValue
13940 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13945 var ans = barChart.getMaxValue();
13948 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13953 //will return 100 for all BarChart instances,
13954 //displaying all of them with the same scale
13955 $jit.BarChart.implement({
13956 'getMaxValue': function() {
13963 getMaxValue: function() {
13964 var maxValue = 0, stacked = true;
13965 this.st.graph.eachNode(function(n) {
13966 var valArray = n.getData('valueArray'),
13968 if(!valArray) return;
13970 $.each(valArray, function(v) {
13974 acum = Math.max.apply(null, valArray);
13976 maxValue = maxValue>acum? maxValue:acum;
13981 setBarType: function(type) {
13982 this.config.type = type;
13983 this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
13986 normalizeDims: function() {
13987 //number of elements
13988 var root = this.st.graph.getNode(this.st.root), l=0;
13989 root.eachAdjacency(function() {
13992 var maxValue = this.getMaxValue() || 1,
13993 size = this.st.canvas.getSize(),
13994 config = this.config,
13995 margin = config.Margin,
13996 title = config.Title,
13997 subtitle = config.Subtitle,
13998 marginWidth = margin.left + margin.right,
13999 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14000 horz = config.orientation == 'horizontal',
14001 animate = config.animate,
14002 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14004 - (config.showLabels && (config.Label.size + config.labelOffset)),
14005 dim1 = horz? 'height':'width',
14006 dim2 = horz? 'width':'height';
14009 minWidth = size.width/8;
14013 this.st.graph.eachNode(function(n) {
14014 var acum = 0, animateValue = [];
14015 $.each(n.getData('valueArray'), function(v) {
14017 animateValue.push(0);
14019 n.setData(dim1, minWidth);
14022 n.setData(dim2, acum * height / maxValue, 'end');
14023 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14024 return n * height / maxValue;
14026 var dimArray = n.getData('dimArray');
14028 n.setData('dimArray', animateValue);
14031 n.setData(dim2, acum * height / maxValue);
14032 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14033 return n * height / maxValue;
14044 * File: Options.PieChart.js
14048 Object: Options.PieChart
14050 <PieChart> options.
14051 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14057 Options.PieChart = {
14063 hoveredColor: '#9fd4ff',
14065 resizeLabels: false,
14066 updateHeights: false
14075 var pie = new $jit.PieChart({
14078 type: 'stacked:gradient'
14085 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14086 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14087 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14088 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14089 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14090 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14091 showLabels - (boolean) Default's *true*. Display the name of the slots.
14092 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.
14093 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.
14096 Options.PieChart = {
14100 offset: 25, // page offset
14102 labelOffset: 3, // label offset
14103 type: 'stacked', // gradient
14105 hoveredColor: '#9fd4ff',
14116 resizeLabels: false,
14118 //only valid for mono-valued datasets
14119 updateHeights: false
14123 * Class: Layouts.Radial
14125 * Implements a Radial Layout.
14129 * <RGraph>, <Hypertree>
14132 Layouts.Radial = new Class({
14137 * Computes nodes' positions.
14141 * property - _optional_ A <Graph.Node> position property to store the new
14142 * positions. Possible values are 'pos', 'end' or 'start'.
14145 compute : function(property) {
14146 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14147 NodeDim.compute(this.graph, prop, this.config);
14148 this.graph.computeLevels(this.root, 0, "ignore");
14149 var lengthFunc = this.createLevelDistanceFunc();
14150 this.computeAngularWidths(prop);
14151 this.computePositions(prop, lengthFunc);
14157 * Performs the main algorithm for computing node positions.
14159 computePositions : function(property, getLength) {
14160 var propArray = property;
14161 var graph = this.graph;
14162 var root = graph.getNode(this.root);
14163 var parent = this.parent;
14164 var config = this.config;
14166 for ( var i=0, l=propArray.length; i < l; i++) {
14167 var pi = propArray[i];
14168 root.setPos($P(0, 0), pi);
14169 root.setData('span', Math.PI * 2, pi);
14177 graph.eachBFS(this.root, function(elem) {
14178 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14179 var angleInit = elem.angleSpan.begin;
14180 var len = getLength(elem);
14181 //Calculate the sum of all angular widths
14182 var totalAngularWidths = 0, subnodes = [], maxDim = {};
14183 elem.eachSubnode(function(sib) {
14184 totalAngularWidths += sib._treeAngularWidth;
14186 for ( var i=0, l=propArray.length; i < l; i++) {
14187 var pi = propArray[i], dim = sib.getData('dim', pi);
14188 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14190 subnodes.push(sib);
14192 //Maintain children order
14193 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14194 if (parent && parent.id == elem.id && subnodes.length > 0
14195 && subnodes[0].dist) {
14196 subnodes.sort(function(a, b) {
14197 return (a.dist >= b.dist) - (a.dist <= b.dist);
14200 //Calculate nodes positions.
14201 for (var k = 0, ls=subnodes.length; k < ls; k++) {
14202 var child = subnodes[k];
14203 if (!child._flag) {
14204 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14205 var theta = angleInit + angleProportion / 2;
14207 for ( var i=0, l=propArray.length; i < l; i++) {
14208 var pi = propArray[i];
14209 child.setPos($P(theta, len), pi);
14210 child.setData('span', angleProportion, pi);
14211 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14214 child.angleSpan = {
14216 end : angleInit + angleProportion
14218 angleInit += angleProportion;
14225 * Method: setAngularWidthForNodes
14227 * Sets nodes angular widths.
14229 setAngularWidthForNodes : function(prop) {
14230 this.graph.eachBFS(this.root, function(elem, i) {
14231 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14232 elem._angularWidth = diamValue / i;
14237 * Method: setSubtreesAngularWidth
14239 * Sets subtrees angular widths.
14241 setSubtreesAngularWidth : function() {
14243 this.graph.eachNode(function(elem) {
14244 that.setSubtreeAngularWidth(elem);
14249 * Method: setSubtreeAngularWidth
14251 * Sets the angular width for a subtree.
14253 setSubtreeAngularWidth : function(elem) {
14254 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14255 elem.eachSubnode(function(child) {
14256 that.setSubtreeAngularWidth(child);
14257 sumAW += child._treeAngularWidth;
14259 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14263 * Method: computeAngularWidths
14265 * Computes nodes and subtrees angular widths.
14267 computeAngularWidths : function(prop) {
14268 this.setAngularWidthForNodes(prop);
14269 this.setSubtreesAngularWidth();
14276 * File: Sunburst.js
14282 A radial space filling tree visualization.
14286 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14290 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.
14294 All <Loader> methods
14296 Constructor Options:
14298 Inherits options from
14301 - <Options.Controller>
14307 - <Options.NodeStyles>
14308 - <Options.Navigation>
14310 Additionally, there are other parameters and some default values changed
14312 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14313 levelDistance - (number) Default's *100*. The distance between levels of the tree.
14314 Node.type - Described in <Options.Node>. Default's to *multipie*.
14315 Node.height - Described in <Options.Node>. Default's *0*.
14316 Edge.type - Described in <Options.Edge>. Default's *none*.
14317 Label.textAlign - Described in <Options.Label>. Default's *start*.
14318 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14320 Instance Properties:
14322 canvas - Access a <Canvas> instance.
14323 graph - Access a <Graph> instance.
14324 op - Access a <Sunburst.Op> instance.
14325 fx - Access a <Sunburst.Plot> instance.
14326 labels - Access a <Sunburst.Label> interface implementation.
14330 $jit.Sunburst = new Class({
14332 Implements: [ Loader, Extras, Layouts.Radial ],
14334 initialize: function(controller) {
14335 var $Sunburst = $jit.Sunburst;
14338 interpolation: 'linear',
14339 levelDistance: 100,
14341 'type': 'multipie',
14348 textAlign: 'start',
14349 textBaseline: 'middle'
14353 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14354 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14356 var canvasConfig = this.config;
14357 if(canvasConfig.useCanvas) {
14358 this.canvas = canvasConfig.useCanvas;
14359 this.config.labelContainer = this.canvas.id + '-label';
14361 if(canvasConfig.background) {
14362 canvasConfig.background = $.merge({
14364 colorStop1: this.config.colorStop1,
14365 colorStop2: this.config.colorStop2
14366 }, canvasConfig.background);
14368 this.canvas = new Canvas(this, canvasConfig);
14369 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14372 this.graphOptions = {
14380 this.graph = new Graph(this.graphOptions, this.config.Node,
14382 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14383 this.fx = new $Sunburst.Plot(this, $Sunburst);
14384 this.op = new $Sunburst.Op(this);
14387 this.rotated = null;
14389 // initialize extras
14390 this.initializeExtras();
14395 createLevelDistanceFunc
14397 Returns the levelDistance function used for calculating a node distance
14398 to its origin. This function returns a function that is computed
14399 per level and not per node, such that all nodes with the same depth will have the
14400 same distance to the origin. The resulting function gets the
14401 parent node as parameter and returns a float.
14404 createLevelDistanceFunc: function() {
14405 var ld = this.config.levelDistance;
14406 return function(elem) {
14407 return (elem._depth + 1) * ld;
14414 Computes positions and plots the tree.
14417 refresh: function() {
14425 An alias for computing new positions to _endPos_
14432 reposition: function() {
14433 this.compute('end');
14439 Rotates the graph so that the selected node is horizontal on the right.
14443 node - (object) A <Graph.Node>.
14444 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14445 opt - (object) Configuration options merged with this visualization configuration options.
14449 <Sunburst.rotateAngle>
14452 rotate: function(node, method, opt) {
14453 var theta = node.getPos(opt.property || 'current').getp(true).theta;
14454 this.rotated = node;
14455 this.rotateAngle(-theta, method, opt);
14459 Method: rotateAngle
14461 Rotates the graph of an angle theta.
14465 node - (object) A <Graph.Node>.
14466 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14467 opt - (object) Configuration options merged with this visualization configuration options.
14474 rotateAngle: function(theta, method, opt) {
14476 var options = $.merge(this.config, opt || {}, {
14479 var prop = opt.property || (method === "animate" ? 'end' : 'current');
14480 if(method === 'animate') {
14481 this.fx.animation.pause();
14483 this.graph.eachNode(function(n) {
14484 var p = n.getPos(prop);
14487 p.theta += Math.PI * 2;
14490 if (method == 'animate') {
14491 this.fx.animate(options);
14492 } else if (method == 'replot') {
14501 Plots the Sunburst. This is a shortcut to *fx.plot*.
14508 $jit.Sunburst.$extend = true;
14510 (function(Sunburst) {
14515 Custom extension of <Graph.Op>.
14519 All <Graph.Op> methods
14526 Sunburst.Op = new Class( {
14528 Implements: Graph.Op
14533 Class: Sunburst.Plot
14535 Custom extension of <Graph.Plot>.
14539 All <Graph.Plot> methods
14546 Sunburst.Plot = new Class( {
14548 Implements: Graph.Plot
14553 Class: Sunburst.Label
14555 Custom extension of <Graph.Label>.
14556 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14560 All <Graph.Label> methods and subclasses.
14564 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14567 Sunburst.Label = {};
14570 Sunburst.Label.Native
14572 Custom extension of <Graph.Label.Native>.
14576 All <Graph.Label.Native> methods
14580 <Graph.Label.Native>
14582 Sunburst.Label.Native = new Class( {
14583 Implements: Graph.Label.Native,
14585 initialize: function(viz) {
14587 this.label = viz.config.Label;
14588 this.config = viz.config;
14591 renderLabel: function(canvas, node, controller) {
14592 var span = node.getData('span');
14593 if(span < Math.PI /2 && Math.tan(span) *
14594 this.config.levelDistance * node._depth < 10) {
14597 var ctx = canvas.getCtx();
14598 var measure = ctx.measureText(node.name);
14599 if (node.id == this.viz.root) {
14600 var x = -measure.width / 2, y = 0, thetap = 0;
14604 var ld = controller.levelDistance - indent;
14605 var clone = node.pos.clone();
14606 clone.rho += indent;
14607 var p = clone.getp(true);
14608 var ct = clone.getc(true);
14609 var x = ct.x, y = ct.y;
14610 // get angle in degrees
14612 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14613 var thetap = cond ? p.theta + pi : p.theta;
14615 x -= Math.abs(Math.cos(p.theta) * measure.width);
14616 y += Math.sin(p.theta) * measure.width;
14617 } else if (node.id == this.viz.root) {
14618 x -= measure.width / 2;
14622 ctx.translate(x, y);
14623 ctx.rotate(thetap);
14624 ctx.fillText(node.name, 0, 0);
14632 Custom extension of <Graph.Label.SVG>.
14636 All <Graph.Label.SVG> methods
14643 Sunburst.Label.SVG = new Class( {
14644 Implements: Graph.Label.SVG,
14646 initialize: function(viz) {
14653 Overrides abstract method placeLabel in <Graph.Plot>.
14657 tag - A DOM label element.
14658 node - A <Graph.Node>.
14659 controller - A configuration/controller object passed to the visualization.
14662 placeLabel: function(tag, node, controller) {
14663 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14664 var radius = canvas.getSize();
14666 x: Math.round(pos.x + radius.width / 2),
14667 y: Math.round(pos.y + radius.height / 2)
14669 tag.setAttribute('x', labelPos.x);
14670 tag.setAttribute('y', labelPos.y);
14672 var bb = tag.getBBox();
14674 // center the label
14675 var x = tag.getAttribute('x');
14676 var y = tag.getAttribute('y');
14677 // get polar coordinates
14678 var p = node.pos.getp(true);
14679 // get angle in degrees
14681 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14683 tag.setAttribute('x', x - bb.width);
14684 tag.setAttribute('y', y - bb.height);
14685 } else if (node.id == viz.root) {
14686 tag.setAttribute('x', x - bb.width / 2);
14689 var thetap = cond ? p.theta + pi : p.theta;
14691 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14695 controller.onPlaceLabel(tag, node);
14700 Sunburst.Label.HTML
14702 Custom extension of <Graph.Label.HTML>.
14706 All <Graph.Label.HTML> methods.
14713 Sunburst.Label.HTML = new Class( {
14714 Implements: Graph.Label.HTML,
14716 initialize: function(viz) {
14722 Overrides abstract method placeLabel in <Graph.Plot>.
14726 tag - A DOM label element.
14727 node - A <Graph.Node>.
14728 controller - A configuration/controller object passed to the visualization.
14731 placeLabel: function(tag, node, controller) {
14732 var pos = node.pos.clone(),
14733 canvas = this.viz.canvas,
14734 height = node.getData('height'),
14735 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14736 radius = canvas.getSize();
14738 pos = pos.getc(true);
14741 x: Math.round(pos.x + radius.width / 2),
14742 y: Math.round(pos.y + radius.height / 2)
14745 var style = tag.style;
14746 style.left = labelPos.x + 'px';
14747 style.top = labelPos.y + 'px';
14748 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14750 controller.onPlaceLabel(tag, node);
14755 Class: Sunburst.Plot.NodeTypes
14757 This class contains a list of <Graph.Node> built-in types.
14758 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14760 You can add your custom node types, customizing your visualization to the extreme.
14765 Sunburst.Plot.NodeTypes.implement({
14767 'render': function(node, canvas) {
14768 //print your custom node to canvas
14771 'contains': function(node, pos) {
14772 //return true if pos is inside the node or false otherwise
14779 Sunburst.Plot.NodeTypes = new Class( {
14782 'contains': $.lambda(false),
14783 'anglecontains': function(node, pos) {
14784 var span = node.getData('span') / 2, theta = node.pos.theta;
14785 var begin = theta - span, end = theta + span;
14787 begin += Math.PI * 2;
14788 var atan = Math.atan2(pos.y, pos.x);
14790 atan += Math.PI * 2;
14792 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14794 return atan > begin && atan < end;
14797 'anglecontainsgauge': function(node, pos) {
14798 var span = node.getData('span') / 2, theta = node.pos.theta;
14799 var config = node.getData('config');
14800 var ld = this.config.levelDistance;
14801 var yOffset = pos.y-(ld/2);
14802 var begin = ((theta - span)/2)+Math.PI,
14803 end = ((theta + span)/2)+Math.PI;
14806 begin += Math.PI * 2;
14807 var atan = Math.atan2(yOffset, pos.x);
14811 atan += Math.PI * 2;
14815 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14817 return atan > begin && atan < end;
14823 'render': function(node, canvas) {
14824 var span = node.getData('span') / 2, theta = node.pos.theta;
14825 var begin = theta - span, end = theta + span;
14826 var polarNode = node.pos.getp(true);
14827 var polar = new Polar(polarNode.rho, begin);
14828 var p1coord = polar.getc(true);
14830 var p2coord = polar.getc(true);
14832 var ctx = canvas.getCtx();
14835 ctx.lineTo(p1coord.x, p1coord.y);
14837 ctx.lineTo(p2coord.x, p2coord.y);
14839 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14843 'contains': function(node, pos) {
14844 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14845 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14846 var ld = this.config.levelDistance, d = node._depth;
14847 return (rho <= ld * d);
14853 'render': function(node, canvas) {
14854 var height = node.getData('height');
14855 var ldist = height? height : this.config.levelDistance;
14856 var span = node.getData('span') / 2, theta = node.pos.theta;
14857 var begin = theta - span, end = theta + span;
14858 var polarNode = node.pos.getp(true);
14860 var polar = new Polar(polarNode.rho, begin);
14861 var p1coord = polar.getc(true);
14864 var p2coord = polar.getc(true);
14866 polar.rho += ldist;
14867 var p3coord = polar.getc(true);
14869 polar.theta = begin;
14870 var p4coord = polar.getc(true);
14872 var ctx = canvas.getCtx();
14875 ctx.arc(0, 0, polarNode.rho, begin, end, false);
14876 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
14877 ctx.moveTo(p1coord.x, p1coord.y);
14878 ctx.lineTo(p4coord.x, p4coord.y);
14879 ctx.moveTo(p2coord.x, p2coord.y);
14880 ctx.lineTo(p3coord.x, p3coord.y);
14883 if (node.collapsed) {
14888 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
14894 'contains': function(node, pos) {
14895 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14896 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14897 var height = node.getData('height');
14898 var ldist = height? height : this.config.levelDistance;
14899 var ld = this.config.levelDistance, d = node._depth;
14900 return (rho >= ld * d) && (rho <= (ld * d + ldist));
14906 'gradient-multipie': {
14907 'render': function(node, canvas) {
14908 var ctx = canvas.getCtx();
14909 var height = node.getData('height');
14910 var ldist = height? height : this.config.levelDistance;
14911 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
14912 0, 0, node.getPos().rho + ldist);
14914 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
14915 $.each(colorArray, function(i) {
14916 ans.push(parseInt(i * 0.5, 10));
14918 var endColor = $.rgbToHex(ans);
14919 radialGradient.addColorStop(0, endColor);
14920 radialGradient.addColorStop(1, node.getData('color'));
14921 ctx.fillStyle = radialGradient;
14922 this.nodeTypes['multipie'].render.call(this, node, canvas);
14924 'contains': function(node, pos) {
14925 return this.nodeTypes['multipie'].contains.call(this, node, pos);
14930 'render': function(node, canvas) {
14931 var ctx = canvas.getCtx();
14932 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
14935 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
14936 $.each(colorArray, function(i) {
14937 ans.push(parseInt(i * 0.5, 10));
14939 var endColor = $.rgbToHex(ans);
14940 radialGradient.addColorStop(1, endColor);
14941 radialGradient.addColorStop(0, node.getData('color'));
14942 ctx.fillStyle = radialGradient;
14943 this.nodeTypes['pie'].render.call(this, node, canvas);
14945 'contains': function(node, pos) {
14946 return this.nodeTypes['pie'].contains.call(this, node, pos);
14952 Class: Sunburst.Plot.EdgeTypes
14954 This class contains a list of <Graph.Adjacence> built-in types.
14955 Edge types implemented are 'none', 'line' and 'arrow'.
14957 You can add your custom edge types, customizing your visualization to the extreme.
14962 Sunburst.Plot.EdgeTypes.implement({
14964 'render': function(adj, canvas) {
14965 //print your custom edge to canvas
14968 'contains': function(adj, pos) {
14969 //return true if pos is inside the arc or false otherwise
14976 Sunburst.Plot.EdgeTypes = new Class({
14979 'render': function(adj, canvas) {
14980 var from = adj.nodeFrom.pos.getc(true),
14981 to = adj.nodeTo.pos.getc(true);
14982 this.edgeHelper.line.render(from, to, canvas);
14984 'contains': function(adj, pos) {
14985 var from = adj.nodeFrom.pos.getc(true),
14986 to = adj.nodeTo.pos.getc(true);
14987 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
14991 'render': function(adj, canvas) {
14992 var from = adj.nodeFrom.pos.getc(true),
14993 to = adj.nodeTo.pos.getc(true),
14994 dim = adj.getData('dim'),
14995 direction = adj.data.$direction,
14996 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
14997 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
14999 'contains': function(adj, pos) {
15000 var from = adj.nodeFrom.pos.getc(true),
15001 to = adj.nodeTo.pos.getc(true);
15002 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15006 'render': function(adj, canvas) {
15007 var from = adj.nodeFrom.pos.getc(),
15008 to = adj.nodeTo.pos.getc(),
15009 dim = Math.max(from.norm(), to.norm());
15010 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15012 'contains': $.lambda(false) //TODO(nico): Implement this!
15020 * File: PieChart.js
15024 $jit.Sunburst.Plot.NodeTypes.implement({
15025 'piechart-stacked' : {
15026 'render' : function(node, canvas) {
15027 var pos = node.pos.getp(true),
15028 dimArray = node.getData('dimArray'),
15029 valueArray = node.getData('valueArray'),
15030 colorArray = node.getData('colorArray'),
15031 colorLength = colorArray.length,
15032 stringArray = node.getData('stringArray'),
15033 span = node.getData('span') / 2,
15034 theta = node.pos.theta,
15035 begin = theta - span,
15036 end = theta + span,
15039 var ctx = canvas.getCtx(),
15041 gradient = node.getData('gradient'),
15042 border = node.getData('border'),
15043 config = node.getData('config'),
15044 showLabels = config.showLabels,
15045 resizeLabels = config.resizeLabels,
15046 label = config.Label;
15048 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15049 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15051 if (colorArray && dimArray && stringArray) {
15052 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15053 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15054 if(dimi <= 0) continue;
15055 ctx.fillStyle = ctx.strokeStyle = colori;
15056 if(gradient && dimi) {
15057 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15058 xpos, ypos, acum + dimi + config.sliceOffset);
15059 var colorRgb = $.hexToRgb(colori),
15060 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15061 endColor = $.rgbToHex(ans);
15063 radialGradient.addColorStop(0, colori);
15064 radialGradient.addColorStop(0.5, colori);
15065 radialGradient.addColorStop(1, endColor);
15066 ctx.fillStyle = radialGradient;
15069 polar.rho = acum + config.sliceOffset;
15070 polar.theta = begin;
15071 var p1coord = polar.getc(true);
15073 var p2coord = polar.getc(true);
15075 var p3coord = polar.getc(true);
15076 polar.theta = begin;
15077 var p4coord = polar.getc(true);
15080 //fixing FF arc method + fill
15081 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15082 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15084 if(border && border.name == stringArray[i]) {
15086 opt.dimValue = dimArray[i];
15090 acum += (dimi || 0);
15091 valAcum += (valueArray[i] || 0);
15095 ctx.globalCompositeOperation = "source-over";
15097 ctx.strokeStyle = border.color;
15098 var s = begin < end? 1 : -1;
15100 //fixing FF arc method + fill
15101 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15102 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15107 if(showLabels && label.type == 'Native') {
15109 ctx.fillStyle = ctx.strokeStyle = label.color;
15110 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15111 fontSize = (label.size * scale) >> 0;
15112 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15114 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15115 ctx.textBaseline = 'middle';
15116 ctx.textAlign = 'center';
15118 polar.rho = acum + config.labelOffset + config.sliceOffset;
15119 polar.theta = node.pos.theta;
15120 var cart = polar.getc(true);
15122 ctx.fillText(node.name, cart.x, cart.y);
15127 'contains': function(node, pos) {
15128 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15129 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15130 var ld = this.config.levelDistance, d = node._depth;
15131 var config = node.getData('config');
15132 if(rho <=ld * d + config.sliceOffset) {
15133 var dimArray = node.getData('dimArray');
15134 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15135 var dimi = dimArray[i];
15136 if(rho >= acum && rho <= acum + dimi) {
15138 name: node.getData('stringArray')[i],
15139 color: node.getData('colorArray')[i],
15140 value: node.getData('valueArray')[i],
15153 'piechart-basic' : {
15154 'render' : function(node, canvas) {
15155 var pos = node.pos.getp(true),
15156 dimArray = node.getData('dimArray'),
15157 valueArray = node.getData('valueArray'),
15158 colorArray = node.getData('colorMono'),
15159 colorLength = colorArray.length,
15160 stringArray = node.getData('stringArray'),
15161 percentage = node.getData('percentage'),
15162 iteration = node.getData('iteration'),
15163 span = node.getData('span') / 2,
15164 theta = node.pos.theta,
15165 begin = theta - span,
15166 end = theta + span,
15169 var ctx = canvas.getCtx(),
15171 gradient = node.getData('gradient'),
15172 border = node.getData('border'),
15173 config = node.getData('config'),
15174 renderSubtitle = node.getData('renderSubtitle'),
15175 renderBackground = config.renderBackground,
15176 showLabels = config.showLabels,
15177 resizeLabels = config.resizeLabels,
15178 label = config.Label;
15180 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15181 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15182 //background rendering for IE
15183 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15184 backgroundColor = config.backgroundColor,
15185 size = canvas.getSize();
15187 ctx.fillStyle = backgroundColor;
15188 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15192 var margin = config.Margin,
15193 title = config.Title,
15194 subtitle = config.Subtitle;
15195 ctx.fillStyle = title.color;
15196 ctx.textAlign = 'left';
15198 if(title.text != "") {
15199 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15201 if(label.type == 'Native') {
15202 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15206 if(subtitle.text != "") {
15207 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15208 if(label.type == 'Native') {
15209 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15214 if (colorArray && dimArray && stringArray) {
15215 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15216 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15217 if(dimi <= 0) continue;
15218 ctx.fillStyle = ctx.strokeStyle = colori;
15220 polar.rho = acum + config.sliceOffset;
15221 polar.theta = begin;
15222 var p1coord = polar.getc(true);
15224 var p2coord = polar.getc(true);
15226 var p3coord = polar.getc(true);
15227 polar.theta = begin;
15228 var p4coord = polar.getc(true);
15230 if(typeof FlashCanvas == "undefined") {
15233 ctx.fillStyle = "rgba(0,0,0,.2)";
15234 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15235 ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);
15238 if(gradient && dimi) {
15239 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15240 xpos, ypos, acum + dimi + config.sliceOffset);
15241 var colorRgb = $.hexToRgb(colori),
15242 endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15243 endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15245 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15246 radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15247 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15248 radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15249 ctx.fillStyle = radialGradient;
15254 //fixing FF arc method + fill
15256 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15257 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15259 if(border && border.name == stringArray[i]) {
15261 opt.dimValue = dimArray[i];
15264 opt.sliceValue = valueArray[i];
15266 acum += (dimi || 0);
15267 valAcum += (valueArray[i] || 0);
15271 ctx.globalCompositeOperation = "source-over";
15273 ctx.strokeStyle = border.color;
15274 var s = begin < end? 1 : -1;
15276 //fixing FF arc method + fill
15277 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15278 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15283 if(showLabels && label.type == 'Native') {
15285 ctx.fillStyle = ctx.strokeStyle = label.color;
15286 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15287 fontSize = (label.size * scale) >> 0;
15288 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15290 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15291 ctx.textBaseline = 'middle';
15292 ctx.textAlign = 'center';
15294 angle = theta * 360 / (2 * pi);
15295 polar.rho = acum + config.labelOffset + config.sliceOffset;
15296 polar.theta = node.pos.theta;
15297 var cart = polar.getc(true);
15298 if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15301 if(config.labelType == 'name') {
15302 ctx.fillText(node.name, cart.x, cart.y);
15304 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15311 'contains': function(node, pos) {
15312 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15313 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15314 var ld = this.config.levelDistance, d = node._depth;
15315 var config = node.getData('config');
15317 if(rho <=ld * d + config.sliceOffset) {
15318 var dimArray = node.getData('dimArray');
15319 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15320 var dimi = dimArray[i];
15321 if(rho >= acum && rho <= acum + dimi) {
15322 var url = Url.decode(node.getData('linkArray')[i]);
15324 name: node.getData('stringArray')[i],
15326 color: node.getData('colorArray')[i],
15327 value: node.getData('valueArray')[i],
15328 percentage: node.getData('percentage'),
15329 valuelabel: node.getData('valuelabelsArray')[i],
15347 A visualization that displays stacked bar charts.
15349 Constructor Options:
15351 See <Options.PieChart>.
15354 $jit.PieChart = new Class({
15356 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15360 initialize: function(opt) {
15361 this.controller = this.config =
15362 $.merge(Options("Canvas", "PieChart", "Label"), {
15363 Label: { type: 'Native' }
15365 this.initializeViz();
15368 initializeViz: function() {
15369 var config = this.config, that = this;
15370 var nodeType = config.type.split(":")[0];
15371 var sb = new $jit.Sunburst({
15372 injectInto: config.injectInto,
15373 useCanvas: config.useCanvas,
15374 withLabels: config.Label.type != 'Native',
15375 background: config.background,
15376 renderBackground: config.renderBackground,
15377 backgroundColor: config.backgroundColor,
15378 colorStop1: config.colorStop1,
15379 colorStop2: config.colorStop2,
15381 type: config.Label.type
15385 type: 'piechart-' + nodeType,
15393 enable: config.Tips.enable,
15396 onShow: function(tip, node, contains) {
15397 var elem = contains;
15398 config.Tips.onShow(tip, elem, node);
15399 if(elem.link != 'undefined' && elem.link != '') {
15400 document.body.style.cursor = 'pointer';
15403 onHide: function() {
15404 document.body.style.cursor = 'default';
15410 onClick: function(node, eventInfo, evt) {
15411 if(!config.Events.enable) return;
15412 var elem = eventInfo.getContains();
15413 config.Events.onClick(elem, eventInfo, evt);
15415 onMouseMove: function(node, eventInfo, evt) {
15416 if(!config.hoveredColor) return;
15418 var elem = eventInfo.getContains();
15419 that.select(node.id, elem.name, elem.index);
15421 that.select(false, false, false);
15425 onCreateLabel: function(domElement, node) {
15426 var labelConf = config.Label;
15427 if(config.showLabels) {
15428 var style = domElement.style;
15429 style.fontSize = labelConf.size + 'px';
15430 style.fontFamily = labelConf.family;
15431 style.color = labelConf.color;
15432 style.textAlign = 'center';
15433 if(config.labelType == 'name') {
15434 domElement.innerHTML = node.name;
15436 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15438 domElement.style.width = '400px';
15441 onPlaceLabel: function(domElement, node) {
15442 if(!config.showLabels) return;
15443 var pos = node.pos.getp(true),
15444 dimArray = node.getData('dimArray'),
15445 span = node.getData('span') / 2,
15446 theta = node.pos.theta,
15447 begin = theta - span,
15448 end = theta + span,
15451 var showLabels = config.showLabels,
15452 resizeLabels = config.resizeLabels,
15453 label = config.Label;
15456 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15457 acum += dimArray[i];
15459 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15460 fontSize = (label.size * scale) >> 0;
15461 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15462 domElement.style.fontSize = fontSize + 'px';
15463 polar.rho = acum + config.labelOffset + config.sliceOffset;
15464 polar.theta = (begin + end) / 2;
15465 var pos = polar.getc(true);
15466 var radius = that.canvas.getSize();
15468 x: Math.round(pos.x + radius.width / 2),
15469 y: Math.round(pos.y + radius.height / 2)
15471 domElement.style.left = (labelPos.x - 200) + 'px';
15472 domElement.style.top = labelPos.y + 'px';
15477 var size = sb.canvas.getSize(),
15479 sb.config.levelDistance = min(size.width, size.height)/2
15480 - config.offset - config.sliceOffset;
15482 this.canvas = this.sb.canvas;
15483 this.canvas.getCtx().globalCompositeOperation = 'lighter';
15485 renderBackground: function() {
15486 var canvas = this.canvas,
15487 config = this.config,
15488 backgroundColor = config.backgroundColor,
15489 size = canvas.getSize(),
15490 ctx = canvas.getCtx();
15491 ctx.globalCompositeOperation = "destination-over";
15492 ctx.fillStyle = backgroundColor;
15493 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15495 renderTitle: function() {
15496 var canvas = this.canvas,
15497 size = canvas.getSize(),
15498 config = this.config,
15499 margin = config.Margin,
15500 radius = this.sb.config.levelDistance,
15501 title = config.Title,
15502 label = config.Label,
15503 subtitle = config.Subtitle;
15504 ctx = canvas.getCtx();
15505 ctx.fillStyle = title.color;
15506 ctx.textAlign = 'left';
15507 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15509 if(label.type == 'Native') {
15510 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15513 renderSubtitle: function() {
15514 var canvas = this.canvas,
15515 size = canvas.getSize(),
15516 config = this.config,
15517 margin = config.Margin,
15518 radius = this.sb.config.levelDistance,
15519 title = config.Title,
15520 label = config.Label,
15521 subtitle = config.Subtitle;
15522 ctx = canvas.getCtx();
15523 ctx.fillStyle = title.color;
15524 ctx.textAlign = 'left';
15525 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15527 if(label.type == 'Native') {
15528 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15534 Loads JSON data into the visualization.
15538 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>.
15542 var pieChart = new $jit.PieChart(options);
15543 pieChart.loadJSON(json);
15546 loadJSON: function(json) {
15547 var prefix = $.time(),
15550 name = $.splat(json.label),
15551 nameLength = name.length,
15552 color = $.splat(json.color || this.colors),
15553 colorLength = color.length,
15554 config = this.config,
15555 renderBackground = config.renderBackground,
15556 title = config.Title,
15557 subtitle = config.Subtitle,
15558 gradient = !!config.type.split(":")[1],
15559 animate = config.animate,
15560 mono = nameLength == 1;
15562 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15563 var val = values[i];
15564 var valArray = $.splat(val.values);
15565 totalValue += parseInt(valArray.sum());
15568 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15569 var val = values[i];
15570 var valArray = $.splat(val.values);
15571 var percentage = (valArray.sum()/totalValue) * 100;
15573 var linkArray = $.splat(val.links);
15574 var valuelabelsArray = $.splat(val.valuelabels);
15578 'id': prefix + val.label,
15582 'valuelabel': valuelabelsArray,
15583 '$linkArray': linkArray,
15584 '$valuelabelsArray': valuelabelsArray,
15585 '$valueArray': valArray,
15586 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15587 '$colorMono': $.splat(color[i % colorLength]),
15588 '$stringArray': name,
15589 '$gradient': gradient,
15592 '$percentage': percentage.toFixed(1),
15593 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15599 'id': prefix + '$root',
15611 this.normalizeDims();
15615 if(title.text != "") {
15616 this.renderTitle();
15619 if(subtitle.text != "") {
15620 this.renderSubtitle();
15622 if(renderBackground && typeof FlashCanvas == "undefined") {
15623 this.renderBackground();
15628 modes: ['node-property:dimArray'],
15637 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.
15641 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15642 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15647 pieChart.updateJSON(json, {
15648 onComplete: function() {
15649 alert('update complete!');
15654 updateJSON: function(json, onComplete) {
15655 if(this.busy) return;
15659 var graph = sb.graph;
15660 var values = json.values;
15661 var animate = this.config.animate;
15663 $.each(values, function(v) {
15664 var n = graph.getByName(v.label),
15665 vals = $.splat(v.values);
15667 n.setData('valueArray', vals);
15668 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15670 n.setData('stringArray', $.splat(json.label));
15674 this.normalizeDims();
15678 modes: ['node-property:dimArray:span', 'linear'],
15680 onComplete: function() {
15682 onComplete && onComplete.onComplete();
15690 //adds the little brown bar when hovering the node
15691 select: function(id, name) {
15692 if(!this.config.hoveredColor) return;
15693 var s = this.selected;
15694 if(s.id != id || s.name != name) {
15697 s.color = this.config.hoveredColor;
15698 this.sb.graph.eachNode(function(n) {
15700 n.setData('border', s);
15702 n.setData('border', false);
15712 Returns an object containing as keys the legend names and as values hex strings with color values.
15717 var legend = pieChart.getLegend();
15720 getLegend: function() {
15721 var legend = new Array();
15722 var name = new Array();
15723 var color = new Array();
15725 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15728 var colors = n.getData('colorArray'),
15729 len = colors.length;
15730 $.each(n.getData('stringArray'), function(s, i) {
15731 color[i] = colors[i % len];
15734 legend['name'] = name;
15735 legend['color'] = color;
15740 Method: getMaxValue
15742 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15747 var ans = pieChart.getMaxValue();
15750 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15755 //will return 100 for all PieChart instances,
15756 //displaying all of them with the same scale
15757 $jit.PieChart.implement({
15758 'getMaxValue': function() {
15765 getMaxValue: function() {
15767 this.sb.graph.eachNode(function(n) {
15768 var valArray = n.getData('valueArray'),
15770 $.each(valArray, function(v) {
15773 maxValue = maxValue>acum? maxValue:acum;
15778 normalizeDims: function() {
15779 //number of elements
15780 var root = this.sb.graph.getNode(this.sb.root), l=0;
15781 root.eachAdjacency(function() {
15784 var maxValue = this.getMaxValue() || 1,
15785 config = this.config,
15786 animate = config.animate,
15787 rho = this.sb.config.levelDistance;
15788 this.sb.graph.eachNode(function(n) {
15789 var acum = 0, animateValue = [];
15790 $.each(n.getData('valueArray'), function(v) {
15792 animateValue.push(1);
15794 var stat = (animateValue.length == 1) && !config.updateHeights;
15796 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15797 return stat? rho: (n * rho / maxValue);
15799 var dimArray = n.getData('dimArray');
15801 n.setData('dimArray', animateValue);
15804 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15805 return stat? rho : (n * rho / maxValue);
15808 n.setData('normalizedDim', acum / maxValue);
15816 Options.GaugeChart = {
15820 offset: 25, // page offset
15822 labelOffset: 3, // label offset
15823 type: 'stacked', // gradient
15825 hoveredColor: '#9fd4ff',
15836 resizeLabels: false,
15838 //only valid for mono-valued datasets
15839 updateHeights: false
15844 $jit.Sunburst.Plot.NodeTypes.implement({
15845 'gaugechart-basic' : {
15846 'render' : function(node, canvas) {
15847 var pos = node.pos.getp(true),
15848 dimArray = node.getData('dimArray'),
15849 valueArray = node.getData('valueArray'),
15850 valuelabelsArray = node.getData('valuelabelsArray'),
15851 gaugeTarget = node.getData('gaugeTarget'),
15852 nodeIteration = node.getData('nodeIteration'),
15853 nodeLength = node.getData('nodeLength'),
15854 colorArray = node.getData('colorMono'),
15855 colorLength = colorArray.length,
15856 stringArray = node.getData('stringArray'),
15857 span = node.getData('span') / 2,
15858 theta = node.pos.theta,
15859 begin = ((theta - span)/2)+Math.PI,
15860 end = ((theta + span)/2)+Math.PI,
15864 var ctx = canvas.getCtx(),
15866 gradient = node.getData('gradient'),
15867 border = node.getData('border'),
15868 config = node.getData('config'),
15869 showLabels = config.showLabels,
15870 resizeLabels = config.resizeLabels,
15871 label = config.Label;
15873 var xpos = Math.cos((begin + end) /2);
15874 var ypos = Math.sin((begin + end) /2);
15876 if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
15877 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15878 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15879 if(dimi <= 0) continue;
15880 ctx.fillStyle = ctx.strokeStyle = colori;
15881 if(gradient && dimi) {
15882 var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
15883 xpos, (ypos + dimi/2), acum + dimi);
15884 var colorRgb = $.hexToRgb(colori),
15885 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
15886 endColor = $.rgbToHex(ans);
15888 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15889 radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
15890 radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
15891 radialGradient.addColorStop(1, 'rgba('+ans+',1)');
15892 ctx.fillStyle = radialGradient;
15896 polar.theta = begin;
15897 var p1coord = polar.getc(true);
15899 var p2coord = polar.getc(true);
15901 var p3coord = polar.getc(true);
15902 polar.theta = begin;
15903 var p4coord = polar.getc(true);
15907 //fixing FF arc method + fill
15908 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
15909 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
15913 acum += (dimi || 0);
15914 valAcum += (valueArray[i] || 0);
15917 if(showLabels && label.type == 'Native') {
15919 ctx.fillStyle = ctx.strokeStyle = label.color;
15922 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
15923 ctx.textBaseline = 'bottom';
15924 ctx.textAlign = 'center';
15926 polar.rho = acum * .65;
15927 polar.theta = begin;
15928 var cart = polar.getc(true);
15930 //changes y pos of first label
15931 if(nodeIteration == 1) {
15932 textY = cart.y - (label.size/2) + acum /2;
15934 textY = cart.y + acum/2;
15937 if(config.labelType == 'name') {
15938 ctx.fillText(node.name, cart.x, textY);
15940 ctx.fillText(valuelabelsArray[0], cart.x, textY);
15944 if(nodeIteration == nodeLength) {
15946 var cart = polar.getc(true);
15947 if(config.labelType == 'name') {
15948 ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
15950 ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
15959 'contains': function(node, pos) {
15963 if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
15964 var config = node.getData('config');
15965 var ld = this.config.levelDistance , d = node._depth;
15966 var yOffset = pos.y - (ld/2);
15967 var xOffset = pos.x;
15968 var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
15969 if(rho <=parseInt(ld * d)) {
15970 var dimArray = node.getData('dimArray');
15971 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15972 var dimi = dimArray[i];
15973 if(rho >= ld * .8 && rho <= acum + dimi) {
15975 var url = Url.decode(node.getData('linkArray')[i]);
15977 name: node.getData('stringArray')[i],
15979 color: node.getData('colorArray')[i],
15980 value: node.getData('valueArray')[i],
15981 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16001 A visualization that displays gauge charts
16003 Constructor Options:
16005 See <Options.Gauge>.
16008 $jit.GaugeChart = new Class({
16010 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16014 initialize: function(opt) {
16015 this.controller = this.config =
16016 $.merge(Options("Canvas", "GaugeChart", "Label"), {
16017 Label: { type: 'Native' }
16019 this.initializeViz();
16022 initializeViz: function() {
16023 var config = this.config, that = this;
16024 var nodeType = config.type.split(":")[0];
16025 var sb = new $jit.Sunburst({
16026 injectInto: config.injectInto,
16027 useCanvas: config.useCanvas,
16028 withLabels: config.Label.type != 'Native',
16029 background: config.background,
16030 renderBackground: config.renderBackground,
16031 backgroundColor: config.backgroundColor,
16032 colorStop1: config.colorStop1,
16033 colorStop2: config.colorStop2,
16035 type: config.Label.type
16039 type: 'gaugechart-' + nodeType,
16047 enable: config.Tips.enable,
16050 onShow: function(tip, node, contains) {
16051 var elem = contains;
16052 config.Tips.onShow(tip, elem, node);
16053 if(elem.link != 'undefined' && elem.link != '') {
16054 document.body.style.cursor = 'pointer';
16057 onHide: function() {
16058 document.body.style.cursor = 'default';
16064 onClick: function(node, eventInfo, evt) {
16065 if(!config.Events.enable) return;
16066 var elem = eventInfo.getContains();
16067 config.Events.onClick(elem, eventInfo, evt);
16070 onCreateLabel: function(domElement, node) {
16071 var labelConf = config.Label;
16072 if(config.showLabels) {
16073 var style = domElement.style;
16074 style.fontSize = labelConf.size + 'px';
16075 style.fontFamily = labelConf.family;
16076 style.color = labelConf.color;
16077 style.textAlign = 'center';
16078 valuelabelsArray = node.getData('valuelabelsArray'),
16079 nodeIteration = node.getData('nodeIteration'),
16080 nodeLength = node.getData('nodeLength'),
16081 canvas = sb.canvas,
16084 if(config.labelType == 'name') {
16085 domElement.innerHTML = node.name;
16087 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16090 domElement.style.width = '400px';
16093 if(nodeIteration == nodeLength && nodeLength != 0) {
16094 idLabel = canvas.id + "-label";
16095 container = document.getElementById(idLabel);
16096 finalLabel = document.createElement('div');
16097 finalLabelStyle = finalLabel.style;
16098 finalLabel.id = prefix + "finalLabel";
16099 finalLabelStyle.position = "absolute";
16100 finalLabelStyle.width = "400px";
16101 finalLabelStyle.left = "0px";
16102 container.appendChild(finalLabel);
16103 if(config.labelType == 'name') {
16104 finalLabel.innerHTML = node.name;
16106 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16112 onPlaceLabel: function(domElement, node) {
16113 if(!config.showLabels) return;
16114 var pos = node.pos.getp(true),
16115 dimArray = node.getData('dimArray'),
16116 nodeIteration = node.getData('nodeIteration'),
16117 nodeLength = node.getData('nodeLength'),
16118 span = node.getData('span') / 2,
16119 theta = node.pos.theta,
16120 begin = ((theta - span)/2)+Math.PI,
16121 end = ((theta + span)/2)+Math.PI,
16124 var showLabels = config.showLabels,
16125 resizeLabels = config.resizeLabels,
16126 label = config.Label,
16127 radiusOffset = sb.config.levelDistance;
16130 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16131 acum += dimArray[i];
16133 var scale = resizeLabels? node.getData('normalizedDim') : 1,
16134 fontSize = (label.size * scale) >> 0;
16135 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16136 domElement.style.fontSize = fontSize + 'px';
16137 polar.rho = acum * .65;
16138 polar.theta = begin;
16139 var pos = polar.getc(true);
16140 var radius = that.canvas.getSize();
16142 x: Math.round(pos.x + radius.width / 2),
16143 y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16148 domElement.style.left = (labelPos.x - 200) + 'px';
16149 domElement.style.top = labelPos.y + 'px';
16151 //reposition first label
16152 if(nodeIteration == 1) {
16153 domElement.style.top = labelPos.y - label.size + 'px';
16157 //position final label
16158 if(nodeIteration == nodeLength && nodeLength != 0) {
16160 var final = polar.getc(true);
16162 x: Math.round(final.x + radius.width / 2),
16163 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16165 finalLabel.style.left = (finalPos.x - 200) + "px";
16166 finalLabel.style.top = finalPos.y - label.size + "px";
16174 this.canvas = this.sb.canvas;
16175 var size = sb.canvas.getSize(),
16177 sb.config.levelDistance = min(size.width, size.height)/2
16178 - config.offset - config.sliceOffset;
16183 renderBackground: function() {
16184 var canvas = this.sb.canvas,
16185 config = this.config,
16186 style = config.gaugeStyle,
16187 ctx = canvas.getCtx(),
16188 size = canvas.getSize(),
16189 radius = this.sb.config.levelDistance,
16190 startAngle = (Math.PI/180)*1,
16191 endAngle = (Math.PI/180)*179;
16194 //background border
16195 ctx.fillStyle = style.borderColor;
16197 ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true);
16201 var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16202 radialGradient.addColorStop(0, '#ffffff');
16203 radialGradient.addColorStop(0.3, style.backgroundColor);
16204 radialGradient.addColorStop(0.6, style.backgroundColor);
16205 radialGradient.addColorStop(1, '#FFFFFF');
16206 ctx.fillStyle = radialGradient;
16209 startAngle = (Math.PI/180)*0;
16210 endAngle = (Math.PI/180)*180;
16212 ctx.arc(0,radius/2,radius,startAngle,endAngle, true);
16220 renderNeedle: function(gaugePosition,target) {
16221 var canvas = this.sb.canvas,
16222 config = this.config,
16223 style = config.gaugeStyle,
16224 ctx = canvas.getCtx(),
16225 size = canvas.getSize(),
16226 radius = this.sb.config.levelDistance;
16227 gaugeCenter = (radius/2);
16229 endAngle = (Math.PI/180)*180;
16233 ctx.fillStyle = style.needleColor;
16234 var segments = 180/target;
16235 needleAngle = gaugePosition * segments;
16236 ctx.translate(0, gaugeCenter);
16238 ctx.rotate(needleAngle * Math.PI / 180);
16242 ctx.lineTo(-radius*.9,-1);
16243 ctx.lineTo(-radius*.9,1);
16253 ctx.strokeStyle = '#aa0000';
16255 ctx.rotate(needleAngle * Math.PI / 180);
16259 ctx.lineTo(-radius*.8,-1);
16260 ctx.lineTo(-radius*.8,1);
16268 ctx.fillStyle = "#000000";
16269 ctx.lineWidth = style.borderSize;
16270 ctx.strokeStyle = style.borderColor;
16271 var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16272 radialGradient.addColorStop(0, '#666666');
16273 radialGradient.addColorStop(0.8, '#444444');
16274 radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
16275 ctx.fillStyle = radialGradient;
16276 ctx.translate(0,5);
16279 ctx.arc(0,0,radius*.2,startAngle,endAngle, true);
16286 renderTicks: function(values) {
16287 var canvas = this.sb.canvas,
16288 config = this.config,
16289 style = config.gaugeStyle,
16290 ctx = canvas.getCtx(),
16291 size = canvas.getSize(),
16292 radius = this.sb.config.levelDistance,
16293 gaugeCenter = (radius/2);
16296 ctx.strokeStyle = style.borderColor;
16298 ctx.lineCap = "round";
16299 for(var i=0, total = 0, l=values.length; i<l; i++) {
16300 var val = values[i];
16301 if(val.label != 'GaugePosition') {
16302 total += (parseInt(val.values) || 0);
16306 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16307 var val = values[i];
16308 if(val.label != 'GaugePosition') {
16309 acum += (parseInt(val.values) || 0);
16311 var segments = 180/total;
16312 angle = acum * segments;
16316 ctx.translate(0, gaugeCenter);
16318 ctx.rotate(angle * (Math.PI/180));
16319 ctx.moveTo(-radius,0);
16320 ctx.lineTo(-radius*.75,0);
16328 renderPositionLabel: function(position) {
16329 var canvas = this.sb.canvas,
16330 config = this.config,
16331 label = config.Label,
16332 style = config.gaugeStyle,
16333 ctx = canvas.getCtx(),
16334 size = canvas.getSize(),
16335 radius = this.sb.config.levelDistance,
16336 gaugeCenter = (radius/2);
16337 ctx.textBaseline = 'middle';
16338 ctx.textAlign = 'center';
16339 ctx.font = style.positionFontSize + 'px ' + label.family;
16340 ctx.fillStyle = "#ffffff";
16342 height = style.positionFontSize + 10,
16344 idLabel = canvas.id + "-label";
16345 container = document.getElementById(idLabel);
16346 if(label.type == 'Native') {
16347 var m = ctx.measureText(position),
16348 width = m.width + 40;
16352 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16353 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16354 if(label.type == 'Native') {
16355 ctx.fillStyle = label.color;
16356 ctx.fillText(position, 0, (height/2) + style.positionOffset);
16358 var labelDiv = document.createElement('div');
16359 labelDivStyle = labelDiv.style;
16360 labelDivStyle.color = label.color;
16361 labelDivStyle.fontSize = style.positionFontSize + "px";
16362 labelDivStyle.position = "absolute";
16363 labelDivStyle.width = width + "px";
16364 labelDivStyle.left = (size.width/2) - (width/2) + "px";
16365 labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16366 labelDiv.innerHTML = position;
16367 container.appendChild(labelDiv);
16372 renderSubtitle: function() {
16373 var canvas = this.canvas,
16374 size = canvas.getSize(),
16375 config = this.config,
16376 margin = config.Margin,
16377 radius = this.sb.config.levelDistance,
16378 title = config.Title,
16379 label = config.Label,
16380 subtitle = config.Subtitle;
16381 ctx = canvas.getCtx();
16382 ctx.fillStyle = title.color;
16383 ctx.textAlign = 'left';
16384 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16386 if(label.type == 'Native') {
16387 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2));
16391 renderChartBackground: function() {
16392 var canvas = this.canvas,
16393 config = this.config,
16394 backgroundColor = config.backgroundColor,
16395 size = canvas.getSize(),
16396 ctx = canvas.getCtx();
16397 //ctx.globalCompositeOperation = "destination-over";
16398 ctx.fillStyle = backgroundColor;
16399 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16402 loadJSON: function(json) {
16404 var prefix = $.time(),
16407 name = $.splat(json.label),
16408 nameLength = name.length,
16409 color = $.splat(json.color || this.colors),
16410 colorLength = color.length,
16411 config = this.config,
16412 renderBackground = config.renderBackground,
16413 gradient = !!config.type.split(":")[1],
16414 animate = config.animate,
16415 mono = nameLength == 1;
16416 var props = $.splat(json.properties)[0];
16418 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16420 var val = values[i];
16421 if(val.label != 'GaugePosition') {
16422 var valArray = $.splat(val.values);
16423 var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16424 var valuelabelsArray = $.splat(val.valuelabels);
16427 'id': prefix + val.label,
16431 'valuelabel': valuelabelsArray,
16432 '$linkArray': linkArray,
16433 '$valuelabelsArray': valuelabelsArray,
16434 '$valueArray': valArray,
16435 '$nodeIteration': i,
16436 '$nodeLength': l-1,
16437 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16438 '$colorMono': $.splat(color[i % colorLength]),
16439 '$stringArray': name,
16440 '$gradient': gradient,
16442 '$gaugeTarget': props['gaugeTarget'],
16443 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16448 var gaugePosition = val.gvalue;
16449 var gaugePositionLabel = val.gvaluelabel;
16453 'id': prefix + '$root',
16466 if(renderBackground) {
16467 this.renderChartBackground();
16470 this.renderBackground();
16471 this.renderSubtitle();
16473 this.normalizeDims();
16478 modes: ['node-property:dimArray'],
16484 this.renderPositionLabel(gaugePositionLabel);
16485 if (props['gaugeTarget'] != 0) {
16486 this.renderTicks(json.values);
16487 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16494 updateJSON: function(json, onComplete) {
16495 if(this.busy) return;
16499 var graph = sb.graph;
16500 var values = json.values;
16501 var animate = this.config.animate;
16503 $.each(values, function(v) {
16504 var n = graph.getByName(v.label),
16505 vals = $.splat(v.values);
16507 n.setData('valueArray', vals);
16508 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16510 n.setData('stringArray', $.splat(json.label));
16514 this.normalizeDims();
16518 modes: ['node-property:dimArray:span', 'linear'],
16520 onComplete: function() {
16522 onComplete && onComplete.onComplete();
16530 //adds the little brown bar when hovering the node
16531 select: function(id, name) {
16532 if(!this.config.hoveredColor) return;
16533 var s = this.selected;
16534 if(s.id != id || s.name != name) {
16537 s.color = this.config.hoveredColor;
16538 this.sb.graph.eachNode(function(n) {
16540 n.setData('border', s);
16542 n.setData('border', false);
16552 Returns an object containing as keys the legend names and as values hex strings with color values.
16557 var legend = pieChart.getLegend();
16560 getLegend: function() {
16561 var legend = new Array();
16562 var name = new Array();
16563 var color = new Array();
16565 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16568 var colors = n.getData('colorArray'),
16569 len = colors.length;
16570 $.each(n.getData('stringArray'), function(s, i) {
16571 color[i] = colors[i % len];
16574 legend['name'] = name;
16575 legend['color'] = color;
16580 Method: getMaxValue
16582 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16587 var ans = pieChart.getMaxValue();
16590 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16595 //will return 100 for all PieChart instances,
16596 //displaying all of them with the same scale
16597 $jit.PieChart.implement({
16598 'getMaxValue': function() {
16605 getMaxValue: function() {
16607 this.sb.graph.eachNode(function(n) {
16608 var valArray = n.getData('valueArray'),
16610 $.each(valArray, function(v) {
16613 maxValue = maxValue>acum? maxValue:acum;
16618 normalizeDims: function() {
16619 //number of elements
16620 var root = this.sb.graph.getNode(this.sb.root), l=0;
16621 root.eachAdjacency(function() {
16624 var maxValue = this.getMaxValue() || 1,
16625 config = this.config,
16626 animate = config.animate,
16627 rho = this.sb.config.levelDistance;
16628 this.sb.graph.eachNode(function(n) {
16629 var acum = 0, animateValue = [];
16630 $.each(n.getData('valueArray'), function(v) {
16632 animateValue.push(1);
16634 var stat = (animateValue.length == 1) && !config.updateHeights;
16636 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16637 return stat? rho: (n * rho / maxValue);
16639 var dimArray = n.getData('dimArray');
16641 n.setData('dimArray', animateValue);
16644 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16645 return stat? rho : (n * rho / maxValue);
16648 n.setData('normalizedDim', acum / maxValue);
16655 * Class: Layouts.TM
16657 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16666 Layouts.TM.SliceAndDice = new Class({
16667 compute: function(prop) {
16668 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16669 this.controller.onBeforeCompute(root);
16670 var size = this.canvas.getSize(),
16671 config = this.config,
16672 width = size.width,
16673 height = size.height;
16674 this.graph.computeLevels(this.root, 0, "ignore");
16675 //set root position and dimensions
16676 root.getPos(prop).setc(-width/2, -height/2);
16677 root.setData('width', width, prop);
16678 root.setData('height', height + config.titleHeight, prop);
16679 this.computePositions(root, root, this.layout.orientation, prop);
16680 this.controller.onAfterCompute(root);
16683 computePositions: function(par, ch, orn, prop) {
16684 //compute children areas
16686 par.eachSubnode(function(n) {
16687 totalArea += n.getData('area', prop);
16690 var config = this.config,
16691 offst = config.offset,
16692 width = par.getData('width', prop),
16693 height = par.getData('height', prop) - config.titleHeight,
16694 fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16696 var otherSize, size, dim, pos, pos2, posth, pos2th;
16697 var horizontal = (orn == "h");
16700 otherSize = height;
16701 size = width * fact;
16705 posth = config.titleHeight;
16709 otherSize = height * fact;
16715 pos2th = config.titleHeight;
16717 var cpos = ch.getPos(prop);
16718 ch.setData('width', size, prop);
16719 ch.setData('height', otherSize, prop);
16720 var offsetSize = 0, tm = this;
16721 ch.eachSubnode(function(n) {
16722 var p = n.getPos(prop);
16723 p[pos] = offsetSize + cpos[pos] + posth;
16724 p[pos2] = cpos[pos2] + pos2th;
16725 tm.computePositions(ch, n, orn, prop);
16726 offsetSize += n.getData(dim, prop);
16732 Layouts.TM.Area = {
16736 Called by loadJSON to calculate recursively all node positions and lay out the tree.
16740 json - A JSON tree. See also <Loader.loadJSON>.
16741 coord - A coordinates object specifying width, height, left and top style properties.
16743 compute: function(prop) {
16744 prop = prop || "current";
16745 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16746 this.controller.onBeforeCompute(root);
16747 var config = this.config,
16748 size = this.canvas.getSize(),
16749 width = size.width,
16750 height = size.height,
16751 offst = config.offset,
16752 offwdth = width - offst,
16753 offhght = height - offst;
16754 this.graph.computeLevels(this.root, 0, "ignore");
16755 //set root position and dimensions
16756 root.getPos(prop).setc(-width/2, -height/2);
16757 root.setData('width', width, prop);
16758 root.setData('height', height, prop);
16759 //create a coordinates object
16761 'top': -height/2 + config.titleHeight,
16764 'height': offhght - config.titleHeight
16766 this.computePositions(root, coord, prop);
16767 this.controller.onAfterCompute(root);
16773 Computes dimensions and positions of a group of nodes
16774 according to a custom layout row condition.
16778 tail - An array of nodes.
16779 initElem - An array of nodes (containing the initial node to be laid).
16780 w - A fixed dimension where nodes will be layed out.
16781 coord - A coordinates object specifying width, height, left and top style properties.
16782 comp - A custom comparison function
16784 computeDim: function(tail, initElem, w, coord, comp, prop) {
16785 if(tail.length + initElem.length == 1) {
16786 var l = (tail.length == 1)? tail : initElem;
16787 this.layoutLast(l, w, coord, prop);
16790 if(tail.length >= 2 && initElem.length == 0) {
16791 initElem = [tail.shift()];
16793 if(tail.length == 0) {
16794 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
16798 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
16799 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
16801 var newCoords = this.layoutRow(initElem, w, coord, prop);
16802 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
16808 Method: worstAspectRatio
16810 Calculates the worst aspect ratio of a group of rectangles.
16814 <http://en.wikipedia.org/wiki/Aspect_ratio>
16818 ch - An array of nodes.
16819 w - The fixed dimension where rectangles are being laid out.
16823 The worst aspect ratio.
16827 worstAspectRatio: function(ch, w) {
16828 if(!ch || ch.length == 0) return Number.MAX_VALUE;
16829 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
16830 for(var i=0, l=ch.length; i<l; i++) {
16831 var area = ch[i]._area;
16833 minArea = minArea < area? minArea : area;
16834 maxArea = maxArea > area? maxArea : area;
16836 var sqw = w * w, sqAreaSum = areaSum * areaSum;
16837 return Math.max(sqw * maxArea / sqAreaSum,
16838 sqAreaSum / (sqw * minArea));
16842 Method: avgAspectRatio
16844 Calculates the average aspect ratio of a group of rectangles.
16848 <http://en.wikipedia.org/wiki/Aspect_ratio>
16852 ch - An array of nodes.
16853 w - The fixed dimension where rectangles are being laid out.
16857 The average aspect ratio.
16861 avgAspectRatio: function(ch, w) {
16862 if(!ch || ch.length == 0) return Number.MAX_VALUE;
16864 for(var i=0, l=ch.length; i<l; i++) {
16865 var area = ch[i]._area;
16867 arSum += w > h? w / h : h / w;
16875 Performs the layout of the last computed sibling.
16879 ch - An array of nodes.
16880 w - A fixed dimension where nodes will be layed out.
16881 coord - A coordinates object specifying width, height, left and top style properties.
16883 layoutLast: function(ch, w, coord, prop) {
16885 child.getPos(prop).setc(coord.left, coord.top);
16886 child.setData('width', coord.width, prop);
16887 child.setData('height', coord.height, prop);
16892 Layouts.TM.Squarified = new Class({
16893 Implements: Layouts.TM.Area,
16895 computePositions: function(node, coord, prop) {
16896 var config = this.config;
16898 if (coord.width >= coord.height)
16899 this.layout.orientation = 'h';
16901 this.layout.orientation = 'v';
16903 var ch = node.getSubnodes([1, 1], "ignore");
16904 if(ch.length > 0) {
16905 this.processChildrenLayout(node, ch, coord, prop);
16906 for(var i=0, l=ch.length; i<l; i++) {
16908 var offst = config.offset,
16909 height = chi.getData('height', prop) - offst - config.titleHeight,
16910 width = chi.getData('width', prop) - offst;
16911 var chipos = chi.getPos(prop);
16915 'top': chipos.y + config.titleHeight,
16918 this.computePositions(chi, coord, prop);
16924 Method: processChildrenLayout
16926 Computes children real areas and other useful parameters for performing the Squarified algorithm.
16930 par - The parent node of the json subtree.
16931 ch - An Array of nodes
16932 coord - A coordinates object specifying width, height, left and top style properties.
16934 processChildrenLayout: function(par, ch, coord, prop) {
16935 //compute children real areas
16936 var parentArea = coord.width * coord.height;
16937 var i, l=ch.length, totalChArea=0, chArea = [];
16938 for(i=0; i<l; i++) {
16939 chArea[i] = parseFloat(ch[i].getData('area', prop));
16940 totalChArea += chArea[i];
16942 for(i=0; i<l; i++) {
16943 ch[i]._area = parentArea * chArea[i] / totalChArea;
16945 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
16946 ch.sort(function(a, b) {
16947 var diff = b._area - a._area;
16948 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
16950 var initElem = [ch[0]];
16951 var tail = ch.slice(1);
16952 this.squarify(tail, initElem, minimumSideValue, coord, prop);
16958 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
16962 tail - An array of nodes.
16963 initElem - An array of nodes, containing the initial node to be laid out.
16964 w - A fixed dimension where nodes will be laid out.
16965 coord - A coordinates object specifying width, height, left and top style properties.
16967 squarify: function(tail, initElem, w, coord, prop) {
16968 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
16974 Performs the layout of an array of nodes.
16978 ch - An array of nodes.
16979 w - A fixed dimension where nodes will be laid out.
16980 coord - A coordinates object specifying width, height, left and top style properties.
16982 layoutRow: function(ch, w, coord, prop) {
16983 if(this.layout.horizontal()) {
16984 return this.layoutV(ch, w, coord, prop);
16986 return this.layoutH(ch, w, coord, prop);
16990 layoutV: function(ch, w, coord, prop) {
16991 var totalArea = 0, rnd = function(x) { return x; };
16992 $.each(ch, function(elem) { totalArea += elem._area; });
16993 var width = rnd(totalArea / w), top = 0;
16994 for(var i=0, l=ch.length; i<l; i++) {
16995 var h = rnd(ch[i]._area / width);
16997 chi.getPos(prop).setc(coord.left, coord.top + top);
16998 chi.setData('width', width, prop);
16999 chi.setData('height', h, prop);
17003 'height': coord.height,
17004 'width': coord.width - width,
17006 'left': coord.left + width
17008 //take minimum side value.
17009 ans.dim = Math.min(ans.width, ans.height);
17010 if(ans.dim != ans.height) this.layout.change();
17014 layoutH: function(ch, w, coord, prop) {
17016 $.each(ch, function(elem) { totalArea += elem._area; });
17017 var height = totalArea / w,
17021 for(var i=0, l=ch.length; i<l; i++) {
17023 var w = chi._area / height;
17024 chi.getPos(prop).setc(coord.left + left, top);
17025 chi.setData('width', w, prop);
17026 chi.setData('height', height, prop);
17030 'height': coord.height - height,
17031 'width': coord.width,
17032 'top': coord.top + height,
17035 ans.dim = Math.min(ans.width, ans.height);
17036 if(ans.dim != ans.width) this.layout.change();
17041 Layouts.TM.Strip = new Class({
17042 Implements: Layouts.TM.Area,
17047 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17051 json - A JSON subtree. See also <Loader.loadJSON>.
17052 coord - A coordinates object specifying width, height, left and top style properties.
17054 computePositions: function(node, coord, prop) {
17055 var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17056 if(ch.length > 0) {
17057 this.processChildrenLayout(node, ch, coord, prop);
17058 for(var i=0, l=ch.length; i<l; i++) {
17060 var offst = config.offset,
17061 height = chi.getData('height', prop) - offst - config.titleHeight,
17062 width = chi.getData('width', prop) - offst;
17063 var chipos = chi.getPos(prop);
17067 'top': chipos.y + config.titleHeight,
17070 this.computePositions(chi, coord, prop);
17076 Method: processChildrenLayout
17078 Computes children real areas and other useful parameters for performing the Strip algorithm.
17082 par - The parent node of the json subtree.
17083 ch - An Array of nodes
17084 coord - A coordinates object specifying width, height, left and top style properties.
17086 processChildrenLayout: function(par, ch, coord, prop) {
17087 //compute children real areas
17088 var parentArea = coord.width * coord.height;
17089 var i, l=ch.length, totalChArea=0, chArea = [];
17090 for(i=0; i<l; i++) {
17091 chArea[i] = +ch[i].getData('area', prop);
17092 totalChArea += chArea[i];
17094 for(i=0; i<l; i++) {
17095 ch[i]._area = parentArea * chArea[i] / totalChArea;
17097 var side = this.layout.horizontal()? coord.width : coord.height;
17098 var initElem = [ch[0]];
17099 var tail = ch.slice(1);
17100 this.stripify(tail, initElem, side, coord, prop);
17106 Performs an heuristic method to calculate div elements sizes in order to have
17107 a good compromise between aspect ratio and order.
17111 tail - An array of nodes.
17112 initElem - An array of nodes.
17113 w - A fixed dimension where nodes will be layed out.
17114 coord - A coordinates object specifying width, height, left and top style properties.
17116 stripify: function(tail, initElem, w, coord, prop) {
17117 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17123 Performs the layout of an array of nodes.
17127 ch - An array of nodes.
17128 w - A fixed dimension where nodes will be laid out.
17129 coord - A coordinates object specifying width, height, left and top style properties.
17131 layoutRow: function(ch, w, coord, prop) {
17132 if(this.layout.horizontal()) {
17133 return this.layoutH(ch, w, coord, prop);
17135 return this.layoutV(ch, w, coord, prop);
17139 layoutV: function(ch, w, coord, prop) {
17141 $.each(ch, function(elem) { totalArea += elem._area; });
17142 var width = totalArea / w, top = 0;
17143 for(var i=0, l=ch.length; i<l; i++) {
17145 var h = chi._area / width;
17146 chi.getPos(prop).setc(coord.left,
17147 coord.top + (w - h - top));
17148 chi.setData('width', width, prop);
17149 chi.setData('height', h, prop);
17154 'height': coord.height,
17155 'width': coord.width - width,
17157 'left': coord.left + width,
17162 layoutH: function(ch, w, coord, prop) {
17164 $.each(ch, function(elem) { totalArea += elem._area; });
17165 var height = totalArea / w,
17166 top = coord.height - height,
17169 for(var i=0, l=ch.length; i<l; i++) {
17171 var s = chi._area / height;
17172 chi.getPos(prop).setc(coord.left + left, coord.top + top);
17173 chi.setData('width', s, prop);
17174 chi.setData('height', height, prop);
17178 'height': coord.height - height,
17179 'width': coord.width,
17181 'left': coord.left,
17188 * Class: Layouts.Icicle
17190 * Implements the icicle tree layout.
17198 Layouts.Icicle = new Class({
17202 * Called by loadJSON to calculate all node positions.
17206 * posType - The nodes' position to compute. Either "start", "end" or
17207 * "current". Defaults to "current".
17209 compute: function(posType) {
17210 posType = posType || "current";
17211 var root = this.graph.getNode(this.root),
17212 config = this.config,
17213 size = this.canvas.getSize(),
17214 width = size.width,
17215 height = size.height,
17216 offset = config.offset,
17217 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17219 this.controller.onBeforeCompute(root);
17221 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17225 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17227 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17228 var maxDepth = Math.min(treeDepth, levelsToShow-1);
17229 var initialDepth = startNode._depth;
17230 if(this.layout.horizontal()) {
17231 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17233 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17237 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17238 root.getPos(posType).setc(x, y);
17239 root.setData('width', width, posType);
17240 root.setData('height', height, posType);
17242 var nodeLength, prevNodeLength = 0, totalDim = 0;
17243 var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17245 if(!children.length)
17248 $.each(children, function(e) { totalDim += e.getData('dim'); });
17250 for(var i=0, l=children.length; i < l; i++) {
17251 if(this.layout.horizontal()) {
17252 nodeLength = height * children[i].getData('dim') / totalDim;
17253 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17256 nodeLength = width * children[i].getData('dim') / totalDim;
17257 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17274 Icicle space filling visualization.
17278 All <Loader> methods
17280 Constructor Options:
17282 Inherits options from
17285 - <Options.Controller>
17291 - <Options.NodeStyles>
17292 - <Options.Navigation>
17294 Additionally, there are other parameters and some default values changed
17296 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17297 offset - (number) Default's *2*. Boxes offset.
17298 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17299 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17300 animate - (boolean) Default's *false*. Whether to animate transitions.
17301 Node.type - Described in <Options.Node>. Default's *rectangle*.
17302 Label.type - Described in <Options.Label>. Default's *Native*.
17303 duration - Described in <Options.Fx>. Default's *700*.
17304 fps - Described in <Options.Fx>. Default's *45*.
17306 Instance Properties:
17308 canvas - Access a <Canvas> instance.
17309 graph - Access a <Graph> instance.
17310 op - Access a <Icicle.Op> instance.
17311 fx - Access a <Icicle.Plot> instance.
17312 labels - Access a <Icicle.Label> interface implementation.
17316 $jit.Icicle = new Class({
17317 Implements: [ Loader, Extras, Layouts.Icicle ],
17321 vertical: function(){
17322 return this.orientation == "v";
17324 horizontal: function(){
17325 return this.orientation == "h";
17327 change: function(){
17328 this.orientation = this.vertical()? "h" : "v";
17332 initialize: function(controller) {
17337 levelsToShow: Number.MAX_VALUE,
17338 constrained: false,
17353 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17354 "Events", "Navigation", "Controller", "Label");
17355 this.controller = this.config = $.merge(opts, config, controller);
17356 this.layout.orientation = this.config.orientation;
17358 var canvasConfig = this.config;
17359 if (canvasConfig.useCanvas) {
17360 this.canvas = canvasConfig.useCanvas;
17361 this.config.labelContainer = this.canvas.id + '-label';
17363 this.canvas = new Canvas(this, canvasConfig);
17364 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17367 this.graphOptions = {
17376 this.graph = new Graph(
17377 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17379 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17380 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17381 this.op = new $jit.Icicle.Op(this);
17382 this.group = new $jit.Icicle.Group(this);
17383 this.clickedNode = null;
17385 this.initializeExtras();
17391 Computes positions and plots the tree.
17393 refresh: function(){
17394 var labelType = this.config.Label.type;
17395 if(labelType != 'Native') {
17397 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17406 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
17410 this.fx.plot(this.config);
17416 Sets the node as root.
17420 node - (object) A <Graph.Node>.
17423 enter: function (node) {
17429 config = this.config;
17432 onComplete: function() {
17433 //compute positions of newly inserted nodes
17437 if(config.animate) {
17438 that.graph.nodeList.setDataset(['current', 'end'], {
17439 'alpha': [1, 0] //fade nodes
17442 Graph.Util.eachSubgraph(node, function(n) {
17443 n.setData('alpha', 1, 'end');
17448 modes:['node-property:alpha'],
17449 onComplete: function() {
17450 that.clickedNode = node;
17451 that.compute('end');
17454 modes:['linear', 'node-property:width:height'],
17456 onComplete: function() {
17458 that.clickedNode = node;
17464 that.clickedNode = node;
17471 if(config.request) {
17472 this.requestNodes(clickedNode, callback);
17474 callback.onComplete();
17481 Sets the parent node of the current selected node as root.
17489 GUtil = Graph.Util,
17490 config = this.config,
17491 graph = this.graph,
17492 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17493 parent = parents[0],
17494 clickedNode = parent,
17495 previousClickedNode = this.clickedNode;
17498 this.events.hoveredNode = false;
17505 //final plot callback
17507 onComplete: function() {
17508 that.clickedNode = parent;
17509 if(config.request) {
17510 that.requestNodes(parent, {
17511 onComplete: function() {
17525 //animate node positions
17526 if(config.animate) {
17527 this.clickedNode = clickedNode;
17528 this.compute('end');
17529 //animate the visible subtree only
17530 this.clickedNode = previousClickedNode;
17532 modes:['linear', 'node-property:width:height'],
17534 onComplete: function() {
17535 //animate the parent subtree
17536 that.clickedNode = clickedNode;
17537 //change nodes alpha
17538 graph.nodeList.setDataset(['current', 'end'], {
17541 GUtil.eachSubgraph(previousClickedNode, function(node) {
17542 node.setData('alpha', 1);
17546 modes:['node-property:alpha'],
17547 onComplete: function() {
17548 callback.onComplete();
17554 callback.onComplete();
17557 requestNodes: function(node, onComplete){
17558 var handler = $.merge(this.controller, onComplete),
17559 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17561 if (handler.request) {
17562 var leaves = [], d = node._depth;
17563 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17564 if (n.drawn && !Graph.Util.anySubnode(n)) {
17566 n._level = n._depth - d;
17567 if (this.config.constrained)
17568 n._level = levelsToShow - n._level;
17572 this.group.requestNodes(leaves, handler);
17574 handler.onComplete();
17582 Custom extension of <Graph.Op>.
17586 All <Graph.Op> methods
17593 $jit.Icicle.Op = new Class({
17595 Implements: Graph.Op
17600 * Performs operations on group of nodes.
17602 $jit.Icicle.Group = new Class({
17604 initialize: function(viz){
17606 this.canvas = viz.canvas;
17607 this.config = viz.config;
17611 * Calls the request method on the controller to request a subtree for each node.
17613 requestNodes: function(nodes, controller){
17614 var counter = 0, len = nodes.length, nodeSelected = {};
17615 var complete = function(){
17616 controller.onComplete();
17618 var viz = this.viz;
17621 for(var i = 0; i < len; i++) {
17622 nodeSelected[nodes[i].id] = nodes[i];
17623 controller.request(nodes[i].id, nodes[i]._level, {
17624 onComplete: function(nodeId, data){
17625 if (data && data.children) {
17631 if (++counter == len) {
17632 Graph.Util.computeLevels(viz.graph, viz.root, 0);
17644 Custom extension of <Graph.Plot>.
17648 All <Graph.Plot> methods
17655 $jit.Icicle.Plot = new Class({
17656 Implements: Graph.Plot,
17658 plot: function(opt, animating){
17659 opt = opt || this.viz.controller;
17660 var viz = this.viz,
17662 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17663 initialDepth = root._depth;
17665 viz.canvas.clear();
17666 this.plotTree(root, $.merge(opt, {
17667 'withLabels': true,
17668 'hideLabels': false,
17669 'plotSubtree': function(root, node) {
17670 return !viz.config.constrained ||
17671 (node._depth - initialDepth < viz.config.levelsToShow);
17678 Class: Icicle.Label
17680 Custom extension of <Graph.Label>.
17681 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17685 All <Graph.Label> methods and subclasses.
17689 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17692 $jit.Icicle.Label = {};
17695 Icicle.Label.Native
17697 Custom extension of <Graph.Label.Native>.
17701 All <Graph.Label.Native> methods
17705 <Graph.Label.Native>
17708 $jit.Icicle.Label.Native = new Class({
17709 Implements: Graph.Label.Native,
17711 renderLabel: function(canvas, node, controller) {
17712 var ctx = canvas.getCtx(),
17713 width = node.getData('width'),
17714 height = node.getData('height'),
17715 size = node.getLabelData('size'),
17716 m = ctx.measureText(node.name);
17718 // Guess as much as possible if the label will fit in the node
17719 if(height < (size * 1.5) || width < m.width)
17722 var pos = node.pos.getc(true);
17723 ctx.fillText(node.name,
17725 pos.y + height / 2);
17732 Custom extension of <Graph.Label.SVG>.
17736 All <Graph.Label.SVG> methods
17742 $jit.Icicle.Label.SVG = new Class( {
17743 Implements: Graph.Label.SVG,
17745 initialize: function(viz){
17752 Overrides abstract method placeLabel in <Graph.Plot>.
17756 tag - A DOM label element.
17757 node - A <Graph.Node>.
17758 controller - A configuration/controller object passed to the visualization.
17760 placeLabel: function(tag, node, controller){
17761 var pos = node.pos.getc(true), canvas = this.viz.canvas;
17762 var radius = canvas.getSize();
17764 x: Math.round(pos.x + radius.width / 2),
17765 y: Math.round(pos.y + radius.height / 2)
17767 tag.setAttribute('x', labelPos.x);
17768 tag.setAttribute('y', labelPos.y);
17770 controller.onPlaceLabel(tag, node);
17777 Custom extension of <Graph.Label.HTML>.
17781 All <Graph.Label.HTML> methods.
17788 $jit.Icicle.Label.HTML = new Class( {
17789 Implements: Graph.Label.HTML,
17791 initialize: function(viz){
17798 Overrides abstract method placeLabel in <Graph.Plot>.
17802 tag - A DOM label element.
17803 node - A <Graph.Node>.
17804 controller - A configuration/controller object passed to the visualization.
17806 placeLabel: function(tag, node, controller){
17807 var pos = node.pos.getc(true), canvas = this.viz.canvas;
17808 var radius = canvas.getSize();
17810 x: Math.round(pos.x + radius.width / 2),
17811 y: Math.round(pos.y + radius.height / 2)
17814 var style = tag.style;
17815 style.left = labelPos.x + 'px';
17816 style.top = labelPos.y + 'px';
17817 style.display = '';
17819 controller.onPlaceLabel(tag, node);
17824 Class: Icicle.Plot.NodeTypes
17826 This class contains a list of <Graph.Node> built-in types.
17827 Node types implemented are 'none', 'rectangle'.
17829 You can add your custom node types, customizing your visualization to the extreme.
17834 Icicle.Plot.NodeTypes.implement({
17836 'render': function(node, canvas) {
17837 //print your custom node to canvas
17840 'contains': function(node, pos) {
17841 //return true if pos is inside the node or false otherwise
17848 $jit.Icicle.Plot.NodeTypes = new Class( {
17854 'render': function(node, canvas, animating) {
17855 var config = this.viz.config;
17856 var offset = config.offset;
17857 var width = node.getData('width');
17858 var height = node.getData('height');
17859 var border = node.getData('border');
17860 var pos = node.pos.getc(true);
17861 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
17862 var ctx = canvas.getCtx();
17864 if(width - offset < 2 || height - offset < 2) return;
17866 if(config.cushion) {
17867 var color = node.getData('color');
17868 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
17869 posy + (height - offset)/2, 1,
17870 posx + (width-offset)/2, posy + (height-offset)/2,
17871 width < height? height : width);
17872 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
17873 function(r) { return r * 0.3 >> 0; }));
17874 lg.addColorStop(0, color);
17875 lg.addColorStop(1, colorGrad);
17876 ctx.fillStyle = lg;
17880 ctx.strokeStyle = border;
17884 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
17885 border && ctx.strokeRect(pos.x, pos.y, width, height);
17888 'contains': function(node, pos) {
17889 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
17890 var npos = node.pos.getc(true),
17891 width = node.getData('width'),
17892 height = node.getData('height');
17893 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
17898 $jit.Icicle.Plot.EdgeTypes = new Class( {
17905 * File: Layouts.ForceDirected.js
17910 * Class: Layouts.ForceDirected
17912 * Implements a Force Directed Layout.
17920 * Marcus Cobden <http://marcuscobden.co.uk>
17923 Layouts.ForceDirected = new Class({
17925 getOptions: function(random) {
17926 var s = this.canvas.getSize();
17927 var w = s.width, h = s.height;
17930 this.graph.eachNode(function(n) {
17933 var k2 = w * h / count, k = Math.sqrt(k2);
17934 var l = this.config.levelDistance;
17940 nodef: function(x) { return k2 / (x || 1); },
17941 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
17945 compute: function(property, incremental) {
17946 var prop = $.splat(property || ['current', 'start', 'end']);
17947 var opt = this.getOptions();
17948 NodeDim.compute(this.graph, prop, this.config);
17949 this.graph.computeLevels(this.root, 0, "ignore");
17950 this.graph.eachNode(function(n) {
17951 $.each(prop, function(p) {
17952 var pos = n.getPos(p);
17953 if(pos.equals(Complex.KER)) {
17954 pos.x = opt.width/5 * (Math.random() - 0.5);
17955 pos.y = opt.height/5 * (Math.random() - 0.5);
17957 //initialize disp vector
17959 $.each(prop, function(p) {
17960 n.disp[p] = $C(0, 0);
17964 this.computePositions(prop, opt, incremental);
17967 computePositions: function(property, opt, incremental) {
17968 var times = this.config.iterations, i = 0, that = this;
17971 for(var total=incremental.iter, j=0; j<total; j++) {
17972 opt.t = opt.tstart * (1 - i++/(times -1));
17973 that.computePositionStep(property, opt);
17975 incremental.onComplete();
17979 incremental.onStep(Math.round(i / (times -1) * 100));
17980 setTimeout(iter, 1);
17983 for(; i < times; i++) {
17984 opt.t = opt.tstart * (1 - i/(times -1));
17985 this.computePositionStep(property, opt);
17990 computePositionStep: function(property, opt) {
17991 var graph = this.graph;
17992 var min = Math.min, max = Math.max;
17993 var dpos = $C(0, 0);
17994 //calculate repulsive forces
17995 graph.eachNode(function(v) {
17997 $.each(property, function(p) {
17998 v.disp[p].x = 0; v.disp[p].y = 0;
18000 graph.eachNode(function(u) {
18002 $.each(property, function(p) {
18003 var vp = v.getPos(p), up = u.getPos(p);
18004 dpos.x = vp.x - up.x;
18005 dpos.y = vp.y - up.y;
18006 var norm = dpos.norm() || 1;
18007 v.disp[p].$add(dpos
18008 .$scale(opt.nodef(norm) / norm));
18013 //calculate attractive forces
18014 var T = !!graph.getNode(this.root).visited;
18015 graph.eachNode(function(node) {
18016 node.eachAdjacency(function(adj) {
18017 var nodeTo = adj.nodeTo;
18018 if(!!nodeTo.visited === T) {
18019 $.each(property, function(p) {
18020 var vp = node.getPos(p), up = nodeTo.getPos(p);
18021 dpos.x = vp.x - up.x;
18022 dpos.y = vp.y - up.y;
18023 var norm = dpos.norm() || 1;
18024 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18025 nodeTo.disp[p].$add(dpos.$scale(-1));
18031 //arrange positions to fit the canvas
18032 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18033 graph.eachNode(function(u) {
18034 $.each(property, function(p) {
18035 var disp = u.disp[p];
18036 var norm = disp.norm() || 1;
18037 var p = u.getPos(p);
18038 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
18039 disp.y * min(Math.abs(disp.y), t) / norm));
18040 p.x = min(w2, max(-w2, p.x));
18041 p.y = min(h2, max(-h2, p.y));
18048 * File: ForceDirected.js
18052 Class: ForceDirected
18054 A visualization that lays graphs using a Force-Directed layout algorithm.
18058 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18062 All <Loader> methods
18064 Constructor Options:
18066 Inherits options from
18069 - <Options.Controller>
18075 - <Options.NodeStyles>
18076 - <Options.Navigation>
18078 Additionally, there are two parameters
18080 levelDistance - (number) Default's *50*. The natural length desired for the edges.
18081 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*.
18083 Instance Properties:
18085 canvas - Access a <Canvas> instance.
18086 graph - Access a <Graph> instance.
18087 op - Access a <ForceDirected.Op> instance.
18088 fx - Access a <ForceDirected.Plot> instance.
18089 labels - Access a <ForceDirected.Label> interface implementation.
18093 $jit.ForceDirected = new Class( {
18095 Implements: [ Loader, Extras, Layouts.ForceDirected ],
18097 initialize: function(controller) {
18098 var $ForceDirected = $jit.ForceDirected;
18105 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18106 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18108 var canvasConfig = this.config;
18109 if(canvasConfig.useCanvas) {
18110 this.canvas = canvasConfig.useCanvas;
18111 this.config.labelContainer = this.canvas.id + '-label';
18113 if(canvasConfig.background) {
18114 canvasConfig.background = $.merge({
18116 }, canvasConfig.background);
18118 this.canvas = new Canvas(this, canvasConfig);
18119 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18122 this.graphOptions = {
18130 this.graph = new Graph(this.graphOptions, this.config.Node,
18132 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18133 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18134 this.op = new $ForceDirected.Op(this);
18137 // initialize extras
18138 this.initializeExtras();
18144 Computes positions and plots the tree.
18146 refresh: function() {
18151 reposition: function() {
18152 this.compute('end');
18156 Method: computeIncremental
18158 Performs the Force Directed algorithm incrementally.
18162 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
18163 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
18164 avoiding browser messages such as "This script is taking too long to complete".
18168 opt - (object) The object properties are described below
18170 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
18171 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18173 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
18174 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
18175 computations for final animation positions then you can just choose 'end'.
18177 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
18178 parameter a percentage value.
18180 onComplete - A callback function called when the algorithm completed.
18184 In this example I calculate the end positions and then animate the graph to those positions
18187 var fd = new $jit.ForceDirected(...);
18188 fd.computeIncremental({
18191 onStep: function(perc) {
18192 Log.write("loading " + perc + "%");
18194 onComplete: function() {
18201 In this example I calculate all positions and (re)plot the graph
18204 var fd = new ForceDirected(...);
18205 fd.computeIncremental({
18207 property: ['end', 'start', 'current'],
18208 onStep: function(perc) {
18209 Log.write("loading " + perc + "%");
18211 onComplete: function() {
18219 computeIncremental: function(opt) {
18224 onComplete: $.empty
18227 this.config.onBeforeCompute(this.graph.getNode(this.root));
18228 this.compute(opt.property, opt);
18234 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18243 Animates the graph from the current positions to the 'end' node positions.
18245 animate: function(opt) {
18246 this.fx.animate($.merge( {
18247 modes: [ 'linear' ]
18252 $jit.ForceDirected.$extend = true;
18254 (function(ForceDirected) {
18257 Class: ForceDirected.Op
18259 Custom extension of <Graph.Op>.
18263 All <Graph.Op> methods
18270 ForceDirected.Op = new Class( {
18272 Implements: Graph.Op
18277 Class: ForceDirected.Plot
18279 Custom extension of <Graph.Plot>.
18283 All <Graph.Plot> methods
18290 ForceDirected.Plot = new Class( {
18292 Implements: Graph.Plot
18297 Class: ForceDirected.Label
18299 Custom extension of <Graph.Label>.
18300 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18304 All <Graph.Label> methods and subclasses.
18308 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18311 ForceDirected.Label = {};
18314 ForceDirected.Label.Native
18316 Custom extension of <Graph.Label.Native>.
18320 All <Graph.Label.Native> methods
18324 <Graph.Label.Native>
18327 ForceDirected.Label.Native = new Class( {
18328 Implements: Graph.Label.Native
18332 ForceDirected.Label.SVG
18334 Custom extension of <Graph.Label.SVG>.
18338 All <Graph.Label.SVG> methods
18345 ForceDirected.Label.SVG = new Class( {
18346 Implements: Graph.Label.SVG,
18348 initialize: function(viz) {
18355 Overrides abstract method placeLabel in <Graph.Label>.
18359 tag - A DOM label element.
18360 node - A <Graph.Node>.
18361 controller - A configuration/controller object passed to the visualization.
18364 placeLabel: function(tag, node, controller) {
18365 var pos = node.pos.getc(true),
18366 canvas = this.viz.canvas,
18367 ox = canvas.translateOffsetX,
18368 oy = canvas.translateOffsetY,
18369 sx = canvas.scaleOffsetX,
18370 sy = canvas.scaleOffsetY,
18371 radius = canvas.getSize();
18373 x: Math.round(pos.x * sx + ox + radius.width / 2),
18374 y: Math.round(pos.y * sy + oy + radius.height / 2)
18376 tag.setAttribute('x', labelPos.x);
18377 tag.setAttribute('y', labelPos.y);
18379 controller.onPlaceLabel(tag, node);
18384 ForceDirected.Label.HTML
18386 Custom extension of <Graph.Label.HTML>.
18390 All <Graph.Label.HTML> methods.
18397 ForceDirected.Label.HTML = new Class( {
18398 Implements: Graph.Label.HTML,
18400 initialize: function(viz) {
18406 Overrides abstract method placeLabel in <Graph.Plot>.
18410 tag - A DOM label element.
18411 node - A <Graph.Node>.
18412 controller - A configuration/controller object passed to the visualization.
18415 placeLabel: function(tag, node, controller) {
18416 var pos = node.pos.getc(true),
18417 canvas = this.viz.canvas,
18418 ox = canvas.translateOffsetX,
18419 oy = canvas.translateOffsetY,
18420 sx = canvas.scaleOffsetX,
18421 sy = canvas.scaleOffsetY,
18422 radius = canvas.getSize();
18424 x: Math.round(pos.x * sx + ox + radius.width / 2),
18425 y: Math.round(pos.y * sy + oy + radius.height / 2)
18427 var style = tag.style;
18428 style.left = labelPos.x + 'px';
18429 style.top = labelPos.y + 'px';
18430 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18432 controller.onPlaceLabel(tag, node);
18437 Class: ForceDirected.Plot.NodeTypes
18439 This class contains a list of <Graph.Node> built-in types.
18440 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18442 You can add your custom node types, customizing your visualization to the extreme.
18447 ForceDirected.Plot.NodeTypes.implement({
18449 'render': function(node, canvas) {
18450 //print your custom node to canvas
18453 'contains': function(node, pos) {
18454 //return true if pos is inside the node or false otherwise
18461 ForceDirected.Plot.NodeTypes = new Class({
18464 'contains': $.lambda(false)
18467 'render': function(node, canvas){
18468 var pos = node.pos.getc(true),
18469 dim = node.getData('dim');
18470 this.nodeHelper.circle.render('fill', pos, dim, canvas);
18472 'contains': function(node, pos){
18473 var npos = node.pos.getc(true),
18474 dim = node.getData('dim');
18475 return this.nodeHelper.circle.contains(npos, pos, dim);
18479 'render': function(node, canvas){
18480 var pos = node.pos.getc(true),
18481 width = node.getData('width'),
18482 height = node.getData('height');
18483 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18485 // TODO(nico): be more precise...
18486 'contains': function(node, pos){
18487 var npos = node.pos.getc(true),
18488 width = node.getData('width'),
18489 height = node.getData('height');
18490 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18494 'render': function(node, canvas){
18495 var pos = node.pos.getc(true),
18496 dim = node.getData('dim');
18497 this.nodeHelper.square.render('fill', pos, dim, canvas);
18499 'contains': function(node, pos){
18500 var npos = node.pos.getc(true),
18501 dim = node.getData('dim');
18502 return this.nodeHelper.square.contains(npos, pos, dim);
18506 'render': function(node, canvas){
18507 var pos = node.pos.getc(true),
18508 width = node.getData('width'),
18509 height = node.getData('height');
18510 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18512 'contains': function(node, pos){
18513 var npos = node.pos.getc(true),
18514 width = node.getData('width'),
18515 height = node.getData('height');
18516 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18520 'render': function(node, canvas){
18521 var pos = node.pos.getc(true),
18522 dim = node.getData('dim');
18523 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18525 'contains': function(node, pos) {
18526 var npos = node.pos.getc(true),
18527 dim = node.getData('dim');
18528 return this.nodeHelper.triangle.contains(npos, pos, dim);
18532 'render': function(node, canvas){
18533 var pos = node.pos.getc(true),
18534 dim = node.getData('dim');
18535 this.nodeHelper.star.render('fill', pos, dim, canvas);
18537 'contains': function(node, pos) {
18538 var npos = node.pos.getc(true),
18539 dim = node.getData('dim');
18540 return this.nodeHelper.star.contains(npos, pos, dim);
18546 Class: ForceDirected.Plot.EdgeTypes
18548 This class contains a list of <Graph.Adjacence> built-in types.
18549 Edge types implemented are 'none', 'line' and 'arrow'.
18551 You can add your custom edge types, customizing your visualization to the extreme.
18556 ForceDirected.Plot.EdgeTypes.implement({
18558 'render': function(adj, canvas) {
18559 //print your custom edge to canvas
18562 'contains': function(adj, pos) {
18563 //return true if pos is inside the arc or false otherwise
18570 ForceDirected.Plot.EdgeTypes = new Class({
18573 'render': function(adj, canvas) {
18574 var from = adj.nodeFrom.pos.getc(true),
18575 to = adj.nodeTo.pos.getc(true);
18576 this.edgeHelper.line.render(from, to, canvas);
18578 'contains': function(adj, pos) {
18579 var from = adj.nodeFrom.pos.getc(true),
18580 to = adj.nodeTo.pos.getc(true);
18581 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18585 'render': function(adj, canvas) {
18586 var from = adj.nodeFrom.pos.getc(true),
18587 to = adj.nodeTo.pos.getc(true),
18588 dim = adj.getData('dim'),
18589 direction = adj.data.$direction,
18590 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18591 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18593 'contains': function(adj, pos) {
18594 var from = adj.nodeFrom.pos.getc(true),
18595 to = adj.nodeTo.pos.getc(true);
18596 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18601 })($jit.ForceDirected);
18613 $jit.TM.$extend = true;
18618 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18622 All <Loader> methods
18624 Constructor Options:
18626 Inherits options from
18629 - <Options.Controller>
18635 - <Options.NodeStyles>
18636 - <Options.Navigation>
18638 Additionally, there are other parameters and some default values changed
18640 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18641 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18642 offset - (number) Default's *2*. Boxes offset.
18643 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18644 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18645 animate - (boolean) Default's *false*. Whether to animate transitions.
18646 Node.type - Described in <Options.Node>. Default's *rectangle*.
18647 duration - Described in <Options.Fx>. Default's *700*.
18648 fps - Described in <Options.Fx>. Default's *45*.
18650 Instance Properties:
18652 canvas - Access a <Canvas> instance.
18653 graph - Access a <Graph> instance.
18654 op - Access a <TM.Op> instance.
18655 fx - Access a <TM.Plot> instance.
18656 labels - Access a <TM.Label> interface implementation.
18660 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18662 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18666 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.
18672 vertical: function(){
18673 return this.orientation == "v";
18675 horizontal: function(){
18676 return this.orientation == "h";
18678 change: function(){
18679 this.orientation = this.vertical()? "h" : "v";
18683 initialize: function(controller){
18689 constrained: false,
18694 //we all know why this is not zero,
18701 textAlign: 'center',
18702 textBaseline: 'top'
18711 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18712 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18713 this.layout.orientation = this.config.orientation;
18715 var canvasConfig = this.config;
18716 if (canvasConfig.useCanvas) {
18717 this.canvas = canvasConfig.useCanvas;
18718 this.config.labelContainer = this.canvas.id + '-label';
18720 if(canvasConfig.background) {
18721 canvasConfig.background = $.merge({
18723 }, canvasConfig.background);
18725 this.canvas = new Canvas(this, canvasConfig);
18726 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18729 this.graphOptions = {
18737 this.graph = new Graph(this.graphOptions, this.config.Node,
18739 this.labels = new TM.Label[canvasConfig.Label.type](this);
18740 this.fx = new TM.Plot(this);
18741 this.op = new TM.Op(this);
18742 this.group = new TM.Group(this);
18743 this.geom = new TM.Geom(this);
18744 this.clickedNode = null;
18746 // initialize extras
18747 this.initializeExtras();
18753 Computes positions and plots the tree.
18755 refresh: function(){
18756 if(this.busy) return;
18759 if(this.config.animate) {
18760 this.compute('end');
18761 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18762 && this.clickedNode.id || this.root));
18763 this.fx.animate($.merge(this.config, {
18764 modes: ['linear', 'node-property:width:height'],
18765 onComplete: function() {
18770 var labelType = this.config.Label.type;
18771 if(labelType != 'Native') {
18773 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18777 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18778 && this.clickedNode.id || this.root));
18786 Plots the TreeMap. This is a shortcut to *fx.plot*.
18796 Returns whether the node is a leaf.
18800 n - (object) A <Graph.Node>.
18804 return n.getSubnodes([
18806 ], "ignore").length == 0;
18812 Sets the node as root.
18816 n - (object) A <Graph.Node>.
18819 enter: function(n){
18820 if(this.busy) return;
18824 config = this.config,
18825 graph = this.graph,
18827 previousClickedNode = this.clickedNode;
18830 onComplete: function() {
18831 //ensure that nodes are shown for that level
18832 if(config.levelsToShow > 0) {
18833 that.geom.setRightLevelToShow(n);
18835 //compute positions of newly inserted nodes
18836 if(config.levelsToShow > 0 || config.request) that.compute();
18837 if(config.animate) {
18839 graph.nodeList.setData('alpha', 0, 'end');
18840 n.eachSubgraph(function(n) {
18841 n.setData('alpha', 1, 'end');
18845 modes:['node-property:alpha'],
18846 onComplete: function() {
18847 //compute end positions
18848 that.clickedNode = clickedNode;
18849 that.compute('end');
18850 //animate positions
18851 //TODO(nico) commenting this line didn't seem to throw errors...
18852 that.clickedNode = previousClickedNode;
18854 modes:['linear', 'node-property:width:height'],
18856 onComplete: function() {
18858 //TODO(nico) check comment above
18859 that.clickedNode = clickedNode;
18866 that.clickedNode = n;
18871 if(config.request) {
18872 this.requestNodes(clickedNode, callback);
18874 callback.onComplete();
18881 Sets the parent node of the current selected node as root.
18885 if(this.busy) return;
18887 this.events.hoveredNode = false;
18889 config = this.config,
18890 graph = this.graph,
18891 parents = graph.getNode(this.clickedNode
18892 && this.clickedNode.id || this.root).getParents(),
18893 parent = parents[0],
18894 clickedNode = parent,
18895 previousClickedNode = this.clickedNode;
18897 //if no parents return
18902 //final plot callback
18904 onComplete: function() {
18905 that.clickedNode = parent;
18906 if(config.request) {
18907 that.requestNodes(parent, {
18908 onComplete: function() {
18922 if (config.levelsToShow > 0)
18923 this.geom.setRightLevelToShow(parent);
18924 //animate node positions
18925 if(config.animate) {
18926 this.clickedNode = clickedNode;
18927 this.compute('end');
18928 //animate the visible subtree only
18929 this.clickedNode = previousClickedNode;
18931 modes:['linear', 'node-property:width:height'],
18933 onComplete: function() {
18934 //animate the parent subtree
18935 that.clickedNode = clickedNode;
18936 //change nodes alpha
18937 graph.eachNode(function(n) {
18938 n.setDataset(['current', 'end'], {
18942 previousClickedNode.eachSubgraph(function(node) {
18943 node.setData('alpha', 1);
18947 modes:['node-property:alpha'],
18948 onComplete: function() {
18949 callback.onComplete();
18955 callback.onComplete();
18959 requestNodes: function(node, onComplete){
18960 var handler = $.merge(this.controller, onComplete),
18961 lev = this.config.levelsToShow;
18962 if (handler.request) {
18963 var leaves = [], d = node._depth;
18964 node.eachLevel(0, lev, function(n){
18965 var nodeLevel = lev - (n._depth - d);
18966 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
18968 n._level = nodeLevel;
18971 this.group.requestNodes(leaves, handler);
18973 handler.onComplete();
18981 Custom extension of <Graph.Op>.
18985 All <Graph.Op> methods
18992 TM.Op = new Class({
18993 Implements: Graph.Op,
18995 initialize: function(viz){
19000 //extend level methods of Graph.Geom
19001 TM.Geom = new Class({
19002 Implements: Graph.Geom,
19004 getRightLevelToShow: function() {
19005 return this.viz.config.levelsToShow;
19008 setRightLevelToShow: function(node) {
19009 var level = this.getRightLevelToShow(),
19010 fx = this.viz.labels;
19011 node.eachLevel(0, level+1, function(n) {
19012 var d = n._depth - node._depth;
19017 fx.hideLabel(n, false);
19025 delete node.ignore;
19031 Performs operations on group of nodes.
19034 TM.Group = new Class( {
19036 initialize: function(viz){
19038 this.canvas = viz.canvas;
19039 this.config = viz.config;
19044 Calls the request method on the controller to request a subtree for each node.
19046 requestNodes: function(nodes, controller){
19047 var counter = 0, len = nodes.length, nodeSelected = {};
19048 var complete = function(){
19049 controller.onComplete();
19051 var viz = this.viz;
19054 for ( var i = 0; i < len; i++) {
19055 nodeSelected[nodes[i].id] = nodes[i];
19056 controller.request(nodes[i].id, nodes[i]._level, {
19057 onComplete: function(nodeId, data){
19058 if (data && data.children) {
19064 if (++counter == len) {
19065 viz.graph.computeLevels(viz.root, 0);
19077 Custom extension of <Graph.Plot>.
19081 All <Graph.Plot> methods
19088 TM.Plot = new Class({
19090 Implements: Graph.Plot,
19092 initialize: function(viz){
19094 this.config = viz.config;
19095 this.node = this.config.Node;
19096 this.edge = this.config.Edge;
19097 this.animation = new Animation;
19098 this.nodeTypes = new TM.Plot.NodeTypes;
19099 this.edgeTypes = new TM.Plot.EdgeTypes;
19100 this.labels = viz.labels;
19103 plot: function(opt, animating){
19104 var viz = this.viz,
19106 viz.canvas.clear();
19107 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19108 'withLabels': true,
19109 'hideLabels': false,
19110 'plotSubtree': function(n, ch){
19111 return n.anySubnode("exist");
19120 Custom extension of <Graph.Label>.
19121 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19125 All <Graph.Label> methods and subclasses.
19129 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19137 Custom extension of <Graph.Label.Native>.
19141 All <Graph.Label.Native> methods
19145 <Graph.Label.Native>
19147 TM.Label.Native = new Class({
19148 Implements: Graph.Label.Native,
19150 initialize: function(viz) {
19151 this.config = viz.config;
19152 this.leaf = viz.leaf;
19155 renderLabel: function(canvas, node, controller){
19156 if(!this.leaf(node) && !this.config.titleHeight) return;
19157 var pos = node.pos.getc(true),
19158 ctx = canvas.getCtx(),
19159 width = node.getData('width'),
19160 height = node.getData('height'),
19161 x = pos.x + width/2,
19164 ctx.fillText(node.name, x, y, width);
19171 Custom extension of <Graph.Label.SVG>.
19175 All <Graph.Label.SVG> methods
19181 TM.Label.SVG = new Class( {
19182 Implements: Graph.Label.SVG,
19184 initialize: function(viz){
19186 this.leaf = viz.leaf;
19187 this.config = viz.config;
19193 Overrides abstract method placeLabel in <Graph.Plot>.
19197 tag - A DOM label element.
19198 node - A <Graph.Node>.
19199 controller - A configuration/controller object passed to the visualization.
19202 placeLabel: function(tag, node, controller){
19203 var pos = node.pos.getc(true),
19204 canvas = this.viz.canvas,
19205 ox = canvas.translateOffsetX,
19206 oy = canvas.translateOffsetY,
19207 sx = canvas.scaleOffsetX,
19208 sy = canvas.scaleOffsetY,
19209 radius = canvas.getSize();
19211 x: Math.round(pos.x * sx + ox + radius.width / 2),
19212 y: Math.round(pos.y * sy + oy + radius.height / 2)
19214 tag.setAttribute('x', labelPos.x);
19215 tag.setAttribute('y', labelPos.y);
19217 if(!this.leaf(node) && !this.config.titleHeight) {
19218 tag.style.display = 'none';
19220 controller.onPlaceLabel(tag, node);
19227 Custom extension of <Graph.Label.HTML>.
19231 All <Graph.Label.HTML> methods.
19238 TM.Label.HTML = new Class( {
19239 Implements: Graph.Label.HTML,
19241 initialize: function(viz){
19243 this.leaf = viz.leaf;
19244 this.config = viz.config;
19250 Overrides abstract method placeLabel in <Graph.Plot>.
19254 tag - A DOM label element.
19255 node - A <Graph.Node>.
19256 controller - A configuration/controller object passed to the visualization.
19259 placeLabel: function(tag, node, controller){
19260 var pos = node.pos.getc(true),
19261 canvas = this.viz.canvas,
19262 ox = canvas.translateOffsetX,
19263 oy = canvas.translateOffsetY,
19264 sx = canvas.scaleOffsetX,
19265 sy = canvas.scaleOffsetY,
19266 radius = canvas.getSize();
19268 x: Math.round(pos.x * sx + ox + radius.width / 2),
19269 y: Math.round(pos.y * sy + oy + radius.height / 2)
19272 var style = tag.style;
19273 style.left = labelPos.x + 'px';
19274 style.top = labelPos.y + 'px';
19275 style.width = node.getData('width') * sx + 'px';
19276 style.height = node.getData('height') * sy + 'px';
19277 style.zIndex = node._depth * 100;
19278 style.display = '';
19280 if(!this.leaf(node) && !this.config.titleHeight) {
19281 tag.style.display = 'none';
19283 controller.onPlaceLabel(tag, node);
19288 Class: TM.Plot.NodeTypes
19290 This class contains a list of <Graph.Node> built-in types.
19291 Node types implemented are 'none', 'rectangle'.
19293 You can add your custom node types, customizing your visualization to the extreme.
19298 TM.Plot.NodeTypes.implement({
19300 'render': function(node, canvas) {
19301 //print your custom node to canvas
19304 'contains': function(node, pos) {
19305 //return true if pos is inside the node or false otherwise
19312 TM.Plot.NodeTypes = new Class( {
19318 'render': function(node, canvas, animating){
19319 var leaf = this.viz.leaf(node),
19320 config = this.config,
19321 offst = config.offset,
19322 titleHeight = config.titleHeight,
19323 pos = node.pos.getc(true),
19324 width = node.getData('width'),
19325 height = node.getData('height'),
19326 border = node.getData('border'),
19327 ctx = canvas.getCtx(),
19328 posx = pos.x + offst / 2,
19329 posy = pos.y + offst / 2;
19330 if(width <= offst || height <= offst) return;
19332 if(config.cushion) {
19333 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
19334 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19335 var color = node.getData('color');
19336 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
19337 function(r) { return r * 0.2 >> 0; }));
19338 lg.addColorStop(0, color);
19339 lg.addColorStop(1, colorGrad);
19340 ctx.fillStyle = lg;
19342 ctx.fillRect(posx, posy, width - offst, height - offst);
19345 ctx.strokeStyle = border;
19346 ctx.strokeRect(posx, posy, width - offst, height - offst);
19349 } else if(titleHeight > 0){
19350 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19351 titleHeight - offst);
19354 ctx.strokeStyle = border;
19355 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19361 'contains': function(node, pos) {
19362 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19363 var npos = node.pos.getc(true),
19364 width = node.getData('width'),
19365 leaf = this.viz.leaf(node),
19366 height = leaf? node.getData('height') : this.config.titleHeight;
19367 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19372 TM.Plot.EdgeTypes = new Class( {
19377 Class: TM.SliceAndDice
19379 A slice and dice TreeMap visualization.
19383 All <TM.Base> methods and properties.
19385 TM.SliceAndDice = new Class( {
19387 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19392 Class: TM.Squarified
19394 A squarified TreeMap visualization.
19398 All <TM.Base> methods and properties.
19400 TM.Squarified = new Class( {
19402 Loader, Extras, TM.Base, Layouts.TM.Squarified
19409 A strip TreeMap visualization.
19413 All <TM.Base> methods and properties.
19415 TM.Strip = new Class( {
19417 Loader, Extras, TM.Base, Layouts.TM.Strip
19430 A radial graph visualization with advanced animations.
19434 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>
19438 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.
19442 All <Loader> methods
19444 Constructor Options:
19446 Inherits options from
19449 - <Options.Controller>
19455 - <Options.NodeStyles>
19456 - <Options.Navigation>
19458 Additionally, there are other parameters and some default values changed
19460 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19461 levelDistance - (number) Default's *100*. The distance between levels of the tree.
19463 Instance Properties:
19465 canvas - Access a <Canvas> instance.
19466 graph - Access a <Graph> instance.
19467 op - Access a <RGraph.Op> instance.
19468 fx - Access a <RGraph.Plot> instance.
19469 labels - Access a <RGraph.Label> interface implementation.
19472 $jit.RGraph = new Class( {
19475 Loader, Extras, Layouts.Radial
19478 initialize: function(controller){
19479 var $RGraph = $jit.RGraph;
19482 interpolation: 'linear',
19486 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19487 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19489 var canvasConfig = this.config;
19490 if(canvasConfig.useCanvas) {
19491 this.canvas = canvasConfig.useCanvas;
19492 this.config.labelContainer = this.canvas.id + '-label';
19494 if(canvasConfig.background) {
19495 canvasConfig.background = $.merge({
19497 }, canvasConfig.background);
19499 this.canvas = new Canvas(this, canvasConfig);
19500 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19503 this.graphOptions = {
19511 this.graph = new Graph(this.graphOptions, this.config.Node,
19513 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19514 this.fx = new $RGraph.Plot(this, $RGraph);
19515 this.op = new $RGraph.Op(this);
19519 this.parent = false;
19520 // initialize extras
19521 this.initializeExtras();
19526 createLevelDistanceFunc
19528 Returns the levelDistance function used for calculating a node distance
19529 to its origin. This function returns a function that is computed
19530 per level and not per node, such that all nodes with the same depth will have the
19531 same distance to the origin. The resulting function gets the
19532 parent node as parameter and returns a float.
19535 createLevelDistanceFunc: function(){
19536 var ld = this.config.levelDistance;
19537 return function(elem){
19538 return (elem._depth + 1) * ld;
19545 Computes positions and plots the tree.
19548 refresh: function(){
19553 reposition: function(){
19554 this.compute('end');
19560 Plots the RGraph. This is a shortcut to *fx.plot*.
19566 getNodeAndParentAngle
19568 Returns the _parent_ of the given node, also calculating its angle span.
19570 getNodeAndParentAngle: function(id){
19572 var n = this.graph.getNode(id);
19573 var ps = n.getParents();
19574 var p = (ps.length > 0)? ps[0] : false;
19576 var posParent = p.pos.getc(), posChild = n.pos.getc();
19577 var newPos = posParent.add(posChild.scale(-1));
19578 theta = Math.atan2(newPos.y, newPos.x);
19580 theta += 2 * Math.PI;
19590 Enumerates the children in order to maintain child ordering (second constraint of the paper).
19592 tagChildren: function(par, id){
19593 if (par.angleSpan) {
19595 par.eachAdjacency(function(elem){
19596 adjs.push(elem.nodeTo);
19598 var len = adjs.length;
19599 for ( var i = 0; i < len && id != adjs[i].id; i++)
19601 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19602 adjs[j].dist = k++;
19609 Animates the <RGraph> to center the node specified by *id*.
19613 id - A <Graph.Node> id.
19614 opt - (optional|object) An object containing some extra properties described below
19615 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19620 rgraph.onClick('someid');
19622 rgraph.onClick('someid', {
19628 onClick: function(id, opt){
19629 if (this.root != id && !this.busy) {
19633 this.controller.onBeforeCompute(this.graph.getNode(id));
19634 var obj = this.getNodeAndParentAngle(id);
19636 // second constraint
19637 this.tagChildren(obj.parent, id);
19638 this.parent = obj.parent;
19639 this.compute('end');
19641 // first constraint
19642 var thetaDiff = obj.theta - obj.parent.endPos.theta;
19643 this.graph.eachNode(function(elem){
19644 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19647 var mode = this.config.interpolation;
19649 onComplete: $.empty
19652 this.fx.animate($.merge( {
19658 onComplete: function(){
19667 $jit.RGraph.$extend = true;
19674 Custom extension of <Graph.Op>.
19678 All <Graph.Op> methods
19685 RGraph.Op = new Class( {
19687 Implements: Graph.Op
19694 Custom extension of <Graph.Plot>.
19698 All <Graph.Plot> methods
19705 RGraph.Plot = new Class( {
19707 Implements: Graph.Plot
19712 Object: RGraph.Label
19714 Custom extension of <Graph.Label>.
19715 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19719 All <Graph.Label> methods and subclasses.
19723 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19729 RGraph.Label.Native
19731 Custom extension of <Graph.Label.Native>.
19735 All <Graph.Label.Native> methods
19739 <Graph.Label.Native>
19742 RGraph.Label.Native = new Class( {
19743 Implements: Graph.Label.Native
19749 Custom extension of <Graph.Label.SVG>.
19753 All <Graph.Label.SVG> methods
19760 RGraph.Label.SVG = new Class( {
19761 Implements: Graph.Label.SVG,
19763 initialize: function(viz){
19770 Overrides abstract method placeLabel in <Graph.Plot>.
19774 tag - A DOM label element.
19775 node - A <Graph.Node>.
19776 controller - A configuration/controller object passed to the visualization.
19779 placeLabel: function(tag, node, controller){
19780 var pos = node.pos.getc(true),
19781 canvas = this.viz.canvas,
19782 ox = canvas.translateOffsetX,
19783 oy = canvas.translateOffsetY,
19784 sx = canvas.scaleOffsetX,
19785 sy = canvas.scaleOffsetY,
19786 radius = canvas.getSize();
19788 x: Math.round(pos.x * sx + ox + radius.width / 2),
19789 y: Math.round(pos.y * sy + oy + radius.height / 2)
19791 tag.setAttribute('x', labelPos.x);
19792 tag.setAttribute('y', labelPos.y);
19794 controller.onPlaceLabel(tag, node);
19801 Custom extension of <Graph.Label.HTML>.
19805 All <Graph.Label.HTML> methods.
19812 RGraph.Label.HTML = new Class( {
19813 Implements: Graph.Label.HTML,
19815 initialize: function(viz){
19821 Overrides abstract method placeLabel in <Graph.Plot>.
19825 tag - A DOM label element.
19826 node - A <Graph.Node>.
19827 controller - A configuration/controller object passed to the visualization.
19830 placeLabel: function(tag, node, controller){
19831 var pos = node.pos.getc(true),
19832 canvas = this.viz.canvas,
19833 ox = canvas.translateOffsetX,
19834 oy = canvas.translateOffsetY,
19835 sx = canvas.scaleOffsetX,
19836 sy = canvas.scaleOffsetY,
19837 radius = canvas.getSize();
19839 x: Math.round(pos.x * sx + ox + radius.width / 2),
19840 y: Math.round(pos.y * sy + oy + radius.height / 2)
19843 var style = tag.style;
19844 style.left = labelPos.x + 'px';
19845 style.top = labelPos.y + 'px';
19846 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
19848 controller.onPlaceLabel(tag, node);
19853 Class: RGraph.Plot.NodeTypes
19855 This class contains a list of <Graph.Node> built-in types.
19856 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
19858 You can add your custom node types, customizing your visualization to the extreme.
19863 RGraph.Plot.NodeTypes.implement({
19865 'render': function(node, canvas) {
19866 //print your custom node to canvas
19869 'contains': function(node, pos) {
19870 //return true if pos is inside the node or false otherwise
19877 RGraph.Plot.NodeTypes = new Class({
19880 'contains': $.lambda(false)
19883 'render': function(node, canvas){
19884 var pos = node.pos.getc(true),
19885 dim = node.getData('dim');
19886 this.nodeHelper.circle.render('fill', pos, dim, canvas);
19888 'contains': function(node, pos){
19889 var npos = node.pos.getc(true),
19890 dim = node.getData('dim');
19891 return this.nodeHelper.circle.contains(npos, pos, dim);
19895 'render': function(node, canvas){
19896 var pos = node.pos.getc(true),
19897 width = node.getData('width'),
19898 height = node.getData('height');
19899 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
19901 // TODO(nico): be more precise...
19902 'contains': function(node, pos){
19903 var npos = node.pos.getc(true),
19904 width = node.getData('width'),
19905 height = node.getData('height');
19906 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
19910 'render': function(node, canvas){
19911 var pos = node.pos.getc(true),
19912 dim = node.getData('dim');
19913 this.nodeHelper.square.render('fill', pos, dim, canvas);
19915 'contains': function(node, pos){
19916 var npos = node.pos.getc(true),
19917 dim = node.getData('dim');
19918 return this.nodeHelper.square.contains(npos, pos, dim);
19922 'render': function(node, canvas){
19923 var pos = node.pos.getc(true),
19924 width = node.getData('width'),
19925 height = node.getData('height');
19926 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
19928 'contains': function(node, pos){
19929 var npos = node.pos.getc(true),
19930 width = node.getData('width'),
19931 height = node.getData('height');
19932 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
19936 'render': function(node, canvas){
19937 var pos = node.pos.getc(true),
19938 dim = node.getData('dim');
19939 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
19941 'contains': function(node, pos) {
19942 var npos = node.pos.getc(true),
19943 dim = node.getData('dim');
19944 return this.nodeHelper.triangle.contains(npos, pos, dim);
19948 'render': function(node, canvas){
19949 var pos = node.pos.getc(true),
19950 dim = node.getData('dim');
19951 this.nodeHelper.star.render('fill', pos, dim, canvas);
19953 'contains': function(node, pos) {
19954 var npos = node.pos.getc(true),
19955 dim = node.getData('dim');
19956 return this.nodeHelper.star.contains(npos, pos, dim);
19962 Class: RGraph.Plot.EdgeTypes
19964 This class contains a list of <Graph.Adjacence> built-in types.
19965 Edge types implemented are 'none', 'line' and 'arrow'.
19967 You can add your custom edge types, customizing your visualization to the extreme.
19972 RGraph.Plot.EdgeTypes.implement({
19974 'render': function(adj, canvas) {
19975 //print your custom edge to canvas
19978 'contains': function(adj, pos) {
19979 //return true if pos is inside the arc or false otherwise
19986 RGraph.Plot.EdgeTypes = new Class({
19989 'render': function(adj, canvas) {
19990 var from = adj.nodeFrom.pos.getc(true),
19991 to = adj.nodeTo.pos.getc(true);
19992 this.edgeHelper.line.render(from, to, canvas);
19994 'contains': function(adj, pos) {
19995 var from = adj.nodeFrom.pos.getc(true),
19996 to = adj.nodeTo.pos.getc(true);
19997 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20001 'render': function(adj, canvas) {
20002 var from = adj.nodeFrom.pos.getc(true),
20003 to = adj.nodeTo.pos.getc(true),
20004 dim = adj.getData('dim'),
20005 direction = adj.data.$direction,
20006 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20007 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20009 'contains': function(adj, pos) {
20010 var from = adj.nodeFrom.pos.getc(true),
20011 to = adj.nodeTo.pos.getc(true);
20012 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20021 * File: Hypertree.js
20028 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
20032 moebiusTransformation
20034 Calculates a moebius transformation for this point / complex.
20035 For more information go to:
20036 http://en.wikipedia.org/wiki/Moebius_transformation.
20040 c - An initialized Complex instance representing a translation Vector.
20043 Complex.prototype.moebiusTransformation = function(c) {
20044 var num = this.add(c);
20045 var den = c.$conjugate().$prod(this);
20047 return num.$div(den);
20051 moebiusTransformation
20053 Calculates a moebius transformation for the hyperbolic tree.
20055 <http://en.wikipedia.org/wiki/Moebius_transformation>
20059 graph - A <Graph> instance.
20061 prop - A property array.
20062 theta - Rotation angle.
20063 startPos - _optional_ start position.
20065 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20066 this.eachNode(graph, function(elem) {
20067 for ( var i = 0; i < prop.length; i++) {
20068 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20069 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20077 A Hyperbolic Tree/Graph visualization.
20081 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
20082 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20086 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.
20090 All <Loader> methods
20092 Constructor Options:
20094 Inherits options from
20097 - <Options.Controller>
20103 - <Options.NodeStyles>
20104 - <Options.Navigation>
20106 Additionally, there are other parameters and some default values changed
20108 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*.
20109 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.
20110 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20111 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20112 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
20114 Instance Properties:
20116 canvas - Access a <Canvas> instance.
20117 graph - Access a <Graph> instance.
20118 op - Access a <Hypertree.Op> instance.
20119 fx - Access a <Hypertree.Plot> instance.
20120 labels - Access a <Hypertree.Label> interface implementation.
20124 $jit.Hypertree = new Class( {
20126 Implements: [ Loader, Extras, Layouts.Radial ],
20128 initialize: function(controller) {
20129 var $Hypertree = $jit.Hypertree;
20140 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20141 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20143 var canvasConfig = this.config;
20144 if(canvasConfig.useCanvas) {
20145 this.canvas = canvasConfig.useCanvas;
20146 this.config.labelContainer = this.canvas.id + '-label';
20148 if(canvasConfig.background) {
20149 canvasConfig.background = $.merge({
20151 }, canvasConfig.background);
20153 this.canvas = new Canvas(this, canvasConfig);
20154 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20157 this.graphOptions = {
20165 this.graph = new Graph(this.graphOptions, this.config.Node,
20167 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20168 this.fx = new $Hypertree.Plot(this, $Hypertree);
20169 this.op = new $Hypertree.Op(this);
20173 // initialize extras
20174 this.initializeExtras();
20179 createLevelDistanceFunc
20181 Returns the levelDistance function used for calculating a node distance
20182 to its origin. This function returns a function that is computed
20183 per level and not per node, such that all nodes with the same depth will have the
20184 same distance to the origin. The resulting function gets the
20185 parent node as parameter and returns a float.
20188 createLevelDistanceFunc: function() {
20189 // get max viz. length.
20190 var r = this.getRadius();
20192 var depth = 0, max = Math.max, config = this.config;
20193 this.graph.eachNode(function(node) {
20194 depth = max(node._depth, depth);
20197 // node distance generator
20198 var genDistFunc = function(a) {
20199 return function(node) {
20201 var d = node._depth + 1;
20202 var acum = 0, pow = Math.pow;
20204 acum += pow(a, d--);
20206 return acum - config.offset;
20209 // estimate better edge length.
20210 for ( var i = 0.51; i <= 1; i += 0.01) {
20211 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20212 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20214 return genDistFunc(0.75);
20220 Returns the current radius of the visualization. If *config.radius* is *auto* then it
20221 calculates the radius by taking the smaller size of the <Canvas> widget.
20228 getRadius: function() {
20229 var rad = this.config.radius;
20230 if (rad !== "auto") { return rad; }
20231 var s = this.canvas.getSize();
20232 return Math.min(s.width, s.height) / 2;
20238 Computes positions and plots the tree.
20242 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20245 refresh: function(reposition) {
20248 this.graph.eachNode(function(node) {
20249 node.startPos.rho = node.pos.rho = node.endPos.rho;
20250 node.startPos.theta = node.pos.theta = node.endPos.theta;
20261 Computes nodes' positions and restores the tree to its previous position.
20263 For calculating nodes' positions the root must be placed on its origin. This method does this
20264 and then attemps to restore the hypertree to its previous position.
20267 reposition: function() {
20268 this.compute('end');
20269 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20270 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20272 this.graph.eachNode(function(node) {
20274 node.endPos.rho = node.pos.rho;
20275 node.endPos.theta = node.pos.theta;
20283 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
20293 Animates the <Hypertree> to center the node specified by *id*.
20297 id - A <Graph.Node> id.
20298 opt - (optional|object) An object containing some extra properties described below
20299 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20304 ht.onClick('someid');
20306 ht.onClick('someid', {
20312 onClick: function(id, opt) {
20313 var pos = this.graph.getNode(id).pos.getc(true);
20314 this.move(pos, opt);
20320 Translates the tree to the given position.
20324 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20325 opt - This object has been defined in <Hypertree.onClick>
20330 ht.move({ x: 0, y: 0.7 }, {
20336 move: function(pos, opt) {
20337 var versor = $C(pos.x, pos.y);
20338 if (this.busy === false && versor.norm() < 1) {
20340 var root = this.graph.getClosestNodeToPos(versor), that = this;
20341 this.graph.computeLevels(root.id, 0);
20342 this.controller.onBeforeCompute(root);
20344 onComplete: $.empty
20346 this.fx.animate($.merge( {
20347 modes: [ 'moebius' ],
20350 onComplete: function() {
20359 $jit.Hypertree.$extend = true;
20361 (function(Hypertree) {
20364 Class: Hypertree.Op
20366 Custom extension of <Graph.Op>.
20370 All <Graph.Op> methods
20377 Hypertree.Op = new Class( {
20379 Implements: Graph.Op
20384 Class: Hypertree.Plot
20386 Custom extension of <Graph.Plot>.
20390 All <Graph.Plot> methods
20397 Hypertree.Plot = new Class( {
20399 Implements: Graph.Plot
20404 Object: Hypertree.Label
20406 Custom extension of <Graph.Label>.
20407 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20411 All <Graph.Label> methods and subclasses.
20415 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20418 Hypertree.Label = {};
20421 Hypertree.Label.Native
20423 Custom extension of <Graph.Label.Native>.
20427 All <Graph.Label.Native> methods
20431 <Graph.Label.Native>
20434 Hypertree.Label.Native = new Class( {
20435 Implements: Graph.Label.Native,
20437 initialize: function(viz) {
20441 renderLabel: function(canvas, node, controller) {
20442 var ctx = canvas.getCtx();
20443 var coord = node.pos.getc(true);
20444 var s = this.viz.getRadius();
20445 ctx.fillText(node.name, coord.x * s, coord.y * s);
20450 Hypertree.Label.SVG
20452 Custom extension of <Graph.Label.SVG>.
20456 All <Graph.Label.SVG> methods
20463 Hypertree.Label.SVG = new Class( {
20464 Implements: Graph.Label.SVG,
20466 initialize: function(viz) {
20473 Overrides abstract method placeLabel in <Graph.Plot>.
20477 tag - A DOM label element.
20478 node - A <Graph.Node>.
20479 controller - A configuration/controller object passed to the visualization.
20482 placeLabel: function(tag, node, controller) {
20483 var pos = node.pos.getc(true),
20484 canvas = this.viz.canvas,
20485 ox = canvas.translateOffsetX,
20486 oy = canvas.translateOffsetY,
20487 sx = canvas.scaleOffsetX,
20488 sy = canvas.scaleOffsetY,
20489 radius = canvas.getSize(),
20490 r = this.viz.getRadius();
20492 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20493 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20495 tag.setAttribute('x', labelPos.x);
20496 tag.setAttribute('y', labelPos.y);
20497 controller.onPlaceLabel(tag, node);
20502 Hypertree.Label.HTML
20504 Custom extension of <Graph.Label.HTML>.
20508 All <Graph.Label.HTML> methods.
20515 Hypertree.Label.HTML = new Class( {
20516 Implements: Graph.Label.HTML,
20518 initialize: function(viz) {
20524 Overrides abstract method placeLabel in <Graph.Plot>.
20528 tag - A DOM label element.
20529 node - A <Graph.Node>.
20530 controller - A configuration/controller object passed to the visualization.
20533 placeLabel: function(tag, node, controller) {
20534 var pos = node.pos.getc(true),
20535 canvas = this.viz.canvas,
20536 ox = canvas.translateOffsetX,
20537 oy = canvas.translateOffsetY,
20538 sx = canvas.scaleOffsetX,
20539 sy = canvas.scaleOffsetY,
20540 radius = canvas.getSize(),
20541 r = this.viz.getRadius();
20543 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20544 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20546 var style = tag.style;
20547 style.left = labelPos.x + 'px';
20548 style.top = labelPos.y + 'px';
20549 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20551 controller.onPlaceLabel(tag, node);
20556 Class: Hypertree.Plot.NodeTypes
20558 This class contains a list of <Graph.Node> built-in types.
20559 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20561 You can add your custom node types, customizing your visualization to the extreme.
20566 Hypertree.Plot.NodeTypes.implement({
20568 'render': function(node, canvas) {
20569 //print your custom node to canvas
20572 'contains': function(node, pos) {
20573 //return true if pos is inside the node or false otherwise
20580 Hypertree.Plot.NodeTypes = new Class({
20583 'contains': $.lambda(false)
20586 'render': function(node, canvas) {
20587 var nconfig = this.node,
20588 dim = node.getData('dim'),
20589 p = node.pos.getc();
20590 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20591 p.$scale(node.scale);
20593 this.nodeHelper.circle.render('fill', p, dim, canvas);
20596 'contains': function(node, pos) {
20597 var dim = node.getData('dim'),
20598 npos = node.pos.getc().$scale(node.scale);
20599 return this.nodeHelper.circle.contains(npos, pos, dim);
20603 'render': function(node, canvas) {
20604 var pos = node.pos.getc().$scale(node.scale),
20605 width = node.getData('width'),
20606 height = node.getData('height');
20607 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20609 'contains': function(node, pos) {
20610 var width = node.getData('width'),
20611 height = node.getData('height'),
20612 npos = node.pos.getc().$scale(node.scale);
20613 return this.nodeHelper.circle.contains(npos, pos, width, height);
20617 'render': function(node, canvas) {
20618 var nconfig = this.node,
20619 dim = node.getData('dim'),
20620 p = node.pos.getc();
20621 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20622 p.$scale(node.scale);
20624 this.nodeHelper.square.render('fill', p, dim, canvas);
20627 'contains': function(node, pos) {
20628 var dim = node.getData('dim'),
20629 npos = node.pos.getc().$scale(node.scale);
20630 return this.nodeHelper.square.contains(npos, pos, dim);
20634 'render': function(node, canvas) {
20635 var nconfig = this.node,
20636 width = node.getData('width'),
20637 height = node.getData('height'),
20638 pos = node.pos.getc();
20639 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20640 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20641 pos.$scale(node.scale);
20642 if (width > 0.2 && height > 0.2) {
20643 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20646 'contains': function(node, pos) {
20647 var width = node.getData('width'),
20648 height = node.getData('height'),
20649 npos = node.pos.getc().$scale(node.scale);
20650 return this.nodeHelper.square.contains(npos, pos, width, height);
20654 'render': function(node, canvas) {
20655 var nconfig = this.node,
20656 dim = node.getData('dim'),
20657 p = node.pos.getc();
20658 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20659 p.$scale(node.scale);
20661 this.nodeHelper.triangle.render('fill', p, dim, canvas);
20664 'contains': function(node, pos) {
20665 var dim = node.getData('dim'),
20666 npos = node.pos.getc().$scale(node.scale);
20667 return this.nodeHelper.triangle.contains(npos, pos, dim);
20671 'render': function(node, canvas) {
20672 var nconfig = this.node,
20673 dim = node.getData('dim'),
20674 p = node.pos.getc();
20675 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20676 p.$scale(node.scale);
20678 this.nodeHelper.star.render('fill', p, dim, canvas);
20681 'contains': function(node, pos) {
20682 var dim = node.getData('dim'),
20683 npos = node.pos.getc().$scale(node.scale);
20684 return this.nodeHelper.star.contains(npos, pos, dim);
20690 Class: Hypertree.Plot.EdgeTypes
20692 This class contains a list of <Graph.Adjacence> built-in types.
20693 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20695 You can add your custom edge types, customizing your visualization to the extreme.
20700 Hypertree.Plot.EdgeTypes.implement({
20702 'render': function(adj, canvas) {
20703 //print your custom edge to canvas
20706 'contains': function(adj, pos) {
20707 //return true if pos is inside the arc or false otherwise
20714 Hypertree.Plot.EdgeTypes = new Class({
20717 'render': function(adj, canvas) {
20718 var from = adj.nodeFrom.pos.getc(true),
20719 to = adj.nodeTo.pos.getc(true),
20720 r = adj.nodeFrom.scale;
20721 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20723 'contains': function(adj, pos) {
20724 var from = adj.nodeFrom.pos.getc(true),
20725 to = adj.nodeTo.pos.getc(true),
20726 r = adj.nodeFrom.scale;
20727 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20731 'render': function(adj, canvas) {
20732 var from = adj.nodeFrom.pos.getc(true),
20733 to = adj.nodeTo.pos.getc(true),
20734 r = adj.nodeFrom.scale,
20735 dim = adj.getData('dim'),
20736 direction = adj.data.$direction,
20737 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20738 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20740 'contains': function(adj, pos) {
20741 var from = adj.nodeFrom.pos.getc(true),
20742 to = adj.nodeTo.pos.getc(true),
20743 r = adj.nodeFrom.scale;
20744 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20748 'render': function(adj, canvas) {
20749 var from = adj.nodeFrom.pos.getc(),
20750 to = adj.nodeTo.pos.getc(),
20751 dim = this.viz.getRadius();
20752 this.edgeHelper.hyperline.render(from, to, dim, canvas);
20754 'contains': $.lambda(false)
20758 })($jit.Hypertree);