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 try{elem=elem.offsetParent;}catch(e){elem=document.body;}
583 function getScrolls(elem) {
588 while (elem && !isBody(elem)) {
589 position.x += elem.scrollLeft;
590 position.y += elem.scrollTop;
591 elem = elem.parentNode;
596 function isBody(element) {
597 return (/^(?:body|html)$/i).test(element.tagName);
602 get: function(e, win) {
604 return e || win.event;
606 getWheel: function(e) {
607 return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
609 isRightClick: function(e) {
610 return (e.which == 3 || e.button == 2);
612 getPos: function(e, win) {
613 // get mouse position
616 var doc = win.document;
617 doc = doc.documentElement || doc.body;
618 //TODO(nico): make touch event handling better
619 if(e.touches && e.touches.length) {
623 x: e.pageX || (e.clientX + doc.scrollLeft),
624 y: e.pageY || (e.clientY + doc.scrollTop)
629 if (e.stopPropagation) e.stopPropagation();
630 e.cancelBubble = true;
631 if (e.preventDefault) e.preventDefault();
632 else e.returnValue = false;
636 $jit.util = $jit.id = $;
638 var Class = function(properties) {
639 properties = properties || {};
640 var klass = function() {
641 for ( var key in this) {
642 if (typeof this[key] != 'function')
643 this[key] = $.unlink(this[key]);
645 this.constructor = klass;
646 if (Class.prototyping)
648 var instance = this.initialize ? this.initialize.apply(this, arguments)
651 this.$$family = 'class';
655 for ( var mutator in Class.Mutators) {
656 if (!properties[mutator])
658 properties = Class.Mutators[mutator](properties, properties[mutator]);
659 delete properties[mutator];
662 $.extend(klass, this);
663 klass.constructor = Class;
664 klass.prototype = properties;
670 Implements: function(self, klasses) {
671 $.each($.splat(klasses), function(klass) {
672 Class.prototyping = klass;
673 var instance = (typeof klass == 'function') ? new klass : klass;
674 for ( var prop in instance) {
675 if (!(prop in self)) {
676 self[prop] = instance[prop];
679 delete Class.prototyping;
688 inherit: function(object, properties) {
689 for ( var key in properties) {
690 var override = properties[key];
691 var previous = object[key];
692 var type = $.type(override);
693 if (previous && type == 'function') {
694 if (override != previous) {
695 Class.override(object, key, override);
697 } else if (type == 'object') {
698 object[key] = $.merge(previous, override);
700 object[key] = override;
706 override: function(object, name, method) {
707 var parent = Class.prototyping;
708 if (parent && object[name] != parent[name])
710 var override = function() {
711 var previous = this.parent;
712 this.parent = parent ? parent[name] : object[name];
713 var value = method.apply(this, arguments);
714 this.parent = previous;
717 object[name] = override;
722 Class.prototype.implement = function() {
723 var proto = this.prototype;
724 $.each(Array.prototype.slice.call(arguments || []), function(properties) {
725 Class.inherit(proto, properties);
735 Provides JSON utility functions.
737 Most of these functions are JSON-tree traversal and manipulation functions.
743 Clears all tree nodes having depth greater than maxLevel.
747 tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
748 maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
751 prune: function(tree, maxLevel) {
752 this.each(tree, function(elem, i) {
753 if (i == maxLevel && elem.children) {
754 delete elem.children;
762 Returns the parent node of the node having _id_ as id.
766 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
767 id - (string) The _id_ of the child node whose parent will be returned.
771 A tree JSON node if any, or false otherwise.
774 getParent: function(tree, id) {
777 var ch = tree.children;
778 if (ch && ch.length > 0) {
779 for ( var i = 0; i < ch.length; i++) {
783 var ans = this.getParent(ch[i], id);
794 Returns the subtree that matches the given id.
798 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
799 id - (string) A node *unique* identifier.
803 A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
806 getSubtree: function(tree, id) {
809 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
810 var t = this.getSubtree(ch[i], id);
819 Iterates on tree nodes with relative depth less or equal than a specified level.
823 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
824 initLevel - (number) An integer specifying the initial relative level. Usually zero.
825 toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
826 action - (function) A function that receives a node and an integer specifying the actual level of the node.
830 $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
831 alert(node.name + ' ' + depth);
835 eachLevel: function(tree, initLevel, toLevel, action) {
836 if (initLevel <= toLevel) {
837 action(tree, initLevel);
838 if(!tree.children) return;
839 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
840 this.eachLevel(ch[i], initLevel + 1, toLevel, action);
847 A JSON tree iterator.
851 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
852 action - (function) A function that receives a node.
856 $jit.json.each(tree, function(node) {
862 each: function(tree, action) {
863 this.eachLevel(tree, 0, Number.MAX_VALUE, action);
869 An object containing multiple type of transformations.
880 var Trans = $jit.Trans;
884 var makeTrans = function(transition, params){
885 params = $.splat(params);
886 return $.extend(transition, {
887 easeIn: function(pos){
888 return transition(pos, params);
890 easeOut: function(pos){
891 return 1 - transition(1 - pos, params);
893 easeInOut: function(pos){
894 return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
895 2 * (1 - pos), params)) / 2;
903 return Math.pow(p, x[0] || 6);
907 return Math.pow(2, 8 * (p - 1));
911 return 1 - Math.sin(Math.acos(p));
915 return 1 - Math.sin((1 - p) * Math.PI / 2);
918 Back: function(p, x){
920 return Math.pow(p, 2) * ((x + 1) * p - x);
925 for ( var a = 0, b = 1; 1; a += b, b /= 2) {
926 if (p >= (7 - 4 * a) / 11) {
927 value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
934 Elastic: function(p, x){
935 return Math.pow(2, 10 * --p)
936 * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
941 $.each(transitions, function(val, key){
942 Trans[key] = makeTrans(val);
946 'Quad', 'Cubic', 'Quart', 'Quint'
947 ], function(elem, i){
948 Trans[elem] = makeTrans(function(p){
958 A Class that can perform animations for generic objects.
960 If you are looking for animation transitions please take a look at the <Trans> object.
968 The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
972 var Animation = new Class( {
974 initialize: function(options){
975 this.setOptions(options);
978 setOptions: function(options){
982 transition: Trans.Quart.easeInOut,
987 this.opt = $.merge(opt, options || {});
992 var time = $.time(), opt = this.opt;
993 if (time < this.time + opt.duration) {
994 var delta = opt.transition((time - this.time) / opt.duration);
997 this.timer = clearInterval(this.timer);
1011 startTimer: function(){
1012 var that = this, fps = this.opt.fps;
1015 this.time = $.time() - this.time;
1016 this.timer = setInterval((function(){
1018 }), Math.round(1000 / fps));
1032 stopTimer: function(){
1035 this.time = $.time() - this.time;
1036 this.timer = clearInterval(this.timer);
1043 if (this.opt.link == 'cancel') {
1052 var Options = function() {
1053 var args = arguments;
1054 for(var i=0, l=args.length, ans={}; i<l; i++) {
1055 var opt = Options[args[i]];
1066 * File: Options.AreaChart.js
1071 Object: Options.AreaChart
1073 <AreaChart> options.
1074 Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1080 Options.AreaChart = {
1084 selectOnHover: true,
1085 showAggregates: true,
1087 filterOnClick: false,
1088 restoreOnRightClick: false
1097 var areaChart = new $jit.AreaChart({
1099 type: 'stacked:gradient',
1100 selectOnHover: true,
1101 filterOnClick: true,
1102 restoreOnRightClick: true
1109 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1110 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1111 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1112 selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1113 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1114 showLabels - (boolean) Default's *true*. Display the name of the slots.
1115 filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1116 restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1120 Options.AreaChart = {
1124 labelOffset: 3, // label offset
1125 type: 'stacked', // gradient
1135 selectOnHover: true,
1136 showAggregates: true,
1138 filterOnClick: false,
1139 restoreOnRightClick: false
1143 * File: Options.Margin.js
1148 Object: Options.Margin
1150 Canvas drawing margins.
1169 var viz = new $jit.Viz({
1180 top - (number) Default's *0*. Top margin.
1181 left - (number) Default's *0*. Left margin.
1182 right - (number) Default's *0*. Right margin.
1183 bottom - (number) Default's *0*. Bottom margin.
1197 * File: Options.Canvas.js
1202 Object: Options.Canvas
1204 These are Canvas general options, like where to append it in the DOM, its dimensions, background,
1205 and other more advanced options.
1224 var viz = new $jit.Viz({
1225 injectInto: 'someContainerId',
1233 injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
1234 width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1235 height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1236 useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1237 withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1238 background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1250 colorStop1: 'rgba(255,255,255,1)',
1251 colorStop2: 'rgba(255,255,255,0)'
1255 * File: Options.Tree.js
1260 Object: Options.Tree
1262 Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1268 orientation: "left",
1280 var st = new $jit.ST({
1281 orientation: 'left',
1290 subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1291 siblingOffset - (number) Default's 5. Separation offset between siblings.
1292 orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1293 align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1294 indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1295 multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1301 orientation: "left",
1311 * File: Options.Node.js
1316 Object: Options.Node
1318 Provides Node rendering options for Tree and Graph based visualizations.
1345 var viz = new $jit.Viz({
1357 overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1358 type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
1359 color - (string) Default's *#ccb*. Node color.
1360 alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1361 dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1362 height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1363 width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1364 autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1365 autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1366 lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1367 transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1368 align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
1369 angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1370 span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1371 CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
1391 //Raw canvas styles to be
1392 //applied to the context instance
1393 //before plotting a node
1399 * File: Options.Edge.js
1404 Object: Options.Edge
1406 Provides Edge rendering options for Tree and Graph based visualizations.
1425 var viz = new $jit.Viz({
1431 shadowColor: '#ccc',
1440 overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1441 type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
1442 color - (string) Default's '#ccb'. Edge color.
1443 lineWidth - (number) Default's *1*. Line/Edge width.
1444 alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1445 dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
1446 epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
1447 CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
1451 If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
1464 //Raw canvas styles to be
1465 //applied to the context instance
1466 //before plotting an edge
1472 * File: Options.Fx.js
1479 Provides animation options like duration of the animations, frames per second and animation transitions.
1487 transition: $jit.Trans.Quart.easeInOut,
1495 var viz = new $jit.Viz({
1498 transition: $jit.Trans.linear
1504 clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1505 duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1506 fps - (number) Default's *40*. Frames per second.
1507 transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1511 This object is used for specifying different animation transitions in all visualizations.
1513 There are many different type of animation transitions.
1517 Displays a linear transition
1525 Displays a Quadratic transition.
1529 >Trans.Quad.easeInOut
1535 Displays a Cubic transition.
1538 >Trans.Cubic.easeOut
1539 >Trans.Cubic.easeInOut
1545 Displays a Quartetic transition.
1548 >Trans.Quart.easeOut
1549 >Trans.Quart.easeInOut
1555 Displays a Quintic transition.
1558 >Trans.Quint.easeOut
1559 >Trans.Quint.easeInOut
1565 Displays an Exponential transition.
1569 >Trans.Expo.easeInOut
1575 Displays a Circular transition.
1579 >Trans.Circ.easeInOut
1585 Displays a Sineousidal transition.
1589 >Trans.Sine.easeInOut
1597 >Trans.Back.easeInOut
1605 >Trans.Bounce.easeIn
1606 >Trans.Bounce.easeOut
1607 >Trans.Bounce.easeInOut
1615 >Trans.Elastic.easeIn
1616 >Trans.Elastic.easeOut
1617 >Trans.Elastic.easeInOut
1623 Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
1632 transition: $jit.Trans.Quart.easeInOut,
1637 * File: Options.Label.js
1641 Object: Options.Label
1643 Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.
1650 type: 'HTML', //'SVG', 'Native'
1653 family: 'sans-serif',
1654 textAlign: 'center',
1655 textBaseline: 'alphabetic',
1663 var viz = new $jit.Viz({
1674 overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1675 type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1676 style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1677 size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1678 family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1679 color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1685 type: 'HTML', //'SVG', 'Native'
1688 family: 'sans-serif',
1689 textAlign: 'center',
1690 textBaseline: 'alphabetic',
1696 * File: Options.Tips.js
1701 Object: Options.Tips
1721 var viz = new $jit.Viz({
1727 onShow: function(tip, node) {
1728 tip.innerHTML = node.name;
1736 enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class.
1737 type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
1738 offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
1739 offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
1740 onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
1741 onHide() - This callack is used when hiding a tooltip.
1758 * File: Options.NodeStyles.js
1763 Object: Options.NodeStyles
1765 Apply different styles when a node is hovered or selected.
1770 Options.NodeStyles = {
1781 var viz = new $jit.Viz({
1796 enable - (boolean) Default's *false*. Whether to enable this option.
1797 type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
1798 stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799 stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1802 Options.NodeStyles = {
1813 * File: Options.Events.js
1818 Object: Options.Events
1820 Configuration for adding mouse/touch event handlers to Nodes.
1827 enableForEdges: false,
1830 onRightClick: $.empty,
1831 onMouseMove: $.empty,
1832 onMouseEnter: $.empty,
1833 onMouseLeave: $.empty,
1834 onDragStart: $.empty,
1835 onDragMove: $.empty,
1836 onDragCancel: $.empty,
1838 onTouchStart: $.empty,
1839 onTouchMove: $.empty,
1840 onTouchEnd: $.empty,
1841 onTouchCancel: $.empty,
1842 onMouseWheel: $.empty
1849 var viz = new $jit.Viz({
1852 onClick: function(node, eventInfo, e) {
1855 onMouseEnter: function(node, eventInfo, e) {
1856 viz.canvas.getElement().style.cursor = 'pointer';
1858 onMouseLeave: function(node, eventInfo, e) {
1859 viz.canvas.getElement().style.cursor = '';
1867 enable - (boolean) Default's *false*. Whether to enable the Event system.
1868 enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
1869 type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
1870 onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1871 onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1872 onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1873 onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1874 onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1875 onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1876 onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1877 onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1878 onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1879 onTouchStart(node, eventInfo, e) - Behaves just like onDragStart.
1880 onTouchMove(node, eventInfo, e) - Behaves just like onDragMove.
1881 onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd.
1882 onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1883 onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
1890 enableForEdges: false,
1893 onRightClick: $.empty,
1894 onMouseMove: $.empty,
1895 onMouseEnter: $.empty,
1896 onMouseLeave: $.empty,
1897 onDragStart: $.empty,
1898 onDragMove: $.empty,
1899 onDragCancel: $.empty,
1901 onTouchStart: $.empty,
1902 onTouchMove: $.empty,
1903 onTouchEnd: $.empty,
1904 onMouseWheel: $.empty
1908 * File: Options.Navigation.js
1913 Object: Options.Navigation
1915 Panning and zooming options for Graph/Tree based visualizations. These options are implemented
1916 by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1922 Options.Navigation = {
1925 panning: false, //true, 'avoid nodes'
1934 var viz = new $jit.Viz({
1937 panning: 'avoid nodes',
1945 enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1946 panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
1947 zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
1951 Options.Navigation = {
1956 panning: false, //true | 'avoid nodes'
1961 * File: Options.Controller.js
1966 Object: Options.Controller
1968 Provides controller methods. Controller methods are callback functions that get called at different stages
1969 of the animation, computing or plotting of the visualization.
1973 All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1979 Options.Controller = {
1980 onBeforeCompute: $.empty,
1981 onAfterCompute: $.empty,
1982 onCreateLabel: $.empty,
1983 onPlaceLabel: $.empty,
1984 onComplete: $.empty,
1985 onBeforePlotLine:$.empty,
1986 onAfterPlotLine: $.empty,
1987 onBeforePlotNode:$.empty,
1988 onAfterPlotNode: $.empty,
1997 var viz = new $jit.Viz({
1998 onBeforePlotNode: function(node) {
2000 node.setData('color', '#ffc');
2002 node.removeData('color');
2005 onBeforePlotLine: function(adj) {
2006 if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2007 adj.setData('color', '#ffc');
2009 adj.removeData('color');
2012 onAfterCompute: function() {
2020 onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2021 onAfterCompute() - This method is triggered after all animations or computations ended.
2022 onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
2023 onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
2024 onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
2025 onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2026 onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
2027 onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2029 *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2031 request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.
2034 Options.Controller = {
2037 onBeforeCompute: $.empty,
2038 onAfterCompute: $.empty,
2039 onCreateLabel: $.empty,
2040 onPlaceLabel: $.empty,
2041 onComplete: $.empty,
2042 onBeforePlotLine:$.empty,
2043 onAfterPlotLine: $.empty,
2044 onBeforePlotNode:$.empty,
2045 onAfterPlotNode: $.empty,
2053 * Provides Extras such as Tips and Style Effects.
2057 * Provides the <Tips> and <NodeStyles> classes and functions.
2062 * Manager for mouse events (clicking and mouse moving).
2064 * This class is used for registering objects implementing onClick
2065 * and onMousemove methods. These methods are called when clicking or
2066 * moving the mouse around the Canvas.
2067 * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2070 var ExtrasInitializer = {
2071 initialize: function(className, viz) {
2073 this.canvas = viz.canvas;
2074 this.config = viz.config[className];
2075 this.nodeTypes = viz.fx.nodeTypes;
2076 var type = this.config.type;
2077 this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2078 this.labelContainer = this.dom && viz.labels.getLabelContainer();
2079 this.isEnabled() && this.initializePost();
2081 initializePost: $.empty,
2082 setAsProperty: $.lambda(false),
2083 isEnabled: function() {
2084 return this.config.enable;
2086 isLabel: function(e, win) {
2087 e = $.event.get(e, win);
2088 var labelContainer = this.labelContainer,
2089 target = e.target || e.srcElement;
2090 if(target && target.parentNode == labelContainer)
2096 var EventsInterface = {
2098 onMouseDown: $.empty,
2099 onMouseMove: $.empty,
2100 onMouseOver: $.empty,
2101 onMouseOut: $.empty,
2102 onMouseWheel: $.empty,
2103 onTouchStart: $.empty,
2104 onTouchMove: $.empty,
2105 onTouchEnd: $.empty,
2106 onTouchCancel: $.empty
2109 var MouseEventsManager = new Class({
2110 initialize: function(viz) {
2112 this.canvas = viz.canvas;
2115 this.registeredObjects = [];
2116 this.attachEvents();
2119 attachEvents: function() {
2120 var htmlCanvas = this.canvas.getElement(),
2122 htmlCanvas.oncontextmenu = $.lambda(false);
2123 $.addEvents(htmlCanvas, {
2124 'mouseup': function(e, win) {
2125 var event = $.event.get(e, win);
2126 that.handleEvent('MouseUp', e, win,
2127 that.makeEventObject(e, win),
2128 $.event.isRightClick(event));
2130 'mousedown': function(e, win) {
2131 var event = $.event.get(e, win);
2132 that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win),
2133 $.event.isRightClick(event));
2135 'mousemove': function(e, win) {
2136 that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2138 'mouseover': function(e, win) {
2139 that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2141 'mouseout': function(e, win) {
2142 that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2144 'touchstart': function(e, win) {
2145 that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2147 'touchmove': function(e, win) {
2148 that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2150 'touchend': function(e, win) {
2151 that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2154 //attach mousewheel event
2155 var handleMouseWheel = function(e, win) {
2156 var event = $.event.get(e, win);
2157 var wheel = $.event.getWheel(event);
2158 that.handleEvent('MouseWheel', e, win, wheel);
2160 //TODO(nico): this is a horrible check for non-gecko browsers!
2161 if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2162 $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2164 htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2168 register: function(obj) {
2169 this.registeredObjects.push(obj);
2172 handleEvent: function() {
2173 var args = Array.prototype.slice.call(arguments),
2174 type = args.shift();
2175 for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2176 regs[i]['on' + type].apply(regs[i], args);
2180 makeEventObject: function(e, win) {
2182 graph = this.viz.graph,
2184 ntypes = fx.nodeTypes,
2185 etypes = fx.edgeTypes;
2191 getNodeCalled: false,
2192 getEdgeCalled: false,
2193 getPos: function() {
2194 //TODO(nico): check why this can't be cache anymore when using edge detection
2195 //if(this.pos) return this.pos;
2196 var canvas = that.viz.canvas,
2197 s = canvas.getSize(),
2198 p = canvas.getPos(),
2199 ox = canvas.translateOffsetX,
2200 oy = canvas.translateOffsetY,
2201 sx = canvas.scaleOffsetX,
2202 sy = canvas.scaleOffsetY,
2203 pos = $.event.getPos(e, win);
2205 x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2206 y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2210 getNode: function() {
2211 if(this.getNodeCalled) return this.node;
2212 this.getNodeCalled = true;
2213 for(var id in graph.nodes) {
2214 var n = graph.nodes[id],
2215 geom = n && ntypes[n.getData('type')],
2216 contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2218 this.contains = contains;
2219 return that.node = this.node = n;
2222 return that.node = this.node = false;
2224 getEdge: function() {
2225 if(this.getEdgeCalled) return this.edge;
2226 this.getEdgeCalled = true;
2228 for(var id in graph.edges) {
2229 var edgeFrom = graph.edges[id];
2231 for(var edgeId in edgeFrom) {
2232 if(edgeId in hashset) continue;
2233 var e = edgeFrom[edgeId],
2234 geom = e && etypes[e.getData('type')],
2235 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2237 this.contains = contains;
2238 return that.edge = this.edge = e;
2242 return that.edge = this.edge = false;
2244 getContains: function() {
2245 if(this.getNodeCalled) return this.contains;
2247 return this.contains;
2254 * Provides the initialization function for <NodeStyles> and <Tips> implemented
2255 * by all main visualizations.
2259 initializeExtras: function() {
2260 var mem = new MouseEventsManager(this), that = this;
2261 $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2262 var obj = new Extras.Classes[k](k, that);
2263 if(obj.isEnabled()) {
2266 if(obj.setAsProperty()) {
2267 that[k.toLowerCase()] = obj;
2273 Extras.Classes = {};
2277 This class defines an Event API to be accessed by the user.
2278 The methods implemented are the ones defined in the <Options.Events> object.
2281 Extras.Classes.Events = new Class({
2282 Implements: [ExtrasInitializer, EventsInterface],
2284 initializePost: function() {
2285 this.fx = this.viz.fx;
2286 this.ntypes = this.viz.fx.nodeTypes;
2287 this.etypes = this.viz.fx.edgeTypes;
2289 this.hovered = false;
2290 this.pressed = false;
2291 this.touched = false;
2293 this.touchMoved = false;
2298 setAsProperty: $.lambda(true),
2300 onMouseUp: function(e, win, event, isRightClick) {
2301 var evt = $.event.get(e, win);
2304 this.config.onRightClick(this.hovered, event, evt);
2306 this.config.onClick(this.pressed, event, evt);
2311 this.config.onDragEnd(this.pressed, event, evt);
2313 this.config.onDragCancel(this.pressed, event, evt);
2315 this.pressed = this.moved = false;
2319 onMouseOut: function(e, win, event) {
2321 var evt = $.event.get(e, win), label;
2322 if(this.dom && (label = this.isLabel(e, win))) {
2323 this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2325 this.hovered = false;
2329 var rt = evt.relatedTarget,
2330 canvasWidget = this.canvas.getElement();
2331 while(rt && rt.parentNode) {
2332 if(canvasWidget == rt.parentNode) return;
2336 this.config.onMouseLeave(this.hovered,
2338 this.hovered = false;
2342 onMouseOver: function(e, win, event) {
2344 var evt = $.event.get(e, win), label;
2345 if(this.dom && (label = this.isLabel(e, win))) {
2346 this.hovered = this.viz.graph.getNode(label.id);
2347 this.config.onMouseEnter(this.hovered,
2352 onMouseMove: function(e, win, event) {
2353 var label, evt = $.event.get(e, win);
2356 this.config.onDragMove(this.pressed, event, evt);
2360 this.config.onMouseMove(this.hovered,
2364 var hn = this.hovered;
2365 var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2366 var contains = geom && geom.contains
2367 && geom.contains.call(this.fx, hn, event.getPos());
2369 this.config.onMouseMove(hn, event, evt);
2372 this.config.onMouseLeave(hn, event, evt);
2373 this.hovered = false;
2376 if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2377 this.config.onMouseEnter(this.hovered, event, evt);
2379 this.config.onMouseMove(false, event, evt);
2384 onMouseWheel: function(e, win, delta) {
2385 this.config.onMouseWheel(delta, $.event.get(e, win));
2388 onMouseDown: function(e, win, event) {
2389 var evt = $.event.get(e, win);
2390 this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2391 this.config.onDragStart(this.pressed, event, evt);
2394 onTouchStart: function(e, win, event) {
2395 var evt = $.event.get(e, win);
2396 this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2397 this.config.onTouchStart(this.touched, event, evt);
2400 onTouchMove: function(e, win, event) {
2401 var evt = $.event.get(e, win);
2403 this.touchMoved = true;
2404 this.config.onTouchMove(this.touched, event, evt);
2408 onTouchEnd: function(e, win, event) {
2409 var evt = $.event.get(e, win);
2411 if(this.touchMoved) {
2412 this.config.onTouchEnd(this.touched, event, evt);
2414 this.config.onTouchCancel(this.touched, event, evt);
2416 this.touched = this.touchMoved = false;
2424 A class containing tip related functions. This class is used internally.
2428 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2435 Extras.Classes.Tips = new Class({
2436 Implements: [ExtrasInitializer, EventsInterface],
2438 initializePost: function() {
2441 var tip = $('_tooltip') || document.createElement('div');
2442 tip.id = '_tooltip';
2443 tip.className = 'tip';
2444 $.extend(tip.style, {
2445 position: 'absolute',
2449 document.body.appendChild(tip);
2455 setAsProperty: $.lambda(true),
2457 onMouseOut: function(e, win) {
2459 if(this.dom && this.isLabel(e, win)) {
2464 var rt = e.relatedTarget,
2465 canvasWidget = this.canvas.getElement();
2466 while(rt && rt.parentNode) {
2467 if(canvasWidget == rt.parentNode) return;
2473 onMouseOver: function(e, win) {
2476 if(this.dom && (label = this.isLabel(e, win))) {
2477 this.node = this.viz.graph.getNode(label.id);
2478 this.config.onShow(this.tip, this.node, label);
2482 onMouseMove: function(e, win, opt) {
2483 if(this.dom && this.isLabel(e, win)) {
2484 this.setTooltipPosition($.event.getPos(e, win));
2487 var node = opt.getNode();
2492 if(this.config.force || !this.node || this.node.id != node.id) {
2494 this.config.onShow(this.tip, node, opt.getContains());
2496 this.setTooltipPosition($.event.getPos(e, win));
2500 setTooltipPosition: function(pos) {
2505 //get window dimensions
2507 'height': document.body.clientHeight,
2508 'width': document.body.clientWidth
2510 //get tooltip dimensions
2512 'width': tip.offsetWidth,
2513 'height': tip.offsetHeight
2515 //set tooltip position
2516 var x = cont.offsetX, y = cont.offsetY;
2517 style.top = ((pos.y + y + obj.height > win.height)?
2518 (pos.y - obj.height - y) : pos.y + y) + 'px';
2519 style.left = ((pos.x + obj.width + x > win.width)?
2520 (pos.x - obj.width - x) : pos.x + x) + 'px';
2523 hide: function(triggerCallback) {
2524 if(!SUGAR.util.isTouchScreen()) {
2525 this.tip.style.display = 'none';
2527 triggerCallback && this.config.onHide();
2534 Change node styles when clicking or hovering a node. This class is used internally.
2538 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2542 <Options.NodeStyles>
2544 Extras.Classes.NodeStyles = new Class({
2545 Implements: [ExtrasInitializer, EventsInterface],
2547 initializePost: function() {
2548 this.fx = this.viz.fx;
2549 this.types = this.viz.fx.nodeTypes;
2550 this.nStyles = this.config;
2551 this.nodeStylesOnHover = this.nStyles.stylesHover;
2552 this.nodeStylesOnClick = this.nStyles.stylesClick;
2553 this.hoveredNode = false;
2554 this.fx.nodeFxAnimation = new Animation();
2560 onMouseOut: function(e, win) {
2561 this.down = this.move = false;
2562 if(!this.hoveredNode) return;
2564 if(this.dom && this.isLabel(e, win)) {
2565 this.toggleStylesOnHover(this.hoveredNode, false);
2568 var rt = e.relatedTarget,
2569 canvasWidget = this.canvas.getElement();
2570 while(rt && rt.parentNode) {
2571 if(canvasWidget == rt.parentNode) return;
2574 this.toggleStylesOnHover(this.hoveredNode, false);
2575 this.hoveredNode = false;
2578 onMouseOver: function(e, win) {
2581 if(this.dom && (label = this.isLabel(e, win))) {
2582 var node = this.viz.graph.getNode(label.id);
2583 if(node.selected) return;
2584 this.hoveredNode = node;
2585 this.toggleStylesOnHover(this.hoveredNode, true);
2589 onMouseDown: function(e, win, event, isRightClick) {
2590 if(isRightClick) return;
2592 if(this.dom && (label = this.isLabel(e, win))) {
2593 this.down = this.viz.graph.getNode(label.id);
2594 } else if(!this.dom) {
2595 this.down = event.getNode();
2600 onMouseUp: function(e, win, event, isRightClick) {
2601 if(isRightClick) return;
2603 this.onClick(event.getNode());
2605 this.down = this.move = false;
2608 getRestoredStyles: function(node, type) {
2609 var restoredStyles = {},
2610 nStyles = this['nodeStylesOn' + type];
2611 for(var prop in nStyles) {
2612 restoredStyles[prop] = node.styles['$' + prop];
2614 return restoredStyles;
2617 toggleStylesOnHover: function(node, set) {
2618 if(this.nodeStylesOnHover) {
2619 this.toggleStylesOn('Hover', node, set);
2623 toggleStylesOnClick: function(node, set) {
2624 if(this.nodeStylesOnClick) {
2625 this.toggleStylesOn('Click', node, set);
2629 toggleStylesOn: function(type, node, set) {
2631 var nStyles = this.nStyles;
2635 node.styles = $.merge(node.data, {});
2637 for(var s in this['nodeStylesOn' + type]) {
2639 if(!($s in node.styles)) {
2640 node.styles[$s] = node.getData(s);
2643 viz.fx.nodeFx($.extend({
2646 'properties': that['nodeStylesOn' + type]
2648 transition: Trans.Quart.easeOut,
2653 var restoredStyles = this.getRestoredStyles(node, type);
2654 viz.fx.nodeFx($.extend({
2657 'properties': restoredStyles
2659 transition: Trans.Quart.easeOut,
2666 onClick: function(node) {
2668 var nStyles = this.nodeStylesOnClick;
2669 if(!nStyles) return;
2670 //if the node is selected then unselect it
2672 this.toggleStylesOnClick(node, false);
2673 delete node.selected;
2675 //unselect all selected nodes...
2676 this.viz.graph.eachNode(function(n) {
2678 for(var s in nStyles) {
2679 n.setData(s, n.styles['$' + s], 'end');
2684 //select clicked node
2685 this.toggleStylesOnClick(node, true);
2686 node.selected = true;
2687 delete node.hovered;
2688 this.hoveredNode = false;
2692 onMouseMove: function(e, win, event) {
2693 //if mouse button is down and moving set move=true
2694 if(this.down) this.move = true;
2695 //already handled by mouseover/out
2696 if(this.dom && this.isLabel(e, win)) return;
2697 var nStyles = this.nodeStylesOnHover;
2698 if(!nStyles) return;
2701 if(this.hoveredNode) {
2702 var geom = this.types[this.hoveredNode.getData('type')];
2703 var contains = geom && geom.contains && geom.contains.call(this.fx,
2704 this.hoveredNode, event.getPos());
2705 if(contains) return;
2707 var node = event.getNode();
2708 //if no node is being hovered then just exit
2709 if(!this.hoveredNode && !node) return;
2710 //if the node is hovered then exit
2711 if(node.hovered) return;
2712 //select hovered node
2713 if(node && !node.selected) {
2714 //check if an animation is running and exit it
2715 this.fx.nodeFxAnimation.stopTimer();
2716 //unselect all hovered nodes...
2717 this.viz.graph.eachNode(function(n) {
2718 if(n.hovered && !n.selected) {
2719 for(var s in nStyles) {
2720 n.setData(s, n.styles['$' + s], 'end');
2725 //select hovered node
2726 node.hovered = true;
2727 this.hoveredNode = node;
2728 this.toggleStylesOnHover(node, true);
2729 } else if(this.hoveredNode && !this.hoveredNode.selected) {
2730 //check if an animation is running and exit it
2731 this.fx.nodeFxAnimation.stopTimer();
2732 //unselect hovered node
2733 this.toggleStylesOnHover(this.hoveredNode, false);
2734 delete this.hoveredNode.hovered;
2735 this.hoveredNode = false;
2741 Extras.Classes.Navigation = new Class({
2742 Implements: [ExtrasInitializer, EventsInterface],
2744 initializePost: function() {
2746 this.pressed = false;
2749 onMouseWheel: function(e, win, scroll) {
2750 if(!this.config.zooming) return;
2751 $.event.stop($.event.get(e, win));
2752 var val = this.config.zooming / 1000,
2753 ans = 1 + scroll * val;
2754 this.canvas.scale(ans, ans);
2757 onMouseDown: function(e, win, eventInfo) {
2758 if(!this.config.panning) return;
2759 if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2760 this.pressed = true;
2761 this.pos = eventInfo.getPos();
2762 var canvas = this.canvas,
2763 ox = canvas.translateOffsetX,
2764 oy = canvas.translateOffsetY,
2765 sx = canvas.scaleOffsetX,
2766 sy = canvas.scaleOffsetY;
2773 onMouseMove: function(e, win, eventInfo) {
2774 if(!this.config.panning) return;
2775 if(!this.pressed) return;
2776 if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2777 var thispos = this.pos,
2778 currentPos = eventInfo.getPos(),
2779 canvas = this.canvas,
2780 ox = canvas.translateOffsetX,
2781 oy = canvas.translateOffsetY,
2782 sx = canvas.scaleOffsetX,
2783 sy = canvas.scaleOffsetY;
2788 var x = currentPos.x - thispos.x,
2789 y = currentPos.y - thispos.y;
2790 this.pos = currentPos;
2791 this.canvas.translate(x * 1/sx, y * 1/sy);
2794 onMouseUp: function(e, win, eventInfo, isRightClick) {
2795 if(!this.config.panning) return;
2796 this.pressed = false;
2809 A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to
2810 know more about <Canvas> options take a look at <Options.Canvas>.
2812 A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior
2813 across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2817 Suppose we have this HTML
2820 <div id="infovis"></div>
2823 Now we create a new Visualization
2826 var viz = new $jit.Viz({
2827 //Where to inject the canvas. Any div container will do.
2828 'injectInto':'infovis',
2829 //width and height for canvas.
2830 //Default's to the container offsetWidth and Height.
2836 The generated HTML will look like this
2840 <div id="infovis-canvaswidget" style="position:relative;">
2841 <canvas id="infovis-canvas" width=900 height=500
2842 style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2843 <div id="infovis-label"
2844 style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2850 As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2851 of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2856 //check for native canvas support
2857 var canvasType = typeof HTMLCanvasElement,
2858 supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2859 //create element function
2860 function $E(tag, props) {
2861 var elem = document.createElement(tag);
2862 for(var p in props) {
2863 if(typeof props[p] == "object") {
2864 $.extend(elem[p], props[p]);
2869 if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2870 elem = G_vmlCanvasManager.initElement(elem);
2874 //canvas widget which we will call just Canvas
2875 $jit.Canvas = Canvas = new Class({
2879 labelContainer: false,
2880 translateOffsetX: 0,
2881 translateOffsetY: 0,
2885 initialize: function(viz, opt) {
2888 var id = $.type(opt.injectInto) == 'string'?
2889 opt.injectInto:opt.injectInto.id,
2890 idLabel = id + "-label",
2892 width = opt.width || wrapper.offsetWidth,
2893 height = opt.height || wrapper.offsetHeight;
2896 var canvasOptions = {
2901 //create main wrapper
2902 this.element = $E('div', {
2903 'id': id + '-canvaswidget',
2905 'position': 'relative',
2906 'width': width + 'px',
2907 'height': height + 'px'
2910 //create label container
2911 this.labelContainer = this.createLabelContainer(opt.Label.type,
2912 idLabel, canvasOptions);
2913 //create primary canvas
2914 this.canvases.push(new Canvas.Base({
2915 config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2916 plot: function(base) {
2919 resize: function() {
2923 //create secondary canvas
2924 var back = opt.background;
2926 var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2927 this.canvases.push(new Canvas.Base(backCanvas));
2930 var len = this.canvases.length;
2932 this.element.appendChild(this.canvases[len].canvas);
2934 this.canvases[len].plot();
2937 this.element.appendChild(this.labelContainer);
2938 wrapper.appendChild(this.element);
2939 //Update canvas position when the page is scrolled.
2940 var timer = null, that = this;
2941 $.addEvent(window, 'scroll', function() {
2942 clearTimeout(timer);
2943 timer = setTimeout(function() {
2944 that.getPos(true); //update canvas position
2947 $.addEvent(window, 'click', function() {
2948 clearTimeout(timer);
2949 timer = setTimeout(function() {
2950 that.getPos(true); //update canvas position
2953 sb = document.getElementById('sb'+id);
2954 $.addEvent(sb, 'scroll', function() {
2955 clearTimeout(timer);
2956 timer = setTimeout(function() {
2957 that.getPos(true); //update canvas position
2964 Returns the main canvas context object
2969 var ctx = canvas.getCtx();
2970 //Now I can use the native canvas context
2971 //and for example change some canvas styles
2972 ctx.globalAlpha = 1;
2975 getCtx: function(i) {
2976 return this.canvases[i || 0].getCtx();
2981 Returns the current Configuration for this Canvas Widget.
2986 var config = canvas.getConfig();
2989 getConfig: function() {
2995 Returns the main Canvas DOM wrapper
3000 var wrapper = canvas.getElement();
3001 //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3004 getElement: function() {
3005 return this.element;
3010 Returns canvas dimensions.
3014 An object with *width* and *height* properties.
3018 canvas.getSize(); //returns { width: 900, height: 500 }
3021 getSize: function(i) {
3022 return this.canvases[i || 0].getSize();
3031 width - New canvas width.
3032 height - New canvas height.
3037 canvas.resize(width, height);
3041 resize: function(width, height) {
3043 this.translateOffsetX = this.translateOffsetY = 0;
3044 this.scaleOffsetX = this.scaleOffsetY = 1;
3045 for(var i=0, l=this.canvases.length; i<l; i++) {
3046 this.canvases[i].resize(width, height);
3048 var style = this.element.style;
3049 style.width = width + 'px';
3050 style.height = height + 'px';
3051 if(this.labelContainer)
3052 this.labelContainer.style.width = width + 'px';
3057 Applies a translation to the canvas.
3061 x - (number) x offset.
3062 y - (number) y offset.
3063 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3068 canvas.translate(30, 30);
3072 translate: function(x, y, disablePlot) {
3073 this.translateOffsetX += x*this.scaleOffsetX;
3074 this.translateOffsetY += y*this.scaleOffsetY;
3075 for(var i=0, l=this.canvases.length; i<l; i++) {
3076 this.canvases[i].translate(x, y, disablePlot);
3086 x - (number) scale value.
3087 y - (number) scale value.
3088 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3093 canvas.scale(0.5, 0.5);
3097 scale: function(x, y, disablePlot) {
3099 disablePlot = false;
3101 var px = this.scaleOffsetX * x,
3102 py = this.scaleOffsetY * y;
3103 var dx = this.translateOffsetX * (x -1) / px,
3104 dy = this.translateOffsetY * (y -1) / py;
3105 this.scaleOffsetX = px;
3106 this.scaleOffsetY = py;
3107 for(var i=0, l=this.canvases.length; i<l; i++) {
3108 this.canvases[i].scale(x, y, true);
3110 this.translate(dx, dy, disablePlot);
3115 Returns the canvas position as an *x, y* object.
3119 force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3123 An object with *x* and *y* properties.
3127 canvas.getPos(true); //returns { x: 900, y: 500 }
3130 getPos: function(force){
3131 if(force || !this.pos) {
3132 return this.pos = $.getPos(this.getElement());
3142 this.canvases[i||0].clear();
3145 path: function(type, action){
3146 var ctx = this.canvases[0].getCtx();
3153 createLabelContainer: function(type, idLabel, dim) {
3154 var NS = 'http://www.w3.org/2000/svg';
3155 if(type == 'HTML' || type == 'Native') {
3159 'overflow': 'visible',
3160 'position': 'absolute',
3163 'width': dim.width + 'px',
3167 } else if(type == 'SVG') {
3168 var svgContainer = document.createElementNS(NS, 'svg:svg');
3169 svgContainer.setAttribute("width", dim.width);
3170 svgContainer.setAttribute('height', dim.height);
3171 var style = svgContainer.style;
3172 style.position = 'absolute';
3173 style.left = style.top = '0px';
3174 var labelContainer = document.createElementNS(NS, 'svg:g');
3175 labelContainer.setAttribute('width', dim.width);
3176 labelContainer.setAttribute('height', dim.height);
3177 labelContainer.setAttribute('x', 0);
3178 labelContainer.setAttribute('y', 0);
3179 labelContainer.setAttribute('id', idLabel);
3180 svgContainer.appendChild(labelContainer);
3181 return svgContainer;
3185 //base canvas wrapper
3186 Canvas.Base = new Class({
3187 translateOffsetX: 0,
3188 translateOffsetY: 0,
3192 initialize: function(viz) {
3194 this.opt = viz.config;
3196 this.createCanvas();
3197 this.translateToCenter();
3199 createCanvas: function() {
3202 height = opt.height;
3203 this.canvas = $E('canvas', {
3204 'id': opt.injectInto + opt.idSuffix,
3208 'position': 'absolute',
3211 'width': width + 'px',
3212 'height': height + 'px'
3216 getCtx: function() {
3218 return this.ctx = this.canvas.getContext('2d');
3221 getSize: function() {
3222 if(this.size) return this.size;
3223 var canvas = this.canvas;
3224 return this.size = {
3225 width: canvas.width,
3226 height: canvas.height
3229 translateToCenter: function(ps) {
3230 var size = this.getSize(),
3231 width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3232 height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3233 var ctx = this.getCtx();
3234 ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3235 ctx.translate(width/2, height/2);
3237 resize: function(width, height) {
3238 var size = this.getSize(),
3239 canvas = this.canvas,
3240 styles = canvas.style;
3242 canvas.width = width;
3243 canvas.height = height;
3244 styles.width = width + "px";
3245 styles.height = height + "px";
3246 //small ExCanvas fix
3247 //if(!supportsCanvas) {
3248 //this.translateToCenter(size);
3250 this.translateToCenter();
3252 this.translateOffsetX =
3253 this.translateOffsetY = 0;
3255 this.scaleOffsetY = 1;
3257 this.viz.resize(width, height, this);
3259 translate: function(x, y, disablePlot) {
3260 var sx = this.scaleOffsetX,
3261 sy = this.scaleOffsetY;
3262 this.translateOffsetX += x*sx;
3263 this.translateOffsetY += y*sy;
3264 this.getCtx().translate(x, y);
3265 !disablePlot && this.plot();
3267 scale: function(x, y, disablePlot) {
3268 this.scaleOffsetX *= x;
3269 this.scaleOffsetY *= y;
3270 this.getCtx().scale(x, y);
3271 !disablePlot && this.plot();
3274 var size = this.getSize(),
3275 ox = this.translateOffsetX,
3276 oy = this.translateOffsetY,
3277 sx = this.scaleOffsetX,
3278 sy = this.scaleOffsetY;
3279 this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx,
3280 (-size.height / 2 - oy) * 1/sy,
3281 size.width * 1/sx, size.height * 1/sy);
3285 this.viz.plot(this);
3288 //background canvases
3289 //TODO(nico): document this!
3290 Canvas.Background = {};
3291 Canvas.Background.Circles = new Class({
3292 initialize: function(viz, options) {
3294 this.config = $.merge({
3295 idSuffix: '-bkcanvas',
3302 resize: function(width, height, base) {
3305 plot: function(base) {
3306 var canvas = base.canvas,
3307 ctx = base.getCtx(),
3309 styles = conf.CanvasStyles;
3311 for(var s in styles) ctx[s] = styles[s];
3312 var n = conf.numberOfCircles,
3313 rho = conf.levelDistance;
3314 for(var i=1; i<=n; i++) {
3316 ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3320 //TODO(nico): print labels too!
3323 Canvas.Background.Fade = new Class({
3324 initialize: function(viz, options) {
3326 this.config = $.merge({
3327 idSuffix: '-bkcanvas',
3332 resize: function(width, height, base) {
3335 plot: function(base) {
3336 var canvas = base.canvas,
3337 ctx = base.getCtx(),
3339 styles = conf.CanvasStyles,
3340 size = base.getSize();
3341 ctx.fillStyle = 'rgb(255,255,255)';
3342 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3343 //TODO(nico): print labels too!
3352 * Defines the <Polar> class.
3356 * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3360 * <http://en.wikipedia.org/wiki/Polar_coordinates>
3367 A multi purpose polar representation.
3371 The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3375 <http://en.wikipedia.org/wiki/Polar_coordinates>
3383 var Polar = function(theta, rho) {
3394 Returns a complex number.
3398 simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3404 getc: function(simple) {
3405 return this.toComplex(simple);
3411 Returns a <Polar> representation.
3415 A variable in polar coordinates.
3429 v - A <Complex> or <Polar> instance.
3434 this.theta = v.theta; this.rho = v.rho;
3440 Sets a <Complex> number.
3444 x - A <Complex> number real part.
3445 y - A <Complex> number imaginary part.
3448 setc: function(x, y) {
3449 this.rho = Math.sqrt(x * x + y * y);
3450 this.theta = Math.atan2(y, x);
3451 if(this.theta < 0) this.theta += Math.PI * 2;
3457 Sets a polar number.
3461 theta - A <Polar> number angle property.
3462 rho - A <Polar> number rho property.
3465 setp: function(theta, rho) {
3473 Returns a copy of the current object.
3477 A copy of the real object.
3480 return new Polar(this.theta, this.rho);
3486 Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3490 simple - _optional_ If *true* this method will only return an object with x and y properties (and not the whole <Complex> instance). Default's *false*.
3494 A new <Complex> instance.
3496 toComplex: function(simple) {
3497 var x = Math.cos(this.theta) * this.rho;
3498 var y = Math.sin(this.theta) * this.rho;
3499 if(simple) return { 'x': x, 'y': y};
3500 return new Complex(x, y);
3506 Adds two <Polar> instances.
3510 polar - A <Polar> number.
3514 A new Polar instance.
3516 add: function(polar) {
3517 return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3523 Scales a polar norm.
3527 number - A scale factor.
3531 A new Polar instance.
3533 scale: function(number) {
3534 return new Polar(this.theta, this.rho * number);
3542 Returns *true* if the theta and rho properties are equal.
3546 c - A <Polar> number.
3550 *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3552 equals: function(c) {
3553 return this.theta == c.theta && this.rho == c.rho;
3559 Adds two <Polar> instances affecting the current object.
3563 polar - A <Polar> instance.
3569 $add: function(polar) {
3570 this.theta = this.theta + polar.theta; this.rho += polar.rho;
3577 Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3581 polar - A <Polar> instance.
3587 $madd: function(polar) {
3588 this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3596 Scales a polar instance affecting the object.
3600 number - A scaling factor.
3606 $scale: function(number) {
3614 Calculates a polar interpolation between two points at a given delta moment.
3618 elem - A <Polar> instance.
3619 delta - A delta factor ranging [0, 1].
3623 A new <Polar> instance representing an interpolation between _this_ and _elem_
3625 interpolate: function(elem, delta) {
3626 var pi = Math.PI, pi2 = pi * 2;
3627 var ch = function(t) {
3628 var a = (t < 0)? (t % pi2) + pi2 : t % pi2;
3631 var tt = this.theta, et = elem.theta;
3632 var sum, diff = Math.abs(tt - et);
3635 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3637 sum = ch((et - pi2 + (tt - (et)) * delta));
3639 } else if(diff >= pi) {
3641 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3643 sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3646 sum = ch((et + (tt - et) * delta)) ;
3648 var r = (this.rho - elem.rho) * delta + elem.rho;
3657 var $P = function(a, b) { return new Polar(a, b); };
3659 Polar.KER = $P(0, 0);
3666 * Defines the <Complex> class.
3670 * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3674 * <http://en.wikipedia.org/wiki/Complex_number>
3681 A multi-purpose Complex Class with common methods.
3685 The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3689 <http://en.wikipedia.org/wiki/Complex_number>
3693 x - _optional_ A Complex number real part.
3694 y - _optional_ A Complex number imaginary part.
3698 var Complex = function(x, y) {
3703 $jit.Complex = Complex;
3705 Complex.prototype = {
3709 Returns a complex number.
3722 Returns a <Polar> representation of this number.
3726 simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3730 A variable in <Polar> coordinates.
3732 getp: function(simple) {
3733 return this.toPolar(simple);
3744 c - A <Complex> or <Polar> instance.
3756 Sets a complex number.
3760 x - A <Complex> number Real part.
3761 y - A <Complex> number Imaginary part.
3764 setc: function(x, y) {
3772 Sets a polar number.
3776 theta - A <Polar> number theta property.
3777 rho - A <Polar> number rho property.
3780 setp: function(theta, rho) {
3781 this.x = Math.cos(theta) * rho;
3782 this.y = Math.sin(theta) * rho;
3788 Returns a copy of the current object.
3792 A copy of the real object.
3795 return new Complex(this.x, this.y);
3801 Transforms cartesian to polar coordinates.
3805 simple - _optional_ If *true* this method will only return an object with theta and rho properties (and not the whole <Polar> instance). Default's *false*.
3809 A new <Polar> instance.
3812 toPolar: function(simple) {
3813 var rho = this.norm();
3814 var atan = Math.atan2(this.y, this.x);
3815 if(atan < 0) atan += Math.PI * 2;
3816 if(simple) return { 'theta': atan, 'rho': rho };
3817 return new Polar(atan, rho);
3822 Calculates a <Complex> number norm.
3826 A real number representing the complex norm.
3829 return Math.sqrt(this.squaredNorm());
3835 Calculates a <Complex> number squared norm.
3839 A real number representing the complex squared norm.
3841 squaredNorm: function () {
3842 return this.x*this.x + this.y*this.y;
3848 Returns the result of adding two complex numbers.
3850 Does not alter the original object.
3854 pos - A <Complex> instance.
3858 The result of adding two complex numbers.
3860 add: function(pos) {
3861 return new Complex(this.x + pos.x, this.y + pos.y);
3867 Returns the result of multiplying two <Complex> numbers.
3869 Does not alter the original object.
3873 pos - A <Complex> instance.
3877 The result of multiplying two complex numbers.
3879 prod: function(pos) {
3880 return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3886 Returns the conjugate of this <Complex> number.
3888 Does not alter the original object.
3892 The conjugate of this <Complex> number.
3894 conjugate: function() {
3895 return new Complex(this.x, -this.y);
3902 Returns the result of scaling a <Complex> instance.
3904 Does not alter the original object.
3908 factor - A scale factor.
3912 The result of scaling this complex to a factor.
3914 scale: function(factor) {
3915 return new Complex(this.x * factor, this.y * factor);
3923 Returns *true* if both real and imaginary parts are equal.
3927 c - A <Complex> instance.
3931 A boolean instance indicating if both <Complex> numbers are equal.
3933 equals: function(c) {
3934 return this.x == c.x && this.y == c.y;
3940 Returns the result of adding two <Complex> numbers.
3942 Alters the original object.
3946 pos - A <Complex> instance.
3950 The result of adding two complex numbers.
3952 $add: function(pos) {
3953 this.x += pos.x; this.y += pos.y;
3960 Returns the result of multiplying two <Complex> numbers.
3962 Alters the original object.
3966 pos - A <Complex> instance.
3970 The result of multiplying two complex numbers.
3972 $prod:function(pos) {
3973 var x = this.x, y = this.y;
3974 this.x = x*pos.x - y*pos.y;
3975 this.y = y*pos.x + x*pos.y;
3982 Returns the conjugate for this <Complex>.
3984 Alters the original object.
3988 The conjugate for this complex.
3990 $conjugate: function() {
3998 Returns the result of scaling a <Complex> instance.
4000 Alters the original object.
4004 factor - A scale factor.
4008 The result of scaling this complex to a factor.
4010 $scale: function(factor) {
4011 this.x *= factor; this.y *= factor;
4018 Returns the division of two <Complex> numbers.
4020 Alters the original object.
4024 pos - A <Complex> number.
4028 The result of scaling this complex to a factor.
4030 $div: function(pos) {
4031 var x = this.x, y = this.y;
4032 var sq = pos.squaredNorm();
4033 this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4034 return this.$scale(1 / sq);
4038 var $C = function(a, b) { return new Complex(a, b); };
4040 Complex.KER = $C(0, 0);
4052 A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4054 An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4059 //create new visualization
4060 var viz = new $jit.Viz(options);
4064 viz.graph; //<Graph> instance
4069 The following <Graph.Util> methods are implemented in <Graph>
4071 - <Graph.Util.getNode>
4072 - <Graph.Util.eachNode>
4073 - <Graph.Util.computeLevels>
4074 - <Graph.Util.eachBFS>
4075 - <Graph.Util.clean>
4076 - <Graph.Util.getClosestNodeToPos>
4077 - <Graph.Util.getClosestNodeToOrigin>
4081 $jit.Graph = new Class({
4083 initialize: function(opt, Node, Edge, Label) {
4084 var innerOptions = {
4091 this.opt = $.merge(innerOptions, opt || {});
4095 //add nodeList methods
4098 for(var p in Accessors) {
4099 that.nodeList[p] = (function(p) {
4101 var args = Array.prototype.slice.call(arguments);
4102 that.eachNode(function(n) {
4103 n[p].apply(n, args);
4114 Returns a <Graph.Node> by *id*.
4118 id - (string) A <Graph.Node> id.
4123 var node = graph.getNode('nodeId');
4126 getNode: function(id) {
4127 if(this.hasNode(id)) return this.nodes[id];
4134 Returns a <Graph.Node> by *name*.
4138 name - (string) A <Graph.Node> name.
4143 var node = graph.getByName('someName');
4146 getByName: function(name) {
4147 for(var id in this.nodes) {
4148 var n = this.nodes[id];
4149 if(n.name == name) return n;
4155 Method: getAdjacence
4157 Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4161 id - (string) A <Graph.Node> id.
4162 id2 - (string) A <Graph.Node> id.
4164 getAdjacence: function (id, id2) {
4165 if(id in this.edges) {
4166 return this.edges[id][id2];
4178 obj - An object with the properties described below
4180 id - (string) A node id
4181 name - (string) A node's name
4182 data - (object) A node's data hash
4188 addNode: function(obj) {
4189 if(!this.nodes[obj.id]) {
4190 var edges = this.edges[obj.id] = {};
4191 this.nodes[obj.id] = new Graph.Node($.extend({
4194 'data': $.merge(obj.data || {}, {}),
4195 'adjacencies': edges
4202 return this.nodes[obj.id];
4206 Method: addAdjacence
4208 Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4212 obj - (object) A <Graph.Node> object.
4213 obj2 - (object) Another <Graph.Node> object.
4214 data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4218 <Graph.Node>, <Graph.Adjacence>
4220 addAdjacence: function (obj, obj2, data) {
4221 if(!this.hasNode(obj.id)) { this.addNode(obj); }
4222 if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4223 obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4224 if(!obj.adjacentTo(obj2)) {
4225 var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4226 var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4227 adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4228 return adjsObj[obj2.id];
4230 return this.edges[obj.id][obj2.id];
4236 Removes a <Graph.Node> matching the specified *id*.
4240 id - (string) A node's id.
4243 removeNode: function(id) {
4244 if(this.hasNode(id)) {
4245 delete this.nodes[id];
4246 var adjs = this.edges[id];
4247 for(var to in adjs) {
4248 delete this.edges[to][id];
4250 delete this.edges[id];
4255 Method: removeAdjacence
4257 Removes a <Graph.Adjacence> matching *id1* and *id2*.
4261 id1 - (string) A <Graph.Node> id.
4262 id2 - (string) A <Graph.Node> id.
4264 removeAdjacence: function(id1, id2) {
4265 delete this.edges[id1][id2];
4266 delete this.edges[id2][id1];
4272 Returns a boolean indicating if the node belongs to the <Graph> or not.
4276 id - (string) Node id.
4278 hasNode: function(id) {
4279 return id in this.nodes;
4288 empty: function() { this.nodes = {}; this.edges = {};}
4292 var Graph = $jit.Graph;
4297 Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4303 var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4305 type = type || 'current';
4306 prefix = "$" + (prefix ? prefix + "-" : "");
4308 if(type == 'current') {
4310 } else if(type == 'start') {
4311 data = this.startData;
4312 } else if(type == 'end') {
4313 data = this.endData;
4316 var dollar = prefix + prop;
4319 return data[dollar];
4322 if(!this.Config.overridable)
4323 return prefixConfig[prop] || 0;
4325 return (dollar in data) ?
4326 data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4329 var setDataInternal = function(prefix, prop, value, type) {
4330 type = type || 'current';
4331 prefix = '$' + (prefix ? prefix + '-' : '');
4335 if(type == 'current') {
4337 } else if(type == 'start') {
4338 data = this.startData;
4339 } else if(type == 'end') {
4340 data = this.endData;
4343 data[prefix + prop] = value;
4346 var removeDataInternal = function(prefix, properties) {
4347 prefix = '$' + (prefix ? prefix + '-' : '');
4349 $.each(properties, function(prop) {
4350 var pref = prefix + prop;
4351 delete that.data[pref];
4352 delete that.endData[pref];
4353 delete that.startData[pref];
4361 Returns the specified data value property.
4362 This is useful for querying special/reserved <Graph.Node> data properties
4363 (i.e dollar prefixed properties).
4367 prop - (string) The name of the property. The dollar sign is not needed. For
4368 example *getData(width)* will return *data.$width*.
4369 type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4370 data properties also. These properties are used when making animations.
4371 force - (boolean) Whether to obtain the true value of the property (equivalent to
4372 *data.$prop*) or to check for *node.overridable = true* first.
4376 The value of the dollar prefixed property or the global Node/Edge property
4377 value if *overridable=false*
4381 node.getData('width'); //will return node.data.$width if Node.overridable=true;
4384 getData: function(prop, type, force) {
4385 return getDataInternal.call(this, "", prop, type, force, this.Config);
4392 Sets the current data property with some specific value.
4393 This method is only useful for reserved (dollar prefixed) properties.
4397 prop - (string) The name of the property. The dollar sign is not necessary. For
4398 example *setData(width)* will set *data.$width*.
4399 value - (mixed) The value to store.
4400 type - (string) The type of the data property to store. Default's "current" but
4401 can also be "start" or "end".
4406 node.setData('width', 30);
4409 If we were to make an animation of a node/edge width then we could do
4412 var node = viz.getNode('nodeId');
4413 //set start and end values
4414 node.setData('width', 10, 'start');
4415 node.setData('width', 30, 'end');
4416 //will animate nodes width property
4418 modes: ['node-property:width'],
4423 setData: function(prop, value, type) {
4424 setDataInternal.call(this, "", prop, value, type);
4430 Convenience method to set multiple data values at once.
4434 types - (array|string) A set of 'current', 'end' or 'start' values.
4435 obj - (object) A hash containing the names and values of the properties to be altered.
4439 node.setDataset(['current', 'end'], {
4441 'color': ['#fff', '#ccc']
4444 node.setDataset('end', {
4455 setDataset: function(types, obj) {
4456 types = $.splat(types);
4457 for(var attr in obj) {
4458 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4459 this.setData(attr, val[i], types[i]);
4467 Remove data properties.
4471 One or more property names as arguments. The dollar sign is not needed.
4475 node.removeData('width'); //now the default width value is returned
4478 removeData: function() {
4479 removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4483 Method: getCanvasStyle
4485 Returns the specified canvas style data value property. This is useful for
4486 querying special/reserved <Graph.Node> canvas style data properties (i.e.
4487 dollar prefixed properties that match with $canvas-<name of canvas style>).
4491 prop - (string) The name of the property. The dollar sign is not needed. For
4492 example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4493 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4494 data properties also.
4498 node.getCanvasStyle('shadowBlur');
4505 getCanvasStyle: function(prop, type, force) {
4506 return getDataInternal.call(
4507 this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4511 Method: setCanvasStyle
4513 Sets the canvas style data property with some specific value.
4514 This method is only useful for reserved (dollar prefixed) properties.
4518 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4519 value - (mixed) The value to set to the property.
4520 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4525 node.setCanvasStyle('shadowBlur', 30);
4528 If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4531 var node = viz.getNode('nodeId');
4532 //set start and end values
4533 node.setCanvasStyle('shadowBlur', 10, 'start');
4534 node.setCanvasStyle('shadowBlur', 30, 'end');
4535 //will animate nodes canvas style property for nodes
4537 modes: ['node-style:shadowBlur'],
4544 <Accessors.setData>.
4546 setCanvasStyle: function(prop, value, type) {
4547 setDataInternal.call(this, 'canvas', prop, value, type);
4551 Method: setCanvasStyles
4553 Convenience method to set multiple styles at once.
4557 types - (array|string) A set of 'current', 'end' or 'start' values.
4558 obj - (object) A hash containing the names and values of the properties to be altered.
4562 <Accessors.setDataset>.
4564 setCanvasStyles: function(types, obj) {
4565 types = $.splat(types);
4566 for(var attr in obj) {
4567 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4568 this.setCanvasStyle(attr, val[i], types[i]);
4574 Method: removeCanvasStyle
4576 Remove canvas style properties from data.
4580 A variable number of canvas style strings.
4584 <Accessors.removeData>.
4586 removeCanvasStyle: function() {
4587 removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4591 Method: getLabelData
4593 Returns the specified label data value property. This is useful for
4594 querying special/reserved <Graph.Node> label options (i.e.
4595 dollar prefixed properties that match with $label-<name of label style>).
4599 prop - (string) The name of the property. The dollar sign prefix is not needed. For
4600 example *getLabelData(size)* will return *data[$label-size]*.
4601 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4602 data properties also.
4606 <Accessors.getData>.
4608 getLabelData: function(prop, type, force) {
4609 return getDataInternal.call(
4610 this, 'label', prop, type, force, this.Label);
4614 Method: setLabelData
4616 Sets the current label data with some specific value.
4617 This method is only useful for reserved (dollar prefixed) properties.
4621 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4622 value - (mixed) The value to set to the property.
4623 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4628 node.setLabelData('size', 30);
4631 If we were to make an animation of a node label size then we could do
4634 var node = viz.getNode('nodeId');
4635 //set start and end values
4636 node.setLabelData('size', 10, 'start');
4637 node.setLabelData('size', 30, 'end');
4638 //will animate nodes label size
4640 modes: ['label-property:size'],
4647 <Accessors.setData>.
4649 setLabelData: function(prop, value, type) {
4650 setDataInternal.call(this, 'label', prop, value, type);
4654 Method: setLabelDataset
4656 Convenience function to set multiple label data at once.
4660 types - (array|string) A set of 'current', 'end' or 'start' values.
4661 obj - (object) A hash containing the names and values of the properties to be altered.
4665 <Accessors.setDataset>.
4667 setLabelDataset: function(types, obj) {
4668 types = $.splat(types);
4669 for(var attr in obj) {
4670 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4671 this.setLabelData(attr, val[i], types[i]);
4677 Method: removeLabelData
4679 Remove label properties from data.
4683 A variable number of label property strings.
4687 <Accessors.removeData>.
4689 removeLabelData: function() {
4690 removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4702 <Accessors> methods.
4704 The following <Graph.Util> methods are implemented by <Graph.Node>
4706 - <Graph.Util.eachAdjacency>
4707 - <Graph.Util.eachLevel>
4708 - <Graph.Util.eachSubgraph>
4709 - <Graph.Util.eachSubnode>
4710 - <Graph.Util.anySubnode>
4711 - <Graph.Util.getSubnodes>
4712 - <Graph.Util.getParents>
4713 - <Graph.Util.isDescendantOf>
4715 Graph.Node = new Class({
4717 initialize: function(opt, complex, Node, Edge, Label) {
4718 var innerOptions = {
4735 'pos': (complex && $C(0, 0)) || $P(0, 0),
4736 'startPos': (complex && $C(0, 0)) || $P(0, 0),
4737 'endPos': (complex && $C(0, 0)) || $P(0, 0)
4740 $.extend(this, $.extend(innerOptions, opt));
4741 this.Config = this.Node = Node;
4749 Indicates if the node is adjacent to the node specified by id
4753 id - (string) A node id.
4757 node.adjacentTo('nodeId') == true;
4760 adjacentTo: function(node) {
4761 return node.id in this.adjacencies;
4765 Method: getAdjacency
4767 Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4771 id - (string) A node id.
4773 getAdjacency: function(id) {
4774 return this.adjacencies[id];
4780 Returns the position of the node.
4784 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4788 A <Complex> or <Polar> instance.
4792 var pos = node.getPos('end');
4795 getPos: function(type) {
4796 type = type || "current";
4797 if(type == "current") {
4799 } else if(type == "end") {
4801 } else if(type == "start") {
4802 return this.startPos;
4808 Sets the node's position.
4812 value - (object) A <Complex> or <Polar> instance.
4813 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4817 node.setPos(new $jit.Complex(0, 0), 'end');
4820 setPos: function(value, type) {
4821 type = type || "current";
4823 if(type == "current") {
4825 } else if(type == "end") {
4827 } else if(type == "start") {
4828 pos = this.startPos;
4834 Graph.Node.implement(Accessors);
4837 Class: Graph.Adjacence
4839 A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4843 <Accessors> methods.
4847 <Graph>, <Graph.Node>
4851 nodeFrom - A <Graph.Node> connected by this edge.
4852 nodeTo - Another <Graph.Node> connected by this edge.
4853 data - Node data property containing a hash (i.e {}) with custom options.
4855 Graph.Adjacence = new Class({
4857 initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4858 this.nodeFrom = nodeFrom;
4859 this.nodeTo = nodeTo;
4860 this.data = data || {};
4861 this.startData = {};
4863 this.Config = this.Edge = Edge;
4868 Graph.Adjacence.implement(Accessors);
4873 <Graph> traversal and processing utility object.
4877 For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4883 For internal use only. Provides a filtering function based on flags.
4885 filter: function(param) {
4886 if(!param || !($.type(param) == 'string')) return function() { return true; };
4887 var props = param.split(" ");
4888 return function(elem) {
4889 for(var i=0; i<props.length; i++) {
4890 if(elem[props[i]]) {
4900 Returns a <Graph.Node> by *id*.
4902 Also implemented by:
4908 graph - (object) A <Graph> instance.
4909 id - (string) A <Graph.Node> id.
4914 $jit.Graph.Util.getNode(graph, 'nodeid');
4916 graph.getNode('nodeid');
4919 getNode: function(graph, id) {
4920 return graph.nodes[id];
4926 Iterates over <Graph> nodes performing an *action*.
4928 Also implemented by:
4934 graph - (object) A <Graph> instance.
4935 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4939 $jit.Graph.Util.eachNode(graph, function(node) {
4943 graph.eachNode(function(node) {
4948 eachNode: function(graph, action, flags) {
4949 var filter = this.filter(flags);
4950 for(var i in graph.nodes) {
4951 if(filter(graph.nodes[i])) action(graph.nodes[i]);
4956 Method: eachAdjacency
4958 Iterates over <Graph.Node> adjacencies applying the *action* function.
4960 Also implemented by:
4966 node - (object) A <Graph.Node>.
4967 action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4971 $jit.Graph.Util.eachAdjacency(node, function(adj) {
4972 alert(adj.nodeTo.name);
4975 node.eachAdjacency(function(adj) {
4976 alert(adj.nodeTo.name);
4980 eachAdjacency: function(node, action, flags) {
4981 var adj = node.adjacencies, filter = this.filter(flags);
4982 for(var id in adj) {
4985 if(a.nodeFrom != node) {
4986 var tmp = a.nodeFrom;
4987 a.nodeFrom = a.nodeTo;
4996 Method: computeLevels
4998 Performs a BFS traversal setting the correct depth for each node.
5000 Also implemented by:
5006 The depth of each node can then be accessed by
5011 graph - (object) A <Graph>.
5012 id - (string) A starting node id for the BFS traversal.
5013 startDepth - (optional|number) A minimum depth value. Default's 0.
5016 computeLevels: function(graph, id, startDepth, flags) {
5017 startDepth = startDepth || 0;
5018 var filter = this.filter(flags);
5019 this.eachNode(graph, function(elem) {
5023 var root = graph.getNode(id);
5024 root._depth = startDepth;
5026 while(queue.length != 0) {
5027 var node = queue.pop();
5029 this.eachAdjacency(node, function(adj) {
5031 if(n._flag == false && filter(n)) {
5032 if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5042 Performs a BFS traversal applying *action* to each <Graph.Node>.
5044 Also implemented by:
5050 graph - (object) A <Graph>.
5051 id - (string) A starting node id for the BFS traversal.
5052 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5056 $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5060 graph.eachBFS('mynodeid', function(node) {
5065 eachBFS: function(graph, id, action, flags) {
5066 var filter = this.filter(flags);
5068 var queue = [graph.getNode(id)];
5069 while(queue.length != 0) {
5070 var node = queue.pop();
5072 action(node, node._depth);
5073 this.eachAdjacency(node, function(adj) {
5075 if(n._flag == false && filter(n)) {
5086 Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5088 Also implemented by:
5094 node - (object) A <Graph.Node>.
5095 levelBegin - (number) A relative level value.
5096 levelEnd - (number) A relative level value.
5097 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5100 eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5101 var d = node._depth, filter = this.filter(flags), that = this;
5102 levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5103 (function loopLevel(node, levelBegin, levelEnd) {
5104 var d = node._depth;
5105 if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5107 that.eachAdjacency(node, function(adj) {
5109 if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5112 })(node, levelBegin + d, levelEnd + d);
5116 Method: eachSubgraph
5118 Iterates over a node's children recursively.
5120 Also implemented by:
5125 node - (object) A <Graph.Node>.
5126 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5130 $jit.Graph.Util.eachSubgraph(node, function(node) {
5134 node.eachSubgraph(function(node) {
5139 eachSubgraph: function(node, action, flags) {
5140 this.eachLevel(node, 0, false, action, flags);
5146 Iterates over a node's children (without deeper recursion).
5148 Also implemented by:
5153 node - (object) A <Graph.Node>.
5154 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5158 $jit.Graph.Util.eachSubnode(node, function(node) {
5162 node.eachSubnode(function(node) {
5167 eachSubnode: function(node, action, flags) {
5168 this.eachLevel(node, 1, 1, action, flags);
5174 Returns *true* if any subnode matches the given condition.
5176 Also implemented by:
5181 node - (object) A <Graph.Node>.
5182 cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5186 $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5188 node.anySubnode(function(node) { return node.name == 'mynodename'; });
5191 anySubnode: function(node, cond, flags) {
5193 cond = cond || $.lambda(true);
5194 var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5195 this.eachSubnode(node, function(elem) {
5196 if(c(elem)) flag = true;
5204 Collects all subnodes for a specified node.
5205 The *level* parameter filters nodes having relative depth of *level* from the root node.
5207 Also implemented by:
5212 node - (object) A <Graph.Node>.
5213 level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5219 getSubnodes: function(node, level, flags) {
5220 var ans = [], that = this;
5222 var levelStart, levelEnd;
5223 if($.type(level) == 'array') {
5224 levelStart = level[0];
5225 levelEnd = level[1];
5228 levelEnd = Number.MAX_VALUE - node._depth;
5230 this.eachLevel(node, levelStart, levelEnd, function(n) {
5240 Returns an Array of <Graph.Nodes> which are parents of the given node.
5242 Also implemented by:
5247 node - (object) A <Graph.Node>.
5250 An Array of <Graph.Nodes>.
5254 var pars = $jit.Graph.Util.getParents(node);
5256 var pars = node.getParents();
5258 if(pars.length > 0) {
5259 //do stuff with parents
5263 getParents: function(node) {
5265 this.eachAdjacency(node, function(adj) {
5267 if(n._depth < node._depth) ans.push(n);
5273 Method: isDescendantOf
5275 Returns a boolean indicating if some node is descendant of the node with the given id.
5277 Also implemented by:
5283 node - (object) A <Graph.Node>.
5284 id - (string) A <Graph.Node> id.
5288 $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5290 node.isDescendantOf('nodeid');//true|false
5293 isDescendantOf: function(node, id) {
5294 if(node.id == id) return true;
5295 var pars = this.getParents(node), ans = false;
5296 for ( var i = 0; !ans && i < pars.length; i++) {
5297 ans = ans || this.isDescendantOf(pars[i], id);
5305 Cleans flags from nodes.
5307 Also implemented by:
5312 graph - A <Graph> instance.
5314 clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5317 Method: getClosestNodeToOrigin
5319 Returns the closest node to the center of canvas.
5321 Also implemented by:
5327 graph - (object) A <Graph> instance.
5328 prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5331 getClosestNodeToOrigin: function(graph, prop, flags) {
5332 return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5336 Method: getClosestNodeToPos
5338 Returns the closest node to the given position.
5340 Also implemented by:
5346 graph - (object) A <Graph> instance.
5347 pos - (object) A <Complex> or <Polar> instance.
5348 prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5351 getClosestNodeToPos: function(graph, pos, prop, flags) {
5353 prop = prop || 'current';
5354 pos = pos && pos.getc(true) || Complex.KER;
5355 var distance = function(a, b) {
5356 var d1 = a.x - b.x, d2 = a.y - b.y;
5357 return d1 * d1 + d2 * d2;
5359 this.eachNode(graph, function(elem) {
5360 node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5361 node.getPos(prop).getc(true), pos)) ? elem : node;
5367 //Append graph methods to <Graph>
5368 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5369 Graph.prototype[m] = function() {
5370 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5374 //Append node methods to <Graph.Node>
5375 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5376 Graph.Node.prototype[m] = function() {
5377 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5389 Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5390 morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5402 initialize: function(viz) {
5409 Removes one or more <Graph.Nodes> from the visualization.
5410 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5414 node - (string|array) The node's id. Can also be an array having many ids.
5415 opt - (object) Animation options. It's an object with optional properties described below
5416 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5417 duration - Described in <Options.Fx>.
5418 fps - Described in <Options.Fx>.
5419 transition - Described in <Options.Fx>.
5420 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5424 var viz = new $jit.Viz(options);
5425 viz.op.removeNode('nodeId', {
5429 transition: $jit.Trans.Quart.easeOut
5432 viz.op.removeNode(['someId', 'otherId'], {
5439 removeNode: function(node, opt) {
5441 var options = $.merge(this.options, viz.controller, opt);
5442 var n = $.splat(node);
5443 var i, that, nodeObj;
5444 switch(options.type) {
5446 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5450 this.removeNode(n, { type: 'nothing' });
5451 viz.labels.clearLabels();
5455 case 'fade:seq': case 'fade':
5457 //set alpha to 0 for nodes to remove.
5458 for(i=0; i<n.length; i++) {
5459 nodeObj = viz.graph.getNode(n[i]);
5460 nodeObj.setData('alpha', 0, 'end');
5462 viz.fx.animate($.merge(options, {
5463 modes: ['node-property:alpha'],
5464 onComplete: function() {
5465 that.removeNode(n, { type: 'nothing' });
5466 viz.labels.clearLabels();
5468 viz.fx.animate($.merge(options, {
5477 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5478 for(i=0; i<n.length; i++) {
5479 nodeObj = viz.graph.getNode(n[i]);
5480 nodeObj.setData('alpha', 0, 'end');
5481 nodeObj.ignore = true;
5484 viz.fx.animate($.merge(options, {
5485 modes: ['node-property:alpha', 'linear'],
5486 onComplete: function() {
5487 that.removeNode(n, { type: 'nothing' });
5495 condition: function() { return n.length != 0; },
5496 step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5497 onComplete: function() { options.onComplete(); },
5498 duration: Math.ceil(options.duration / n.length)
5502 default: this.doError();
5509 Removes one or more <Graph.Adjacences> from the visualization.
5510 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5514 vertex - (array) An array having two strings which are the ids of the nodes connected by this edge (i.e ['id1', 'id2']). Can also be a two dimensional array holding many edges (i.e [['id1', 'id2'], ['id3', 'id4'], ...]).
5515 opt - (object) Animation options. It's an object with optional properties described below
5516 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5517 duration - Described in <Options.Fx>.
5518 fps - Described in <Options.Fx>.
5519 transition - Described in <Options.Fx>.
5520 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5524 var viz = new $jit.Viz(options);
5525 viz.op.removeEdge(['nodeId', 'otherId'], {
5529 transition: $jit.Trans.Quart.easeOut
5532 viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5539 removeEdge: function(vertex, opt) {
5541 var options = $.merge(this.options, viz.controller, opt);
5542 var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5544 switch(options.type) {
5546 for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5550 this.removeEdge(v, { type: 'nothing' });
5554 case 'fade:seq': case 'fade':
5556 //set alpha to 0 for edges to remove.
5557 for(i=0; i<v.length; i++) {
5558 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5560 adj.setData('alpha', 0,'end');
5563 viz.fx.animate($.merge(options, {
5564 modes: ['edge-property:alpha'],
5565 onComplete: function() {
5566 that.removeEdge(v, { type: 'nothing' });
5568 viz.fx.animate($.merge(options, {
5577 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5578 for(i=0; i<v.length; i++) {
5579 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5581 adj.setData('alpha',0 ,'end');
5586 viz.fx.animate($.merge(options, {
5587 modes: ['edge-property:alpha', 'linear'],
5588 onComplete: function() {
5589 that.removeEdge(v, { type: 'nothing' });
5597 condition: function() { return v.length != 0; },
5598 step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5599 onComplete: function() { options.onComplete(); },
5600 duration: Math.ceil(options.duration / v.length)
5604 default: this.doError();
5611 Adds a new graph to the visualization.
5612 The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5613 The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5617 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5618 opt - (object) Animation options. It's an object with optional properties described below
5619 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5620 duration - Described in <Options.Fx>.
5621 fps - Described in <Options.Fx>.
5622 transition - Described in <Options.Fx>.
5623 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5627 //...json contains a tree or graph structure...
5629 var viz = new $jit.Viz(options);
5634 transition: $jit.Trans.Quart.easeOut
5644 sum: function(json, opt) {
5646 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5648 viz.root = opt.id || viz.root;
5649 switch(options.type) {
5651 graph = viz.construct(json);
5652 graph.eachNode(function(elem) {
5653 elem.eachAdjacency(function(adj) {
5654 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5661 this.sum(json, { type: 'nothing' });
5665 case 'fade:seq': case 'fade': case 'fade:con':
5667 graph = viz.construct(json);
5669 //set alpha to 0 for nodes to add.
5670 var fadeEdges = this.preprocessSum(graph);
5671 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5673 if(options.type != 'fade:con') {
5674 viz.fx.animate($.merge(options, {
5676 onComplete: function() {
5677 viz.fx.animate($.merge(options, {
5679 onComplete: function() {
5680 options.onComplete();
5686 viz.graph.eachNode(function(elem) {
5687 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5688 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5691 viz.fx.animate($.merge(options, {
5692 modes: ['linear'].concat(modes)
5697 default: this.doError();
5704 This method will transform the current visualized graph into the new JSON representation passed in the method.
5705 The JSON object must at least have the root node in common with the current visualized graph.
5709 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5710 opt - (object) Animation options. It's an object with optional properties described below
5711 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5712 duration - Described in <Options.Fx>.
5713 fps - Described in <Options.Fx>.
5714 transition - Described in <Options.Fx>.
5715 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5716 id - (string) The shared <Graph.Node> id between both graphs.
5718 extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5719 *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5720 For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5721 properties as values, just like specified in <Graph.Plot.animate>.
5725 //...json contains a tree or graph structure...
5727 var viz = new $jit.Viz(options);
5728 viz.op.morph(json, {
5732 transition: $jit.Trans.Quart.easeOut
5735 viz.op.morph(json, {
5739 //if the json data contains dollar prefixed params
5740 //like $width or $height these too can be animated
5741 viz.op.morph(json, {
5745 'node-property': ['width', 'height']
5750 morph: function(json, opt, extraModes) {
5752 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5754 //TODO(nico) this hack makes morphing work with the Hypertree.
5755 //Need to check if it has been solved and this can be removed.
5756 viz.root = opt.id || viz.root;
5757 switch(options.type) {
5759 graph = viz.construct(json);
5760 graph.eachNode(function(elem) {
5761 var nodeExists = viz.graph.hasNode(elem.id);
5762 elem.eachAdjacency(function(adj) {
5763 var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5764 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5765 //Update data properties if the node existed
5767 var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5768 for(var prop in (adj.data || {})) {
5769 addedAdj.data[prop] = adj.data[prop];
5773 //Update data properties if the node existed
5775 var addedNode = viz.graph.getNode(elem.id);
5776 for(var prop in (elem.data || {})) {
5777 addedNode.data[prop] = elem.data[prop];
5781 viz.graph.eachNode(function(elem) {
5782 elem.eachAdjacency(function(adj) {
5783 if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5784 viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5787 if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5793 viz.labels.clearLabels(true);
5794 this.morph(json, { type: 'nothing' });
5799 case 'fade:seq': case 'fade': case 'fade:con':
5801 graph = viz.construct(json);
5802 //preprocessing for nodes to delete.
5803 //get node property modes to interpolate
5804 var nodeModes = extraModes && ('node-property' in extraModes)
5805 && $.map($.splat(extraModes['node-property']),
5806 function(n) { return '$' + n; });
5807 viz.graph.eachNode(function(elem) {
5808 var graphNode = graph.getNode(elem.id);
5810 elem.setData('alpha', 1);
5811 elem.setData('alpha', 1, 'start');
5812 elem.setData('alpha', 0, 'end');
5815 //Update node data information
5816 var graphNodeData = graphNode.data;
5817 for(var prop in graphNodeData) {
5818 if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5819 elem.endData[prop] = graphNodeData[prop];
5821 elem.data[prop] = graphNodeData[prop];
5826 viz.graph.eachNode(function(elem) {
5827 if(elem.ignore) return;
5828 elem.eachAdjacency(function(adj) {
5829 if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5830 var nodeFrom = graph.getNode(adj.nodeFrom.id);
5831 var nodeTo = graph.getNode(adj.nodeTo.id);
5832 if(!nodeFrom.adjacentTo(nodeTo)) {
5833 var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5835 adj.setData('alpha', 1);
5836 adj.setData('alpha', 1, 'start');
5837 adj.setData('alpha', 0, 'end');
5841 //preprocessing for adding nodes.
5842 var fadeEdges = this.preprocessSum(graph);
5844 var modes = !fadeEdges? ['node-property:alpha'] :
5845 ['node-property:alpha',
5846 'edge-property:alpha'];
5847 //Append extra node-property animations (if any)
5848 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))?
5849 (':' + $.splat(extraModes['node-property']).join(':')) : '');
5850 //Append extra edge-property animations (if any)
5851 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))?
5852 (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5853 //Add label-property animations (if any)
5854 if(extraModes && ('label-property' in extraModes)) {
5855 modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5858 viz.graph.eachNode(function(elem) {
5859 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5860 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5863 viz.fx.animate($.merge(options, {
5864 modes: ['polar'].concat(modes),
5865 onComplete: function() {
5866 viz.graph.eachNode(function(elem) {
5867 if(elem.ignore) viz.graph.removeNode(elem.id);
5869 viz.graph.eachNode(function(elem) {
5870 elem.eachAdjacency(function(adj) {
5871 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5874 options.onComplete();
5887 Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5891 node - (object) A <Graph.Node>.
5892 opt - (object) An object containing options described below
5893 type - (string) Whether to 'replot' or 'animate' the contraction.
5895 There are also a number of Animation options. For more information see <Options.Fx>.
5899 var viz = new $jit.Viz(options);
5900 viz.op.contract(node, {
5904 transition: $jit.Trans.Quart.easeOut
5909 contract: function(node, opt) {
5911 if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5912 opt = $.merge(this.options, viz.config, opt || {}, {
5913 'modes': ['node-property:alpha:span', 'linear']
5915 node.collapsed = true;
5917 n.eachSubnode(function(ch) {
5919 ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5923 if(opt.type == 'animate') {
5926 viz.rotate(viz.rotated, 'none', {
5931 n.eachSubnode(function(ch) {
5932 ch.setPos(node.getPos('end'), 'end');
5936 viz.fx.animate(opt);
5937 } else if(opt.type == 'replot'){
5945 Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5949 node - (object) A <Graph.Node>.
5950 opt - (object) An object containing options described below
5951 type - (string) Whether to 'replot' or 'animate'.
5953 There are also a number of Animation options. For more information see <Options.Fx>.
5957 var viz = new $jit.Viz(options);
5958 viz.op.expand(node, {
5962 transition: $jit.Trans.Quart.easeOut
5967 expand: function(node, opt) {
5968 if(!('collapsed' in node)) return;
5970 opt = $.merge(this.options, viz.config, opt || {}, {
5971 'modes': ['node-property:alpha:span', 'linear']
5973 delete node.collapsed;
5975 n.eachSubnode(function(ch) {
5977 ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5981 if(opt.type == 'animate') {
5984 viz.rotate(viz.rotated, 'none', {
5988 viz.fx.animate(opt);
5989 } else if(opt.type == 'replot'){
5994 preprocessSum: function(graph) {
5996 graph.eachNode(function(elem) {
5997 if(!viz.graph.hasNode(elem.id)) {
5998 viz.graph.addNode(elem);
5999 var n = viz.graph.getNode(elem.id);
6000 n.setData('alpha', 0);
6001 n.setData('alpha', 0, 'start');
6002 n.setData('alpha', 1, 'end');
6005 var fadeEdges = false;
6006 graph.eachNode(function(elem) {
6007 elem.eachAdjacency(function(adj) {
6008 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6009 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6010 if(!nodeFrom.adjacentTo(nodeTo)) {
6011 var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6012 if(nodeFrom.startAlpha == nodeFrom.endAlpha
6013 && nodeTo.startAlpha == nodeTo.endAlpha) {
6015 adj.setData('alpha', 0);
6016 adj.setData('alpha', 0, 'start');
6017 adj.setData('alpha', 1, 'end');
6031 Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6032 Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6033 position is over the rendered shape.
6035 Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
6036 *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6040 //implement a new node type
6041 $jit.Viz.Plot.NodeTypes.implement({
6043 'render': function(node, canvas) {
6044 this.nodeHelper.circle.render ...
6046 'contains': function(node, pos) {
6047 this.nodeHelper.circle.contains ...
6051 //implement an edge type
6052 $jit.Viz.Plot.EdgeTypes.implement({
6054 'render': function(node, canvas) {
6055 this.edgeHelper.circle.render ...
6058 'contains': function(node, pos) {
6059 this.edgeHelper.circle.contains ...
6070 Contains rendering and other type of primitives for simple shapes.
6075 'contains': $.lambda(false)
6078 Object: NodeHelper.circle
6084 Renders a circle into the canvas.
6088 type - (string) Possible options are 'fill' or 'stroke'.
6089 pos - (object) An *x*, *y* object with the position of the center of the circle.
6090 radius - (number) The radius of the circle to be rendered.
6091 canvas - (object) A <Canvas> instance.
6095 NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6098 'render': function(type, pos, radius, canvas){
6099 var ctx = canvas.getCtx();
6101 ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6108 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6112 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6113 pos - (object) An *x*, *y* object with the position to check.
6114 radius - (number) The radius of the rendered circle.
6118 NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6121 'contains': function(npos, pos, radius){
6122 var diffx = npos.x - pos.x,
6123 diffy = npos.y - pos.y,
6124 diff = diffx * diffx + diffy * diffy;
6125 return diff <= radius * radius;
6129 Object: NodeHelper.ellipse
6135 Renders an ellipse into the canvas.
6139 type - (string) Possible options are 'fill' or 'stroke'.
6140 pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6141 width - (number) The width of the ellipse.
6142 height - (number) The height of the ellipse.
6143 canvas - (object) A <Canvas> instance.
6147 NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6150 'render': function(type, pos, width, height, canvas){
6151 var ctx = canvas.getCtx();
6155 ctx.scale(width / height, height / width);
6157 ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6166 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6170 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6171 pos - (object) An *x*, *y* object with the position to check.
6172 width - (number) The width of the rendered ellipse.
6173 height - (number) The height of the rendered ellipse.
6177 NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6180 'contains': function(npos, pos, width, height){
6181 // TODO(nico): be more precise...
6184 var dist = (width + height) / 2,
6185 diffx = npos.x - pos.x,
6186 diffy = npos.y - pos.y,
6187 diff = diffx * diffx + diffy * diffy;
6188 return diff <= dist * dist;
6192 Object: NodeHelper.square
6198 Renders a square into the canvas.
6202 type - (string) Possible options are 'fill' or 'stroke'.
6203 pos - (object) An *x*, *y* object with the position of the center of the square.
6204 dim - (number) The radius (or half-diameter) of the square.
6205 canvas - (object) A <Canvas> instance.
6209 NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6212 'render': function(type, pos, dim, canvas){
6213 canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6218 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6222 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6223 pos - (object) An *x*, *y* object with the position to check.
6224 dim - (number) The radius (or half-diameter) of the square.
6228 NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6231 'contains': function(npos, pos, dim){
6232 return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6236 Object: NodeHelper.rectangle
6242 Renders a rectangle into the canvas.
6246 type - (string) Possible options are 'fill' or 'stroke'.
6247 pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6248 width - (number) The width of the rectangle.
6249 height - (number) The height of the rectangle.
6250 canvas - (object) A <Canvas> instance.
6254 NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6257 'render': function(type, pos, width, height, canvas){
6258 canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6264 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6268 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6269 pos - (object) An *x*, *y* object with the position to check.
6270 width - (number) The width of the rendered rectangle.
6271 height - (number) The height of the rendered rectangle.
6275 NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6278 'contains': function(npos, pos, width, height){
6279 return Math.abs(pos.x - npos.x) <= width / 2
6280 && Math.abs(pos.y - npos.y) <= height / 2;
6284 Object: NodeHelper.triangle
6290 Renders a triangle into the canvas.
6294 type - (string) Possible options are 'fill' or 'stroke'.
6295 pos - (object) An *x*, *y* object with the position of the center of the triangle.
6296 dim - (number) The dimension of the triangle.
6297 canvas - (object) A <Canvas> instance.
6301 NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6304 'render': function(type, pos, dim, canvas){
6305 var ctx = canvas.getCtx(),
6313 ctx.moveTo(c1x, c1y);
6314 ctx.lineTo(c2x, c2y);
6315 ctx.lineTo(c3x, c3y);
6322 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6326 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6327 pos - (object) An *x*, *y* object with the position to check.
6328 dim - (number) The dimension of the shape.
6332 NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6335 'contains': function(npos, pos, dim) {
6336 return NodeHelper.circle.contains(npos, pos, dim);
6340 Object: NodeHelper.star
6346 Renders a star into the canvas.
6350 type - (string) Possible options are 'fill' or 'stroke'.
6351 pos - (object) An *x*, *y* object with the position of the center of the star.
6352 dim - (number) The dimension of the star.
6353 canvas - (object) A <Canvas> instance.
6357 NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6360 'render': function(type, pos, dim, canvas){
6361 var ctx = canvas.getCtx(),
6364 ctx.translate(pos.x, pos.y);
6367 for (var i = 0; i < 9; i++) {
6370 ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6382 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6386 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6387 pos - (object) An *x*, *y* object with the position to check.
6388 dim - (number) The dimension of the shape.
6392 NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6395 'contains': function(npos, pos, dim) {
6396 return NodeHelper.circle.contains(npos, pos, dim);
6404 Contains rendering primitives for simple edge shapes.
6408 Object: EdgeHelper.line
6414 Renders a line into the canvas.
6418 from - (object) An *x*, *y* object with the starting position of the line.
6419 to - (object) An *x*, *y* object with the ending position of the line.
6420 canvas - (object) A <Canvas> instance.
6424 EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6427 'render': function(from, to, canvas){
6428 var ctx = canvas.getCtx();
6430 ctx.moveTo(from.x, from.y);
6431 ctx.lineTo(to.x, to.y);
6437 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6441 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6442 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6443 pos - (object) An *x*, *y* object with the position to check.
6444 epsilon - (number) The dimension of the shape.
6448 EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6451 'contains': function(posFrom, posTo, pos, epsilon) {
6454 minPosX = min(posFrom.x, posTo.x),
6455 maxPosX = max(posFrom.x, posTo.x),
6456 minPosY = min(posFrom.y, posTo.y),
6457 maxPosY = max(posFrom.y, posTo.y);
6459 if(pos.x >= minPosX && pos.x <= maxPosX
6460 && pos.y >= minPosY && pos.y <= maxPosY) {
6461 if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6464 var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6465 return Math.abs(dist - pos.y) <= epsilon;
6471 Object: EdgeHelper.arrow
6477 Renders an arrow into the canvas.
6481 from - (object) An *x*, *y* object with the starting position of the arrow.
6482 to - (object) An *x*, *y* object with the ending position of the arrow.
6483 dim - (number) The dimension of the arrow.
6484 swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6485 canvas - (object) A <Canvas> instance.
6489 EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6492 'render': function(from, to, dim, swap, canvas){
6493 var ctx = canvas.getCtx();
6494 // invert edge direction
6500 var vect = new Complex(to.x - from.x, to.y - from.y);
6501 vect.$scale(dim / vect.norm());
6502 var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6503 normal = new Complex(-vect.y / 2, vect.x / 2),
6504 v1 = intermediatePoint.add(normal),
6505 v2 = intermediatePoint.$add(normal.$scale(-1));
6508 ctx.moveTo(from.x, from.y);
6509 ctx.lineTo(to.x, to.y);
6512 ctx.moveTo(v1.x, v1.y);
6513 ctx.lineTo(v2.x, v2.y);
6514 ctx.lineTo(to.x, to.y);
6521 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6525 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6526 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6527 pos - (object) An *x*, *y* object with the position to check.
6528 epsilon - (number) The dimension of the shape.
6532 EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6535 'contains': function(posFrom, posTo, pos, epsilon) {
6536 return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6540 Object: EdgeHelper.hyperline
6546 Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6550 from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6551 to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6552 r - (number) The scaling factor.
6553 canvas - (object) A <Canvas> instance.
6557 EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6560 'render': function(from, to, r, canvas){
6561 var ctx = canvas.getCtx();
6562 var centerOfCircle = computeArcThroughTwoPoints(from, to);
6563 if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6564 || centerOfCircle.ratio < 0) {
6566 ctx.moveTo(from.x * r, from.y * r);
6567 ctx.lineTo(to.x * r, to.y * r);
6570 var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6571 - centerOfCircle.x);
6572 var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6573 - centerOfCircle.x);
6574 var sense = sense(angleBegin, angleEnd);
6576 ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6577 * r, angleBegin, angleEnd, sense);
6581 Calculates the arc parameters through two points.
6583 More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6587 p1 - A <Complex> instance.
6588 p2 - A <Complex> instance.
6589 scale - The Disk's diameter.
6593 An object containing some arc properties.
6595 function computeArcThroughTwoPoints(p1, p2){
6596 var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6597 var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6598 // Fall back to a straight line
6606 var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6607 var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6610 var squaredRatio = (a * a + b * b) / 4 - 1;
6611 // Fall back to a straight line
6612 if (squaredRatio < 0)
6618 var ratio = Math.sqrt(squaredRatio);
6622 ratio: ratio > 1000? -1 : ratio,
6630 Sets angle direction to clockwise (true) or counterclockwise (false).
6634 angleBegin - Starting angle for drawing the arc.
6635 angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6639 A Boolean instance describing the sense for drawing the HyperLine.
6641 function sense(angleBegin, angleEnd){
6642 return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6643 : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6651 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6655 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6656 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6657 pos - (object) An *x*, *y* object with the position to check.
6658 epsilon - (number) The dimension of the shape.
6662 EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6665 'contains': $.lambda(false)
6671 * File: Graph.Plot.js
6677 <Graph> rendering and animation methods.
6681 nodeHelper - <NodeHelper> object.
6682 edgeHelper - <EdgeHelper> object.
6685 //Default intializer
6686 initialize: function(viz, klass){
6688 this.config = viz.config;
6689 this.node = viz.config.Node;
6690 this.edge = viz.config.Edge;
6691 this.animation = new Animation;
6692 this.nodeTypes = new klass.Plot.NodeTypes;
6693 this.edgeTypes = new klass.Plot.EdgeTypes;
6694 this.labels = viz.labels;
6698 nodeHelper: NodeHelper,
6699 edgeHelper: EdgeHelper,
6702 //node/edge property parsers
6710 'lineWidth': 'number',
6711 'angularWidth':'number',
6713 'valueArray':'array-number',
6714 'dimArray':'array-number'
6715 //'colorArray':'array-color'
6718 //canvas specific parsers
6720 'globalAlpha': 'number',
6721 'fillStyle': 'color',
6722 'strokeStyle': 'color',
6723 'lineWidth': 'number',
6724 'shadowBlur': 'number',
6725 'shadowColor': 'color',
6726 'shadowOffsetX': 'number',
6727 'shadowOffsetY': 'number',
6728 'miterLimit': 'number'
6737 //Number interpolator
6738 'compute': function(from, to, delta) {
6739 return from + (to - from) * delta;
6742 //Position interpolators
6743 'moebius': function(elem, props, delta, vector) {
6744 var v = vector.scale(-delta);
6746 var x = v.x, y = v.y;
6747 var ans = elem.startPos
6748 .getc().moebiusTransformation(v);
6749 elem.pos.setc(ans.x, ans.y);
6754 'linear': function(elem, props, delta) {
6755 var from = elem.startPos.getc(true);
6756 var to = elem.endPos.getc(true);
6757 elem.pos.setc(this.compute(from.x, to.x, delta),
6758 this.compute(from.y, to.y, delta));
6761 'polar': function(elem, props, delta) {
6762 var from = elem.startPos.getp(true);
6763 var to = elem.endPos.getp();
6764 var ans = to.interpolate(from, delta);
6765 elem.pos.setp(ans.theta, ans.rho);
6768 //Graph's Node/Edge interpolators
6769 'number': function(elem, prop, delta, getter, setter) {
6770 var from = elem[getter](prop, 'start');
6771 var to = elem[getter](prop, 'end');
6772 elem[setter](prop, this.compute(from, to, delta));
6775 'color': function(elem, prop, delta, getter, setter) {
6776 var from = $.hexToRgb(elem[getter](prop, 'start'));
6777 var to = $.hexToRgb(elem[getter](prop, 'end'));
6778 var comp = this.compute;
6779 var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6780 parseInt(comp(from[1], to[1], delta)),
6781 parseInt(comp(from[2], to[2], delta))]);
6783 elem[setter](prop, val);
6786 'array-number': function(elem, prop, delta, getter, setter) {
6787 var from = elem[getter](prop, 'start'),
6788 to = elem[getter](prop, 'end'),
6790 for(var i=0, l=from.length; i<l; i++) {
6791 var fromi = from[i], toi = to[i];
6793 for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6794 curi.push(this.compute(fromi[j], toi[j], delta));
6798 cur.push(this.compute(fromi, toi, delta));
6801 elem[setter](prop, cur);
6804 'node': function(elem, props, delta, map, getter, setter) {
6807 var len = props.length;
6808 for(var i=0; i<len; i++) {
6810 this[map[pi]](elem, pi, delta, getter, setter);
6813 for(var pi in map) {
6814 this[map[pi]](elem, pi, delta, getter, setter);
6819 'edge': function(elem, props, delta, mapKey, getter, setter) {
6820 var adjs = elem.adjacencies;
6821 for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6824 'node-property': function(elem, props, delta) {
6825 this['node'](elem, props, delta, 'map', 'getData', 'setData');
6828 'edge-property': function(elem, props, delta) {
6829 this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6832 'label-property': function(elem, props, delta) {
6833 this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6836 'node-style': function(elem, props, delta) {
6837 this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6840 'edge-style': function(elem, props, delta) {
6841 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6849 Iteratively performs an action while refreshing the state of the visualization.
6853 options - (object) An object containing some sequence options described below
6854 condition - (function) A function returning a boolean instance in order to stop iterations.
6855 step - (function) A function to execute on each step of the iteration.
6856 onComplete - (function) A function to execute when the sequence finishes.
6857 duration - (number) Duration (in milliseconds) of each step.
6861 var rg = new $jit.RGraph(options);
6864 condition: function() {
6870 onComplete: function() {
6877 sequence: function(options) {
6880 condition: $.lambda(false),
6882 onComplete: $.empty,
6886 var interval = setInterval(function() {
6887 if(options.condition()) {
6890 clearInterval(interval);
6891 options.onComplete();
6893 that.viz.refresh(true);
6894 }, options.duration);
6900 Prepare graph position and other attribute values before performing an Animation.
6901 This method is used internally by the Toolkit.
6905 <Animation>, <Graph.Plot.animate>
6908 prepare: function(modes) {
6909 var graph = this.viz.graph,
6912 'getter': 'getData',
6916 'getter': 'getData',
6920 'getter': 'getCanvasStyle',
6921 'setter': 'setCanvasStyle'
6924 'getter': 'getCanvasStyle',
6925 'setter': 'setCanvasStyle'
6931 if($.type(modes) == 'array') {
6932 for(var i=0, len=modes.length; i < len; i++) {
6933 var elems = modes[i].split(':');
6934 m[elems.shift()] = elems;
6937 for(var p in modes) {
6938 if(p == 'position') {
6939 m[modes.position] = [];
6941 m[p] = $.splat(modes[p]);
6946 graph.eachNode(function(node) {
6947 node.startPos.set(node.pos);
6948 $.each(['node-property', 'node-style'], function(p) {
6951 for(var i=0, l=prop.length; i < l; i++) {
6952 node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6956 $.each(['edge-property', 'edge-style'], function(p) {
6959 node.eachAdjacency(function(adj) {
6960 for(var i=0, l=prop.length; i < l; i++) {
6961 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6973 Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6977 opt - (object) Animation options. The object properties are described below
6978 duration - (optional) Described in <Options.Fx>.
6979 fps - (optional) Described in <Options.Fx>.
6980 hideLabels - (optional|boolean) Whether to hide labels during the animation.
6981 modes - (required|object) An object with animation modes (described below).
6985 Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6986 They are represented by an object that has as keys main categories of properties to animate and as values a list
6987 of these specific properties. The properties are described below
6989 position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6990 node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6991 edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6992 label-property - Describes which Label properties will be interpolated. These properties can be any of the ones defined in <Options.Label> like color or size.
6993 node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6994 edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6998 var viz = new $jit.Viz(options);
6999 //...tweak some Data, CanvasStyles or LabelData properties...
7002 'position': 'linear',
7003 'node-property': ['width', 'height'],
7004 'node-style': 'shadowColor',
7005 'label-property': 'size'
7009 //...can also be written like this...
7012 'node-property:width:height',
7013 'node-style:shadowColor',
7014 'label-property:size'],
7019 animate: function(opt, versor) {
7020 opt = $.merge(this.viz.config, opt || {});
7024 interp = this.Interpolator,
7025 animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7026 //prepare graph values
7027 var m = this.prepare(opt.modes);
7030 if(opt.hideLabels) this.labels.hideLabels(true);
7031 animation.setOptions($.merge(opt, {
7033 compute: function(delta) {
7034 graph.eachNode(function(node) {
7036 interp[p](node, m[p], delta, versor);
7039 that.plot(opt, this.$animating, delta);
7040 this.$animating = true;
7042 complete: function() {
7043 if(opt.hideLabels) that.labels.hideLabels(false);
7046 opt.onAfterCompute();
7054 Apply animation to node properties like color, width, height, dim, etc.
7058 options - Animation options. This object properties is described below
7059 elements - The Elements to be transformed. This is an object that has a properties
7063 //can also be an array of ids
7064 'id': 'id-of-node-to-transform',
7065 //properties to be modified. All properties are optional.
7067 'color': '#ccc', //some color
7068 'width': 10, //some width
7069 'height': 10, //some height
7070 'dim': 20, //some dim
7071 'lineWidth': 10 //some line width
7076 - _reposition_ Whether to recalculate positions and add a motion animation.
7077 This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7079 - _onComplete_ A method that is called when the animation completes.
7081 ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7085 var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7092 'transition': Trans.Quart.easeOut
7097 nodeFx: function(opt) {
7100 animation = this.nodeFxAnimation,
7101 options = $.merge(this.viz.config, {
7108 opt = $.merge(options, opt || {}, {
7109 onBeforeCompute: $.empty,
7110 onAfterCompute: $.empty
7112 //check if an animation is running
7113 animation.stopTimer();
7114 var props = opt.elements.properties;
7115 //set end values for nodes
7116 if(!opt.elements.id) {
7117 graph.eachNode(function(n) {
7118 for(var prop in props) {
7119 n.setData(prop, props[prop], 'end');
7123 var ids = $.splat(opt.elements.id);
7124 $.each(ids, function(id) {
7125 var n = graph.getNode(id);
7127 for(var prop in props) {
7128 n.setData(prop, props[prop], 'end');
7135 for(var prop in props) propnames.push(prop);
7136 //add node properties modes
7137 var modes = ['node-property:' + propnames.join(':')];
7138 //set new node positions
7139 if(opt.reposition) {
7140 modes.push('linear');
7144 this.animate($.merge(opt, {
7158 opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7163 var viz = new $jit.Viz(options);
7168 plot: function(opt, animating) {
7171 canvas = viz.canvas,
7174 ctx = canvas.getCtx(),
7176 opt = opt || this.viz.controller;
7177 opt.clearCanvas && canvas.clear();
7179 var root = aGraph.getNode(id);
7182 var T = !!root.visited;
7183 aGraph.eachNode(function(node) {
7184 var nodeAlpha = node.getData('alpha');
7185 node.eachAdjacency(function(adj) {
7186 var nodeTo = adj.nodeTo;
7187 if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7188 !animating && opt.onBeforePlotLine(adj);
7190 ctx.globalAlpha = min(nodeAlpha,
7191 nodeTo.getData('alpha'),
7192 adj.getData('alpha'));
7193 that.plotLine(adj, canvas, animating);
7195 !animating && opt.onAfterPlotLine(adj);
7200 !animating && opt.onBeforePlotNode(node);
7201 that.plotNode(node, canvas, animating);
7202 !animating && opt.onAfterPlotNode(node);
7204 if(!that.labelsHidden && opt.withLabels) {
7205 if(node.drawn && nodeAlpha >= 0.95) {
7206 that.labels.plotLabel(canvas, node, opt);
7208 that.labels.hideLabel(node, false);
7219 plotTree: function(node, opt, animating) {
7222 canvas = viz.canvas,
7223 config = this.config,
7224 ctx = canvas.getCtx();
7225 var nodeAlpha = node.getData('alpha');
7226 node.eachSubnode(function(elem) {
7227 if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7228 var adj = node.getAdjacency(elem.id);
7229 !animating && opt.onBeforePlotLine(adj);
7230 ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7231 that.plotLine(adj, canvas, animating);
7232 !animating && opt.onAfterPlotLine(adj);
7233 that.plotTree(elem, opt, animating);
7237 !animating && opt.onBeforePlotNode(node);
7238 this.plotNode(node, canvas, animating);
7239 !animating && opt.onAfterPlotNode(node);
7240 if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7241 this.labels.plotLabel(canvas, node, opt);
7243 this.labels.hideLabel(node, false);
7245 this.labels.hideLabel(node, true);
7252 Plots a <Graph.Node>.
7256 node - (object) A <Graph.Node>.
7257 canvas - (object) A <Canvas> element.
7260 plotNode: function(node, canvas, animating) {
7261 var f = node.getData('type'),
7262 ctxObj = this.node.CanvasStyles;
7264 var width = node.getData('lineWidth'),
7265 color = node.getData('color'),
7266 alpha = node.getData('alpha'),
7267 ctx = canvas.getCtx();
7269 ctx.lineWidth = width;
7270 ctx.fillStyle = ctx.strokeStyle = color;
7271 ctx.globalAlpha = alpha;
7273 for(var s in ctxObj) {
7274 ctx[s] = node.getCanvasStyle(s);
7277 this.nodeTypes[f].render.call(this, node, canvas, animating);
7284 Plots a <Graph.Adjacence>.
7288 adj - (object) A <Graph.Adjacence>.
7289 canvas - (object) A <Canvas> instance.
7292 plotLine: function(adj, canvas, animating) {
7293 var f = adj.getData('type'),
7294 ctxObj = this.edge.CanvasStyles;
7296 var width = adj.getData('lineWidth'),
7297 color = adj.getData('color'),
7298 ctx = canvas.getCtx();
7300 ctx.lineWidth = width;
7301 ctx.fillStyle = ctx.strokeStyle = color;
7303 for(var s in ctxObj) {
7304 ctx[s] = adj.getCanvasStyle(s);
7307 this.edgeTypes[f].render.call(this, adj, canvas, animating);
7316 * File: Graph.Label.js
7323 An interface for plotting/hiding/showing labels.
7327 This is a generic interface for plotting/hiding/showing labels.
7328 The <Graph.Label> interface is implemented in multiple ways to provide
7329 different label types.
7331 For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7332 HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7333 The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7335 All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7341 Class: Graph.Label.Native
7343 Implements labels natively, using the Canvas text API.
7345 Graph.Label.Native = new Class({
7349 Plots a label for a given node.
7353 canvas - (object) A <Canvas> instance.
7354 node - (object) A <Graph.Node>.
7355 controller - (object) A configuration object.
7360 var viz = new $jit.Viz(options);
7361 var node = viz.graph.getNode('nodeId');
7362 viz.labels.plotLabel(viz.canvas, node, viz.config);
7365 plotLabel: function(canvas, node, controller) {
7366 var ctx = canvas.getCtx();
7367 var pos = node.pos.getc(true);
7369 ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7370 ctx.textAlign = node.getLabelData('textAlign');
7371 ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7372 ctx.textBaseline = node.getLabelData('textBaseline');
7374 this.renderLabel(canvas, node, controller);
7380 Does the actual rendering of the label in the canvas. The default
7381 implementation renders the label close to the position of the node, this
7382 method should be overriden to position the labels differently.
7386 canvas - A <Canvas> instance.
7387 node - A <Graph.Node>.
7388 controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7390 renderLabel: function(canvas, node, controller) {
7391 var ctx = canvas.getCtx();
7392 var pos = node.pos.getc(true);
7393 ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7401 Class: Graph.Label.DOM
7403 Abstract Class implementing some DOM label methods.
7407 <Graph.Label.HTML> and <Graph.Label.SVG>.
7410 Graph.Label.DOM = new Class({
7411 //A flag value indicating if node labels are being displayed or not.
7412 labelsHidden: false,
7414 labelContainer: false,
7415 //Label elements hash.
7419 Method: getLabelContainer
7421 Lazy fetcher for the label container.
7425 The label container DOM element.
7430 var viz = new $jit.Viz(options);
7431 var labelContainer = viz.labels.getLabelContainer();
7432 alert(labelContainer.innerHTML);
7435 getLabelContainer: function() {
7436 return this.labelContainer ?
7437 this.labelContainer :
7438 this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7444 Lazy fetcher for the label element.
7448 id - (string) The label id (which is also a <Graph.Node> id).
7457 var viz = new $jit.Viz(options);
7458 var label = viz.labels.getLabel('someid');
7459 alert(label.innerHTML);
7463 getLabel: function(id) {
7464 return (id in this.labels && this.labels[id] != null) ?
7466 this.labels[id] = document.getElementById(id);
7472 Hides all labels (by hiding the label container).
7476 hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7480 var viz = new $jit.Viz(options);
7481 rg.labels.hideLabels(true);
7485 hideLabels: function (hide) {
7486 var container = this.getLabelContainer();
7488 container.style.display = 'none';
7490 container.style.display = '';
7491 this.labelsHidden = hide;
7497 Clears the label container.
7499 Useful when using a new visualization with the same canvas element/widget.
7503 force - (boolean) Forces deletion of all labels.
7507 var viz = new $jit.Viz(options);
7508 viz.labels.clearLabels();
7511 clearLabels: function(force) {
7512 for(var id in this.labels) {
7513 if (force || !this.viz.graph.hasNode(id)) {
7514 this.disposeLabel(id);
7515 delete this.labels[id];
7521 Method: disposeLabel
7527 id - (string) A label id (which generally is also a <Graph.Node> id).
7531 var viz = new $jit.Viz(options);
7532 viz.labels.disposeLabel('labelid');
7535 disposeLabel: function(id) {
7536 var elem = this.getLabel(id);
7537 if(elem && elem.parentNode) {
7538 elem.parentNode.removeChild(elem);
7545 Hides the corresponding <Graph.Node> label.
7549 node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7550 show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7554 var rg = new $jit.Viz(options);
7555 viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7558 hideLabel: function(node, show) {
7559 node = $.splat(node);
7560 var st = show ? "" : "none", lab, that = this;
7561 $.each(node, function(n) {
7562 var lab = that.getLabel(n.id);
7564 lab.style.display = st;
7572 Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7576 pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7577 canvas - A <Canvas> instance.
7581 A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7584 fitsInCanvas: function(pos, canvas) {
7585 var size = canvas.getSize();
7586 if(pos.x >= size.width || pos.x < 0
7587 || pos.y >= size.height || pos.y < 0) return false;
7593 Class: Graph.Label.HTML
7595 Implements HTML labels.
7599 All <Graph.Label.DOM> methods.
7602 Graph.Label.HTML = new Class({
7603 Implements: Graph.Label.DOM,
7608 Plots a label for a given node.
7612 canvas - (object) A <Canvas> instance.
7613 node - (object) A <Graph.Node>.
7614 controller - (object) A configuration object.
7619 var viz = new $jit.Viz(options);
7620 var node = viz.graph.getNode('nodeId');
7621 viz.labels.plotLabel(viz.canvas, node, viz.config);
7626 plotLabel: function(canvas, node, controller) {
7627 var id = node.id, tag = this.getLabel(id);
7629 if(!tag && !(tag = document.getElementById(id))) {
7630 tag = document.createElement('div');
7631 var container = this.getLabelContainer();
7633 tag.className = 'node';
7634 tag.style.position = 'absolute';
7635 controller.onCreateLabel(tag, node);
7636 container.appendChild(tag);
7637 this.labels[node.id] = tag;
7640 this.placeLabel(tag, node, controller);
7645 Class: Graph.Label.SVG
7647 Implements SVG labels.
7651 All <Graph.Label.DOM> methods.
7653 Graph.Label.SVG = new Class({
7654 Implements: Graph.Label.DOM,
7659 Plots a label for a given node.
7663 canvas - (object) A <Canvas> instance.
7664 node - (object) A <Graph.Node>.
7665 controller - (object) A configuration object.
7670 var viz = new $jit.Viz(options);
7671 var node = viz.graph.getNode('nodeId');
7672 viz.labels.plotLabel(viz.canvas, node, viz.config);
7677 plotLabel: function(canvas, node, controller) {
7678 var id = node.id, tag = this.getLabel(id);
7679 if(!tag && !(tag = document.getElementById(id))) {
7680 var ns = 'http://www.w3.org/2000/svg';
7681 tag = document.createElementNS(ns, 'svg:text');
7682 var tspan = document.createElementNS(ns, 'svg:tspan');
7683 tag.appendChild(tspan);
7684 var container = this.getLabelContainer();
7685 tag.setAttribute('id', id);
7686 tag.setAttribute('class', 'node');
7687 container.appendChild(tag);
7688 controller.onCreateLabel(tag, node);
7689 this.labels[node.id] = tag;
7691 this.placeLabel(tag, node, controller);
7697 Graph.Geom = new Class({
7699 initialize: function(viz) {
7701 this.config = viz.config;
7702 this.node = viz.config.Node;
7703 this.edge = viz.config.Edge;
7706 Applies a translation to the tree.
7710 pos - A <Complex> number specifying translation vector.
7711 prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7716 st.geom.translate(new Complex(300, 100), 'end');
7719 translate: function(pos, prop) {
7720 prop = $.splat(prop);
7721 this.viz.graph.eachNode(function(elem) {
7722 $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7726 Hides levels of the tree until it properly fits in canvas.
7728 setRightLevelToShow: function(node, canvas, callback) {
7729 var level = this.getRightLevelToShow(node, canvas),
7730 fx = this.viz.labels,
7737 node.eachLevel(0, this.config.levelsToShow, function(n) {
7738 var d = n._depth - node._depth;
7744 fx.hideLabel(n, false);
7756 Returns the right level to show for the current tree in order to fit in canvas.
7758 getRightLevelToShow: function(node, canvas) {
7759 var config = this.config;
7760 var level = config.levelsToShow;
7761 var constrained = config.constrained;
7762 if(!constrained) return level;
7763 while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7776 Provides methods for loading and serving JSON data.
7779 construct: function(json) {
7780 var isGraph = ($.type(json) == 'array');
7781 var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7784 (function (ans, json) {
7787 for(var i=0, ch = json.children; i<ch.length; i++) {
7788 ans.addAdjacence(json, ch[i]);
7789 arguments.callee(ans, ch[i]);
7795 (function (ans, json) {
7796 var getNode = function(id) {
7797 for(var i=0, l=json.length; i<l; i++) {
7798 if(json[i].id == id) {
7802 // The node was not defined in the JSON
7808 return ans.addNode(newNode);
7811 for(var i=0, l=json.length; i<l; i++) {
7812 ans.addNode(json[i]);
7813 var adj = json[i].adjacencies;
7815 for(var j=0, lj=adj.length; j<lj; j++) {
7816 var node = adj[j], data = {};
7817 if(typeof adj[j] != 'string') {
7818 data = $.merge(node.data, {});
7821 ans.addAdjacence(json[i], getNode(node), data);
7833 Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7835 A JSON tree or graph structure consists of nodes, each having as properties
7837 id - (string) A unique identifier for the node
7838 name - (string) A node's name
7839 data - (object) The data optional property contains a hash (i.e {})
7840 where you can store all the information you want about this node.
7842 For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7848 "id": "aUniqueIdentifier",
7849 "name": "usually a nodes name",
7851 "some key": "some value",
7852 "some other key": "some other value"
7854 "children": [ *other nodes or empty* ]
7858 JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7859 For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7861 There are two types of *Graph* structures, *simple* and *extended* graph structures.
7863 For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7864 id of the node connected to the main node.
7871 "id": "aUniqueIdentifier",
7872 "name": "usually a nodes name",
7874 "some key": "some value",
7875 "some other key": "some other value"
7877 "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7880 'other nodes go here...'
7884 For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7886 nodeTo - (string) The other node connected by this adjacency.
7887 data - (object) A data property, where we can store custom key/value information.
7894 "id": "aUniqueIdentifier",
7895 "name": "usually a nodes name",
7897 "some key": "some value",
7898 "some other key": "some other value"
7903 data: {} //put whatever you want here
7905 'other adjacencies go here...'
7908 'other nodes go here...'
7912 About the data property:
7914 As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
7915 You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
7916 have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7918 For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
7919 <Options.Node> will override the general value for that option with that particular value. For this to work
7920 however, you do have to set *overridable = true* in <Options.Node>.
7922 The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
7923 if <Options.Edge> has *overridable = true*.
7925 When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
7926 since this is the value which will be taken into account when creating the layout.
7927 The same thing goes for the *$color* parameter.
7929 In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
7930 *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
7931 canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
7932 to the *shadowBlur* property.
7934 These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
7935 by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7937 Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
7938 information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7940 loadJSON Parameters:
7942 json - A JSON Tree or Graph structure.
7943 i - For Graph structures only. Sets the indexed node as root for the visualization.
7946 loadJSON: function(json, i) {
7948 //if they're canvas labels erase them.
7949 if(this.labels && this.labels.clearLabels) {
7950 this.labels.clearLabels(true);
7952 this.graph = this.construct(json);
7953 if($.type(json) != 'array'){
7954 this.root = json.id;
7956 this.root = json[i? i : 0].id;
7963 Returns a JSON tree/graph structure from the visualization's <Graph>.
7964 See <Loader.loadJSON> for the graph formats available.
7972 type - (string) Default's "tree". The type of the JSON structure to be returned.
7973 Possible options are "tree" or "graph".
7975 toJSON: function(type) {
7976 type = type || "tree";
7977 if(type == 'tree') {
7979 var rootNode = this.graph.getNode(this.root);
7980 var ans = (function recTree(node) {
7983 ans.name = node.name;
7984 ans.data = node.data;
7986 node.eachSubnode(function(n) {
7987 ch.push(recTree(n));
7995 var T = !!this.graph.getNode(this.root).visited;
7996 this.graph.eachNode(function(node) {
7998 ansNode.id = node.id;
7999 ansNode.name = node.name;
8000 ansNode.data = node.data;
8002 node.eachAdjacency(function(adj) {
8003 var nodeTo = adj.nodeTo;
8004 if(!!nodeTo.visited === T) {
8006 ansAdj.nodeTo = nodeTo.id;
8007 ansAdj.data = adj.data;
8011 ansNode.adjacencies = adjs;
8025 * Implements base Tree and Graph layouts.
8029 * Implements base Tree and Graph layouts like Radial, Tree, etc.
8036 * Parent object for common layouts.
8039 var Layouts = $jit.Layouts = {};
8042 //Some util shared layout functions are defined here.
8046 compute: function(graph, prop, opt) {
8047 this.initializeLabel(opt);
8048 var label = this.label, style = label.style;
8049 graph.eachNode(function(n) {
8050 var autoWidth = n.getData('autoWidth'),
8051 autoHeight = n.getData('autoHeight');
8052 if(autoWidth || autoHeight) {
8053 //delete dimensions since these are
8054 //going to be overridden now.
8055 delete n.data.$width;
8056 delete n.data.$height;
8059 var width = n.getData('width'),
8060 height = n.getData('height');
8061 //reset label dimensions
8062 style.width = autoWidth? 'auto' : width + 'px';
8063 style.height = autoHeight? 'auto' : height + 'px';
8065 //TODO(nico) should let the user choose what to insert here.
8066 label.innerHTML = n.name;
8068 var offsetWidth = label.offsetWidth,
8069 offsetHeight = label.offsetHeight;
8070 var type = n.getData('type');
8071 if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8072 n.setData('width', offsetWidth);
8073 n.setData('height', offsetHeight);
8075 var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8076 n.setData('width', dim);
8077 n.setData('height', dim);
8078 n.setData('dim', dim);
8084 initializeLabel: function(opt) {
8086 this.label = document.createElement('div');
8087 document.body.appendChild(this.label);
8089 this.setLabelStyles(opt);
8092 setLabelStyles: function(opt) {
8093 $.extend(this.label.style, {
8094 'visibility': 'hidden',
8095 'position': 'absolute',
8099 this.label.className = 'jit-autoadjust-label';
8105 * Class: Layouts.Tree
8107 * Implements a Tree Layout.
8115 * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8118 Layouts.Tree = (function() {
8120 var slice = Array.prototype.slice;
8123 Calculates the max width and height nodes for a tree level
8125 function getBoundaries(graph, config, level, orn, prop) {
8126 var dim = config.Node;
8127 var multitree = config.multitree;
8128 if (dim.overridable) {
8130 graph.eachNode(function(n) {
8131 if (n._depth == level
8132 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8133 var dw = n.getData('width', prop);
8134 var dh = n.getData('height', prop);
8135 w = (w < dw) ? dw : w;
8136 h = (h < dh) ? dh : h;
8140 'width' : w < 0 ? dim.width : w,
8141 'height' : h < 0 ? dim.height : h
8149 function movetree(node, prop, val, orn) {
8150 var p = (orn == "left" || orn == "right") ? "y" : "x";
8151 node.getPos(prop)[p] += val;
8155 function moveextent(extent, val) {
8157 $.each(extent, function(elem) {
8158 elem = slice.call(elem);
8167 function merge(ps, qs) {
8172 var p = ps.shift(), q = qs.shift();
8173 return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8177 function mergelist(ls, def) {
8182 return mergelist(ls, merge(ps, def));
8186 function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8187 if (ext1.length <= i || ext2.length <= i)
8190 var p = ext1[i][1], q = ext2[i][0];
8191 return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8192 + subtreeOffset, p - q + siblingOffset);
8196 function fitlistl(es, subtreeOffset, siblingOffset) {
8197 function $fitlistl(acc, es, i) {
8200 var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8201 return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8204 return $fitlistl( [], es, 0);
8208 function fitlistr(es, subtreeOffset, siblingOffset) {
8209 function $fitlistr(acc, es, i) {
8212 var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8213 return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8216 es = slice.call(es);
8217 var ans = $fitlistr( [], es.reverse(), 0);
8218 return ans.reverse();
8222 function fitlist(es, subtreeOffset, siblingOffset, align) {
8223 var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8224 subtreeOffset, siblingOffset);
8226 if (align == "left")
8228 else if (align == "right")
8231 for ( var i = 0, ans = []; i < esl.length; i++) {
8232 ans[i] = (esl[i] + esr[i]) / 2;
8238 function design(graph, node, prop, config, orn) {
8239 var multitree = config.multitree;
8240 var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8241 var ind = +(orn == "left" || orn == "right");
8242 var p = auxp[ind], notp = auxp[1 - ind];
8244 var cnode = config.Node;
8245 var s = auxs[ind], nots = auxs[1 - ind];
8247 var siblingOffset = config.siblingOffset;
8248 var subtreeOffset = config.subtreeOffset;
8249 var align = config.align;
8251 function $design(node, maxsize, acum) {
8252 var sval = node.getData(s, prop);
8253 var notsval = maxsize
8254 || (node.getData(nots, prop));
8256 var trees = [], extents = [], chmaxsize = false;
8257 var chacum = notsval + config.levelDistance;
8258 node.eachSubnode(function(n) {
8260 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8263 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8265 var s = $design(n, chmaxsize[nots], acum + chacum);
8267 extents.push(s.extent);
8270 var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8271 for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8272 movetree(trees[i], prop, positions[i], orn);
8273 pextents.push(moveextent(extents[i], positions[i]));
8275 var resultextent = [ [ -sval / 2, sval / 2 ] ]
8276 .concat(mergelist(pextents));
8277 node.getPos(prop)[p] = 0;
8279 if (orn == "top" || orn == "left") {
8280 node.getPos(prop)[notp] = acum;
8282 node.getPos(prop)[notp] = -acum;
8287 extent : resultextent
8291 $design(node, false, 0);
8299 Computes nodes' positions.
8302 compute : function(property, computeLevels) {
8303 var prop = property || 'start';
8304 var node = this.graph.getNode(this.root);
8310 NodeDim.compute(this.graph, prop, this.config);
8311 if (!!computeLevels || !("_depth" in node)) {
8312 this.graph.computeLevels(this.root, 0, "ignore");
8315 this.computePositions(node, prop);
8318 computePositions : function(node, prop) {
8319 var config = this.config;
8320 var multitree = config.multitree;
8321 var align = config.align;
8322 var indent = align !== 'center' && config.indent;
8323 var orn = config.orientation;
8324 var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8326 $.each(orns, function(orn) {
8328 design(that.graph, node, prop, that.config, orn, prop);
8329 var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8331 (function red(node) {
8332 node.eachSubnode(function(n) {
8334 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8336 n.getPos(prop)[i] += node.getPos(prop)[i];
8338 n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8351 * File: Spacetree.js
8357 A Tree layout with advanced contraction and expansion animations.
8361 SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8362 <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8364 Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8368 This visualization was built and engineered from scratch, taking only the papers as inspiration, and only shares some features with the visualization described in those papers.
8372 All <Loader> methods
8374 Constructor Options:
8376 Inherits options from
8379 - <Options.Controller>
8386 - <Options.NodeStyles>
8387 - <Options.Navigation>
8389 Additionally, there are other parameters and some default values changed
8391 constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8392 levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8393 levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8394 Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8395 offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8396 offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8397 duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8399 Instance Properties:
8401 canvas - Access a <Canvas> instance.
8402 graph - Access a <Graph> instance.
8403 op - Access a <ST.Op> instance.
8404 fx - Access a <ST.Plot> instance.
8405 labels - Access a <ST.Label> interface implementation.
8409 $jit.ST= (function() {
8410 // Define some private methods first...
8412 var nodesInPath = [];
8413 // Nodes to contract
8414 function getNodesToHide(node) {
8415 node = node || this.clickedNode;
8416 if(!this.config.constrained) {
8419 var Geom = this.geom;
8420 var graph = this.graph;
8421 var canvas = this.canvas;
8422 var level = node._depth, nodeArray = [];
8423 graph.eachNode(function(n) {
8424 if(n.exist && !n.selected) {
8425 if(n.isDescendantOf(node.id)) {
8426 if(n._depth <= level) nodeArray.push(n);
8432 var leafLevel = Geom.getRightLevelToShow(node, canvas);
8433 node.eachLevel(leafLevel, leafLevel, function(n) {
8434 if(n.exist && !n.selected) nodeArray.push(n);
8437 for (var i = 0; i < nodesInPath.length; i++) {
8438 var n = this.graph.getNode(nodesInPath[i]);
8439 if(!n.isDescendantOf(node.id)) {
8446 function getNodesToShow(node) {
8447 var nodeArray = [], config = this.config;
8448 node = node || this.clickedNode;
8449 this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8450 if(config.multitree && !('$orn' in n.data)
8451 && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8453 } else if(n.drawn && !n.anySubnode("drawn")) {
8459 // Now define the actual class.
8462 Implements: [Loader, Extras, Layouts.Tree],
8464 initialize: function(controller) {
8479 this.controller = this.config = $.merge(
8480 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8481 "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8483 var canvasConfig = this.config;
8484 if(canvasConfig.useCanvas) {
8485 this.canvas = canvasConfig.useCanvas;
8486 this.config.labelContainer = this.canvas.id + '-label';
8488 if(canvasConfig.background) {
8489 canvasConfig.background = $.merge({
8491 colorStop1: this.config.colorStop1,
8492 colorStop2: this.config.colorStop2
8493 }, canvasConfig.background);
8495 this.canvas = new Canvas(this, canvasConfig);
8496 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8499 this.graphOptions = {
8502 this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8503 this.labels = new $ST.Label[canvasConfig.Label.type](this);
8504 this.fx = new $ST.Plot(this, $ST);
8505 this.op = new $ST.Op(this);
8506 this.group = new $ST.Group(this);
8507 this.geom = new $ST.Geom(this);
8508 this.clickedNode= null;
8509 // initialize extras
8510 this.initializeExtras();
8516 Plots the <ST>. This is a shortcut to *fx.plot*.
8519 plot: function() { this.fx.plot(this.controller); },
8523 Method: switchPosition
8525 Switches the tree orientation.
8529 pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8530 method - (string) Set this to "animate" if you want to animate the tree when switching its position. You can also set this parameter to "replot" to just replot the subtree.
8531 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8536 st.switchPosition("right", "animate", {
8537 onComplete: function() {
8538 alert('completed!');
8543 switchPosition: function(pos, method, onComplete) {
8544 var Geom = this.geom, Plot = this.fx, that = this;
8548 onComplete: function() {
8549 Geom.switchOrientation(pos);
8550 that.compute('end', false);
8552 if(method == 'animate') {
8553 that.onClick(that.clickedNode.id, onComplete);
8554 } else if(method == 'replot') {
8555 that.select(that.clickedNode.id, onComplete);
8563 Method: switchAlignment
8565 Switches the tree alignment.
8569 align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8570 method - (string) Set this to "animate" if you want to animate the tree after aligning its position. You can also set this parameter to "replot" to just replot the subtree.
8571 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8576 st.switchAlignment("right", "animate", {
8577 onComplete: function() {
8578 alert('completed!');
8583 switchAlignment: function(align, method, onComplete) {
8584 this.config.align = align;
8585 if(method == 'animate') {
8586 this.select(this.clickedNode.id, onComplete);
8587 } else if(method == 'replot') {
8588 this.onClick(this.clickedNode.id, onComplete);
8593 Method: addNodeInPath
8595 Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8600 id - (string) A <Graph.Node> id.
8605 st.addNodeInPath("nodeId");
8608 addNodeInPath: function(id) {
8609 nodesInPath.push(id);
8610 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8614 Method: clearNodesInPath
8616 Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8625 st.clearNodesInPath();
8628 clearNodesInPath: function(id) {
8629 nodesInPath.length = 0;
8630 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8636 Computes positions and plots the tree.
8639 refresh: function() {
8641 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8644 reposition: function() {
8645 this.graph.computeLevels(this.root, 0, "ignore");
8646 this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8647 this.graph.eachNode(function(n) {
8648 if(n.exist) n.drawn = true;
8650 this.compute('end');
8653 requestNodes: function(node, onComplete) {
8654 var handler = $.merge(this.controller, onComplete),
8655 lev = this.config.levelsToShow;
8656 if(handler.request) {
8657 var leaves = [], d = node._depth;
8658 node.eachLevel(0, lev, function(n) {
8662 n._level = lev - (n._depth - d);
8665 this.group.requestNodes(leaves, handler);
8668 handler.onComplete();
8671 contract: function(onComplete, switched) {
8672 var orn = this.config.orientation;
8673 var Geom = this.geom, Group = this.group;
8674 if(switched) Geom.switchOrientation(switched);
8675 var nodes = getNodesToHide.call(this);
8676 if(switched) Geom.switchOrientation(orn);
8677 Group.contract(nodes, $.merge(this.controller, onComplete));
8680 move: function(node, onComplete) {
8681 this.compute('end', false);
8682 var move = onComplete.Move, offset = {
8687 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8689 this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8692 expand: function (node, onComplete) {
8693 var nodeArray = getNodesToShow.call(this, node);
8694 this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8697 selectPath: function(node) {
8699 this.graph.eachNode(function(n) { n.selected = false; });
8700 function path(node) {
8701 if(node == null || node.selected) return;
8702 node.selected = true;
8703 $.each(that.group.getSiblings([node])[node.id],
8708 var parents = node.getParents();
8709 parents = (parents.length > 0)? parents[0] : null;
8712 for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8713 path(this.graph.getNode(ns[i]));
8720 Switches the current root node. Changes the topology of the Tree.
8723 id - (string) The id of the node to be set as root.
8724 method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8725 onComplete - (optional|object) An action to perform after the animation (if any).
8730 st.setRoot('nodeId', 'animate', {
8731 onComplete: function() {
8737 setRoot: function(id, method, onComplete) {
8738 if(this.busy) return;
8740 var that = this, canvas = this.canvas;
8741 var rootNode = this.graph.getNode(this.root);
8742 var clickedNode = this.graph.getNode(id);
8743 function $setRoot() {
8744 if(this.config.multitree && clickedNode.data.$orn) {
8745 var orn = clickedNode.data.$orn;
8752 rootNode.data.$orn = opp;
8753 (function tag(rootNode) {
8754 rootNode.eachSubnode(function(n) {
8761 delete clickedNode.data.$orn;
8764 this.clickedNode = clickedNode;
8765 this.graph.computeLevels(this.root, 0, "ignore");
8766 this.geom.setRightLevelToShow(clickedNode, canvas, {
8768 onShow: function(node) {
8771 node.setData('alpha', 1, 'end');
8772 node.setData('alpha', 0);
8773 node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8777 this.compute('end');
8780 modes: ['linear', 'node-property:alpha'],
8781 onComplete: function() {
8784 onComplete: function() {
8785 onComplete && onComplete.onComplete();
8792 // delete previous orientations (if any)
8793 delete rootNode.data.$orns;
8795 if(method == 'animate') {
8796 $setRoot.call(this);
8797 that.selectPath(clickedNode);
8798 } else if(method == 'replot') {
8799 $setRoot.call(this);
8800 this.select(this.root);
8810 subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8811 method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8812 onComplete - (optional|object) An action to perform after the animation (if any).
8817 st.addSubtree(json, 'animate', {
8818 onComplete: function() {
8824 addSubtree: function(subtree, method, onComplete) {
8825 if(method == 'replot') {
8826 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8827 } else if (method == 'animate') {
8828 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8833 Method: removeSubtree
8838 id - (string) The _id_ of the subtree to be removed.
8839 removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8840 method - (string) Set this to "animate" if you want to animate the tree after removing the subtree. You can also set this parameter to "replot" to just replot the subtree.
8841 onComplete - (optional|object) An action to perform after the animation (if any).
8846 st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8847 onComplete: function() {
8854 removeSubtree: function(id, removeRoot, method, onComplete) {
8855 var node = this.graph.getNode(id), subids = [];
8856 node.eachLevel(+!removeRoot, false, function(n) {
8859 if(method == 'replot') {
8860 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8861 } else if (method == 'animate') {
8862 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8869 Selects a node in the <ST> without performing an animation. Useful when selecting
8870 nodes which are currently hidden or deep inside the tree.
8873 id - (string) The id of the node to select.
8874 onComplete - (optional|object) an onComplete callback.
8878 st.select('mynodeid', {
8879 onComplete: function() {
8885 select: function(id, onComplete) {
8886 var group = this.group, geom = this.geom;
8887 var node= this.graph.getNode(id), canvas = this.canvas;
8888 var root = this.graph.getNode(this.root);
8889 var complete = $.merge(this.controller, onComplete);
8892 complete.onBeforeCompute(node);
8893 this.selectPath(node);
8894 this.clickedNode= node;
8895 this.requestNodes(node, {
8896 onComplete: function(){
8897 group.hide(group.prepare(getNodesToHide.call(that)), complete);
8898 geom.setRightLevelToShow(node, canvas);
8899 that.compute("current");
8900 that.graph.eachNode(function(n) {
8901 var pos = n.pos.getc(true);
8902 n.startPos.setc(pos.x, pos.y);
8903 n.endPos.setc(pos.x, pos.y);
8906 var offset = { x: complete.offsetX, y: complete.offsetY };
8907 that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8908 group.show(getNodesToShow.call(that));
8910 complete.onAfterCompute(that.clickedNode);
8911 complete.onComplete();
8919 Animates the <ST> to center the node specified by *id*.
8923 id - (string) A node id.
8924 options - (optional|object) A group of options and callbacks described below.
8925 onComplete - (object) An object callback called when the animation finishes.
8926 Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8931 st.onClick('mynodeid', {
8937 onComplete: function() {
8944 onClick: function (id, options) {
8945 var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8946 var innerController = {
8949 offsetX: config.offsetX || 0,
8950 offsetY: config.offsetY || 0
8952 setRightLevelToShowConfig: false,
8953 onBeforeRequest: $.empty,
8954 onBeforeContract: $.empty,
8955 onBeforeMove: $.empty,
8956 onBeforeExpand: $.empty
8958 var complete = $.merge(this.controller, innerController, options);
8962 var node = this.graph.getNode(id);
8963 this.selectPath(node, this.clickedNode);
8964 this.clickedNode = node;
8965 complete.onBeforeCompute(node);
8966 complete.onBeforeRequest(node);
8967 this.requestNodes(node, {
8968 onComplete: function() {
8969 complete.onBeforeContract(node);
8971 onComplete: function() {
8972 Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8973 complete.onBeforeMove(node);
8975 Move: complete.Move,
8976 onComplete: function() {
8977 complete.onBeforeExpand(node);
8979 onComplete: function() {
8981 complete.onAfterCompute(id);
8982 complete.onComplete();
8997 $jit.ST.$extend = true;
9002 Custom extension of <Graph.Op>.
9006 All <Graph.Op> methods
9013 $jit.ST.Op = new Class({
9015 Implements: Graph.Op
9021 Performs operations on group of nodes.
9024 $jit.ST.Group = new Class({
9026 initialize: function(viz) {
9028 this.canvas = viz.canvas;
9029 this.config = viz.config;
9030 this.animation = new Animation;
9036 Calls the request method on the controller to request a subtree for each node.
9038 requestNodes: function(nodes, controller) {
9039 var counter = 0, len = nodes.length, nodeSelected = {};
9040 var complete = function() { controller.onComplete(); };
9042 if(len == 0) complete();
9043 for(var i=0; i<len; i++) {
9044 nodeSelected[nodes[i].id] = nodes[i];
9045 controller.request(nodes[i].id, nodes[i]._level, {
9046 onComplete: function(nodeId, data) {
9047 if(data && data.children) {
9049 viz.op.sum(data, { type: 'nothing' });
9051 if(++counter == len) {
9052 viz.graph.computeLevels(viz.root, 0);
9062 Collapses group of nodes.
9064 contract: function(nodes, controller) {
9068 nodes = this.prepare(nodes);
9069 this.animation.setOptions($.merge(controller, {
9071 compute: function(delta) {
9072 if(delta == 1) delta = 0.99;
9073 that.plotStep(1 - delta, controller, this.$animating);
9074 this.$animating = 'contract';
9077 complete: function() {
9078 that.hide(nodes, controller);
9083 hide: function(nodes, controller) {
9085 for(var i=0; i<nodes.length; i++) {
9086 // TODO nodes are requested on demand, but not
9087 // deleted when hidden. Would that be a good feature?
9088 // Currently that feature is buggy, so I'll turn it off
9089 // Actually this feature is buggy because trimming should take
9090 // place onAfterCompute and not right after collapsing nodes.
9091 if (true || !controller || !controller.request) {
9092 nodes[i].eachLevel(1, false, function(elem){
9102 nodes[i].eachLevel(1, false, function(n) {
9105 viz.op.removeNode(ids, { 'type': 'nothing' });
9106 viz.labels.clearLabels();
9109 controller.onComplete();
9114 Expands group of nodes.
9116 expand: function(nodes, controller) {
9119 this.animation.setOptions($.merge(controller, {
9121 compute: function(delta) {
9122 that.plotStep(delta, controller, this.$animating);
9123 this.$animating = 'expand';
9126 complete: function() {
9127 that.plotStep(undefined, controller, false);
9128 controller.onComplete();
9134 show: function(nodes) {
9135 var config = this.config;
9136 this.prepare(nodes);
9137 $.each(nodes, function(n) {
9138 // check for root nodes if multitree
9139 if(config.multitree && !('$orn' in n.data)) {
9140 delete n.data.$orns;
9142 n.eachSubnode(function(ch) {
9143 if(('$orn' in ch.data)
9144 && orns.indexOf(ch.data.$orn) < 0
9145 && ch.exist && !ch.drawn) {
9146 orns += ch.data.$orn + ' ';
9149 n.data.$orns = orns;
9151 n.eachLevel(0, config.levelsToShow, function(n) {
9152 if(n.exist) n.drawn = true;
9157 prepare: function(nodes) {
9158 this.nodes = this.getNodesWithChildren(nodes);
9163 Filters an array of nodes leaving only nodes with children.
9165 getNodesWithChildren: function(nodes) {
9166 var ans = [], config = this.config, root = this.viz.root;
9167 nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9168 for(var i=0; i<nodes.length; i++) {
9169 if(nodes[i].anySubnode("exist")) {
9170 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9171 if(!config.multitree || '$orn' in nodes[j].data) {
9172 desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9175 if(!desc) ans.push(nodes[i]);
9181 plotStep: function(delta, controller, animating) {
9183 config = this.config,
9184 canvas = viz.canvas,
9185 ctx = canvas.getCtx(),
9188 // hide nodes that are meant to be collapsed/expanded
9190 for(i=0; i<nodes.length; i++) {
9193 var root = config.multitree && !('$orn' in node.data);
9194 var orns = root && node.data.$orns;
9195 node.eachSubgraph(function(n) {
9196 // TODO(nico): Cleanup
9197 // special check for root node subnodes when
9198 // multitree is checked.
9199 if(root && orns && orns.indexOf(n.data.$orn) > 0
9202 nds[node.id].push(n);
9203 } else if((!root || !orns) && n.drawn) {
9205 nds[node.id].push(n);
9210 // plot the whole (non-scaled) tree
9211 if(nodes.length > 0) viz.fx.plot();
9212 // show nodes that were previously hidden
9214 $.each(nds[i], function(n) { n.drawn = true; });
9216 // plot each scaled subtree
9217 for(i=0; i<nodes.length; i++) {
9220 viz.fx.plotSubtree(node, controller, delta, animating);
9225 getSiblings: function(nodes) {
9227 $.each(nodes, function(n) {
9228 var par = n.getParents();
9229 if (par.length == 0) {
9230 siblings[n.id] = [n];
9233 par[0].eachSubnode(function(sn) {
9236 siblings[n.id] = ans;
9246 Performs low level geometrical computations.
9250 This instance can be accessed with the _geom_ parameter of the st instance created.
9255 var st = new ST(canvas, config);
9256 st.geom.translate //or can also call any other <ST.Geom> method
9261 $jit.ST.Geom = new Class({
9262 Implements: Graph.Geom,
9264 Changes the tree current orientation to the one specified.
9266 You should usually use <ST.switchPosition> instead.
9268 switchOrientation: function(orn) {
9269 this.config.orientation = orn;
9273 Makes a value dispatch according to the current layout
9274 Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9276 dispatch: function() {
9277 // TODO(nico) should store Array.prototype.slice.call somewhere.
9278 var args = Array.prototype.slice.call(arguments);
9279 var s = args.shift(), len = args.length;
9280 var val = function(a) { return typeof a == 'function'? a() : a; };
9282 return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9283 } else if(len == 4) {
9285 case "top": return val(args[0]);
9286 case "right": return val(args[1]);
9287 case "bottom": return val(args[2]);
9288 case "left": return val(args[3]);
9295 Returns label height or with, depending on the tree current orientation.
9297 getSize: function(n, invert) {
9298 var data = n.data, config = this.config;
9299 var siblingOffset = config.siblingOffset;
9300 var s = (config.multitree
9302 && data.$orn) || config.orientation;
9303 var w = n.getData('width') + siblingOffset;
9304 var h = n.getData('height') + siblingOffset;
9306 return this.dispatch(s, h, w);
9308 return this.dispatch(s, w, h);
9312 Calculates a subtree base size. This is an utility function used by _getBaseSize_
9314 getTreeBaseSize: function(node, level, leaf) {
9315 var size = this.getSize(node, true), baseHeight = 0, that = this;
9316 if(leaf(level, node)) return size;
9317 if(level === 0) return 0;
9318 node.eachSubnode(function(elem) {
9319 baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9321 return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9328 Returns a Complex instance with the begin or end position of the edge to be plotted.
9332 node - A <Graph.Node> that is connected to this edge.
9333 type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9337 A <Complex> number specifying the begin or end position.
9339 getEdge: function(node, type, s) {
9340 var $C = function(a, b) {
9342 return node.pos.add(new Complex(a, b));
9345 var dim = this.node;
9346 var w = node.getData('width');
9347 var h = node.getData('height');
9349 if(type == 'begin') {
9350 if(dim.align == "center") {
9351 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9352 $C(0, -h/2),$C(w/2, 0));
9353 } else if(dim.align == "left") {
9354 return this.dispatch(s, $C(0, h), $C(0, 0),
9355 $C(0, 0), $C(w, 0));
9356 } else if(dim.align == "right") {
9357 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9358 $C(0, -h),$C(0, 0));
9359 } else throw "align: not implemented";
9362 } else if(type == 'end') {
9363 if(dim.align == "center") {
9364 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9365 $C(0, h/2), $C(-w/2, 0));
9366 } else if(dim.align == "left") {
9367 return this.dispatch(s, $C(0, 0), $C(w, 0),
9368 $C(0, h), $C(0, 0));
9369 } else if(dim.align == "right") {
9370 return this.dispatch(s, $C(0, -h),$C(0, 0),
9371 $C(0, 0), $C(-w, 0));
9372 } else throw "align: not implemented";
9377 Adjusts the tree position due to canvas scaling or translation.
9379 getScaledTreePosition: function(node, scale) {
9380 var dim = this.node;
9381 var w = node.getData('width');
9382 var h = node.getData('height');
9383 var s = (this.config.multitree
9384 && ('$orn' in node.data)
9385 && node.data.$orn) || this.config.orientation;
9387 var $C = function(a, b) {
9389 return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9392 if(dim.align == "left") {
9393 return this.dispatch(s, $C(0, h), $C(0, 0),
9394 $C(0, 0), $C(w, 0));
9395 } else if(dim.align == "center") {
9396 return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9397 $C(0, -h / 2),$C(w / 2, 0));
9398 } else if(dim.align == "right") {
9399 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9400 $C(0, -h),$C(0, 0));
9401 } else throw "align: not implemented";
9407 Returns a Boolean if the current subtree fits in canvas.
9411 node - A <Graph.Node> which is the current root of the subtree.
9412 canvas - The <Canvas> object.
9413 level - The depth of the subtree to be considered.
9415 treeFitsInCanvas: function(node, canvas, level) {
9416 var csize = canvas.getSize();
9417 var s = (this.config.multitree
9418 && ('$orn' in node.data)
9419 && node.data.$orn) || this.config.orientation;
9421 var size = this.dispatch(s, csize.width, csize.height);
9422 var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9423 return level === 0 || !node.anySubnode();
9425 return (baseSize < size);
9432 Custom extension of <Graph.Plot>.
9436 All <Graph.Plot> methods
9443 $jit.ST.Plot = new Class({
9445 Implements: Graph.Plot,
9448 Plots a subtree from the spacetree.
9450 plotSubtree: function(node, opt, scale, animating) {
9451 var viz = this.viz, canvas = viz.canvas, config = viz.config;
9452 scale = Math.min(Math.max(0.001, scale), 1);
9455 var ctx = canvas.getCtx();
9456 var diff = viz.geom.getScaledTreePosition(node, scale);
9457 ctx.translate(diff.x, diff.y);
9458 ctx.scale(scale, scale);
9460 this.plotTree(node, $.merge(opt, {
9462 'hideLabels': !!scale,
9463 'plotSubtree': function(n, ch) {
9464 var root = config.multitree && !('$orn' in node.data);
9465 var orns = root && node.getData('orns');
9466 return !root || orns.indexOf(elem.getData('orn')) > -1;
9469 if(scale >= 0) node.drawn = true;
9473 Method: getAlignedPos
9475 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9479 pos - (object) A <Graph.Node> position.
9480 width - (number) The width of the node.
9481 height - (number) The height of the node.
9484 getAlignedPos: function(pos, width, height) {
9485 var nconfig = this.node;
9487 if(nconfig.align == "center") {
9489 x: pos.x - width / 2,
9490 y: pos.y - height / 2
9492 } else if (nconfig.align == "left") {
9493 orn = this.config.orientation;
9494 if(orn == "bottom" || orn == "top") {
9496 x: pos.x - width / 2,
9502 y: pos.y - height / 2
9505 } else if(nconfig.align == "right") {
9506 orn = this.config.orientation;
9507 if(orn == "bottom" || orn == "top") {
9509 x: pos.x - width / 2,
9515 y: pos.y - height / 2
9518 } else throw "align: not implemented";
9523 getOrientation: function(adj) {
9524 var config = this.config;
9525 var orn = config.orientation;
9527 if(config.multitree) {
9528 var nodeFrom = adj.nodeFrom;
9529 var nodeTo = adj.nodeTo;
9530 orn = (('$orn' in nodeFrom.data)
9531 && nodeFrom.data.$orn)
9532 || (('$orn' in nodeTo.data)
9533 && nodeTo.data.$orn);
9543 Custom extension of <Graph.Label>.
9544 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9548 All <Graph.Label> methods and subclasses.
9552 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9559 Custom extension of <Graph.Label.Native>.
9563 All <Graph.Label.Native> methods
9567 <Graph.Label.Native>
9569 $jit.ST.Label.Native = new Class({
9570 Implements: Graph.Label.Native,
9572 renderLabel: function(canvas, node, controller) {
9573 var ctx = canvas.getCtx();
9574 var coord = node.pos.getc(true);
9575 ctx.fillText(node.name, coord.x, coord.y);
9579 $jit.ST.Label.DOM = new Class({
9580 Implements: Graph.Label.DOM,
9585 Overrides abstract method placeLabel in <Graph.Plot>.
9589 tag - A DOM label element.
9590 node - A <Graph.Node>.
9591 controller - A configuration/controller object passed to the visualization.
9594 placeLabel: function(tag, node, controller) {
9595 var pos = node.pos.getc(true),
9596 config = this.viz.config,
9598 canvas = this.viz.canvas,
9599 w = node.getData('width'),
9600 h = node.getData('height'),
9601 radius = canvas.getSize(),
9604 var ox = canvas.translateOffsetX,
9605 oy = canvas.translateOffsetY,
9606 sx = canvas.scaleOffsetX,
9607 sy = canvas.scaleOffsetY,
9608 posx = pos.x * sx + ox,
9609 posy = pos.y * sy + oy;
9611 if(dim.align == "center") {
9613 x: Math.round(posx - w / 2 + radius.width/2),
9614 y: Math.round(posy - h / 2 + radius.height/2)
9616 } else if (dim.align == "left") {
9617 orn = config.orientation;
9618 if(orn == "bottom" || orn == "top") {
9620 x: Math.round(posx - w / 2 + radius.width/2),
9621 y: Math.round(posy + radius.height/2)
9625 x: Math.round(posx + radius.width/2),
9626 y: Math.round(posy - h / 2 + radius.height/2)
9629 } else if(dim.align == "right") {
9630 orn = config.orientation;
9631 if(orn == "bottom" || orn == "top") {
9633 x: Math.round(posx - w / 2 + radius.width/2),
9634 y: Math.round(posy - h + radius.height/2)
9638 x: Math.round(posx - w + radius.width/2),
9639 y: Math.round(posy - h / 2 + radius.height/2)
9642 } else throw "align: not implemented";
9644 var style = tag.style;
9645 style.left = labelPos.x + 'px';
9646 style.top = labelPos.y + 'px';
9647 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9648 controller.onPlaceLabel(tag, node);
9655 Custom extension of <Graph.Label.SVG>.
9659 All <Graph.Label.SVG> methods
9665 $jit.ST.Label.SVG = new Class({
9666 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9668 initialize: function(viz) {
9676 Custom extension of <Graph.Label.HTML>.
9680 All <Graph.Label.HTML> methods.
9687 $jit.ST.Label.HTML = new Class({
9688 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9690 initialize: function(viz) {
9697 Class: ST.Plot.NodeTypes
9699 This class contains a list of <Graph.Node> built-in types.
9700 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9702 You can add your custom node types, customizing your visualization to the extreme.
9707 ST.Plot.NodeTypes.implement({
9709 'render': function(node, canvas) {
9710 //print your custom node to canvas
9713 'contains': function(node, pos) {
9714 //return true if pos is inside the node or false otherwise
9721 $jit.ST.Plot.NodeTypes = new Class({
9724 'contains': $.lambda(false)
9727 'render': function(node, canvas) {
9728 var dim = node.getData('dim'),
9729 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9731 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9733 'contains': function(node, pos) {
9734 var dim = node.getData('dim'),
9735 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9737 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9741 'render': function(node, canvas) {
9742 var dim = node.getData('dim'),
9744 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9745 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9747 'contains': function(node, pos) {
9748 var dim = node.getData('dim'),
9749 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9751 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9755 'render': function(node, canvas) {
9756 var width = node.getData('width'),
9757 height = node.getData('height'),
9758 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9759 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9761 'contains': function(node, pos) {
9762 var width = node.getData('width'),
9763 height = node.getData('height'),
9764 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9765 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9769 'render': function(node, canvas) {
9770 var width = node.getData('width'),
9771 height = node.getData('height'),
9772 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9773 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9775 'contains': function(node, pos) {
9776 var width = node.getData('width'),
9777 height = node.getData('height'),
9778 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9779 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9785 Class: ST.Plot.EdgeTypes
9787 This class contains a list of <Graph.Adjacence> built-in types.
9788 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9790 You can add your custom edge types, customizing your visualization to the extreme.
9795 ST.Plot.EdgeTypes.implement({
9797 'render': function(adj, canvas) {
9798 //print your custom edge to canvas
9801 'contains': function(adj, pos) {
9802 //return true if pos is inside the arc or false otherwise
9809 $jit.ST.Plot.EdgeTypes = new Class({
9812 'render': function(adj, canvas) {
9813 var orn = this.getOrientation(adj),
9814 nodeFrom = adj.nodeFrom,
9815 nodeTo = adj.nodeTo,
9816 rel = nodeFrom._depth < nodeTo._depth,
9817 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9818 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9819 this.edgeHelper.line.render(from, to, canvas);
9821 'contains': function(adj, pos) {
9822 var orn = this.getOrientation(adj),
9823 nodeFrom = adj.nodeFrom,
9824 nodeTo = adj.nodeTo,
9825 rel = nodeFrom._depth < nodeTo._depth,
9826 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9827 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9828 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9832 'render': function(adj, canvas) {
9833 var orn = this.getOrientation(adj),
9834 node = adj.nodeFrom,
9836 dim = adj.getData('dim'),
9837 from = this.viz.geom.getEdge(node, 'begin', orn),
9838 to = this.viz.geom.getEdge(child, 'end', orn),
9839 direction = adj.data.$direction,
9840 inv = (direction && direction.length>1 && direction[0] != node.id);
9841 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9843 'contains': function(adj, pos) {
9844 var orn = this.getOrientation(adj),
9845 nodeFrom = adj.nodeFrom,
9846 nodeTo = adj.nodeTo,
9847 rel = nodeFrom._depth < nodeTo._depth,
9848 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9849 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9850 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9853 'quadratic:begin': {
9854 'render': function(adj, canvas) {
9855 var orn = this.getOrientation(adj);
9856 var nodeFrom = adj.nodeFrom,
9857 nodeTo = adj.nodeTo,
9858 rel = nodeFrom._depth < nodeTo._depth,
9859 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9860 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9861 dim = adj.getData('dim'),
9862 ctx = canvas.getCtx();
9864 ctx.moveTo(begin.x, begin.y);
9867 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9870 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9873 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9876 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9883 'render': function(adj, canvas) {
9884 var orn = this.getOrientation(adj);
9885 var nodeFrom = adj.nodeFrom,
9886 nodeTo = adj.nodeTo,
9887 rel = nodeFrom._depth < nodeTo._depth,
9888 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9889 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9890 dim = adj.getData('dim'),
9891 ctx = canvas.getCtx();
9893 ctx.moveTo(begin.x, begin.y);
9896 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9899 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9902 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9905 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9912 'render': function(adj, canvas) {
9913 var orn = this.getOrientation(adj),
9914 nodeFrom = adj.nodeFrom,
9915 nodeTo = adj.nodeTo,
9916 rel = nodeFrom._depth < nodeTo._depth,
9917 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9918 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9919 dim = adj.getData('dim'),
9920 ctx = canvas.getCtx();
9922 ctx.moveTo(begin.x, begin.y);
9925 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9928 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9931 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9934 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9943 Options.LineChart = {
9947 labelOffset: 3, // label offset
9948 type: 'basic', // gradient
9964 selectOnHover: true,
9965 showAggregates: true,
9967 filterOnClick: false,
9968 restoreOnRightClick: false
9973 * File: LineChart.js
9977 $jit.ST.Plot.NodeTypes.implement({
9978 'linechart-basic' : {
9979 'render' : function(node, canvas) {
9980 var pos = node.pos.getc(true),
9981 width = node.getData('width'),
9982 height = node.getData('height'),
9983 algnPos = this.getAlignedPos(pos, width, height),
9984 x = algnPos.x + width/2 , y = algnPos.y,
9985 stringArray = node.getData('stringArray'),
9986 lastNode = node.getData('lastNode'),
9987 dimArray = node.getData('dimArray'),
9988 valArray = node.getData('valueArray'),
9989 colorArray = node.getData('colorArray'),
9990 colorLength = colorArray.length,
9991 config = node.getData('config'),
9992 gradient = node.getData('gradient'),
9993 showLabels = config.showLabels,
9994 aggregates = config.showAggregates,
9995 label = config.Label,
9996 prev = node.getData('prev'),
9997 dataPointSize = config.dataPointSize;
9999 var ctx = canvas.getCtx(), border = node.getData('border');
10000 if (colorArray && dimArray && stringArray) {
10002 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10003 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10005 ctx.lineCap = "round";
10009 //render line segment, dimarray[i][0] is the curent datapoint, dimarrya[i][1] is the next datapoint, we need both in the current iteration to draw the line segment
10011 ctx.moveTo(x, y - dimArray[i][0]);
10012 ctx.lineTo(x + width, y - dimArray[i][1]);
10016 //render data point
10017 ctx.fillRect(x - (dataPointSize/2), y - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10021 if(label.type == 'Native' && showLabels) {
10023 ctx.fillStyle = ctx.strokeStyle = label.color;
10024 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10025 ctx.textAlign = 'center';
10026 ctx.textBaseline = 'middle';
10027 ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10033 'contains': function(node, mpos) {
10034 var pos = node.pos.getc(true),
10035 width = node.getData('width'),
10036 height = node.getData('height'),
10037 config = node.getData('config'),
10038 dataPointSize = config.dataPointSize,
10039 dataPointMidPoint = dataPointSize/2,
10040 algnPos = this.getAlignedPos(pos, width, height),
10041 x = algnPos.x + width/2, y = algnPos.y,
10042 dimArray = node.getData('dimArray');
10043 //bounding box check
10044 if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10048 for(var i=0, l=dimArray.length; i<l; i++) {
10049 var dimi = dimArray[i];
10050 var url = Url.decode(node.getData('linkArray')[i]);
10051 if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10052 var valArrayCur = node.getData('valArrayCur');
10053 var results = array_match(valArrayCur[i],valArrayCur);
10054 var matches = results[0];
10055 var indexValues = results[1];
10057 var names = new Array(),
10058 values = new Array(),
10059 percentages = new Array(),
10060 linksArr = new Array();
10061 for(var j=0, il=indexValues.length; j<il; j++) {
10062 names[j] = node.getData('stringArray')[indexValues[j]];
10063 values[j] = valArrayCur[indexValues[j]];
10064 percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10065 linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10070 'color': node.getData('colorArray')[i],
10072 'percentage': percentages,
10079 'name': node.getData('stringArray')[i],
10080 'color': node.getData('colorArray')[i],
10081 'value': node.getData('valueArray')[i][0],
10082 // 'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10083 'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10098 A visualization that displays line charts.
10100 Constructor Options:
10102 See <Options.Line>.
10105 $jit.LineChart = new Class({
10107 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10111 initialize: function(opt) {
10112 this.controller = this.config =
10113 $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10114 Label: { type: 'Native' }
10116 //set functions for showLabels and showAggregates
10117 var showLabels = this.config.showLabels,
10118 typeLabels = $.type(showLabels),
10119 showAggregates = this.config.showAggregates,
10120 typeAggregates = $.type(showAggregates);
10121 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10122 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10123 Options.Fx.clearCanvas = false;
10124 this.initializeViz();
10127 initializeViz: function() {
10128 var config = this.config,
10130 nodeType = config.type.split(":")[0],
10133 var st = new $jit.ST({
10134 injectInto: config.injectInto,
10135 orientation: "bottom",
10136 backgroundColor: config.backgroundColor,
10137 renderBackground: config.renderBackground,
10141 withLabels: config.Label.type != 'Native',
10142 useCanvas: config.useCanvas,
10144 type: config.Label.type
10148 type: 'linechart-' + nodeType,
10157 enable: config.Tips.enable,
10160 onShow: function(tip, node, contains) {
10161 var elem = contains;
10162 config.Tips.onShow(tip, elem, node);
10168 onClick: function(node, eventInfo, evt) {
10169 if(!config.filterOnClick && !config.Events.enable) return;
10170 var elem = eventInfo.getContains();
10171 if(elem) config.filterOnClick && that.filter(elem.name);
10172 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10174 onRightClick: function(node, eventInfo, evt) {
10175 if(!config.restoreOnRightClick) return;
10178 onMouseMove: function(node, eventInfo, evt) {
10179 if(!config.selectOnHover) return;
10181 var elem = eventInfo.getContains();
10182 that.select(node.id, elem.name, elem.index);
10184 that.select(false, false, false);
10188 onCreateLabel: function(domElement, node) {
10189 var labelConf = config.Label,
10190 valueArray = node.getData('valueArray'),
10191 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10192 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10193 if(node.getData('prev')) {
10195 wrapper: document.createElement('div'),
10196 aggregate: document.createElement('div'),
10197 label: document.createElement('div')
10199 var wrapper = nlbs.wrapper,
10200 label = nlbs.label,
10201 aggregate = nlbs.aggregate,
10202 wrapperStyle = wrapper.style,
10203 labelStyle = label.style,
10204 aggregateStyle = aggregate.style;
10205 //store node labels
10206 nodeLabels[node.id] = nlbs;
10208 wrapper.appendChild(label);
10209 wrapper.appendChild(aggregate);
10210 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10211 label.style.display = 'none';
10213 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10214 aggregate.style.display = 'none';
10216 wrapperStyle.position = 'relative';
10217 wrapperStyle.overflow = 'visible';
10218 wrapperStyle.fontSize = labelConf.size + 'px';
10219 wrapperStyle.fontFamily = labelConf.family;
10220 wrapperStyle.color = labelConf.color;
10221 wrapperStyle.textAlign = 'center';
10222 aggregateStyle.position = labelStyle.position = 'absolute';
10224 domElement.style.width = node.getData('width') + 'px';
10225 domElement.style.height = node.getData('height') + 'px';
10226 label.innerHTML = node.name;
10228 domElement.appendChild(wrapper);
10231 onPlaceLabel: function(domElement, node) {
10232 if(!node.getData('prev')) return;
10233 var labels = nodeLabels[node.id],
10234 wrapperStyle = labels.wrapper.style,
10235 labelStyle = labels.label.style,
10236 aggregateStyle = labels.aggregate.style,
10237 width = node.getData('width'),
10238 height = node.getData('height'),
10239 dimArray = node.getData('dimArray'),
10240 valArray = node.getData('valueArray'),
10241 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10242 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10243 font = parseInt(wrapperStyle.fontSize, 10),
10244 domStyle = domElement.style;
10246 if(dimArray && valArray) {
10247 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10248 labelStyle.display = '';
10250 labelStyle.display = 'none';
10252 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10253 aggregateStyle.display = '';
10255 aggregateStyle.display = 'none';
10257 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10258 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10259 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10260 if(dimArray[i][0] > 0) {
10261 acum+= valArray[i][0];
10262 leftAcum+= dimArray[i][0];
10265 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10266 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10267 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10268 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10269 labels.aggregate.innerHTML = acum;
10274 var size = st.canvas.getSize(),
10275 margin = config.Margin;
10276 st.config.offsetY = -size.height/2 + margin.bottom
10277 + (config.showLabels && (config.labelOffset + config.Label.size));
10278 st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10280 this.canvas = this.st.canvas;
10283 renderTitle: function() {
10284 var canvas = this.canvas,
10285 size = canvas.getSize(),
10286 config = this.config,
10287 margin = config.Margin,
10288 label = config.Label,
10289 title = config.Title;
10290 ctx = canvas.getCtx();
10291 ctx.fillStyle = title.color;
10292 ctx.textAlign = 'left';
10293 ctx.textBaseline = 'top';
10294 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10295 if(label.type == 'Native') {
10296 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10300 renderTicks: function() {
10302 var canvas = this.canvas,
10303 size = canvas.getSize(),
10304 config = this.config,
10305 margin = config.Margin,
10306 ticks = config.Ticks,
10307 title = config.Title,
10308 subtitle = config.Subtitle,
10309 label = config.Label,
10310 maxValue = this.maxValue,
10311 maxTickValue = Math.ceil(maxValue*.1)*10;
10312 if(maxTickValue == maxValue) {
10313 var length = maxTickValue.toString().length;
10314 maxTickValue = maxTickValue + parseInt(pad(1,length));
10319 labelIncrement = maxTickValue/ticks.segments,
10320 ctx = canvas.getCtx();
10321 ctx.strokeStyle = ticks.color;
10322 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10323 ctx.textAlign = 'center';
10324 ctx.textBaseline = 'middle';
10326 idLabel = canvas.id + "-label";
10328 container = document.getElementById(idLabel);
10331 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10332 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10333 grid = -size.height+(margin.bottom+config.labelOffset+label.size+margin.top+(title.text? title.size+title.offset:0)+(subtitle.text? subtitle.size+subtitle.offset:0)),
10334 segmentLength = grid/ticks.segments;
10335 ctx.fillStyle = ticks.color;
10336 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size-1, -(size.height/2)+margin.top+(title.text? title.size+title.offset:0),1,size.height-margin.top-margin.bottom-label.size-config.labelOffset-(title.text? title.size+title.offset:0)-(subtitle.text? subtitle.size+subtitle.offset:0));
10338 while(axis>=grid) {
10340 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10341 ctx.rotate(Math.PI / 2);
10342 ctx.fillStyle = label.color;
10343 if(config.showLabels) {
10344 if(label.type == 'Native') {
10345 ctx.fillText(labelValue, 0, 0);
10347 //html labels on y axis
10348 labelDiv = document.createElement('div');
10349 labelDiv.innerHTML = labelValue;
10350 labelDiv.className = "rotatedLabel";
10351 // labelDiv.class = "rotatedLabel";
10352 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10353 labelDiv.style.left = margin.left + "px";
10354 labelDiv.style.width = labelDim + "px";
10355 labelDiv.style.height = labelDim + "px";
10356 labelDiv.style.textAlign = "center";
10357 labelDiv.style.verticalAlign = "middle";
10358 labelDiv.style.position = "absolute";
10359 container.appendChild(labelDiv);
10363 ctx.fillStyle = ticks.color;
10364 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size, Math.round(axis), size.width-margin.right-margin.left-config.labelOffset-label.size,1 );
10365 htmlOrigin += segmentLength;
10366 axis += segmentLength;
10367 labelValue += labelIncrement;
10377 renderBackground: function() {
10378 var canvas = this.canvas,
10379 config = this.config,
10380 backgroundColor = config.backgroundColor,
10381 size = canvas.getSize(),
10382 ctx = canvas.getCtx();
10383 //ctx.globalCompositeOperation = "destination-over";
10384 ctx.fillStyle = backgroundColor;
10385 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10387 clear: function() {
10388 var canvas = this.canvas;
10389 var ctx = canvas.getCtx(),
10390 size = canvas.getSize();
10391 ctx.fillStyle = "rgba(255,255,255,0)";
10392 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10393 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
10395 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
10396 var canvas = this.canvas,
10397 size = canvas.getSize(),
10398 config = this.config,
10399 orgHeight = size.height,
10400 margin = config.Margin,
10402 horz = config.orientation == 'horizontal';
10405 var newWindowWidth = document.body.offsetWidth;
10406 var diff = newWindowWidth - orgWindowWidth;
10407 var newWidth = orgContainerDivWidth + (diff/cols);
10408 canvas.resize(newWidth,orgHeight);
10409 if(typeof FlashCanvas == "undefined") {
10412 this.clear();// hack for flashcanvas bug not properly clearing rectangle
10414 this.loadJSON(json);
10420 Loads JSON data into the visualization.
10424 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
10428 var areaChart = new $jit.AreaChart(options);
10429 areaChart.loadJSON(json);
10432 loadJSON: function(json) {
10433 var prefix = $.time(),
10436 name = $.splat(json.label),
10437 color = $.splat(json.color || this.colors),
10438 config = this.config,
10439 ticks = config.Ticks,
10440 renderBackground = config.renderBackground,
10441 gradient = !!config.type.split(":")[1],
10442 animate = config.animate,
10443 title = config.Title,
10444 groupTotalValue = 0;
10446 var valArrayAll = new Array();
10448 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10449 var val = values[i];
10450 var valArray = $.splat(val.values);
10451 for (var j=0, len=valArray.length; j<len; j++) {
10452 valArrayAll.push(parseInt(valArray[j]));
10454 groupTotalValue += parseInt(valArray.sum());
10457 this.maxValue = Math.max.apply(null, valArrayAll);
10459 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10460 var val = values[i], prev = values[i-1];
10462 var next = (i+1 < l) ? values[i+1] : 0;
10463 var valLeft = $.splat(values[i].values);
10464 var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10465 var valArray = $.zip(valLeft, valRight);
10466 var valArrayCur = $.splat(values[i].values);
10467 var linkArray = $.splat(values[i].links);
10468 var acumLeft = 0, acumRight = 0;
10469 var lastNode = (l-1 == i) ? true : false;
10471 'id': prefix + val.label,
10475 '$valueArray': valArray,
10476 '$valArrayCur': valArrayCur,
10477 '$colorArray': color,
10478 '$linkArray': linkArray,
10479 '$stringArray': name,
10480 '$next': next? next.label:false,
10481 '$prev': prev? prev.label:false,
10483 '$lastNode': lastNode,
10484 '$groupTotalValue': groupTotalValue,
10485 '$gradient': gradient
10491 'id': prefix + '$root',
10502 this.normalizeDims();
10504 if(renderBackground) {
10505 this.renderBackground();
10508 if(!animate && ticks.enable) {
10509 this.renderTicks();
10514 this.renderTitle();
10518 st.select(st.root);
10521 modes: ['node-property:height:dimArray'],
10530 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
10534 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10535 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10540 areaChart.updateJSON(json, {
10541 onComplete: function() {
10542 alert('update complete!');
10547 updateJSON: function(json, onComplete) {
10548 if(this.busy) return;
10553 labels = json.label && $.splat(json.label),
10554 values = json.values,
10555 animate = this.config.animate,
10557 $.each(values, function(v) {
10558 var n = graph.getByName(v.label);
10560 v.values = $.splat(v.values);
10561 var stringArray = n.getData('stringArray'),
10562 valArray = n.getData('valueArray');
10563 $.each(valArray, function(a, i) {
10564 a[0] = v.values[i];
10565 if(labels) stringArray[i] = labels[i];
10567 n.setData('valueArray', valArray);
10568 var prev = n.getData('prev'),
10569 next = n.getData('next'),
10570 nextNode = graph.getByName(next);
10572 var p = graph.getByName(prev);
10574 var valArray = p.getData('valueArray');
10575 $.each(valArray, function(a, i) {
10576 a[1] = v.values[i];
10581 var valArray = n.getData('valueArray');
10582 $.each(valArray, function(a, i) {
10583 a[1] = v.values[i];
10588 this.normalizeDims();
10591 st.select(st.root);
10594 modes: ['node-property:height:dimArray'],
10596 onComplete: function() {
10598 onComplete && onComplete.onComplete();
10607 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10611 Variable strings arguments with the name of the stacks.
10616 areaChart.filter('label A', 'label C');
10621 <AreaChart.restore>.
10623 filter: function() {
10624 if(this.busy) return;
10626 if(this.config.Tips.enable) this.st.tips.hide();
10627 this.select(false, false, false);
10628 var args = Array.prototype.slice.call(arguments);
10629 var rt = this.st.graph.getNode(this.st.root);
10631 rt.eachAdjacency(function(adj) {
10632 var n = adj.nodeTo,
10633 dimArray = n.getData('dimArray'),
10634 stringArray = n.getData('stringArray');
10635 n.setData('dimArray', $.map(dimArray, function(d, i) {
10636 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10639 this.st.fx.animate({
10640 modes: ['node-property:dimArray'],
10642 onComplete: function() {
10651 Sets all stacks that could have been filtered visible.
10656 areaChart.restore();
10661 <AreaChart.filter>.
10663 restore: function() {
10664 if(this.busy) return;
10666 if(this.config.Tips.enable) this.st.tips.hide();
10667 this.select(false, false, false);
10668 this.normalizeDims();
10670 this.st.fx.animate({
10671 modes: ['node-property:height:dimArray'],
10673 onComplete: function() {
10678 //adds the little brown bar when hovering the node
10679 select: function(id, name, index) {
10680 if(!this.config.selectOnHover) return;
10681 var s = this.selected;
10682 if(s.id != id || s.name != name
10683 || s.index != index) {
10687 this.st.graph.eachNode(function(n) {
10688 n.setData('border', false);
10691 var n = this.st.graph.getNode(id);
10692 n.setData('border', s);
10693 var link = index === 0? 'prev':'next';
10694 link = n.getData(link);
10696 n = this.st.graph.getByName(link);
10698 n.setData('border', {
10712 Returns an object containing as keys the legend names and as values hex strings with color values.
10717 var legend = areaChart.getLegend();
10720 getLegend: function() {
10721 var legend = new Array();
10722 var name = new Array();
10723 var color = new Array();
10725 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10728 var colors = n.getData('colorArray'),
10729 len = colors.length;
10730 $.each(n.getData('stringArray'), function(s, i) {
10731 color[i] = colors[i % len];
10734 legend['name'] = name;
10735 legend['color'] = color;
10740 Method: getMaxValue
10742 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10747 var ans = areaChart.getMaxValue();
10750 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10755 //will return 100 for all AreaChart instances,
10756 //displaying all of them with the same scale
10757 $jit.AreaChart.implement({
10758 'getMaxValue': function() {
10766 normalizeDims: function() {
10767 //number of elements
10768 var root = this.st.graph.getNode(this.st.root), l=0;
10769 root.eachAdjacency(function() {
10774 var maxValue = this.maxValue || 1,
10775 size = this.st.canvas.getSize(),
10776 config = this.config,
10777 margin = config.Margin,
10778 labelOffset = config.labelOffset + config.Label.size,
10779 fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10780 animate = config.animate,
10781 ticks = config.Ticks,
10782 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10783 - (config.showLabels && labelOffset);
10786 var maxTickValue = Math.ceil(maxValue*.1)*10;
10787 if(maxTickValue == maxValue) {
10788 var length = maxTickValue.toString().length;
10789 maxTickValue = maxTickValue + parseInt(pad(1,length));
10794 this.st.graph.eachNode(function(n) {
10795 var acumLeft = 0, acumRight = 0, animateValue = [];
10796 $.each(n.getData('valueArray'), function(v) {
10798 acumRight += +v[1];
10799 animateValue.push([0, 0]);
10801 var acum = acumRight>acumLeft? acumRight:acumLeft;
10803 n.setData('width', fixedDim);
10805 n.setData('height', acum * height / maxValue, 'end');
10806 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10807 return [n[0] * height / maxValue, n[1] * height / maxValue];
10809 var dimArray = n.getData('dimArray');
10811 n.setData('dimArray', animateValue);
10816 n.setData('height', acum * height / maxValue);
10817 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10818 return [n[0] * height / maxTickValue, n[1] * height / maxTickValue];
10821 n.setData('height', acum * height / maxValue);
10822 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10823 return [n[0] * height / maxValue, n[1] * height / maxValue];
10838 * File: AreaChart.js
10842 $jit.ST.Plot.NodeTypes.implement({
10843 'areachart-stacked' : {
10844 'render' : function(node, canvas) {
10845 var pos = node.pos.getc(true),
10846 width = node.getData('width'),
10847 height = node.getData('height'),
10848 algnPos = this.getAlignedPos(pos, width, height),
10849 x = algnPos.x, y = algnPos.y,
10850 stringArray = node.getData('stringArray'),
10851 dimArray = node.getData('dimArray'),
10852 valArray = node.getData('valueArray'),
10853 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10854 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10855 colorArray = node.getData('colorArray'),
10856 colorLength = colorArray.length,
10857 config = node.getData('config'),
10858 gradient = node.getData('gradient'),
10859 showLabels = config.showLabels,
10860 aggregates = config.showAggregates,
10861 label = config.Label,
10862 prev = node.getData('prev');
10864 var ctx = canvas.getCtx(), border = node.getData('border');
10865 if (colorArray && dimArray && stringArray) {
10866 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10867 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10869 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10870 var h1 = acumLeft + dimArray[i][0],
10871 h2 = acumRight + dimArray[i][1],
10872 alpha = Math.atan((h2 - h1) / width),
10874 var linear = ctx.createLinearGradient(x + width/2,
10876 x + width/2 + delta * Math.sin(alpha),
10877 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10878 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10879 function(v) { return (v * 0.85) >> 0; }));
10880 linear.addColorStop(0, colorArray[i % colorLength]);
10881 linear.addColorStop(1, color);
10882 ctx.fillStyle = linear;
10885 ctx.moveTo(x, y - acumLeft);
10886 ctx.lineTo(x + width, y - acumRight);
10887 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10888 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10889 ctx.lineTo(x, y - acumLeft);
10893 var strong = border.name == stringArray[i];
10894 var perc = strong? 0.7 : 0.8;
10895 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10896 function(v) { return (v * perc) >> 0; }));
10897 ctx.strokeStyle = color;
10898 ctx.lineWidth = strong? 4 : 1;
10901 if(border.index === 0) {
10902 ctx.moveTo(x, y - acumLeft);
10903 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10905 ctx.moveTo(x + width, y - acumRight);
10906 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10911 acumLeft += (dimArray[i][0] || 0);
10912 acumRight += (dimArray[i][1] || 0);
10914 if(dimArray[i][0] > 0)
10915 valAcum += (valArray[i][0] || 0);
10917 if(prev && label.type == 'Native') {
10920 ctx.fillStyle = ctx.strokeStyle = label.color;
10921 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10922 ctx.textAlign = 'center';
10923 ctx.textBaseline = 'middle';
10924 if(aggregates(node.name, valLeft, valRight, node)) {
10925 ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10927 if(showLabels(node.name, valLeft, valRight, node)) {
10928 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10934 'contains': function(node, mpos) {
10935 var pos = node.pos.getc(true),
10936 width = node.getData('width'),
10937 height = node.getData('height'),
10938 algnPos = this.getAlignedPos(pos, width, height),
10939 x = algnPos.x, y = algnPos.y,
10940 dimArray = node.getData('dimArray'),
10942 //bounding box check
10943 if(mpos.x < x || mpos.x > x + width
10944 || mpos.y > y || mpos.y < y - height) {
10948 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10949 var dimi = dimArray[i];
10952 var intersec = lAcum + (rAcum - lAcum) * rx / width;
10953 if(mpos.y >= intersec) {
10954 var index = +(rx > width/2);
10956 'name': node.getData('stringArray')[i],
10957 'color': node.getData('colorArray')[i],
10958 'value': node.getData('valueArray')[i][index],
10971 A visualization that displays stacked area charts.
10973 Constructor Options:
10975 See <Options.AreaChart>.
10978 $jit.AreaChart = new Class({
10980 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10984 initialize: function(opt) {
10985 this.controller = this.config =
10986 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10987 Label: { type: 'Native' }
10989 //set functions for showLabels and showAggregates
10990 var showLabels = this.config.showLabels,
10991 typeLabels = $.type(showLabels),
10992 showAggregates = this.config.showAggregates,
10993 typeAggregates = $.type(showAggregates);
10994 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10995 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10997 this.initializeViz();
11000 initializeViz: function() {
11001 var config = this.config,
11003 nodeType = config.type.split(":")[0],
11006 var st = new $jit.ST({
11007 injectInto: config.injectInto,
11008 orientation: "bottom",
11012 withLabels: config.Label.type != 'Native',
11013 useCanvas: config.useCanvas,
11015 type: config.Label.type
11019 type: 'areachart-' + nodeType,
11028 enable: config.Tips.enable,
11031 onShow: function(tip, node, contains) {
11032 var elem = contains;
11033 config.Tips.onShow(tip, elem, node);
11039 onClick: function(node, eventInfo, evt) {
11040 if(!config.filterOnClick && !config.Events.enable) return;
11041 var elem = eventInfo.getContains();
11042 if(elem) config.filterOnClick && that.filter(elem.name);
11043 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11045 onRightClick: function(node, eventInfo, evt) {
11046 if(!config.restoreOnRightClick) return;
11049 onMouseMove: function(node, eventInfo, evt) {
11050 if(!config.selectOnHover) return;
11052 var elem = eventInfo.getContains();
11053 that.select(node.id, elem.name, elem.index);
11055 that.select(false, false, false);
11059 onCreateLabel: function(domElement, node) {
11060 var labelConf = config.Label,
11061 valueArray = node.getData('valueArray'),
11062 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11063 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11064 if(node.getData('prev')) {
11066 wrapper: document.createElement('div'),
11067 aggregate: document.createElement('div'),
11068 label: document.createElement('div')
11070 var wrapper = nlbs.wrapper,
11071 label = nlbs.label,
11072 aggregate = nlbs.aggregate,
11073 wrapperStyle = wrapper.style,
11074 labelStyle = label.style,
11075 aggregateStyle = aggregate.style;
11076 //store node labels
11077 nodeLabels[node.id] = nlbs;
11079 wrapper.appendChild(label);
11080 wrapper.appendChild(aggregate);
11081 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11082 label.style.display = 'none';
11084 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11085 aggregate.style.display = 'none';
11087 wrapperStyle.position = 'relative';
11088 wrapperStyle.overflow = 'visible';
11089 wrapperStyle.fontSize = labelConf.size + 'px';
11090 wrapperStyle.fontFamily = labelConf.family;
11091 wrapperStyle.color = labelConf.color;
11092 wrapperStyle.textAlign = 'center';
11093 aggregateStyle.position = labelStyle.position = 'absolute';
11095 domElement.style.width = node.getData('width') + 'px';
11096 domElement.style.height = node.getData('height') + 'px';
11097 label.innerHTML = node.name;
11099 domElement.appendChild(wrapper);
11102 onPlaceLabel: function(domElement, node) {
11103 if(!node.getData('prev')) return;
11104 var labels = nodeLabels[node.id],
11105 wrapperStyle = labels.wrapper.style,
11106 labelStyle = labels.label.style,
11107 aggregateStyle = labels.aggregate.style,
11108 width = node.getData('width'),
11109 height = node.getData('height'),
11110 dimArray = node.getData('dimArray'),
11111 valArray = node.getData('valueArray'),
11112 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11113 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11114 font = parseInt(wrapperStyle.fontSize, 10),
11115 domStyle = domElement.style;
11117 if(dimArray && valArray) {
11118 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11119 labelStyle.display = '';
11121 labelStyle.display = 'none';
11123 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11124 aggregateStyle.display = '';
11126 aggregateStyle.display = 'none';
11128 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11129 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11130 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11131 if(dimArray[i][0] > 0) {
11132 acum+= valArray[i][0];
11133 leftAcum+= dimArray[i][0];
11136 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11137 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11138 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11139 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11140 labels.aggregate.innerHTML = acum;
11145 var size = st.canvas.getSize(),
11146 margin = config.Margin;
11147 st.config.offsetY = -size.height/2 + margin.bottom
11148 + (config.showLabels && (config.labelOffset + config.Label.size));
11149 st.config.offsetX = (margin.right - margin.left)/2;
11151 this.canvas = this.st.canvas;
11157 Loads JSON data into the visualization.
11161 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
11165 var areaChart = new $jit.AreaChart(options);
11166 areaChart.loadJSON(json);
11169 loadJSON: function(json) {
11170 var prefix = $.time(),
11173 name = $.splat(json.label),
11174 color = $.splat(json.color || this.colors),
11175 config = this.config,
11176 gradient = !!config.type.split(":")[1],
11177 animate = config.animate;
11179 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11180 var val = values[i], prev = values[i-1], next = values[i+1];
11181 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11182 var valArray = $.zip(valLeft, valRight);
11183 var acumLeft = 0, acumRight = 0;
11185 'id': prefix + val.label,
11189 '$valueArray': valArray,
11190 '$colorArray': color,
11191 '$stringArray': name,
11192 '$next': next.label,
11193 '$prev': prev? prev.label:false,
11195 '$gradient': gradient
11201 'id': prefix + '$root',
11212 this.normalizeDims();
11214 st.select(st.root);
11217 modes: ['node-property:height:dimArray'],
11226 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
11230 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11231 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11236 areaChart.updateJSON(json, {
11237 onComplete: function() {
11238 alert('update complete!');
11243 updateJSON: function(json, onComplete) {
11244 if(this.busy) return;
11249 labels = json.label && $.splat(json.label),
11250 values = json.values,
11251 animate = this.config.animate,
11253 $.each(values, function(v) {
11254 var n = graph.getByName(v.label);
11256 v.values = $.splat(v.values);
11257 var stringArray = n.getData('stringArray'),
11258 valArray = n.getData('valueArray');
11259 $.each(valArray, function(a, i) {
11260 a[0] = v.values[i];
11261 if(labels) stringArray[i] = labels[i];
11263 n.setData('valueArray', valArray);
11264 var prev = n.getData('prev'),
11265 next = n.getData('next'),
11266 nextNode = graph.getByName(next);
11268 var p = graph.getByName(prev);
11270 var valArray = p.getData('valueArray');
11271 $.each(valArray, function(a, i) {
11272 a[1] = v.values[i];
11277 var valArray = n.getData('valueArray');
11278 $.each(valArray, function(a, i) {
11279 a[1] = v.values[i];
11284 this.normalizeDims();
11286 st.select(st.root);
11289 modes: ['node-property:height:dimArray'],
11291 onComplete: function() {
11293 onComplete && onComplete.onComplete();
11302 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11306 Variable strings arguments with the name of the stacks.
11311 areaChart.filter('label A', 'label C');
11316 <AreaChart.restore>.
11318 filter: function() {
11319 if(this.busy) return;
11321 if(this.config.Tips.enable) this.st.tips.hide();
11322 this.select(false, false, false);
11323 var args = Array.prototype.slice.call(arguments);
11324 var rt = this.st.graph.getNode(this.st.root);
11326 rt.eachAdjacency(function(adj) {
11327 var n = adj.nodeTo,
11328 dimArray = n.getData('dimArray'),
11329 stringArray = n.getData('stringArray');
11330 n.setData('dimArray', $.map(dimArray, function(d, i) {
11331 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11334 this.st.fx.animate({
11335 modes: ['node-property:dimArray'],
11337 onComplete: function() {
11346 Sets all stacks that could have been filtered visible.
11351 areaChart.restore();
11356 <AreaChart.filter>.
11358 restore: function() {
11359 if(this.busy) return;
11361 if(this.config.Tips.enable) this.st.tips.hide();
11362 this.select(false, false, false);
11363 this.normalizeDims();
11365 this.st.fx.animate({
11366 modes: ['node-property:height:dimArray'],
11368 onComplete: function() {
11373 //adds the little brown bar when hovering the node
11374 select: function(id, name, index) {
11375 if(!this.config.selectOnHover) return;
11376 var s = this.selected;
11377 if(s.id != id || s.name != name
11378 || s.index != index) {
11382 this.st.graph.eachNode(function(n) {
11383 n.setData('border', false);
11386 var n = this.st.graph.getNode(id);
11387 n.setData('border', s);
11388 var link = index === 0? 'prev':'next';
11389 link = n.getData(link);
11391 n = this.st.graph.getByName(link);
11393 n.setData('border', {
11407 Returns an object containing as keys the legend names and as values hex strings with color values.
11412 var legend = areaChart.getLegend();
11415 getLegend: function() {
11418 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11421 var colors = n.getData('colorArray'),
11422 len = colors.length;
11423 $.each(n.getData('stringArray'), function(s, i) {
11424 legend[s] = colors[i % len];
11430 Method: getMaxValue
11432 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11437 var ans = areaChart.getMaxValue();
11440 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11445 //will return 100 for all AreaChart instances,
11446 //displaying all of them with the same scale
11447 $jit.AreaChart.implement({
11448 'getMaxValue': function() {
11455 getMaxValue: function() {
11457 this.st.graph.eachNode(function(n) {
11458 var valArray = n.getData('valueArray'),
11459 acumLeft = 0, acumRight = 0;
11460 $.each(valArray, function(v) {
11462 acumRight += +v[1];
11464 var acum = acumRight>acumLeft? acumRight:acumLeft;
11465 maxValue = maxValue>acum? maxValue:acum;
11470 normalizeDims: function() {
11471 //number of elements
11472 var root = this.st.graph.getNode(this.st.root), l=0;
11473 root.eachAdjacency(function() {
11476 var maxValue = this.getMaxValue() || 1,
11477 size = this.st.canvas.getSize(),
11478 config = this.config,
11479 margin = config.Margin,
11480 labelOffset = config.labelOffset + config.Label.size,
11481 fixedDim = (size.width - (margin.left + margin.right)) / l,
11482 animate = config.animate,
11483 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
11484 - (config.showLabels && labelOffset);
11485 this.st.graph.eachNode(function(n) {
11486 var acumLeft = 0, acumRight = 0, animateValue = [];
11487 $.each(n.getData('valueArray'), function(v) {
11489 acumRight += +v[1];
11490 animateValue.push([0, 0]);
11492 var acum = acumRight>acumLeft? acumRight:acumLeft;
11493 n.setData('width', fixedDim);
11495 n.setData('height', acum * height / maxValue, 'end');
11496 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11497 return [n[0] * height / maxValue, n[1] * height / maxValue];
11499 var dimArray = n.getData('dimArray');
11501 n.setData('dimArray', animateValue);
11504 n.setData('height', acum * height / maxValue);
11505 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11506 return [n[0] * height / maxValue, n[1] * height / maxValue];
11514 * File: Options.BarChart.js
11519 Object: Options.BarChart
11521 <BarChart> options.
11522 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11528 Options.BarChart = {
11533 hoveredColor: '#9fd4ff',
11534 orientation: 'horizontal',
11535 showAggregates: true,
11545 var barChart = new $jit.BarChart({
11548 type: 'stacked:gradient'
11555 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11556 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11557 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11558 barsOffset - (number) Default's *0*. Separation between bars.
11559 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11560 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11561 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11562 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11563 showLabels - (boolean) Default's *true*. Display the name of the slots.
11567 Options.BarChart = {
11571 type: 'stacked', //stacked, grouped, : gradient
11572 labelOffset: 3, //label offset
11573 barsOffset: 0, //distance between bars
11574 nodeCount: 0, //number of bars
11575 hoveredColor: '#9fd4ff',
11577 renderBackground: false,
11578 orientation: 'horizontal',
11579 showAggregates: true,
11598 * File: BarChart.js
11602 $jit.ST.Plot.NodeTypes.implement({
11603 'barchart-stacked' : {
11604 'render' : function(node, canvas) {
11605 var pos = node.pos.getc(true),
11606 width = node.getData('width'),
11607 height = node.getData('height'),
11608 algnPos = this.getAlignedPos(pos, width, height),
11609 x = algnPos.x, y = algnPos.y,
11610 dimArray = node.getData('dimArray'),
11611 valueArray = node.getData('valueArray'),
11612 stringArray = node.getData('stringArray'),
11613 linkArray = node.getData('linkArray'),
11614 gvl = node.getData('gvl'),
11615 colorArray = node.getData('colorArray'),
11616 colorLength = colorArray.length,
11617 nodeCount = node.getData('nodeCount');
11618 var ctx = canvas.getCtx(),
11619 canvasSize = canvas.getSize(),
11621 border = node.getData('border'),
11622 gradient = node.getData('gradient'),
11623 config = node.getData('config'),
11624 horz = config.orientation == 'horizontal',
11625 aggregates = config.showAggregates,
11626 showLabels = config.showLabels,
11627 label = config.Label,
11628 margin = config.Margin;
11631 if (colorArray && dimArray && stringArray) {
11632 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11633 acum += (dimArray[i] || 0);
11638 if(config.shadow.enable) {
11639 shadowThickness = config.shadow.size;
11640 ctx.fillStyle = "rgba(0,0,0,.2)";
11642 ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11644 ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11648 if (colorArray && dimArray && stringArray) {
11649 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11650 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11657 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
11658 x + acum + dimArray[i]/2, y + height);
11660 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
11661 x + width, y - acum- dimArray[i]/2);
11663 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11664 function(v) { return (v * 0.8) >> 0; }));
11665 linear.addColorStop(0, color);
11666 linear.addColorStop(0.3, colorArray[i % colorLength]);
11667 linear.addColorStop(0.7, colorArray[i % colorLength]);
11668 linear.addColorStop(1, color);
11669 ctx.fillStyle = linear;
11675 chartBarWidth = dimArray[i];
11676 chartBarHeight = height;
11679 yCoord = y - acum - dimArray[i];
11680 chartBarWidth = width;
11681 chartBarHeight = dimArray[i];
11684 ctx.fillRect(xCoord, yCoord, chartBarWidth, chartBarHeight);
11687 if(chartBarHeight > 0) {
11688 ctx.font = label.style + ' ' + (label.size - 2) + 'px ' + label.family;
11689 labelText = valueArray[i];
11690 mtxt = ctx.measureText(labelText);
11692 labelTextPaddingX = 10;
11693 labelTextPaddingY = 6;
11695 labelBoxWidth = mtxt.width + labelTextPaddingX;
11696 labelBoxHeight = label.size + labelTextPaddingY;
11698 // do NOT draw label if label box is smaller than chartBarHeight
11699 if((horz && (labelBoxWidth < chartBarWidth)) || (!horz && (labelBoxHeight < chartBarHeight))) {
11700 labelBoxX = xCoord + chartBarWidth/2 - mtxt.width/2 - labelTextPaddingX/2;
11701 labelBoxY = yCoord + chartBarHeight/2 - labelBoxHeight/2;
11703 ctx.fillStyle = "rgba(255,255,255,.2)";
11704 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "fill");
11705 ctx.fillStyle = "rgba(0,0,0,.8)";
11706 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "stroke");
11707 ctx.textAlign = 'center';
11708 ctx.fillStyle = "rgba(255,255,255,.6)";
11709 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2, labelBoxY + labelBoxHeight/2);
11710 ctx.fillStyle = "rgba(0,0,0,.6)";
11711 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2 + 1, labelBoxY + labelBoxHeight/2 + 1);
11715 if(border && border.name == stringArray[i]) {
11717 opt.dimValue = dimArray[i];
11719 acum += (dimArray[i] || 0);
11720 valAcum += (valueArray[i] || 0);
11725 ctx.strokeStyle = border.color;
11727 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11729 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11733 if(label.type == 'Native') {
11735 ctx.fillStyle = ctx.strokeStyle = label.color;
11736 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11737 ctx.textBaseline = 'middle';
11739 acumValueLabel = gvl;
11741 acumValueLabel = valAcum;
11743 if(aggregates(node.name, valAcum)) {
11745 ctx.textAlign = 'center';
11746 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11749 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11750 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11751 (label ? label.size + config.labelOffset : 0));
11752 mtxt = ctx.measureText(acumValueLabel);
11753 boxWidth = mtxt.width+10;
11755 boxHeight = label.size+6;
11757 if(boxHeight + acum + config.labelOffset > gridHeight) {
11758 bottomPadding = acum - config.labelOffset - boxHeight;
11760 bottomPadding = acum + config.labelOffset + inset;
11764 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11767 boxY = -boxHeight/2;
11769 ctx.rotate(0 * Math.PI / 180);
11770 ctx.fillStyle = "rgba(255,255,255,.8)";
11771 if(boxHeight + acum + config.labelOffset > gridHeight) {
11772 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11774 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11775 ctx.fillStyle = ctx.strokeStyle = label.color;
11776 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11781 if(showLabels(node.name, valAcum, node)) {
11786 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11789 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11790 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11791 boxWidth = mtxt.width+10;
11794 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11795 leftPadding = acum - config.labelOffset - boxWidth - inset;
11797 leftPadding = acum + config.labelOffset;
11801 ctx.textAlign = 'left';
11802 ctx.translate(x + inset + leftPadding, y + height/2);
11803 boxHeight = label.size+6;
11805 boxY = -boxHeight/2;
11806 ctx.fillStyle = "rgba(255,255,255,.8)";
11808 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11809 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11811 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11813 ctx.fillStyle = label.color;
11814 ctx.rotate(0 * Math.PI / 180);
11815 ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11819 //if the number of nodes greater than 8 rotate labels 45 degrees
11820 if(nodeCount > 8) {
11821 ctx.textAlign = 'left';
11822 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11823 ctx.rotate(45* Math.PI / 180);
11824 ctx.fillText(node.name, 0, 0);
11826 ctx.textAlign = 'center';
11827 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11835 'contains': function(node, mpos) {
11836 var pos = node.pos.getc(true),
11837 width = node.getData('width'),
11838 height = node.getData('height'),
11839 algnPos = this.getAlignedPos(pos, width, height),
11840 x = algnPos.x, y = algnPos.y,
11841 dimArray = node.getData('dimArray'),
11842 config = node.getData('config'),
11844 horz = config.orientation == 'horizontal';
11845 //bounding box check
11847 if(mpos.x < x || mpos.x > x + width
11848 || mpos.y > y + height || mpos.y < y) {
11852 if(mpos.x < x || mpos.x > x + width
11853 || mpos.y > y || mpos.y < y - height) {
11858 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11859 var dimi = dimArray[i];
11860 var url = Url.decode(node.getData('linkArray')[i]);
11863 var intersec = acum;
11864 if(mpos.x <= intersec) {
11866 'name': node.getData('stringArray')[i],
11867 'color': node.getData('colorArray')[i],
11868 'value': node.getData('valueArray')[i],
11869 'valuelabel': node.getData('valuelabelArray')[i],
11870 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11877 var intersec = acum;
11878 if(mpos.y >= intersec) {
11880 'name': node.getData('stringArray')[i],
11881 'color': node.getData('colorArray')[i],
11882 'value': node.getData('valueArray')[i],
11883 'valuelabel': node.getData('valuelabelArray')[i],
11884 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11894 'barchart-grouped' : {
11895 'render' : function(node, canvas) {
11896 var pos = node.pos.getc(true),
11897 width = node.getData('width'),
11898 height = node.getData('height'),
11899 algnPos = this.getAlignedPos(pos, width, height),
11900 x = algnPos.x, y = algnPos.y,
11901 dimArray = node.getData('dimArray'),
11902 valueArray = node.getData('valueArray'),
11903 valuelabelArray = node.getData('valuelabelArray'),
11904 linkArray = node.getData('linkArray'),
11905 valueLength = valueArray.length,
11906 colorArray = node.getData('colorArray'),
11907 colorLength = colorArray.length,
11908 stringArray = node.getData('stringArray');
11910 var ctx = canvas.getCtx(),
11911 canvasSize = canvas.getSize(),
11913 border = node.getData('border'),
11914 gradient = node.getData('gradient'),
11915 config = node.getData('config'),
11916 horz = config.orientation == 'horizontal',
11917 aggregates = config.showAggregates,
11918 showLabels = config.showLabels,
11919 label = config.Label,
11920 shadow = config.shadow,
11921 margin = config.Margin,
11922 fixedDim = (horz? height : width) / valueLength;
11926 maxValue = Math.max.apply(null, dimArray);
11930 ctx.fillStyle = "rgba(0,0,0,.2)";
11931 if (colorArray && dimArray && stringArray && shadow.enable) {
11932 shadowThickness = shadow.size;
11934 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11935 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11936 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11939 ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11944 if(nextBar && nextBar > dimArray[i]) {
11945 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11946 } else if (nextBar && nextBar < dimArray[i]){
11947 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11949 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11951 } else if (i> 0 && i<l-1) {
11952 if(nextBar && nextBar > dimArray[i]) {
11953 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11954 } else if (nextBar && nextBar < dimArray[i]){
11955 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11957 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11959 } else if (i == l-1) {
11960 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11970 if (colorArray && dimArray && stringArray) {
11971 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11972 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11976 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
11977 x + dimArray[i]/2, y + fixedDim * (i + 1));
11979 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
11980 x + fixedDim * (i + 1), y - dimArray[i]/2);
11982 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11983 function(v) { return (v * 0.8) >> 0; }));
11984 linear.addColorStop(0, color);
11985 linear.addColorStop(0.3, colorArray[i % colorLength]);
11986 linear.addColorStop(0.7, colorArray[i % colorLength]);
11987 linear.addColorStop(1, color);
11988 ctx.fillStyle = linear;
11991 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11993 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11995 if(border && border.name == stringArray[i]) {
11996 opt.acum = fixedDim * i;
11997 opt.dimValue = dimArray[i];
11999 acum += (dimArray[i] || 0);
12000 valAcum += (valueArray[i] || 0);
12001 ctx.fillStyle = ctx.strokeStyle = label.color;
12002 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12005 if(aggregates(node.name, valAcum) && label.type == 'Native') {
12006 if(valuelabelArray[i]) {
12007 acumValueLabel = valuelabelArray[i];
12009 acumValueLabel = valueArray[i];
12012 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12013 ctx.textAlign = 'left';
12014 ctx.textBaseline = 'top';
12015 ctx.fillStyle = "rgba(255,255,255,.8)";
12017 gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
12018 mtxt = ctx.measureText(acumValueLabel);
12019 boxWidth = mtxt.width+10;
12021 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12022 leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
12024 leftPadding = dimArray[i] + config.labelOffset + inset;
12026 boxHeight = label.size+6;
12027 boxX = x + leftPadding;
12028 boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
12032 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12033 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12035 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12037 ctx.fillStyle = ctx.strokeStyle = label.color;
12038 ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12045 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12047 ctx.textAlign = 'center';
12050 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12051 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12052 (label ? label.size + config.labelOffset : 0));
12054 mtxt = ctx.measureText(acumValueLabel);
12055 boxWidth = mtxt.width+10;
12056 boxHeight = label.size+6;
12057 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12058 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12060 bottomPadding = dimArray[i] + config.labelOffset + inset;
12064 ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12066 boxX = -boxWidth/2;
12067 boxY = -boxHeight/2;
12068 ctx.fillStyle = "rgba(255,255,255,.8)";
12072 //ctx.rotate(270* Math.PI / 180);
12073 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12074 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12076 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12078 ctx.fillStyle = ctx.strokeStyle = label.color;
12079 ctx.fillText(acumValueLabel, 0,0);
12088 ctx.strokeStyle = border.color;
12090 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12092 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12096 if(label.type == 'Native') {
12098 ctx.fillStyle = ctx.strokeStyle = label.color;
12099 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12100 ctx.textBaseline = 'middle';
12102 if(showLabels(node.name, valAcum, node)) {
12104 ctx.textAlign = 'center';
12105 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12106 ctx.rotate(Math.PI / 2);
12107 ctx.fillText(node.name, 0, 0);
12109 ctx.textAlign = 'center';
12110 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12117 'contains': function(node, mpos) {
12118 var pos = node.pos.getc(true),
12119 width = node.getData('width'),
12120 height = node.getData('height'),
12121 algnPos = this.getAlignedPos(pos, width, height),
12122 x = algnPos.x, y = algnPos.y,
12123 dimArray = node.getData('dimArray'),
12124 len = dimArray.length,
12125 config = node.getData('config'),
12127 horz = config.orientation == 'horizontal',
12128 fixedDim = (horz? height : width) / len;
12129 //bounding box check
12131 if(mpos.x < x || mpos.x > x + width
12132 || mpos.y > y + height || mpos.y < y) {
12136 if(mpos.x < x || mpos.x > x + width
12137 || mpos.y > y || mpos.y < y - height) {
12142 for(var i=0, l=dimArray.length; i<l; i++) {
12143 var dimi = dimArray[i];
12144 var url = Url.decode(node.getData('linkArray')[i]);
12146 var limit = y + fixedDim * i;
12147 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12149 'name': node.getData('stringArray')[i],
12150 'color': node.getData('colorArray')[i],
12151 'value': node.getData('valueArray')[i],
12152 'valuelabel': node.getData('valuelabelArray')[i],
12153 'title': node.getData('titleArray')[i],
12154 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12160 var limit = x + fixedDim * i;
12161 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12163 'name': node.getData('stringArray')[i],
12164 'color': node.getData('colorArray')[i],
12165 'value': node.getData('valueArray')[i],
12166 'valuelabel': node.getData('valuelabelArray')[i],
12167 'title': node.getData('titleArray')[i],
12168 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12178 'barchart-basic' : {
12179 'render' : function(node, canvas) {
12180 var pos = node.pos.getc(true),
12181 width = node.getData('width'),
12182 height = node.getData('height'),
12183 algnPos = this.getAlignedPos(pos, width, height),
12184 x = algnPos.x, y = algnPos.y,
12185 dimArray = node.getData('dimArray'),
12186 valueArray = node.getData('valueArray'),
12187 valuelabelArray = node.getData('valuelabelArray'),
12188 linkArray = node.getData('linkArray'),
12189 valueLength = valueArray.length,
12190 colorArray = node.getData('colorMono'),
12191 colorLength = colorArray.length,
12192 stringArray = node.getData('stringArray');
12194 var ctx = canvas.getCtx(),
12195 canvasSize = canvas.getSize(),
12197 border = node.getData('border'),
12198 gradient = node.getData('gradient'),
12199 config = node.getData('config'),
12200 horz = config.orientation == 'horizontal',
12201 aggregates = config.showAggregates,
12202 showLabels = config.showLabels,
12203 label = config.Label,
12204 fixedDim = (horz? height : width) / valueLength,
12205 margin = config.Margin;
12207 if (colorArray && dimArray && stringArray) {
12208 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12209 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12214 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
12215 x + dimArray[i]/2, y + fixedDim * (i + 1));
12217 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
12218 x + fixedDim * (i + 1), y - dimArray[i]/2);
12221 if(config.shadow.size) {
12222 shadowThickness = config.shadow.size;
12223 ctx.fillStyle = "rgba(0,0,0,.2)";
12225 ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12227 ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12231 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
12232 function(v) { return (v * 0.8) >> 0; }));
12233 linear.addColorStop(0, color);
12234 linear.addColorStop(0.3, colorArray[i % colorLength]);
12235 linear.addColorStop(0.7, colorArray[i % colorLength]);
12236 linear.addColorStop(1, color);
12237 ctx.fillStyle = linear;
12240 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12242 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12244 if(border && border.name == stringArray[i]) {
12245 opt.acum = fixedDim * i;
12246 opt.dimValue = dimArray[i];
12248 acum += (dimArray[i] || 0);
12249 valAcum += (valueArray[i] || 0);
12251 if(label.type == 'Native') {
12252 ctx.fillStyle = ctx.strokeStyle = label.color;
12253 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12254 if(aggregates(node.name, valAcum)) {
12255 if(valuelabelArray[i]) {
12256 acumValueLabel = valuelabelArray[i];
12258 acumValueLabel = valueArray[i];
12261 ctx.textAlign = 'center';
12262 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12265 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12266 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12267 (label ? label.size + config.labelOffset : 0));
12268 mtxt = ctx.measureText(acumValueLabel);
12269 boxWidth = mtxt.width+10;
12271 boxHeight = label.size+6;
12273 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12274 bottomPadding = dimArray[i] - config.labelOffset - inset;
12276 bottomPadding = dimArray[i] + config.labelOffset + inset;
12280 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12283 boxY = -boxHeight/2;
12285 //ctx.rotate(270* Math.PI / 180);
12286 ctx.fillStyle = "rgba(255,255,255,.6)";
12287 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12288 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12290 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12291 ctx.fillStyle = ctx.strokeStyle = label.color;
12292 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12301 ctx.strokeStyle = border.color;
12303 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12305 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12309 if(label.type == 'Native') {
12311 ctx.fillStyle = ctx.strokeStyle = label.color;
12312 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12313 ctx.textBaseline = 'middle';
12314 if(showLabels(node.name, valAcum, node)) {
12318 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12319 mtxt = ctx.measureText(node.name + ": " + valAcum);
12320 boxWidth = mtxt.width+10;
12323 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12324 leftPadding = acum - config.labelOffset - boxWidth - inset;
12326 leftPadding = acum + config.labelOffset;
12330 ctx.textAlign = 'left';
12331 ctx.translate(x + inset + leftPadding, y + height/2);
12332 boxHeight = label.size+6;
12334 boxY = -boxHeight/2;
12335 ctx.fillStyle = "rgba(255,255,255,.8)";
12338 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12339 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12341 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12344 ctx.fillStyle = label.color;
12345 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12349 if(stringArray.length > 8) {
12350 ctx.textAlign = 'left';
12351 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12352 ctx.rotate(45* Math.PI / 180);
12353 ctx.fillText(node.name, 0, 0);
12355 ctx.textAlign = 'center';
12356 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12365 'contains': function(node, mpos) {
12366 var pos = node.pos.getc(true),
12367 width = node.getData('width'),
12368 height = node.getData('height'),
12369 config = node.getData('config'),
12370 algnPos = this.getAlignedPos(pos, width, height),
12371 x = algnPos.x, y = algnPos.y ,
12372 dimArray = node.getData('dimArray'),
12373 len = dimArray.length,
12375 horz = config.orientation == 'horizontal',
12376 fixedDim = (horz? height : width) / len;
12378 //bounding box check
12380 if(mpos.x < x || mpos.x > x + width
12381 || mpos.y > y + height || mpos.y < y) {
12385 if(mpos.x < x || mpos.x > x + width
12386 || mpos.y > y || mpos.y < y - height) {
12391 for(var i=0, l=dimArray.length; i<l; i++) {
12392 var dimi = dimArray[i];
12393 var url = Url.decode(node.getData('linkArray')[i]);
12395 var limit = y + fixedDim * i;
12396 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12398 'name': node.getData('stringArray')[i],
12399 'color': node.getData('colorArray')[i],
12400 'value': node.getData('valueArray')[i],
12401 'valuelabel': node.getData('valuelabelArray')[i],
12402 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12408 var limit = x + fixedDim * i;
12409 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12411 'name': node.getData('stringArray')[i],
12412 'color': node.getData('colorArray')[i],
12413 'value': node.getData('valueArray')[i],
12414 'valuelabel': node.getData('valuelabelArray')[i],
12415 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12430 A visualization that displays stacked bar charts.
12432 Constructor Options:
12434 See <Options.BarChart>.
12437 $jit.BarChart = new Class({
12439 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12443 initialize: function(opt) {
12444 this.controller = this.config =
12445 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12446 Label: { type: 'Native' }
12448 //set functions for showLabels and showAggregates
12449 var showLabels = this.config.showLabels,
12450 typeLabels = $.type(showLabels),
12451 showAggregates = this.config.showAggregates,
12452 typeAggregates = $.type(showAggregates);
12453 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12454 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12455 Options.Fx.clearCanvas = false;
12456 this.initializeViz();
12459 initializeViz: function() {
12460 var config = this.config, that = this;
12461 var nodeType = config.type.split(":")[0],
12462 horz = config.orientation == 'horizontal',
12464 var st = new $jit.ST({
12465 injectInto: config.injectInto,
12466 orientation: horz? 'left' : 'bottom',
12467 background: config.background,
12468 renderBackground: config.renderBackground,
12469 backgroundColor: config.backgroundColor,
12470 colorStop1: config.colorStop1,
12471 colorStop2: config.colorStop2,
12473 nodeCount: config.nodeCount,
12474 siblingOffset: config.barsOffset,
12476 withLabels: config.Label.type != 'Native',
12477 useCanvas: config.useCanvas,
12479 type: config.Label.type
12483 type: 'barchart-' + nodeType,
12492 enable: config.Tips.enable,
12495 onShow: function(tip, node, contains) {
12496 var elem = contains;
12497 config.Tips.onShow(tip, elem, node);
12498 if(elem.link != 'undefined' && elem.link != '') {
12499 document.body.style.cursor = 'pointer';
12502 onHide: function(call) {
12503 document.body.style.cursor = 'default';
12510 onClick: function(node, eventInfo, evt) {
12511 if(!config.Events.enable) return;
12512 var elem = eventInfo.getContains();
12513 config.Events.onClick(elem, eventInfo, evt);
12515 onMouseMove: function(node, eventInfo, evt) {
12516 if(!config.hoveredColor) return;
12518 var elem = eventInfo.getContains();
12519 that.select(node.id, elem.name, elem.index);
12521 that.select(false, false, false);
12525 onCreateLabel: function(domElement, node) {
12526 var labelConf = config.Label,
12527 valueArray = node.getData('valueArray'),
12528 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12529 grouped = config.type.split(':')[0] == 'grouped',
12530 horz = config.orientation == 'horizontal';
12532 wrapper: document.createElement('div'),
12533 aggregate: document.createElement('div'),
12534 label: document.createElement('div')
12537 var wrapper = nlbs.wrapper,
12538 label = nlbs.label,
12539 aggregate = nlbs.aggregate,
12540 wrapperStyle = wrapper.style,
12541 labelStyle = label.style,
12542 aggregateStyle = aggregate.style;
12543 //store node labels
12544 nodeLabels[node.id] = nlbs;
12546 wrapper.appendChild(label);
12547 wrapper.appendChild(aggregate);
12548 if(!config.showLabels(node.name, acum, node)) {
12549 labelStyle.display = 'none';
12551 if(!config.showAggregates(node.name, acum, node)) {
12552 aggregateStyle.display = 'none';
12554 wrapperStyle.position = 'relative';
12555 wrapperStyle.overflow = 'visible';
12556 wrapperStyle.fontSize = labelConf.size + 'px';
12557 wrapperStyle.fontFamily = labelConf.family;
12558 wrapperStyle.color = labelConf.color;
12559 wrapperStyle.textAlign = 'center';
12560 aggregateStyle.position = labelStyle.position = 'absolute';
12562 domElement.style.width = node.getData('width') + 'px';
12563 domElement.style.height = node.getData('height') + 'px';
12564 aggregateStyle.left = "0px";
12565 labelStyle.left = config.labelOffset + 'px';
12566 labelStyle.whiteSpace = "nowrap";
12567 label.innerHTML = node.name;
12569 domElement.appendChild(wrapper);
12571 onPlaceLabel: function(domElement, node) {
12572 if(!nodeLabels[node.id]) return;
12573 var labels = nodeLabels[node.id],
12574 wrapperStyle = labels.wrapper.style,
12575 labelStyle = labels.label.style,
12576 aggregateStyle = labels.aggregate.style,
12577 grouped = config.type.split(':')[0] == 'grouped',
12578 horz = config.orientation == 'horizontal',
12579 dimArray = node.getData('dimArray'),
12580 valArray = node.getData('valueArray'),
12581 nodeCount = node.getData('nodeCount'),
12582 valueLength = valArray.length;
12583 valuelabelArray = node.getData('valuelabelArray'),
12584 stringArray = node.getData('stringArray'),
12585 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12586 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12587 font = parseInt(wrapperStyle.fontSize, 10),
12588 domStyle = domElement.style,
12589 fixedDim = (horz? height : width) / valueLength;
12592 if(dimArray && valArray) {
12593 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12595 aggregateStyle.width = width - config.labelOffset + "px";
12596 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12597 if(dimArray[i] > 0) {
12598 acum+= valArray[i];
12601 if(config.showLabels(node.name, acum, node)) {
12602 labelStyle.display = '';
12604 labelStyle.display = 'none';
12606 if(config.showAggregates(node.name, acum, node)) {
12607 aggregateStyle.display = '';
12609 aggregateStyle.display = 'none';
12611 if(config.orientation == 'horizontal') {
12612 aggregateStyle.textAlign = 'right';
12613 labelStyle.textAlign = 'left';
12614 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12615 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12616 domElement.style.height = wrapperStyle.height = height + 'px';
12618 aggregateStyle.top = (-font - config.labelOffset) + 'px';
12619 labelStyle.top = (config.labelOffset + height) + 'px';
12620 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12621 domElement.style.height = wrapperStyle.height = height + 'px';
12622 if(stringArray.length > 8) {
12623 labels.label.className = "rotatedLabelReverse";
12624 labelStyle.textAlign = "left";
12625 labelStyle.top = config.labelOffset + height + width/2 + "px";
12631 labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12632 labels.aggregate.innerHTML = "";
12637 maxValue = Math.max.apply(null,dimArray);
12638 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12639 valueLabelDim = 50;
12640 valueLabel = document.createElement('div');
12641 valueLabel.innerHTML = valuelabelArray[i];
12642 // valueLabel.class = "rotatedLabel";
12643 valueLabel.className = "rotatedLabel";
12644 valueLabel.style.position = "absolute";
12645 valueLabel.style.textAlign = "left";
12646 valueLabel.style.verticalAlign = "middle";
12647 valueLabel.style.height = valueLabelDim + "px";
12648 valueLabel.style.width = valueLabelDim + "px";
12649 valueLabel.style.top = (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12650 valueLabel.style.left = (fixedDim * i) + "px";
12651 labels.wrapper.appendChild(valueLabel);
12654 labels.aggregate.innerHTML = acum;
12661 var size = st.canvas.getSize(),
12662 l = config.nodeCount,
12663 margin = config.Margin;
12664 title = config.Title;
12665 subtitle = config.Subtitle,
12666 grouped = config.type.split(':')[0] == 'grouped',
12667 margin = config.Margin,
12668 ticks = config.Ticks,
12669 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12670 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12671 horz = config.orientation == 'horizontal',
12672 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12673 fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12674 whiteSpace = size.width - (marginWidth + (fixedDim * l));
12675 //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
12676 if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12678 //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12679 if(!grouped && !horz) {
12680 st.config.siblingOffset = whiteSpace/(l+1);
12687 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12688 if(config.Ticks.enable) {
12689 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;
12691 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12694 st.config.offsetY = -size.height/2 + margin.bottom
12695 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12696 if(config.Ticks.enable) {
12697 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12699 st.config.offsetX = (margin.right - margin.left)/2;
12703 this.canvas = this.st.canvas;
12708 renderTitle: function() {
12709 var canvas = this.canvas,
12710 size = canvas.getSize(),
12711 config = this.config,
12712 margin = config.Margin,
12713 label = config.Label,
12714 title = config.Title;
12715 ctx = canvas.getCtx();
12716 ctx.fillStyle = title.color;
12717 ctx.textAlign = 'left';
12718 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12719 if(label.type == 'Native') {
12720 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12724 renderSubtitle: function() {
12725 var canvas = this.canvas,
12726 size = canvas.getSize(),
12727 config = this.config,
12728 margin = config.Margin,
12729 label = config.Label,
12730 subtitle = config.Subtitle,
12731 nodeCount = config.nodeCount,
12732 horz = config.orientation == 'horizontal' ? true : false,
12733 ctx = canvas.getCtx();
12734 ctx.fillStyle = title.color;
12735 ctx.textAlign = 'left';
12736 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12737 if(label.type == 'Native') {
12738 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12742 renderScrollNote: function() {
12743 var canvas = this.canvas,
12744 size = canvas.getSize(),
12745 config = this.config,
12746 margin = config.Margin,
12747 label = config.Label,
12748 note = config.ScrollNote;
12749 ctx = canvas.getCtx();
12750 ctx.fillStyle = title.color;
12751 title = config.Title;
12752 ctx.textAlign = 'center';
12753 ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12754 if(label.type == 'Native') {
12755 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12759 renderTicks: function() {
12761 var canvas = this.canvas,
12762 size = canvas.getSize(),
12763 config = this.config,
12764 margin = config.Margin,
12765 ticks = config.Ticks,
12766 title = config.Title,
12767 subtitle = config.Subtitle,
12768 label = config.Label,
12769 shadow = config.shadow;
12770 horz = config.orientation == 'horizontal',
12771 maxValue = this.getMaxValue(),
12772 maxTickValue = Math.ceil(maxValue*.1)*10;
12773 if(maxTickValue == maxValue) {
12774 var length = maxTickValue.toString().length;
12775 maxTickValue = maxTickValue + parseInt(pad(1,length));
12777 grouped = config.type.split(':')[0] == 'grouped',
12779 labelIncrement = maxTickValue/ticks.segments,
12780 ctx = canvas.getCtx();
12781 ctx.strokeStyle = ticks.color;
12782 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12784 ctx.textAlign = 'center';
12785 ctx.textBaseline = 'middle';
12787 idLabel = canvas.id + "-label";
12789 container = document.getElementById(idLabel);
12793 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12794 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12795 segmentLength = grid/ticks.segments;
12796 ctx.fillStyle = ticks.color;
12798 (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12799 size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12801 while(axis<=grid) {
12802 ctx.fillStyle = ticks.color;
12803 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);
12804 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));
12805 ctx.fillStyle = label.color;
12807 if(label.type == 'Native' && config.showLabels) {
12808 ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12810 axis += segmentLength;
12811 labelValue += labelIncrement;
12816 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12817 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12818 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)),
12819 segmentLength = grid/ticks.segments;
12820 ctx.fillStyle = ticks.color;
12821 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));
12823 while(axis>=grid) {
12825 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12826 ctx.rotate(0 * Math.PI / 180 );
12827 ctx.fillStyle = label.color;
12828 if(config.showLabels) {
12829 if(label.type == 'Native') {
12830 ctx.fillText(labelValue, 0, 0);
12832 //html labels on y axis
12833 labelDiv = document.createElement('div');
12834 labelDiv.innerHTML = labelValue;
12835 labelDiv.className = "rotatedLabel";
12836 // labelDiv.class = "rotatedLabel";
12837 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12838 labelDiv.style.left = margin.left + "px";
12839 labelDiv.style.width = labelDim + "px";
12840 labelDiv.style.height = labelDim + "px";
12841 labelDiv.style.textAlign = "center";
12842 labelDiv.style.verticalAlign = "middle";
12843 labelDiv.style.position = "absolute";
12844 container.appendChild(labelDiv);
12848 ctx.fillStyle = ticks.color;
12849 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 );
12850 htmlOrigin += segmentLength;
12851 axis += segmentLength;
12852 labelValue += labelIncrement;
12861 renderBackground: function() {
12862 var canvas = this.canvas,
12863 config = this.config,
12864 backgroundColor = config.backgroundColor,
12865 size = canvas.getSize(),
12866 ctx = canvas.getCtx();
12867 ctx.fillStyle = backgroundColor;
12868 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12871 clear: function() {
12872 var canvas = this.canvas;
12873 var ctx = canvas.getCtx(),
12874 size = canvas.getSize();
12875 ctx.fillStyle = "rgba(255,255,255,0)";
12876 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12877 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
12879 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
12880 var canvas = this.canvas,
12881 size = canvas.getSize(),
12882 config = this.config,
12883 orgHeight = size.height,
12884 margin = config.Margin,
12886 grouped = config.type.split(':')[0] == 'grouped',
12887 horz = config.orientation == 'horizontal',
12888 ctx = canvas.getCtx();
12890 var newWindowWidth = document.body.offsetWidth;
12891 var diff = newWindowWidth - orgWindowWidth;
12892 var newWidth = orgContainerDivWidth + (diff/cols);
12893 var scale = newWidth/orgContainerDivWidth;
12894 canvas.resize(newWidth,orgHeight);
12895 if(typeof FlashCanvas == "undefined") {
12898 this.clear();// hack for flashcanvas bug not properly clearing rectangle
12901 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12904 this.loadJSON(json);
12911 Loads JSON data into the visualization.
12915 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>.
12919 var barChart = new $jit.BarChart(options);
12920 barChart.loadJSON(json);
12923 loadJSON: function(json) {
12924 if(this.busy) return;
12927 var prefix = $.time(),
12930 name = $.splat(json.label),
12931 color = $.splat(json.color || this.colors),
12932 config = this.config,
12933 gradient = !!config.type.split(":")[1],
12934 renderBackground = config.renderBackground,
12935 animate = config.animate,
12936 ticks = config.Ticks,
12937 title = config.Title,
12938 note = config.ScrollNote,
12939 subtitle = config.Subtitle,
12940 horz = config.orientation == 'horizontal',
12942 colorLength = color.length,
12943 nameLength = name.length;
12944 groupTotalValue = 0;
12945 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12946 var val = values[i];
12947 var valArray = $.splat(val.values);
12948 groupTotalValue += parseFloat(valArray.sum());
12951 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12952 var val = values[i];
12953 var valArray = $.splat(values[i].values);
12954 var valuelabelArray = $.splat(values[i].valuelabels);
12955 var linkArray = $.splat(values[i].links);
12956 var titleArray = $.splat(values[i].titles);
12957 var barTotalValue = valArray.sum();
12960 'id': prefix + val.label,
12965 '$linkArray': linkArray,
12966 '$gvl': val.gvaluelabel,
12967 '$titleArray': titleArray,
12968 '$valueArray': valArray,
12969 '$valuelabelArray': valuelabelArray,
12970 '$colorArray': color,
12971 '$colorMono': $.splat(color[i % colorLength]),
12972 '$stringArray': name,
12973 '$barTotalValue': barTotalValue,
12974 '$groupTotalValue': groupTotalValue,
12975 '$nodeCount': values.length,
12976 '$gradient': gradient,
12983 'id': prefix + '$root',
12994 this.normalizeDims();
12996 if(renderBackground) {
12997 this.renderBackground();
13000 if(!animate && ticks.enable) {
13001 this.renderTicks();
13003 if(!animate && note.text) {
13004 this.renderScrollNote();
13006 if(!animate && title.text) {
13007 this.renderTitle();
13009 if(!animate && subtitle.text) {
13010 this.renderSubtitle();
13014 st.select(st.root);
13018 modes: ['node-property:width:dimArray'],
13020 onComplete: function() {
13026 modes: ['node-property:height:dimArray'],
13028 onComplete: function() {
13041 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.
13045 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13046 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13051 barChart.updateJSON(json, {
13052 onComplete: function() {
13053 alert('update complete!');
13058 updateJSON: function(json, onComplete) {
13059 if(this.busy) return;
13063 var graph = st.graph;
13064 var values = json.values;
13065 var animate = this.config.animate;
13067 var horz = this.config.orientation == 'horizontal';
13068 $.each(values, function(v) {
13069 var n = graph.getByName(v.label);
13071 n.setData('valueArray', $.splat(v.values));
13073 n.setData('stringArray', $.splat(json.label));
13077 this.normalizeDims();
13079 st.select(st.root);
13083 modes: ['node-property:width:dimArray'],
13085 onComplete: function() {
13087 onComplete && onComplete.onComplete();
13092 modes: ['node-property:height:dimArray'],
13094 onComplete: function() {
13096 onComplete && onComplete.onComplete();
13103 //adds the little brown bar when hovering the node
13104 select: function(id, name) {
13106 if(!this.config.hoveredColor) return;
13107 var s = this.selected;
13108 if(s.id != id || s.name != name) {
13111 s.color = this.config.hoveredColor;
13112 this.st.graph.eachNode(function(n) {
13114 n.setData('border', s);
13116 n.setData('border', false);
13126 Returns an object containing as keys the legend names and as values hex strings with color values.
13131 var legend = barChart.getLegend();
13134 getLegend: function() {
13135 var legend = new Array();
13136 var name = new Array();
13137 var color = new Array();
13139 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13142 var colors = n.getData('colorArray'),
13143 len = colors.length;
13144 $.each(n.getData('stringArray'), function(s, i) {
13145 color[i] = colors[i % len];
13148 legend['name'] = name;
13149 legend['color'] = color;
13154 Method: getMaxValue
13156 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13161 var ans = barChart.getMaxValue();
13164 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13169 //will return 100 for all BarChart instances,
13170 //displaying all of them with the same scale
13171 $jit.BarChart.implement({
13172 'getMaxValue': function() {
13179 getMaxValue: function() {
13180 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13181 this.st.graph.eachNode(function(n) {
13182 var valArray = n.getData('valueArray'),
13184 if(!valArray) return;
13186 $.each(valArray, function(v) {
13190 acum = Math.max.apply(null, valArray);
13192 maxValue = maxValue>acum? maxValue:acum;
13197 setBarType: function(type) {
13198 this.config.type = type;
13199 this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13202 normalizeDims: function() {
13203 //number of elements
13204 var root = this.st.graph.getNode(this.st.root), l=0;
13205 root.eachAdjacency(function() {
13208 var maxValue = this.getMaxValue() || 1,
13209 size = this.st.canvas.getSize(),
13210 config = this.config,
13211 margin = config.Margin,
13212 ticks = config.Ticks,
13213 title = config.Title,
13214 subtitle = config.Subtitle,
13215 grouped = config.type.split(':')[0] == 'grouped',
13216 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13217 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13218 horz = config.orientation == 'horizontal',
13219 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13220 animate = config.animate,
13221 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13223 - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13224 dim1 = horz? 'height':'width',
13225 dim2 = horz? 'width':'height',
13226 basic = config.type.split(':')[0] == 'basic';
13229 var maxTickValue = Math.ceil(maxValue*.1)*10;
13230 if(maxTickValue == maxValue) {
13231 var length = maxTickValue.toString().length;
13232 maxTickValue = maxTickValue + parseInt(pad(1,length));
13235 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13238 this.st.graph.eachNode(function(n) {
13239 var acum = 0, animateValue = [];
13240 $.each(n.getData('valueArray'), function(v) {
13242 animateValue.push(0);
13246 fixedDim = animateValue.length * 40;
13248 n.setData(dim1, fixedDim);
13252 n.setData(dim2, acum * height / maxValue, 'end');
13253 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13254 return n * height / maxValue;
13256 var dimArray = n.getData('dimArray');
13258 n.setData('dimArray', animateValue);
13264 n.setData(dim2, acum * height / maxTickValue);
13265 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13266 return n * height / maxTickValue;
13269 n.setData(dim2, acum * height / maxValue);
13270 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13271 return n * height / maxValue;
13279 //funnel chart options
13282 Options.FunnelChart = {
13286 type: 'stacked', //stacked, grouped, : gradient
13287 labelOffset: 3, //label offset
13288 barsOffset: 0, //distance between bars
13289 hoveredColor: '#9fd4ff',
13290 orientation: 'vertical',
13291 showAggregates: true,
13304 $jit.ST.Plot.NodeTypes.implement({
13305 'funnelchart-basic' : {
13306 'render' : function(node, canvas) {
13307 var pos = node.pos.getc(true),
13308 width = node.getData('width'),
13309 height = node.getData('height'),
13310 algnPos = this.getAlignedPos(pos, width, height),
13311 x = algnPos.x, y = algnPos.y,
13312 dimArray = node.getData('dimArray'),
13313 valueArray = node.getData('valueArray'),
13314 valuelabelArray = node.getData('valuelabelArray'),
13315 linkArray = node.getData('linkArray'),
13316 colorArray = node.getData('colorArray'),
13317 colorLength = colorArray.length,
13318 stringArray = node.getData('stringArray');
13319 var ctx = canvas.getCtx(),
13321 border = node.getData('border'),
13322 gradient = node.getData('gradient'),
13323 config = node.getData('config'),
13324 horz = config.orientation == 'horizontal',
13325 aggregates = config.showAggregates,
13326 showLabels = config.showLabels,
13327 label = config.Label,
13328 size = canvas.getSize(),
13329 labelOffset = config.labelOffset + 10;
13330 minWidth = width * .25;
13333 if (colorArray && dimArray && stringArray) {
13336 // horizontal lines
13337 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13338 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13340 if(label.type == 'Native') {
13341 if(showLabels(node.name, valAcum, node)) {
13342 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13343 var stringValue = stringArray[i];
13344 var valueLabel = String(valuelabelArray[i]);
13345 var mV = ctx.measureText(stringValue);
13346 var mVL = ctx.measureText(valueLabel);
13347 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13348 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13349 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13350 var bottomWidth = minWidth + ((acum) * ratio);
13351 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13352 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13353 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13354 // ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13358 ctx.moveTo(bottomWidth/2,y - acum); //
13359 ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight); // top right
13360 ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight); // bottom right
13364 ctx.moveTo(-bottomWidth/2,y - acum); //
13365 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight); // top right
13366 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight); // bottom right
13371 acum += (dimArray[i] || 0);
13372 valAcum += (valueArray[i] || 0);
13379 //funnel segments and labels
13380 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13381 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13382 var colori = colorArray[i % colorLength];
13383 if(label.type == 'Native') {
13384 var stringValue = stringArray[i];
13385 var valueLabel = String(valuelabelArray[i]);
13386 var mV = ctx.measureText(stringValue);
13387 var mVL = ctx.measureText(valueLabel);
13392 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13393 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13394 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13395 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13397 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13398 var bottomWidth = minWidth + ((acum) * ratio);
13399 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13404 linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13405 var colorRgb = $.hexToRgb(colori);
13406 var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
13407 function(v) { return (v * .5) >> 0; });
13408 linear.addColorStop(0, 'rgba('+color+',1)');
13409 linear.addColorStop(0.5, 'rgba('+colorRgb+',1)');
13410 linear.addColorStop(1, 'rgba('+color+',1)');
13411 ctx.fillStyle = linear;
13415 ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13416 ctx.lineTo(topWidth/2,y - acum - dimArray[i]); // top right
13417 ctx.lineTo(bottomWidth/2,y - acum); // bottom right
13418 ctx.lineTo(-bottomWidth/2,y - acum); // bottom left
13423 if(border && border.name == stringArray[i]) {
13425 opt.dimValue = dimArray[i];
13432 ctx.strokeStyle = border.color;
13434 //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13438 if(label.type == 'Native') {
13440 ctx.fillStyle = ctx.strokeStyle = label.color;
13441 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13442 ctx.textBaseline = 'middle';
13444 acumValueLabel = valAcum;
13446 if(showLabels(node.name, valAcum, node)) {
13449 ctx.textAlign = 'left';
13450 ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13451 ctx.textAlign = 'right';
13452 ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13457 acum += (dimArray[i] || 0);
13458 valAcum += (valueArray[i] || 0);
13464 'contains': function(node, mpos) {
13465 var pos = node.pos.getc(true),
13466 width = node.getData('width'),
13467 height = node.getData('height'),
13468 algnPos = this.getAlignedPos(pos, width, height),
13469 x = algnPos.x, y = algnPos.y,
13470 dimArray = node.getData('dimArray'),
13471 config = node.getData('config'),
13472 st = node.getData('st'),
13474 horz = config.orientation == 'horizontal',
13475 minWidth = width * .25;
13477 canvas = node.getData('canvas'),
13478 size = canvas.getSize(),
13479 offsetY = st.config.offsetY;
13480 //bounding box check
13482 if(mpos.y > y || mpos.y < y - height) {
13486 var newY = Math.abs(mpos.y + offsetY);
13487 var bound = minWidth + (newY * ratio);
13488 var boundLeft = -bound/2;
13489 var boundRight = bound/2;
13490 if(mpos.x < boundLeft || mpos.x > boundRight ) {
13496 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13497 var dimi = dimArray[i];
13501 var url = Url.decode(node.getData('linkArray')[i]);
13503 var intersec = acum;
13504 if(mpos.y >= intersec) {
13506 'name': node.getData('stringArray')[i],
13507 'color': node.getData('colorArray')[i],
13508 'value': node.getData('valueArray')[i],
13509 'percentage': node.getData('percentageArray')[i],
13510 'valuelabel': node.getData('valuelabelArray')[i],
13525 A visualization that displays funnel charts.
13527 Constructor Options:
13529 See <Options.FunnelChart>.
13532 $jit.FunnelChart = new Class({
13534 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13538 initialize: function(opt) {
13539 this.controller = this.config =
13540 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13541 Label: { type: 'Native' }
13543 //set functions for showLabels and showAggregates
13544 var showLabels = this.config.showLabels,
13545 typeLabels = $.type(showLabels),
13546 showAggregates = this.config.showAggregates,
13547 typeAggregates = $.type(showAggregates);
13548 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13549 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13550 Options.Fx.clearCanvas = false;
13551 this.initializeViz();
13554 initializeViz: function() {
13555 var config = this.config, that = this;
13556 var nodeType = config.type.split(":")[0],
13557 horz = config.orientation == 'horizontal',
13559 var st = new $jit.ST({
13560 injectInto: config.injectInto,
13561 orientation: horz? 'left' : 'bottom',
13563 background: config.background,
13564 renderBackground: config.renderBackground,
13565 backgroundColor: config.backgroundColor,
13566 colorStop1: config.colorStop1,
13567 colorStop2: config.colorStop2,
13568 siblingOffset: config.segmentOffset,
13570 withLabels: config.Label.type != 'Native',
13571 useCanvas: config.useCanvas,
13573 type: config.Label.type
13577 type: 'funnelchart-' + nodeType,
13586 enable: config.Tips.enable,
13589 onShow: function(tip, node, contains) {
13590 var elem = contains;
13591 config.Tips.onShow(tip, elem, node);
13592 if(elem.link != 'undefined' && elem.link != '') {
13593 document.body.style.cursor = 'pointer';
13596 onHide: function(call) {
13597 document.body.style.cursor = 'default';
13604 onClick: function(node, eventInfo, evt) {
13605 if(!config.Events.enable) return;
13606 var elem = eventInfo.getContains();
13607 config.Events.onClick(elem, eventInfo, evt);
13609 onMouseMove: function(node, eventInfo, evt) {
13610 if(!config.hoveredColor) return;
13612 var elem = eventInfo.getContains();
13613 that.select(node.id, elem.name, elem.index);
13615 that.select(false, false, false);
13619 onCreateLabel: function(domElement, node) {
13620 var labelConf = config.Label,
13621 valueArray = node.getData('valueArray'),
13622 idArray = node.getData('idArray'),
13623 valuelabelArray = node.getData('valuelabelArray'),
13624 stringArray = node.getData('stringArray');
13625 size = st.canvas.getSize()
13628 for(var i=0, l=valueArray.length; i<l; i++) {
13630 wrapper: document.createElement('div'),
13631 valueLabel: document.createElement('div'),
13632 label: document.createElement('div')
13634 var wrapper = nlbs.wrapper,
13635 label = nlbs.label,
13636 valueLabel = nlbs.valueLabel,
13637 wrapperStyle = wrapper.style,
13638 labelStyle = label.style,
13639 valueLabelStyle = valueLabel.style;
13640 //store node labels
13641 nodeLabels[idArray[i]] = nlbs;
13643 wrapper.appendChild(label);
13644 wrapper.appendChild(valueLabel);
13646 wrapperStyle.position = 'relative';
13647 wrapperStyle.overflow = 'visible';
13648 wrapperStyle.fontSize = labelConf.size + 'px';
13649 wrapperStyle.fontFamily = labelConf.family;
13650 wrapperStyle.color = labelConf.color;
13651 wrapperStyle.textAlign = 'center';
13652 wrapperStyle.width = size.width + 'px';
13653 valueLabelStyle.position = labelStyle.position = 'absolute';
13654 valueLabelStyle.left = labelStyle.left = '0px';
13655 valueLabelStyle.width = (size.width/3) + 'px';
13656 valueLabelStyle.textAlign = 'right';
13657 label.innerHTML = stringArray[i];
13658 valueLabel.innerHTML = valuelabelArray[i];
13659 domElement.id = prefix+'funnel';
13660 domElement.style.width = size.width + 'px';
13662 domElement.appendChild(wrapper);
13666 onPlaceLabel: function(domElement, node) {
13668 var dimArray = node.getData('dimArray'),
13669 idArray = node.getData('idArray'),
13670 valueArray = node.getData('valueArray'),
13671 valuelabelArray = node.getData('valuelabelArray'),
13672 stringArray = node.getData('stringArray');
13673 size = st.canvas.getSize(),
13674 pos = node.pos.getc(true),
13675 domElement.style.left = "0px",
13676 domElement.style.top = "0px",
13677 minWidth = node.getData('width') * .25,
13679 pos = node.pos.getc(true),
13680 labelConf = config.Label;
13683 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13685 var labels = nodeLabels[idArray[i]],
13686 wrapperStyle = labels.wrapper.style,
13687 labelStyle = labels.label.style,
13688 valueLabelStyle = labels.valueLabel.style;
13689 var bottomWidth = minWidth + (acum * ratio);
13691 font = parseInt(wrapperStyle.fontSize, 10),
13692 domStyle = domElement.style;
13696 wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13697 valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13698 labelStyle.left = (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13700 acum += (dimArray[i] || 0);
13708 var size = st.canvas.getSize(),
13709 margin = config.Margin;
13710 title = config.Title;
13711 subtitle = config.Subtitle;
13714 st.config.offsetY = -size.height/2 + margin.bottom
13715 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13717 st.config.offsetX = (margin.right - margin.left)/2;
13721 this.canvas = this.st.canvas;
13724 renderTitle: function() {
13725 var canvas = this.canvas,
13726 size = canvas.getSize(),
13727 config = this.config,
13728 margin = config.Margin,
13729 label = config.Label,
13730 title = config.Title;
13731 ctx = canvas.getCtx();
13732 ctx.fillStyle = title.color;
13733 ctx.textAlign = 'left';
13734 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13735 if(label.type == 'Native') {
13736 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13740 renderSubtitle: function() {
13741 var canvas = this.canvas,
13742 size = canvas.getSize(),
13743 config = this.config,
13744 margin = config.Margin,
13745 label = config.Label,
13746 subtitle = config.Subtitle;
13747 ctx = canvas.getCtx();
13748 ctx.fillStyle = title.color;
13749 ctx.textAlign = 'left';
13750 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13751 if(label.type == 'Native') {
13752 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13757 renderDropShadow: function() {
13758 var canvas = this.canvas,
13759 size = canvas.getSize(),
13760 config = this.config,
13761 margin = config.Margin,
13762 horz = config.orientation == 'horizontal',
13763 label = config.Label,
13764 title = config.Title,
13765 shadowThickness = 4,
13766 subtitle = config.Subtitle,
13767 ctx = canvas.getCtx(),
13768 minwidth = (size.width/8) * .25,
13769 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13770 topMargin = (title.text? title.size + title.offset : 0) + margin.top,
13771 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13772 - (config.showLabels && (config.Label.size + config.labelOffset)),
13774 topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13775 topY = (-size.height/2) + topMargin - shadowThickness;
13776 bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13777 bottomWidth = minwidth + shadowThickness;
13779 ctx.fillStyle = "rgba(0,0,0,.2)";
13780 ctx.moveTo(0,topY);
13781 ctx.lineTo(-topWidth/2,topY); //top left
13782 ctx.lineTo(-bottomWidth/2,bottomY); // bottom left
13783 ctx.lineTo(bottomWidth/2,bottomY); // bottom right
13784 ctx.lineTo(topWidth/2,topY); // top right
13791 renderBackground: function() {
13792 var canvas = this.canvas,
13793 config = this.config,
13794 backgroundColor = config.backgroundColor,
13795 size = canvas.getSize(),
13796 ctx = canvas.getCtx();
13797 //ctx.globalCompositeOperation = "destination-over";
13798 ctx.fillStyle = backgroundColor;
13799 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13801 clear: function() {
13802 var canvas = this.canvas;
13803 var ctx = canvas.getCtx(),
13804 size = canvas.getSize();
13805 ctx.fillStyle = "rgba(255,255,255,0)";
13806 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13807 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13809 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
13810 var canvas = this.canvas,
13811 size = canvas.getSize(),
13812 config = this.config,
13813 orgHeight = size.height,
13814 margin = config.Margin,
13816 label = config.Label,
13817 horz = config.orientation == 'horizontal',
13818 ctx = canvas.getCtx();
13821 var newWindowWidth = document.body.offsetWidth;
13822 var diff = newWindowWidth - orgWindowWidth;
13823 var newWidth = orgContainerDivWidth + (diff/cols);
13824 canvas.resize(newWidth,orgHeight);
13826 if(typeof FlashCanvas == "undefined") {
13829 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13831 this.loadJSON(json);
13835 loadJSON: function(json) {
13836 if(this.busy) return;
13838 var prefix = $.time(),
13841 name = $.splat(json.label),
13842 color = $.splat(json.color || this.colors),
13843 config = this.config,
13844 canvas = this.canvas,
13845 gradient = !!config.type.split(":")[1],
13846 animate = config.animate,
13847 title = config.Title,
13848 subtitle = config.Subtitle,
13849 renderBackground = config.renderBackground,
13850 horz = config.orientation == 'horizontal',
13852 colorLength = color.length,
13853 nameLength = name.length,
13855 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13856 var val = values[i];
13857 var valArray = $.splat(val.values);
13858 totalValue += parseFloat(valArray.sum());
13862 var nameArray = new Array();
13863 var idArray = new Array();
13864 var valArray = new Array();
13865 var valuelabelArray = new Array();
13866 var linkArray = new Array();
13867 var titleArray = new Array();
13868 var percentageArray = new Array();
13870 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13871 var val = values[i];
13872 nameArray[i] = $.splat(val.label);
13873 idArray[i] = $.splat(prefix + val.label);
13874 valArray[i] = $.splat(val.values);
13875 valuelabelArray[i] = $.splat(val.valuelabels);
13876 linkArray[i] = $.splat(val.links);
13877 titleArray[i] = $.splat(val.titles);
13878 percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13883 nameArray.reverse();
13884 valArray.reverse();
13885 valuelabelArray.reverse();
13886 linkArray.reverse();
13887 titleArray.reverse();
13888 percentageArray.reverse();
13891 'id': prefix + val.label,
13896 '$idArray': idArray,
13897 '$linkArray': linkArray,
13898 '$titleArray': titleArray,
13899 '$valueArray': valArray,
13900 '$valuelabelArray': valuelabelArray,
13901 '$colorArray': color,
13902 '$colorMono': $.splat(color[i % colorLength]),
13903 '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
13904 '$gradient': gradient,
13906 '$percentageArray' : percentageArray,
13914 'id': prefix + '$root',
13925 this.normalizeDims();
13927 if(renderBackground) {
13928 this.renderBackground();
13930 if(!animate && title.text) {
13931 this.renderTitle();
13933 if(!animate && subtitle.text) {
13934 this.renderSubtitle();
13936 if(typeof FlashCanvas == "undefined") {
13937 this.renderDropShadow();
13940 st.select(st.root);
13944 modes: ['node-property:width:dimArray'],
13946 onComplete: function() {
13952 modes: ['node-property:height:dimArray'],
13954 onComplete: function() {
13967 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.
13971 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13972 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13977 barChart.updateJSON(json, {
13978 onComplete: function() {
13979 alert('update complete!');
13984 updateJSON: function(json, onComplete) {
13985 if(this.busy) return;
13989 var graph = st.graph;
13990 var values = json.values;
13991 var animate = this.config.animate;
13993 var horz = this.config.orientation == 'horizontal';
13994 $.each(values, function(v) {
13995 var n = graph.getByName(v.label);
13997 n.setData('valueArray', $.splat(v.values));
13999 n.setData('stringArray', $.splat(json.label));
14003 this.normalizeDims();
14005 st.select(st.root);
14009 modes: ['node-property:width:dimArray'],
14011 onComplete: function() {
14013 onComplete && onComplete.onComplete();
14018 modes: ['node-property:height:dimArray'],
14020 onComplete: function() {
14022 onComplete && onComplete.onComplete();
14029 //adds the little brown bar when hovering the node
14030 select: function(id, name) {
14032 if(!this.config.hoveredColor) return;
14033 var s = this.selected;
14034 if(s.id != id || s.name != name) {
14037 s.color = this.config.hoveredColor;
14038 this.st.graph.eachNode(function(n) {
14040 n.setData('border', s);
14042 n.setData('border', false);
14052 Returns an object containing as keys the legend names and as values hex strings with color values.
14057 var legend = barChart.getLegend();
14060 getLegend: function() {
14061 var legend = new Array();
14062 var name = new Array();
14063 var color = new Array();
14065 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14068 var colors = n.getData('colorArray'),
14069 len = colors.length;
14070 $.each(n.getData('stringArray'), function(s, i) {
14071 color[i] = colors[i % len];
14074 legend['name'] = name;
14075 legend['color'] = color;
14080 Method: getMaxValue
14082 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14087 var ans = barChart.getMaxValue();
14090 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14095 //will return 100 for all BarChart instances,
14096 //displaying all of them with the same scale
14097 $jit.BarChart.implement({
14098 'getMaxValue': function() {
14105 getMaxValue: function() {
14106 var maxValue = 0, stacked = true;
14107 this.st.graph.eachNode(function(n) {
14108 var valArray = n.getData('valueArray'),
14110 if(!valArray) return;
14112 $.each(valArray, function(v) {
14116 acum = Math.max.apply(null, valArray);
14118 maxValue = maxValue>acum? maxValue:acum;
14123 setBarType: function(type) {
14124 this.config.type = type;
14125 this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14128 normalizeDims: function() {
14129 //number of elements
14130 var root = this.st.graph.getNode(this.st.root), l=0;
14131 root.eachAdjacency(function() {
14134 var maxValue = this.getMaxValue() || 1,
14135 size = this.st.canvas.getSize(),
14136 config = this.config,
14137 margin = config.Margin,
14138 title = config.Title,
14139 subtitle = config.Subtitle,
14140 marginWidth = margin.left + margin.right,
14141 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14142 horz = config.orientation == 'horizontal',
14143 animate = config.animate,
14144 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14146 - (config.showLabels && (config.Label.size + config.labelOffset)),
14147 dim1 = horz? 'height':'width',
14148 dim2 = horz? 'width':'height';
14151 minWidth = size.width/8;
14155 this.st.graph.eachNode(function(n) {
14156 var acum = 0, animateValue = [];
14157 $.each(n.getData('valueArray'), function(v) {
14159 animateValue.push(0);
14161 n.setData(dim1, minWidth);
14164 n.setData(dim2, acum * height / maxValue, 'end');
14165 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14166 return n * height / maxValue;
14168 var dimArray = n.getData('dimArray');
14170 n.setData('dimArray', animateValue);
14173 n.setData(dim2, acum * height / maxValue);
14174 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14175 return n * height / maxValue;
14186 * File: Options.PieChart.js
14190 Object: Options.PieChart
14192 <PieChart> options.
14193 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14199 Options.PieChart = {
14205 hoveredColor: '#9fd4ff',
14207 resizeLabels: false,
14208 updateHeights: false
14217 var pie = new $jit.PieChart({
14220 type: 'stacked:gradient'
14227 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14228 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14229 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14230 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14231 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14232 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14233 showLabels - (boolean) Default's *true*. Display the name of the slots.
14234 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.
14235 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.
14238 Options.PieChart = {
14242 offset: 25, // page offset
14244 labelOffset: 3, // label offset
14245 type: 'stacked', // gradient
14247 hoveredColor: '#9fd4ff',
14258 resizeLabels: false,
14260 //only valid for mono-valued datasets
14261 updateHeights: false
14265 * Class: Layouts.Radial
14267 * Implements a Radial Layout.
14271 * <RGraph>, <Hypertree>
14274 Layouts.Radial = new Class({
14279 * Computes nodes' positions.
14283 * property - _optional_ A <Graph.Node> position property to store the new
14284 * positions. Possible values are 'pos', 'end' or 'start'.
14287 compute : function(property) {
14288 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14289 NodeDim.compute(this.graph, prop, this.config);
14290 this.graph.computeLevels(this.root, 0, "ignore");
14291 var lengthFunc = this.createLevelDistanceFunc();
14292 this.computeAngularWidths(prop);
14293 this.computePositions(prop, lengthFunc);
14299 * Performs the main algorithm for computing node positions.
14301 computePositions : function(property, getLength) {
14302 var propArray = property;
14303 var graph = this.graph;
14304 var root = graph.getNode(this.root);
14305 var parent = this.parent;
14306 var config = this.config;
14308 for ( var i=0, l=propArray.length; i < l; i++) {
14309 var pi = propArray[i];
14310 root.setPos($P(0, 0), pi);
14311 root.setData('span', Math.PI * 2, pi);
14319 graph.eachBFS(this.root, function(elem) {
14320 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14321 var angleInit = elem.angleSpan.begin;
14322 var len = getLength(elem);
14323 //Calculate the sum of all angular widths
14324 var totalAngularWidths = 0, subnodes = [], maxDim = {};
14325 elem.eachSubnode(function(sib) {
14326 totalAngularWidths += sib._treeAngularWidth;
14328 for ( var i=0, l=propArray.length; i < l; i++) {
14329 var pi = propArray[i], dim = sib.getData('dim', pi);
14330 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14332 subnodes.push(sib);
14334 //Maintain children order
14335 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14336 if (parent && parent.id == elem.id && subnodes.length > 0
14337 && subnodes[0].dist) {
14338 subnodes.sort(function(a, b) {
14339 return (a.dist >= b.dist) - (a.dist <= b.dist);
14342 //Calculate nodes positions.
14343 for (var k = 0, ls=subnodes.length; k < ls; k++) {
14344 var child = subnodes[k];
14345 if (!child._flag) {
14346 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14347 var theta = angleInit + angleProportion / 2;
14349 for ( var i=0, l=propArray.length; i < l; i++) {
14350 var pi = propArray[i];
14351 child.setPos($P(theta, len), pi);
14352 child.setData('span', angleProportion, pi);
14353 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14356 child.angleSpan = {
14358 end : angleInit + angleProportion
14360 angleInit += angleProportion;
14367 * Method: setAngularWidthForNodes
14369 * Sets nodes angular widths.
14371 setAngularWidthForNodes : function(prop) {
14372 this.graph.eachBFS(this.root, function(elem, i) {
14373 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14374 elem._angularWidth = diamValue / i;
14379 * Method: setSubtreesAngularWidth
14381 * Sets subtrees angular widths.
14383 setSubtreesAngularWidth : function() {
14385 this.graph.eachNode(function(elem) {
14386 that.setSubtreeAngularWidth(elem);
14391 * Method: setSubtreeAngularWidth
14393 * Sets the angular width for a subtree.
14395 setSubtreeAngularWidth : function(elem) {
14396 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14397 elem.eachSubnode(function(child) {
14398 that.setSubtreeAngularWidth(child);
14399 sumAW += child._treeAngularWidth;
14401 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14405 * Method: computeAngularWidths
14407 * Computes nodes and subtrees angular widths.
14409 computeAngularWidths : function(prop) {
14410 this.setAngularWidthForNodes(prop);
14411 this.setSubtreesAngularWidth();
14418 * File: Sunburst.js
14424 A radial space filling tree visualization.
14428 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14432 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.
14436 All <Loader> methods
14438 Constructor Options:
14440 Inherits options from
14443 - <Options.Controller>
14449 - <Options.NodeStyles>
14450 - <Options.Navigation>
14452 Additionally, there are other parameters and some default values changed
14454 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14455 levelDistance - (number) Default's *100*. The distance between levels of the tree.
14456 Node.type - Described in <Options.Node>. Default's to *multipie*.
14457 Node.height - Described in <Options.Node>. Default's *0*.
14458 Edge.type - Described in <Options.Edge>. Default's *none*.
14459 Label.textAlign - Described in <Options.Label>. Default's *start*.
14460 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14462 Instance Properties:
14464 canvas - Access a <Canvas> instance.
14465 graph - Access a <Graph> instance.
14466 op - Access a <Sunburst.Op> instance.
14467 fx - Access a <Sunburst.Plot> instance.
14468 labels - Access a <Sunburst.Label> interface implementation.
14472 $jit.Sunburst = new Class({
14474 Implements: [ Loader, Extras, Layouts.Radial ],
14476 initialize: function(controller) {
14477 var $Sunburst = $jit.Sunburst;
14480 interpolation: 'linear',
14481 levelDistance: 100,
14483 'type': 'multipie',
14490 textAlign: 'start',
14491 textBaseline: 'middle'
14495 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14496 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14498 var canvasConfig = this.config;
14499 if(canvasConfig.useCanvas) {
14500 this.canvas = canvasConfig.useCanvas;
14501 this.config.labelContainer = this.canvas.id + '-label';
14503 if(canvasConfig.background) {
14504 canvasConfig.background = $.merge({
14506 colorStop1: this.config.colorStop1,
14507 colorStop2: this.config.colorStop2
14508 }, canvasConfig.background);
14510 this.canvas = new Canvas(this, canvasConfig);
14511 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14514 this.graphOptions = {
14522 this.graph = new Graph(this.graphOptions, this.config.Node,
14524 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14525 this.fx = new $Sunburst.Plot(this, $Sunburst);
14526 this.op = new $Sunburst.Op(this);
14529 this.rotated = null;
14531 // initialize extras
14532 this.initializeExtras();
14537 createLevelDistanceFunc
14539 Returns the levelDistance function used for calculating a node distance
14540 to its origin. This function returns a function that is computed
14541 per level and not per node, such that all nodes with the same depth will have the
14542 same distance to the origin. The resulting function gets the
14543 parent node as parameter and returns a float.
14546 createLevelDistanceFunc: function() {
14547 var ld = this.config.levelDistance;
14548 return function(elem) {
14549 return (elem._depth + 1) * ld;
14556 Computes positions and plots the tree.
14559 refresh: function() {
14567 An alias for computing new positions to _endPos_
14574 reposition: function() {
14575 this.compute('end');
14581 Rotates the graph so that the selected node is horizontal on the right.
14585 node - (object) A <Graph.Node>.
14586 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14587 opt - (object) Configuration options merged with this visualization configuration options.
14591 <Sunburst.rotateAngle>
14594 rotate: function(node, method, opt) {
14595 var theta = node.getPos(opt.property || 'current').getp(true).theta;
14596 this.rotated = node;
14597 this.rotateAngle(-theta, method, opt);
14601 Method: rotateAngle
14603 Rotates the graph of an angle theta.
14607 node - (object) A <Graph.Node>.
14608 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14609 opt - (object) Configuration options merged with this visualization configuration options.
14616 rotateAngle: function(theta, method, opt) {
14618 var options = $.merge(this.config, opt || {}, {
14621 var prop = opt.property || (method === "animate" ? 'end' : 'current');
14622 if(method === 'animate') {
14623 this.fx.animation.pause();
14625 this.graph.eachNode(function(n) {
14626 var p = n.getPos(prop);
14629 p.theta += Math.PI * 2;
14632 if (method == 'animate') {
14633 this.fx.animate(options);
14634 } else if (method == 'replot') {
14643 Plots the Sunburst. This is a shortcut to *fx.plot*.
14650 $jit.Sunburst.$extend = true;
14652 (function(Sunburst) {
14657 Custom extension of <Graph.Op>.
14661 All <Graph.Op> methods
14668 Sunburst.Op = new Class( {
14670 Implements: Graph.Op
14675 Class: Sunburst.Plot
14677 Custom extension of <Graph.Plot>.
14681 All <Graph.Plot> methods
14688 Sunburst.Plot = new Class( {
14690 Implements: Graph.Plot
14695 Class: Sunburst.Label
14697 Custom extension of <Graph.Label>.
14698 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14702 All <Graph.Label> methods and subclasses.
14706 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14709 Sunburst.Label = {};
14712 Sunburst.Label.Native
14714 Custom extension of <Graph.Label.Native>.
14718 All <Graph.Label.Native> methods
14722 <Graph.Label.Native>
14724 Sunburst.Label.Native = new Class( {
14725 Implements: Graph.Label.Native,
14727 initialize: function(viz) {
14729 this.label = viz.config.Label;
14730 this.config = viz.config;
14733 renderLabel: function(canvas, node, controller) {
14734 var span = node.getData('span');
14735 if(span < Math.PI /2 && Math.tan(span) *
14736 this.config.levelDistance * node._depth < 10) {
14739 var ctx = canvas.getCtx();
14740 var measure = ctx.measureText(node.name);
14741 if (node.id == this.viz.root) {
14742 var x = -measure.width / 2, y = 0, thetap = 0;
14746 var ld = controller.levelDistance - indent;
14747 var clone = node.pos.clone();
14748 clone.rho += indent;
14749 var p = clone.getp(true);
14750 var ct = clone.getc(true);
14751 var x = ct.x, y = ct.y;
14752 // get angle in degrees
14754 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14755 var thetap = cond ? p.theta + pi : p.theta;
14757 x -= Math.abs(Math.cos(p.theta) * measure.width);
14758 y += Math.sin(p.theta) * measure.width;
14759 } else if (node.id == this.viz.root) {
14760 x -= measure.width / 2;
14764 ctx.translate(x, y);
14765 ctx.rotate(thetap);
14766 ctx.fillText(node.name, 0, 0);
14774 Custom extension of <Graph.Label.SVG>.
14778 All <Graph.Label.SVG> methods
14785 Sunburst.Label.SVG = new Class( {
14786 Implements: Graph.Label.SVG,
14788 initialize: function(viz) {
14795 Overrides abstract method placeLabel in <Graph.Plot>.
14799 tag - A DOM label element.
14800 node - A <Graph.Node>.
14801 controller - A configuration/controller object passed to the visualization.
14804 placeLabel: function(tag, node, controller) {
14805 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14806 var radius = canvas.getSize();
14808 x: Math.round(pos.x + radius.width / 2),
14809 y: Math.round(pos.y + radius.height / 2)
14811 tag.setAttribute('x', labelPos.x);
14812 tag.setAttribute('y', labelPos.y);
14814 var bb = tag.getBBox();
14816 // center the label
14817 var x = tag.getAttribute('x');
14818 var y = tag.getAttribute('y');
14819 // get polar coordinates
14820 var p = node.pos.getp(true);
14821 // get angle in degrees
14823 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14825 tag.setAttribute('x', x - bb.width);
14826 tag.setAttribute('y', y - bb.height);
14827 } else if (node.id == viz.root) {
14828 tag.setAttribute('x', x - bb.width / 2);
14831 var thetap = cond ? p.theta + pi : p.theta;
14833 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14837 controller.onPlaceLabel(tag, node);
14842 Sunburst.Label.HTML
14844 Custom extension of <Graph.Label.HTML>.
14848 All <Graph.Label.HTML> methods.
14855 Sunburst.Label.HTML = new Class( {
14856 Implements: Graph.Label.HTML,
14858 initialize: function(viz) {
14864 Overrides abstract method placeLabel in <Graph.Plot>.
14868 tag - A DOM label element.
14869 node - A <Graph.Node>.
14870 controller - A configuration/controller object passed to the visualization.
14873 placeLabel: function(tag, node, controller) {
14874 var pos = node.pos.clone(),
14875 canvas = this.viz.canvas,
14876 height = node.getData('height'),
14877 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14878 radius = canvas.getSize();
14880 pos = pos.getc(true);
14883 x: Math.round(pos.x + radius.width / 2),
14884 y: Math.round(pos.y + radius.height / 2)
14887 var style = tag.style;
14888 style.left = labelPos.x + 'px';
14889 style.top = labelPos.y + 'px';
14890 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14892 controller.onPlaceLabel(tag, node);
14897 Class: Sunburst.Plot.NodeTypes
14899 This class contains a list of <Graph.Node> built-in types.
14900 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14902 You can add your custom node types, customizing your visualization to the extreme.
14907 Sunburst.Plot.NodeTypes.implement({
14909 'render': function(node, canvas) {
14910 //print your custom node to canvas
14913 'contains': function(node, pos) {
14914 //return true if pos is inside the node or false otherwise
14921 Sunburst.Plot.NodeTypes = new Class( {
14924 'contains': $.lambda(false),
14925 'anglecontains': function(node, pos) {
14926 var span = node.getData('span') / 2, theta = node.pos.theta;
14927 var begin = theta - span, end = theta + span;
14929 begin += Math.PI * 2;
14930 var atan = Math.atan2(pos.y, pos.x);
14932 atan += Math.PI * 2;
14934 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14936 return atan > begin && atan < end;
14939 'anglecontainsgauge': function(node, pos) {
14940 var span = node.getData('span') / 2, theta = node.pos.theta;
14941 var config = node.getData('config');
14942 var ld = this.config.levelDistance;
14943 var yOffset = pos.y-(ld/2);
14944 var begin = ((theta - span)/2)+Math.PI,
14945 end = ((theta + span)/2)+Math.PI;
14948 begin += Math.PI * 2;
14949 var atan = Math.atan2(yOffset, pos.x);
14953 atan += Math.PI * 2;
14957 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14959 return atan > begin && atan < end;
14965 'render': function(node, canvas) {
14966 var span = node.getData('span') / 2, theta = node.pos.theta;
14967 var begin = theta - span, end = theta + span;
14968 var polarNode = node.pos.getp(true);
14969 var polar = new Polar(polarNode.rho, begin);
14970 var p1coord = polar.getc(true);
14972 var p2coord = polar.getc(true);
14974 var ctx = canvas.getCtx();
14977 ctx.lineTo(p1coord.x, p1coord.y);
14979 ctx.lineTo(p2coord.x, p2coord.y);
14981 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14985 'contains': function(node, pos) {
14986 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14987 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14988 var ld = this.config.levelDistance, d = node._depth;
14989 return (rho <= ld * d);
14995 'render': function(node, canvas) {
14996 var height = node.getData('height');
14997 var ldist = height? height : this.config.levelDistance;
14998 var span = node.getData('span') / 2, theta = node.pos.theta;
14999 var begin = theta - span, end = theta + span;
15000 var polarNode = node.pos.getp(true);
15002 var polar = new Polar(polarNode.rho, begin);
15003 var p1coord = polar.getc(true);
15006 var p2coord = polar.getc(true);
15008 polar.rho += ldist;
15009 var p3coord = polar.getc(true);
15011 polar.theta = begin;
15012 var p4coord = polar.getc(true);
15014 var ctx = canvas.getCtx();
15017 ctx.arc(0, 0, polarNode.rho, begin, end, false);
15018 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15019 ctx.moveTo(p1coord.x, p1coord.y);
15020 ctx.lineTo(p4coord.x, p4coord.y);
15021 ctx.moveTo(p2coord.x, p2coord.y);
15022 ctx.lineTo(p3coord.x, p3coord.y);
15025 if (node.collapsed) {
15030 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15036 'contains': function(node, pos) {
15037 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15038 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15039 var height = node.getData('height');
15040 var ldist = height? height : this.config.levelDistance;
15041 var ld = this.config.levelDistance, d = node._depth;
15042 return (rho >= ld * d) && (rho <= (ld * d + ldist));
15048 'gradient-multipie': {
15049 'render': function(node, canvas) {
15050 var ctx = canvas.getCtx();
15051 var height = node.getData('height');
15052 var ldist = height? height : this.config.levelDistance;
15053 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15054 0, 0, node.getPos().rho + ldist);
15056 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15057 $.each(colorArray, function(i) {
15058 ans.push(parseInt(i * 0.5, 10));
15060 var endColor = $.rgbToHex(ans);
15061 radialGradient.addColorStop(0, endColor);
15062 radialGradient.addColorStop(1, node.getData('color'));
15063 ctx.fillStyle = radialGradient;
15064 this.nodeTypes['multipie'].render.call(this, node, canvas);
15066 'contains': function(node, pos) {
15067 return this.nodeTypes['multipie'].contains.call(this, node, pos);
15072 'render': function(node, canvas) {
15073 var ctx = canvas.getCtx();
15074 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15077 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15078 $.each(colorArray, function(i) {
15079 ans.push(parseInt(i * 0.5, 10));
15081 var endColor = $.rgbToHex(ans);
15082 radialGradient.addColorStop(1, endColor);
15083 radialGradient.addColorStop(0, node.getData('color'));
15084 ctx.fillStyle = radialGradient;
15085 this.nodeTypes['pie'].render.call(this, node, canvas);
15087 'contains': function(node, pos) {
15088 return this.nodeTypes['pie'].contains.call(this, node, pos);
15094 Class: Sunburst.Plot.EdgeTypes
15096 This class contains a list of <Graph.Adjacence> built-in types.
15097 Edge types implemented are 'none', 'line' and 'arrow'.
15099 You can add your custom edge types, customizing your visualization to the extreme.
15104 Sunburst.Plot.EdgeTypes.implement({
15106 'render': function(adj, canvas) {
15107 //print your custom edge to canvas
15110 'contains': function(adj, pos) {
15111 //return true if pos is inside the arc or false otherwise
15118 Sunburst.Plot.EdgeTypes = new Class({
15121 'render': function(adj, canvas) {
15122 var from = adj.nodeFrom.pos.getc(true),
15123 to = adj.nodeTo.pos.getc(true);
15124 this.edgeHelper.line.render(from, to, canvas);
15126 'contains': function(adj, pos) {
15127 var from = adj.nodeFrom.pos.getc(true),
15128 to = adj.nodeTo.pos.getc(true);
15129 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15133 'render': function(adj, canvas) {
15134 var from = adj.nodeFrom.pos.getc(true),
15135 to = adj.nodeTo.pos.getc(true),
15136 dim = adj.getData('dim'),
15137 direction = adj.data.$direction,
15138 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15139 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15141 'contains': function(adj, pos) {
15142 var from = adj.nodeFrom.pos.getc(true),
15143 to = adj.nodeTo.pos.getc(true);
15144 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15148 'render': function(adj, canvas) {
15149 var from = adj.nodeFrom.pos.getc(),
15150 to = adj.nodeTo.pos.getc(),
15151 dim = Math.max(from.norm(), to.norm());
15152 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15154 'contains': $.lambda(false) //TODO(nico): Implement this!
15162 * File: PieChart.js
15166 $jit.Sunburst.Plot.NodeTypes.implement({
15167 'piechart-stacked' : {
15168 'render' : function(node, canvas) {
15169 var pos = node.pos.getp(true),
15170 dimArray = node.getData('dimArray'),
15171 valueArray = node.getData('valueArray'),
15172 colorArray = node.getData('colorArray'),
15173 colorLength = colorArray.length,
15174 stringArray = node.getData('stringArray'),
15175 span = node.getData('span') / 2,
15176 theta = node.pos.theta,
15177 begin = theta - span,
15178 end = theta + span,
15181 var ctx = canvas.getCtx(),
15183 gradient = node.getData('gradient'),
15184 border = node.getData('border'),
15185 config = node.getData('config'),
15186 showLabels = config.showLabels,
15187 resizeLabels = config.resizeLabels,
15188 label = config.Label;
15190 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15191 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15193 if (colorArray && dimArray && stringArray) {
15194 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15195 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15196 if(dimi <= 0) continue;
15197 ctx.fillStyle = ctx.strokeStyle = colori;
15198 if(gradient && dimi) {
15199 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15200 xpos, ypos, acum + dimi + config.sliceOffset);
15201 var colorRgb = $.hexToRgb(colori),
15202 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15203 endColor = $.rgbToHex(ans);
15205 radialGradient.addColorStop(0, colori);
15206 radialGradient.addColorStop(0.5, colori);
15207 radialGradient.addColorStop(1, endColor);
15208 ctx.fillStyle = radialGradient;
15211 polar.rho = acum + config.sliceOffset;
15212 polar.theta = begin;
15213 var p1coord = polar.getc(true);
15215 var p2coord = polar.getc(true);
15217 var p3coord = polar.getc(true);
15218 polar.theta = begin;
15219 var p4coord = polar.getc(true);
15222 //fixing FF arc method + fill
15223 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15224 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15226 if(border && border.name == stringArray[i]) {
15228 opt.dimValue = dimArray[i];
15232 acum += (dimi || 0);
15233 valAcum += (valueArray[i] || 0);
15237 ctx.globalCompositeOperation = "source-over";
15239 ctx.strokeStyle = border.color;
15240 var s = begin < end? 1 : -1;
15242 //fixing FF arc method + fill
15243 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15244 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15249 if(showLabels && label.type == 'Native') {
15251 ctx.fillStyle = ctx.strokeStyle = label.color;
15252 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15253 fontSize = (label.size * scale) >> 0;
15254 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15256 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15257 ctx.textBaseline = 'middle';
15258 ctx.textAlign = 'center';
15260 polar.rho = acum + config.labelOffset + config.sliceOffset;
15261 polar.theta = node.pos.theta;
15262 var cart = polar.getc(true);
15264 ctx.fillText(node.name, cart.x, cart.y);
15269 'contains': function(node, pos) {
15270 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15271 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15272 var ld = this.config.levelDistance, d = node._depth;
15273 var config = node.getData('config');
15274 if(rho <=ld * d + config.sliceOffset) {
15275 var dimArray = node.getData('dimArray');
15276 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15277 var dimi = dimArray[i];
15278 if(rho >= acum && rho <= acum + dimi) {
15280 name: node.getData('stringArray')[i],
15281 color: node.getData('colorArray')[i],
15282 value: node.getData('valueArray')[i],
15295 'piechart-basic' : {
15296 'render' : function(node, canvas) {
15297 var pos = node.pos.getp(true),
15298 dimArray = node.getData('dimArray'),
15299 valueArray = node.getData('valueArray'),
15300 colorArray = node.getData('colorMono'),
15301 colorLength = colorArray.length,
15302 stringArray = node.getData('stringArray'),
15303 percentage = node.getData('percentage'),
15304 iteration = node.getData('iteration'),
15305 span = node.getData('span') / 2,
15306 theta = node.pos.theta,
15307 begin = theta - span,
15308 end = theta + span,
15311 var ctx = canvas.getCtx(),
15313 gradient = node.getData('gradient'),
15314 border = node.getData('border'),
15315 config = node.getData('config'),
15316 renderSubtitle = node.getData('renderSubtitle'),
15317 renderBackground = config.renderBackground,
15318 showLabels = config.showLabels,
15319 resizeLabels = config.resizeLabels,
15320 label = config.Label;
15322 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15323 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15324 //background rendering for IE
15325 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15326 backgroundColor = config.backgroundColor,
15327 size = canvas.getSize();
15329 ctx.fillStyle = backgroundColor;
15330 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15334 var margin = config.Margin,
15335 title = config.Title,
15336 subtitle = config.Subtitle;
15337 ctx.fillStyle = title.color;
15338 ctx.textAlign = 'left';
15340 if(title.text != "") {
15341 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15343 if(label.type == 'Native') {
15344 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15348 if(subtitle.text != "") {
15349 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15350 if(label.type == 'Native') {
15351 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15356 if (colorArray && dimArray && stringArray) {
15357 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15358 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15359 if(dimi <= 0) continue;
15360 ctx.fillStyle = ctx.strokeStyle = colori;
15362 polar.rho = acum + config.sliceOffset;
15363 polar.theta = begin;
15364 var p1coord = polar.getc(true);
15366 var p2coord = polar.getc(true);
15368 var p3coord = polar.getc(true);
15369 polar.theta = begin;
15370 var p4coord = polar.getc(true);
15372 if(typeof FlashCanvas == "undefined") {
15375 ctx.fillStyle = "rgba(0,0,0,.2)";
15376 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15377 ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);
15380 if(gradient && dimi) {
15381 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15382 xpos, ypos, acum + dimi + config.sliceOffset);
15383 var colorRgb = $.hexToRgb(colori),
15384 endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15385 endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15387 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15388 radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15389 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15390 radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15391 ctx.fillStyle = radialGradient;
15396 //fixing FF arc method + fill
15398 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15399 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15401 if(border && border.name == stringArray[i]) {
15403 opt.dimValue = dimArray[i];
15406 opt.sliceValue = valueArray[i];
15408 acum += (dimi || 0);
15409 valAcum += (valueArray[i] || 0);
15413 ctx.globalCompositeOperation = "source-over";
15415 ctx.strokeStyle = border.color;
15416 var s = begin < end? 1 : -1;
15418 //fixing FF arc method + fill
15419 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15420 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15425 if(showLabels && label.type == 'Native') {
15427 ctx.fillStyle = ctx.strokeStyle = label.color;
15428 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15429 fontSize = (label.size * scale) >> 0;
15430 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15432 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15433 ctx.textBaseline = 'middle';
15434 ctx.textAlign = 'center';
15436 angle = theta * 360 / (2 * pi);
15437 polar.rho = acum + config.labelOffset + config.sliceOffset;
15438 polar.theta = node.pos.theta;
15439 var cart = polar.getc(true);
15440 if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15443 if(config.labelType == 'name') {
15444 ctx.fillText(node.name, cart.x, cart.y);
15446 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15453 'contains': function(node, pos) {
15454 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15455 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15456 var ld = this.config.levelDistance, d = node._depth;
15457 var config = node.getData('config');
15459 if(rho <=ld * d + config.sliceOffset) {
15460 var dimArray = node.getData('dimArray');
15461 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15462 var dimi = dimArray[i];
15463 if(rho >= acum && rho <= acum + dimi) {
15464 var url = Url.decode(node.getData('linkArray')[i]);
15466 name: node.getData('stringArray')[i],
15468 color: node.getData('colorArray')[i],
15469 value: node.getData('valueArray')[i],
15470 percentage: node.getData('percentage'),
15471 valuelabel: node.getData('valuelabelsArray')[i],
15489 A visualization that displays stacked bar charts.
15491 Constructor Options:
15493 See <Options.PieChart>.
15496 $jit.PieChart = new Class({
15498 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15502 initialize: function(opt) {
15503 this.controller = this.config =
15504 $.merge(Options("Canvas", "PieChart", "Label"), {
15505 Label: { type: 'Native' }
15507 this.initializeViz();
15510 initializeViz: function() {
15511 var config = this.config, that = this;
15512 var nodeType = config.type.split(":")[0];
15513 var sb = new $jit.Sunburst({
15514 injectInto: config.injectInto,
15515 useCanvas: config.useCanvas,
15516 withLabels: config.Label.type != 'Native',
15517 background: config.background,
15518 renderBackground: config.renderBackground,
15519 backgroundColor: config.backgroundColor,
15520 colorStop1: config.colorStop1,
15521 colorStop2: config.colorStop2,
15523 type: config.Label.type
15527 type: 'piechart-' + nodeType,
15535 enable: config.Tips.enable,
15538 onShow: function(tip, node, contains) {
15539 var elem = contains;
15540 config.Tips.onShow(tip, elem, node);
15541 if(elem.link != 'undefined' && elem.link != '') {
15542 document.body.style.cursor = 'pointer';
15545 onHide: function() {
15546 document.body.style.cursor = 'default';
15552 onClick: function(node, eventInfo, evt) {
15553 if(!config.Events.enable) return;
15554 var elem = eventInfo.getContains();
15555 config.Events.onClick(elem, eventInfo, evt);
15557 onMouseMove: function(node, eventInfo, evt) {
15558 if(!config.hoveredColor) return;
15560 var elem = eventInfo.getContains();
15561 that.select(node.id, elem.name, elem.index);
15563 that.select(false, false, false);
15567 onCreateLabel: function(domElement, node) {
15568 var labelConf = config.Label;
15569 if(config.showLabels) {
15570 var style = domElement.style;
15571 style.fontSize = labelConf.size + 'px';
15572 style.fontFamily = labelConf.family;
15573 style.color = labelConf.color;
15574 style.textAlign = 'center';
15575 if(config.labelType == 'name') {
15576 domElement.innerHTML = node.name;
15578 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15580 domElement.style.width = '400px';
15583 onPlaceLabel: function(domElement, node) {
15584 if(!config.showLabels) return;
15585 var pos = node.pos.getp(true),
15586 dimArray = node.getData('dimArray'),
15587 span = node.getData('span') / 2,
15588 theta = node.pos.theta,
15589 begin = theta - span,
15590 end = theta + span,
15593 var showLabels = config.showLabels,
15594 resizeLabels = config.resizeLabels,
15595 label = config.Label;
15598 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15599 acum += dimArray[i];
15601 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15602 fontSize = (label.size * scale) >> 0;
15603 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15604 domElement.style.fontSize = fontSize + 'px';
15605 polar.rho = acum + config.labelOffset + config.sliceOffset;
15606 polar.theta = (begin + end) / 2;
15607 var pos = polar.getc(true);
15608 var radius = that.canvas.getSize();
15610 x: Math.round(pos.x + radius.width / 2),
15611 y: Math.round(pos.y + radius.height / 2)
15613 domElement.style.left = (labelPos.x - 200) + 'px';
15614 domElement.style.top = labelPos.y + 'px';
15619 var size = sb.canvas.getSize(),
15621 sb.config.levelDistance = min(size.width, size.height)/2
15622 - config.offset - config.sliceOffset;
15624 this.canvas = this.sb.canvas;
15625 this.canvas.getCtx().globalCompositeOperation = 'lighter';
15627 renderBackground: function() {
15628 var canvas = this.canvas,
15629 config = this.config,
15630 backgroundColor = config.backgroundColor,
15631 size = canvas.getSize(),
15632 ctx = canvas.getCtx();
15633 ctx.globalCompositeOperation = "destination-over";
15634 ctx.fillStyle = backgroundColor;
15635 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15637 renderTitle: function() {
15638 var canvas = this.canvas,
15639 size = canvas.getSize(),
15640 config = this.config,
15641 margin = config.Margin,
15642 radius = this.sb.config.levelDistance,
15643 title = config.Title,
15644 label = config.Label,
15645 subtitle = config.Subtitle;
15646 ctx = canvas.getCtx();
15647 ctx.fillStyle = title.color;
15648 ctx.textAlign = 'left';
15649 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15651 if(label.type == 'Native') {
15652 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15655 renderSubtitle: function() {
15656 var canvas = this.canvas,
15657 size = canvas.getSize(),
15658 config = this.config,
15659 margin = config.Margin,
15660 radius = this.sb.config.levelDistance,
15661 title = config.Title,
15662 label = config.Label,
15663 subtitle = config.Subtitle;
15664 ctx = canvas.getCtx();
15665 ctx.fillStyle = title.color;
15666 ctx.textAlign = 'left';
15667 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15669 if(label.type == 'Native') {
15670 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15673 clear: function() {
15674 var canvas = this.canvas;
15675 var ctx = canvas.getCtx(),
15676 size = canvas.getSize();
15677 ctx.fillStyle = "rgba(255,255,255,0)";
15678 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15679 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15681 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
15682 var canvas = this.canvas,
15683 size = canvas.getSize(),
15684 config = this.config,
15685 orgHeight = size.height,
15686 margin = config.Margin,
15688 horz = config.orientation == 'horizontal';
15691 var newWindowWidth = document.body.offsetWidth;
15692 var diff = newWindowWidth - orgWindowWidth;
15693 var newWidth = orgContainerDivWidth + (diff/cols);
15694 var scale = newWidth/orgContainerDivWidth;
15695 canvas.resize(newWidth,orgHeight);
15696 if(typeof FlashCanvas == "undefined") {
15699 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15701 this.loadJSON(json);
15707 Loads JSON data into the visualization.
15711 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>.
15715 var pieChart = new $jit.PieChart(options);
15716 pieChart.loadJSON(json);
15719 loadJSON: function(json) {
15720 var prefix = $.time(),
15723 name = $.splat(json.label),
15724 nameLength = name.length,
15725 color = $.splat(json.color || this.colors),
15726 colorLength = color.length,
15727 config = this.config,
15728 renderBackground = config.renderBackground,
15729 title = config.Title,
15730 subtitle = config.Subtitle,
15731 gradient = !!config.type.split(":")[1],
15732 animate = config.animate,
15733 mono = nameLength == 1;
15735 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15736 var val = values[i];
15737 var valArray = $.splat(val.values);
15738 totalValue += parseFloat(valArray.sum());
15741 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15742 var val = values[i];
15743 var valArray = $.splat(val.values);
15744 var percentage = (valArray.sum()/totalValue) * 100;
15746 var linkArray = $.splat(val.links);
15747 var valuelabelsArray = $.splat(val.valuelabels);
15751 'id': prefix + val.label,
15755 'valuelabel': valuelabelsArray,
15756 '$linkArray': linkArray,
15757 '$valuelabelsArray': valuelabelsArray,
15758 '$valueArray': valArray,
15759 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15760 '$colorMono': $.splat(color[i % colorLength]),
15761 '$stringArray': name,
15762 '$gradient': gradient,
15765 '$percentage': percentage.toFixed(1),
15766 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15772 'id': prefix + '$root',
15784 this.normalizeDims();
15788 if(title.text != "") {
15789 this.renderTitle();
15792 if(subtitle.text != "") {
15793 this.renderSubtitle();
15795 if(renderBackground && typeof FlashCanvas == "undefined") {
15796 this.renderBackground();
15801 modes: ['node-property:dimArray'],
15810 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.
15814 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15815 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15820 pieChart.updateJSON(json, {
15821 onComplete: function() {
15822 alert('update complete!');
15827 updateJSON: function(json, onComplete) {
15828 if(this.busy) return;
15832 var graph = sb.graph;
15833 var values = json.values;
15834 var animate = this.config.animate;
15836 $.each(values, function(v) {
15837 var n = graph.getByName(v.label),
15838 vals = $.splat(v.values);
15840 n.setData('valueArray', vals);
15841 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15843 n.setData('stringArray', $.splat(json.label));
15847 this.normalizeDims();
15851 modes: ['node-property:dimArray:span', 'linear'],
15853 onComplete: function() {
15855 onComplete && onComplete.onComplete();
15863 //adds the little brown bar when hovering the node
15864 select: function(id, name) {
15865 if(!this.config.hoveredColor) return;
15866 var s = this.selected;
15867 if(s.id != id || s.name != name) {
15870 s.color = this.config.hoveredColor;
15871 this.sb.graph.eachNode(function(n) {
15873 n.setData('border', s);
15875 n.setData('border', false);
15885 Returns an object containing as keys the legend names and as values hex strings with color values.
15890 var legend = pieChart.getLegend();
15893 getLegend: function() {
15894 var legend = new Array();
15895 var name = new Array();
15896 var color = new Array();
15898 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15901 var colors = n.getData('colorArray'),
15902 len = colors.length;
15903 $.each(n.getData('stringArray'), function(s, i) {
15904 color[i] = colors[i % len];
15907 legend['name'] = name;
15908 legend['color'] = color;
15913 Method: getMaxValue
15915 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15920 var ans = pieChart.getMaxValue();
15923 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15928 //will return 100 for all PieChart instances,
15929 //displaying all of them with the same scale
15930 $jit.PieChart.implement({
15931 'getMaxValue': function() {
15938 getMaxValue: function() {
15940 this.sb.graph.eachNode(function(n) {
15941 var valArray = n.getData('valueArray'),
15943 $.each(valArray, function(v) {
15946 maxValue = maxValue>acum? maxValue:acum;
15951 normalizeDims: function() {
15952 //number of elements
15953 var root = this.sb.graph.getNode(this.sb.root), l=0;
15954 root.eachAdjacency(function() {
15957 var maxValue = this.getMaxValue() || 1,
15958 config = this.config,
15959 animate = config.animate,
15960 rho = this.sb.config.levelDistance;
15961 this.sb.graph.eachNode(function(n) {
15962 var acum = 0, animateValue = [];
15963 $.each(n.getData('valueArray'), function(v) {
15965 animateValue.push(1);
15967 var stat = (animateValue.length == 1) && !config.updateHeights;
15969 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15970 return stat? rho: (n * rho / maxValue);
15972 var dimArray = n.getData('dimArray');
15974 n.setData('dimArray', animateValue);
15977 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15978 return stat? rho : (n * rho / maxValue);
15981 n.setData('normalizedDim', acum / maxValue);
15989 Options.GaugeChart = {
15993 offset: 25, // page offset
15995 labelOffset: 3, // label offset
15996 type: 'stacked', // gradient
15998 hoveredColor: '#9fd4ff',
16009 resizeLabels: false,
16011 //only valid for mono-valued datasets
16012 updateHeights: false
16017 $jit.Sunburst.Plot.NodeTypes.implement({
16018 'gaugechart-basic' : {
16019 'render' : function(node, canvas) {
16020 var pos = node.pos.getp(true),
16021 dimArray = node.getData('dimArray'),
16022 valueArray = node.getData('valueArray'),
16023 valuelabelsArray = node.getData('valuelabelsArray'),
16024 gaugeTarget = node.getData('gaugeTarget'),
16025 nodeIteration = node.getData('nodeIteration'),
16026 nodeLength = node.getData('nodeLength'),
16027 colorArray = node.getData('colorMono'),
16028 colorLength = colorArray.length,
16029 stringArray = node.getData('stringArray'),
16030 span = node.getData('span') / 2,
16031 theta = node.pos.theta,
16032 begin = ((theta - span)/2)+Math.PI,
16033 end = ((theta + span)/2)+Math.PI,
16037 var ctx = canvas.getCtx(),
16039 gradient = node.getData('gradient'),
16040 border = node.getData('border'),
16041 config = node.getData('config'),
16042 showLabels = config.showLabels,
16043 resizeLabels = config.resizeLabels,
16044 label = config.Label;
16046 var xpos = Math.cos((begin + end) /2);
16047 var ypos = Math.sin((begin + end) /2);
16049 if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16050 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16051 var dimi = dimArray[i], colori = colorArray[i % colorLength];
16052 if(dimi <= 0) continue;
16053 ctx.fillStyle = ctx.strokeStyle = colori;
16054 if(gradient && dimi) {
16055 var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16056 xpos, (ypos + dimi/2), acum + dimi);
16057 var colorRgb = $.hexToRgb(colori),
16058 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16059 endColor = $.rgbToHex(ans);
16061 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16062 radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16063 radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16064 radialGradient.addColorStop(1, 'rgba('+ans+',1)');
16065 ctx.fillStyle = radialGradient;
16069 polar.theta = begin;
16070 var p1coord = polar.getc(true);
16072 var p2coord = polar.getc(true);
16074 var p3coord = polar.getc(true);
16075 polar.theta = begin;
16076 var p4coord = polar.getc(true);
16080 //fixing FF arc method + fill
16081 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16082 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16086 acum += (dimi || 0);
16087 valAcum += (valueArray[i] || 0);
16090 if(showLabels && label.type == 'Native') {
16092 ctx.fillStyle = ctx.strokeStyle = label.color;
16095 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16096 ctx.textBaseline = 'bottom';
16097 ctx.textAlign = 'center';
16099 polar.rho = acum * .65;
16100 polar.theta = begin;
16101 var cart = polar.getc(true);
16103 //changes y pos of first label
16104 if(nodeIteration == 1) {
16105 textY = cart.y - (label.size/2) + acum /2;
16107 textY = cart.y + acum/2;
16110 if(config.labelType == 'name') {
16111 ctx.fillText(node.name, cart.x, textY);
16113 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16117 if(nodeIteration == nodeLength) {
16119 var cart = polar.getc(true);
16120 if(config.labelType == 'name') {
16121 ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16123 ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16132 'contains': function(node, pos) {
16136 if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16137 var config = node.getData('config');
16138 var ld = this.config.levelDistance , d = node._depth;
16139 var yOffset = pos.y - (ld/2);
16140 var xOffset = pos.x;
16141 var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16142 if(rho <=parseInt(ld * d)) {
16143 var dimArray = node.getData('dimArray');
16144 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16145 var dimi = dimArray[i];
16146 if(rho >= ld * .8 && rho <= acum + dimi) {
16148 var url = Url.decode(node.getData('linkArray')[i]);
16150 name: node.getData('stringArray')[i],
16152 color: node.getData('colorArray')[i],
16153 value: node.getData('valueArray')[i],
16154 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16174 A visualization that displays gauge charts
16176 Constructor Options:
16178 See <Options.Gauge>.
16181 $jit.GaugeChart = new Class({
16183 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16187 initialize: function(opt) {
16188 this.controller = this.config =
16189 $.merge(Options("Canvas", "GaugeChart", "Label"), {
16190 Label: { type: 'Native' }
16192 this.initializeViz();
16195 initializeViz: function() {
16196 var config = this.config, that = this;
16197 var nodeType = config.type.split(":")[0];
16198 var sb = new $jit.Sunburst({
16199 injectInto: config.injectInto,
16200 useCanvas: config.useCanvas,
16201 withLabels: config.Label.type != 'Native',
16202 background: config.background,
16203 renderBackground: config.renderBackground,
16204 backgroundColor: config.backgroundColor,
16205 colorStop1: config.colorStop1,
16206 colorStop2: config.colorStop2,
16208 type: config.Label.type
16212 type: 'gaugechart-' + nodeType,
16220 enable: config.Tips.enable,
16223 onShow: function(tip, node, contains) {
16224 var elem = contains;
16225 config.Tips.onShow(tip, elem, node);
16226 if(elem.link != 'undefined' && elem.link != '') {
16227 document.body.style.cursor = 'pointer';
16230 onHide: function() {
16231 document.body.style.cursor = 'default';
16237 onClick: function(node, eventInfo, evt) {
16238 if(!config.Events.enable) return;
16239 var elem = eventInfo.getContains();
16240 config.Events.onClick(elem, eventInfo, evt);
16243 onCreateLabel: function(domElement, node) {
16244 var labelConf = config.Label;
16245 if(config.showLabels) {
16246 var style = domElement.style;
16247 style.fontSize = labelConf.size + 'px';
16248 style.fontFamily = labelConf.family;
16249 style.color = labelConf.color;
16250 style.textAlign = 'center';
16251 valuelabelsArray = node.getData('valuelabelsArray'),
16252 nodeIteration = node.getData('nodeIteration'),
16253 nodeLength = node.getData('nodeLength'),
16254 canvas = sb.canvas,
16257 if(config.labelType == 'name') {
16258 domElement.innerHTML = node.name;
16260 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16263 domElement.style.width = '400px';
16266 if(nodeIteration == nodeLength && nodeLength != 0) {
16267 idLabel = canvas.id + "-label";
16268 container = document.getElementById(idLabel);
16269 finalLabel = document.createElement('div');
16270 finalLabelStyle = finalLabel.style;
16271 finalLabel.id = prefix + "finalLabel";
16272 finalLabelStyle.position = "absolute";
16273 finalLabelStyle.width = "400px";
16274 finalLabelStyle.left = "0px";
16275 container.appendChild(finalLabel);
16276 if(config.labelType == 'name') {
16277 finalLabel.innerHTML = node.name;
16279 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16285 onPlaceLabel: function(domElement, node) {
16286 if(!config.showLabels) return;
16287 var pos = node.pos.getp(true),
16288 dimArray = node.getData('dimArray'),
16289 nodeIteration = node.getData('nodeIteration'),
16290 nodeLength = node.getData('nodeLength'),
16291 span = node.getData('span') / 2,
16292 theta = node.pos.theta,
16293 begin = ((theta - span)/2)+Math.PI,
16294 end = ((theta + span)/2)+Math.PI,
16297 var showLabels = config.showLabels,
16298 resizeLabels = config.resizeLabels,
16299 label = config.Label,
16300 radiusOffset = sb.config.levelDistance;
16303 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16304 acum += dimArray[i];
16306 var scale = resizeLabels? node.getData('normalizedDim') : 1,
16307 fontSize = (label.size * scale) >> 0;
16308 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16309 domElement.style.fontSize = fontSize + 'px';
16310 polar.rho = acum * .65;
16311 polar.theta = begin;
16312 var pos = polar.getc(true);
16313 var radius = that.canvas.getSize();
16315 x: Math.round(pos.x + radius.width / 2),
16316 y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16321 domElement.style.left = (labelPos.x - 200) + 'px';
16322 domElement.style.top = labelPos.y + 'px';
16324 //reposition first label
16325 if(nodeIteration == 1) {
16326 domElement.style.top = labelPos.y - label.size + 'px';
16330 //position final label
16331 if(nodeIteration == nodeLength && nodeLength != 0) {
16333 var final = polar.getc(true);
16335 x: Math.round(final.x + radius.width / 2),
16336 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16338 finalLabel.style.left = (finalPos.x - 200) + "px";
16339 finalLabel.style.top = finalPos.y - label.size + "px";
16347 this.canvas = this.sb.canvas;
16348 var size = sb.canvas.getSize(),
16350 sb.config.levelDistance = min(size.width, size.height)/2
16351 - config.offset - config.sliceOffset;
16356 renderBackground: function() {
16357 var canvas = this.sb.canvas,
16358 config = this.config,
16359 style = config.gaugeStyle,
16360 ctx = canvas.getCtx(),
16361 size = canvas.getSize(),
16362 radius = this.sb.config.levelDistance,
16363 startAngle = (Math.PI/180)*1,
16364 endAngle = (Math.PI/180)*179;
16367 //background border
16368 ctx.fillStyle = style.borderColor;
16370 ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true);
16374 var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16375 radialGradient.addColorStop(0, '#ffffff');
16376 radialGradient.addColorStop(0.3, style.backgroundColor);
16377 radialGradient.addColorStop(0.6, style.backgroundColor);
16378 radialGradient.addColorStop(1, '#FFFFFF');
16379 ctx.fillStyle = radialGradient;
16382 startAngle = (Math.PI/180)*0;
16383 endAngle = (Math.PI/180)*180;
16385 ctx.arc(0,radius/2,radius,startAngle,endAngle, true);
16393 renderNeedle: function(gaugePosition,target) {
16394 var canvas = this.sb.canvas,
16395 config = this.config,
16396 style = config.gaugeStyle,
16397 ctx = canvas.getCtx(),
16398 size = canvas.getSize(),
16399 radius = this.sb.config.levelDistance;
16400 gaugeCenter = (radius/2);
16402 endAngle = (Math.PI/180)*180;
16406 ctx.fillStyle = style.needleColor;
16407 var segments = 180/target;
16408 needleAngle = gaugePosition * segments;
16409 ctx.translate(0, gaugeCenter);
16411 ctx.rotate(needleAngle * Math.PI / 180);
16415 ctx.lineTo(-radius*.9,-1);
16416 ctx.lineTo(-radius*.9,1);
16426 ctx.strokeStyle = '#aa0000';
16428 ctx.rotate(needleAngle * Math.PI / 180);
16432 ctx.lineTo(-radius*.8,-1);
16433 ctx.lineTo(-radius*.8,1);
16441 ctx.fillStyle = "#000000";
16442 ctx.lineWidth = style.borderSize;
16443 ctx.strokeStyle = style.borderColor;
16444 var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16445 radialGradient.addColorStop(0, '#666666');
16446 radialGradient.addColorStop(0.8, '#444444');
16447 radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
16448 ctx.fillStyle = radialGradient;
16449 ctx.translate(0,5);
16452 ctx.arc(0,0,radius*.2,startAngle,endAngle, true);
16459 renderTicks: function(values) {
16460 var canvas = this.sb.canvas,
16461 config = this.config,
16462 style = config.gaugeStyle,
16463 ctx = canvas.getCtx(),
16464 size = canvas.getSize(),
16465 radius = this.sb.config.levelDistance,
16466 gaugeCenter = (radius/2);
16469 ctx.strokeStyle = style.borderColor;
16471 ctx.lineCap = "round";
16472 for(var i=0, total = 0, l=values.length; i<l; i++) {
16473 var val = values[i];
16474 if(val.label != 'GaugePosition') {
16475 total += (parseInt(val.values) || 0);
16479 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16480 var val = values[i];
16481 if(val.label != 'GaugePosition') {
16482 acum += (parseInt(val.values) || 0);
16484 var segments = 180/total;
16485 angle = acum * segments;
16489 ctx.translate(0, gaugeCenter);
16491 ctx.rotate(angle * (Math.PI/180));
16492 ctx.moveTo(-radius,0);
16493 ctx.lineTo(-radius*.75,0);
16501 renderPositionLabel: function(position) {
16502 var canvas = this.sb.canvas,
16503 config = this.config,
16504 label = config.Label,
16505 style = config.gaugeStyle,
16506 ctx = canvas.getCtx(),
16507 size = canvas.getSize(),
16508 radius = this.sb.config.levelDistance,
16509 gaugeCenter = (radius/2);
16510 ctx.textBaseline = 'middle';
16511 ctx.textAlign = 'center';
16512 ctx.font = style.positionFontSize + 'px ' + label.family;
16513 ctx.fillStyle = "#ffffff";
16515 height = style.positionFontSize + 10,
16517 idLabel = canvas.id + "-label";
16518 container = document.getElementById(idLabel);
16519 if(label.type == 'Native') {
16520 var m = ctx.measureText(position),
16521 width = m.width + 40;
16525 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16526 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16527 if(label.type == 'Native') {
16528 ctx.fillStyle = label.color;
16529 ctx.fillText(position, 0, (height/2) + style.positionOffset);
16531 var labelDiv = document.createElement('div');
16532 labelDivStyle = labelDiv.style;
16533 labelDivStyle.color = label.color;
16534 labelDivStyle.fontSize = style.positionFontSize + "px";
16535 labelDivStyle.position = "absolute";
16536 labelDivStyle.width = width + "px";
16537 labelDivStyle.left = (size.width/2) - (width/2) + "px";
16538 labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16539 labelDiv.innerHTML = position;
16540 container.appendChild(labelDiv);
16545 renderSubtitle: function() {
16546 var canvas = this.canvas,
16547 size = canvas.getSize(),
16548 config = this.config,
16549 margin = config.Margin,
16550 radius = this.sb.config.levelDistance,
16551 title = config.Title,
16552 label = config.Label,
16553 subtitle = config.Subtitle;
16554 ctx = canvas.getCtx();
16555 ctx.fillStyle = title.color;
16556 ctx.textAlign = 'left';
16557 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16559 if(label.type == 'Native') {
16560 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2));
16564 renderChartBackground: function() {
16565 var canvas = this.canvas,
16566 config = this.config,
16567 backgroundColor = config.backgroundColor,
16568 size = canvas.getSize(),
16569 ctx = canvas.getCtx();
16570 //ctx.globalCompositeOperation = "destination-over";
16571 ctx.fillStyle = backgroundColor;
16572 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16574 clear: function() {
16575 var canvas = this.canvas;
16576 var ctx = canvas.getCtx(),
16577 size = canvas.getSize();
16578 ctx.fillStyle = "rgba(255,255,255,0)";
16579 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16580 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16582 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
16583 var canvas = this.canvas,
16584 size = canvas.getSize(),
16585 config = this.config,
16586 orgHeight = size.height,
16587 margin = config.Margin,
16589 horz = config.orientation == 'horizontal';
16592 var newWindowWidth = document.body.offsetWidth;
16593 var diff = newWindowWidth - orgWindowWidth;
16594 var newWidth = orgContainerDivWidth + (diff/cols);
16595 canvas.resize(newWidth,orgHeight);
16596 if(typeof FlashCanvas == "undefined") {
16599 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16601 this.loadJSON(json);
16604 loadJSON: function(json) {
16606 var prefix = $.time(),
16609 name = $.splat(json.label),
16610 nameLength = name.length,
16611 color = $.splat(json.color || this.colors),
16612 colorLength = color.length,
16613 config = this.config,
16614 renderBackground = config.renderBackground,
16615 gradient = !!config.type.split(":")[1],
16616 animate = config.animate,
16617 mono = nameLength == 1;
16618 var props = $.splat(json.properties)[0];
16620 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16622 var val = values[i];
16623 if(val.label != 'GaugePosition') {
16624 var valArray = $.splat(val.values);
16625 var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16626 var valuelabelsArray = $.splat(val.valuelabels);
16629 'id': prefix + val.label,
16633 'valuelabel': valuelabelsArray,
16634 '$linkArray': linkArray,
16635 '$valuelabelsArray': valuelabelsArray,
16636 '$valueArray': valArray,
16637 '$nodeIteration': i,
16638 '$nodeLength': l-1,
16639 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16640 '$colorMono': $.splat(color[i % colorLength]),
16641 '$stringArray': name,
16642 '$gradient': gradient,
16644 '$gaugeTarget': props['gaugeTarget'],
16645 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16650 var gaugePosition = val.gvalue;
16651 var gaugePositionLabel = val.gvaluelabel;
16655 'id': prefix + '$root',
16668 if(renderBackground) {
16669 this.renderChartBackground();
16672 this.renderBackground();
16673 this.renderSubtitle();
16675 this.normalizeDims();
16680 modes: ['node-property:dimArray'],
16686 this.renderPositionLabel(gaugePositionLabel);
16687 if (props['gaugeTarget'] != 0) {
16688 this.renderTicks(json.values);
16689 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16696 updateJSON: function(json, onComplete) {
16697 if(this.busy) return;
16701 var graph = sb.graph;
16702 var values = json.values;
16703 var animate = this.config.animate;
16705 $.each(values, function(v) {
16706 var n = graph.getByName(v.label),
16707 vals = $.splat(v.values);
16709 n.setData('valueArray', vals);
16710 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16712 n.setData('stringArray', $.splat(json.label));
16716 this.normalizeDims();
16720 modes: ['node-property:dimArray:span', 'linear'],
16722 onComplete: function() {
16724 onComplete && onComplete.onComplete();
16732 //adds the little brown bar when hovering the node
16733 select: function(id, name) {
16734 if(!this.config.hoveredColor) return;
16735 var s = this.selected;
16736 if(s.id != id || s.name != name) {
16739 s.color = this.config.hoveredColor;
16740 this.sb.graph.eachNode(function(n) {
16742 n.setData('border', s);
16744 n.setData('border', false);
16754 Returns an object containing as keys the legend names and as values hex strings with color values.
16759 var legend = pieChart.getLegend();
16762 getLegend: function() {
16763 var legend = new Array();
16764 var name = new Array();
16765 var color = new Array();
16767 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16770 var colors = n.getData('colorArray'),
16771 len = colors.length;
16772 $.each(n.getData('stringArray'), function(s, i) {
16773 color[i] = colors[i % len];
16776 legend['name'] = name;
16777 legend['color'] = color;
16782 Method: getMaxValue
16784 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16789 var ans = pieChart.getMaxValue();
16792 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16797 //will return 100 for all PieChart instances,
16798 //displaying all of them with the same scale
16799 $jit.PieChart.implement({
16800 'getMaxValue': function() {
16807 getMaxValue: function() {
16809 this.sb.graph.eachNode(function(n) {
16810 var valArray = n.getData('valueArray'),
16812 $.each(valArray, function(v) {
16815 maxValue = maxValue>acum? maxValue:acum;
16820 normalizeDims: function() {
16821 //number of elements
16822 var root = this.sb.graph.getNode(this.sb.root), l=0;
16823 root.eachAdjacency(function() {
16826 var maxValue = this.getMaxValue() || 1,
16827 config = this.config,
16828 animate = config.animate,
16829 rho = this.sb.config.levelDistance;
16830 this.sb.graph.eachNode(function(n) {
16831 var acum = 0, animateValue = [];
16832 $.each(n.getData('valueArray'), function(v) {
16834 animateValue.push(1);
16836 var stat = (animateValue.length == 1) && !config.updateHeights;
16838 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16839 return stat? rho: (n * rho / maxValue);
16841 var dimArray = n.getData('dimArray');
16843 n.setData('dimArray', animateValue);
16846 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16847 return stat? rho : (n * rho / maxValue);
16850 n.setData('normalizedDim', acum / maxValue);
16857 * Class: Layouts.TM
16859 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16868 Layouts.TM.SliceAndDice = new Class({
16869 compute: function(prop) {
16870 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16871 this.controller.onBeforeCompute(root);
16872 var size = this.canvas.getSize(),
16873 config = this.config,
16874 width = size.width,
16875 height = size.height;
16876 this.graph.computeLevels(this.root, 0, "ignore");
16877 //set root position and dimensions
16878 root.getPos(prop).setc(-width/2, -height/2);
16879 root.setData('width', width, prop);
16880 root.setData('height', height + config.titleHeight, prop);
16881 this.computePositions(root, root, this.layout.orientation, prop);
16882 this.controller.onAfterCompute(root);
16885 computePositions: function(par, ch, orn, prop) {
16886 //compute children areas
16888 par.eachSubnode(function(n) {
16889 totalArea += n.getData('area', prop);
16892 var config = this.config,
16893 offst = config.offset,
16894 width = par.getData('width', prop),
16895 height = par.getData('height', prop) - config.titleHeight,
16896 fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16898 var otherSize, size, dim, pos, pos2, posth, pos2th;
16899 var horizontal = (orn == "h");
16902 otherSize = height;
16903 size = width * fact;
16907 posth = config.titleHeight;
16911 otherSize = height * fact;
16917 pos2th = config.titleHeight;
16919 var cpos = ch.getPos(prop);
16920 ch.setData('width', size, prop);
16921 ch.setData('height', otherSize, prop);
16922 var offsetSize = 0, tm = this;
16923 ch.eachSubnode(function(n) {
16924 var p = n.getPos(prop);
16925 p[pos] = offsetSize + cpos[pos] + posth;
16926 p[pos2] = cpos[pos2] + pos2th;
16927 tm.computePositions(ch, n, orn, prop);
16928 offsetSize += n.getData(dim, prop);
16934 Layouts.TM.Area = {
16938 Called by loadJSON to calculate recursively all node positions and lay out the tree.
16942 json - A JSON tree. See also <Loader.loadJSON>.
16943 coord - A coordinates object specifying width, height, left and top style properties.
16945 compute: function(prop) {
16946 prop = prop || "current";
16947 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16948 this.controller.onBeforeCompute(root);
16949 var config = this.config,
16950 size = this.canvas.getSize(),
16951 width = size.width,
16952 height = size.height,
16953 offst = config.offset,
16954 offwdth = width - offst,
16955 offhght = height - offst;
16956 this.graph.computeLevels(this.root, 0, "ignore");
16957 //set root position and dimensions
16958 root.getPos(prop).setc(-width/2, -height/2);
16959 root.setData('width', width, prop);
16960 root.setData('height', height, prop);
16961 //create a coordinates object
16963 'top': -height/2 + config.titleHeight,
16966 'height': offhght - config.titleHeight
16968 this.computePositions(root, coord, prop);
16969 this.controller.onAfterCompute(root);
16975 Computes dimensions and positions of a group of nodes
16976 according to a custom layout row condition.
16980 tail - An array of nodes.
16981 initElem - An array of nodes (containing the initial node to be laid).
16982 w - A fixed dimension where nodes will be layed out.
16983 coord - A coordinates object specifying width, height, left and top style properties.
16984 comp - A custom comparison function
16986 computeDim: function(tail, initElem, w, coord, comp, prop) {
16987 if(tail.length + initElem.length == 1) {
16988 var l = (tail.length == 1)? tail : initElem;
16989 this.layoutLast(l, w, coord, prop);
16992 if(tail.length >= 2 && initElem.length == 0) {
16993 initElem = [tail.shift()];
16995 if(tail.length == 0) {
16996 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17000 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17001 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17003 var newCoords = this.layoutRow(initElem, w, coord, prop);
17004 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17010 Method: worstAspectRatio
17012 Calculates the worst aspect ratio of a group of rectangles.
17016 <http://en.wikipedia.org/wiki/Aspect_ratio>
17020 ch - An array of nodes.
17021 w - The fixed dimension where rectangles are being laid out.
17025 The worst aspect ratio.
17029 worstAspectRatio: function(ch, w) {
17030 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17031 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17032 for(var i=0, l=ch.length; i<l; i++) {
17033 var area = ch[i]._area;
17035 minArea = minArea < area? minArea : area;
17036 maxArea = maxArea > area? maxArea : area;
17038 var sqw = w * w, sqAreaSum = areaSum * areaSum;
17039 return Math.max(sqw * maxArea / sqAreaSum,
17040 sqAreaSum / (sqw * minArea));
17044 Method: avgAspectRatio
17046 Calculates the average aspect ratio of a group of rectangles.
17050 <http://en.wikipedia.org/wiki/Aspect_ratio>
17054 ch - An array of nodes.
17055 w - The fixed dimension where rectangles are being laid out.
17059 The average aspect ratio.
17063 avgAspectRatio: function(ch, w) {
17064 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17066 for(var i=0, l=ch.length; i<l; i++) {
17067 var area = ch[i]._area;
17069 arSum += w > h? w / h : h / w;
17077 Performs the layout of the last computed sibling.
17081 ch - An array of nodes.
17082 w - A fixed dimension where nodes will be layed out.
17083 coord - A coordinates object specifying width, height, left and top style properties.
17085 layoutLast: function(ch, w, coord, prop) {
17087 child.getPos(prop).setc(coord.left, coord.top);
17088 child.setData('width', coord.width, prop);
17089 child.setData('height', coord.height, prop);
17094 Layouts.TM.Squarified = new Class({
17095 Implements: Layouts.TM.Area,
17097 computePositions: function(node, coord, prop) {
17098 var config = this.config;
17100 if (coord.width >= coord.height)
17101 this.layout.orientation = 'h';
17103 this.layout.orientation = 'v';
17105 var ch = node.getSubnodes([1, 1], "ignore");
17106 if(ch.length > 0) {
17107 this.processChildrenLayout(node, ch, coord, prop);
17108 for(var i=0, l=ch.length; i<l; i++) {
17110 var offst = config.offset,
17111 height = chi.getData('height', prop) - offst - config.titleHeight,
17112 width = chi.getData('width', prop) - offst;
17113 var chipos = chi.getPos(prop);
17117 'top': chipos.y + config.titleHeight,
17120 this.computePositions(chi, coord, prop);
17126 Method: processChildrenLayout
17128 Computes children real areas and other useful parameters for performing the Squarified algorithm.
17132 par - The parent node of the json subtree.
17133 ch - An Array of nodes
17134 coord - A coordinates object specifying width, height, left and top style properties.
17136 processChildrenLayout: function(par, ch, coord, prop) {
17137 //compute children real areas
17138 var parentArea = coord.width * coord.height;
17139 var i, l=ch.length, totalChArea=0, chArea = [];
17140 for(i=0; i<l; i++) {
17141 chArea[i] = parseFloat(ch[i].getData('area', prop));
17142 totalChArea += chArea[i];
17144 for(i=0; i<l; i++) {
17145 ch[i]._area = parentArea * chArea[i] / totalChArea;
17147 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17148 ch.sort(function(a, b) {
17149 var diff = b._area - a._area;
17150 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
17152 var initElem = [ch[0]];
17153 var tail = ch.slice(1);
17154 this.squarify(tail, initElem, minimumSideValue, coord, prop);
17160 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17164 tail - An array of nodes.
17165 initElem - An array of nodes, containing the initial node to be laid out.
17166 w - A fixed dimension where nodes will be laid out.
17167 coord - A coordinates object specifying width, height, left and top style properties.
17169 squarify: function(tail, initElem, w, coord, prop) {
17170 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17176 Performs the layout of an array of nodes.
17180 ch - An array of nodes.
17181 w - A fixed dimension where nodes will be laid out.
17182 coord - A coordinates object specifying width, height, left and top style properties.
17184 layoutRow: function(ch, w, coord, prop) {
17185 if(this.layout.horizontal()) {
17186 return this.layoutV(ch, w, coord, prop);
17188 return this.layoutH(ch, w, coord, prop);
17192 layoutV: function(ch, w, coord, prop) {
17193 var totalArea = 0, rnd = function(x) { return x; };
17194 $.each(ch, function(elem) { totalArea += elem._area; });
17195 var width = rnd(totalArea / w), top = 0;
17196 for(var i=0, l=ch.length; i<l; i++) {
17197 var h = rnd(ch[i]._area / width);
17199 chi.getPos(prop).setc(coord.left, coord.top + top);
17200 chi.setData('width', width, prop);
17201 chi.setData('height', h, prop);
17205 'height': coord.height,
17206 'width': coord.width - width,
17208 'left': coord.left + width
17210 //take minimum side value.
17211 ans.dim = Math.min(ans.width, ans.height);
17212 if(ans.dim != ans.height) this.layout.change();
17216 layoutH: function(ch, w, coord, prop) {
17218 $.each(ch, function(elem) { totalArea += elem._area; });
17219 var height = totalArea / w,
17223 for(var i=0, l=ch.length; i<l; i++) {
17225 var w = chi._area / height;
17226 chi.getPos(prop).setc(coord.left + left, top);
17227 chi.setData('width', w, prop);
17228 chi.setData('height', height, prop);
17232 'height': coord.height - height,
17233 'width': coord.width,
17234 'top': coord.top + height,
17237 ans.dim = Math.min(ans.width, ans.height);
17238 if(ans.dim != ans.width) this.layout.change();
17243 Layouts.TM.Strip = new Class({
17244 Implements: Layouts.TM.Area,
17249 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17253 json - A JSON subtree. See also <Loader.loadJSON>.
17254 coord - A coordinates object specifying width, height, left and top style properties.
17256 computePositions: function(node, coord, prop) {
17257 var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17258 if(ch.length > 0) {
17259 this.processChildrenLayout(node, ch, coord, prop);
17260 for(var i=0, l=ch.length; i<l; i++) {
17262 var offst = config.offset,
17263 height = chi.getData('height', prop) - offst - config.titleHeight,
17264 width = chi.getData('width', prop) - offst;
17265 var chipos = chi.getPos(prop);
17269 'top': chipos.y + config.titleHeight,
17272 this.computePositions(chi, coord, prop);
17278 Method: processChildrenLayout
17280 Computes children real areas and other useful parameters for performing the Strip algorithm.
17284 par - The parent node of the json subtree.
17285 ch - An Array of nodes
17286 coord - A coordinates object specifying width, height, left and top style properties.
17288 processChildrenLayout: function(par, ch, coord, prop) {
17289 //compute children real areas
17290 var parentArea = coord.width * coord.height;
17291 var i, l=ch.length, totalChArea=0, chArea = [];
17292 for(i=0; i<l; i++) {
17293 chArea[i] = +ch[i].getData('area', prop);
17294 totalChArea += chArea[i];
17296 for(i=0; i<l; i++) {
17297 ch[i]._area = parentArea * chArea[i] / totalChArea;
17299 var side = this.layout.horizontal()? coord.width : coord.height;
17300 var initElem = [ch[0]];
17301 var tail = ch.slice(1);
17302 this.stripify(tail, initElem, side, coord, prop);
17308 Performs an heuristic method to calculate div elements sizes in order to have
17309 a good compromise between aspect ratio and order.
17313 tail - An array of nodes.
17314 initElem - An array of nodes.
17315 w - A fixed dimension where nodes will be layed out.
17316 coord - A coordinates object specifying width, height, left and top style properties.
17318 stripify: function(tail, initElem, w, coord, prop) {
17319 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17325 Performs the layout of an array of nodes.
17329 ch - An array of nodes.
17330 w - A fixed dimension where nodes will be laid out.
17331 coord - A coordinates object specifying width, height, left and top style properties.
17333 layoutRow: function(ch, w, coord, prop) {
17334 if(this.layout.horizontal()) {
17335 return this.layoutH(ch, w, coord, prop);
17337 return this.layoutV(ch, w, coord, prop);
17341 layoutV: function(ch, w, coord, prop) {
17343 $.each(ch, function(elem) { totalArea += elem._area; });
17344 var width = totalArea / w, top = 0;
17345 for(var i=0, l=ch.length; i<l; i++) {
17347 var h = chi._area / width;
17348 chi.getPos(prop).setc(coord.left,
17349 coord.top + (w - h - top));
17350 chi.setData('width', width, prop);
17351 chi.setData('height', h, prop);
17356 'height': coord.height,
17357 'width': coord.width - width,
17359 'left': coord.left + width,
17364 layoutH: function(ch, w, coord, prop) {
17366 $.each(ch, function(elem) { totalArea += elem._area; });
17367 var height = totalArea / w,
17368 top = coord.height - height,
17371 for(var i=0, l=ch.length; i<l; i++) {
17373 var s = chi._area / height;
17374 chi.getPos(prop).setc(coord.left + left, coord.top + top);
17375 chi.setData('width', s, prop);
17376 chi.setData('height', height, prop);
17380 'height': coord.height - height,
17381 'width': coord.width,
17383 'left': coord.left,
17390 * Class: Layouts.Icicle
17392 * Implements the icicle tree layout.
17400 Layouts.Icicle = new Class({
17404 * Called by loadJSON to calculate all node positions.
17408 * posType - The nodes' position to compute. Either "start", "end" or
17409 * "current". Defaults to "current".
17411 compute: function(posType) {
17412 posType = posType || "current";
17413 var root = this.graph.getNode(this.root),
17414 config = this.config,
17415 size = this.canvas.getSize(),
17416 width = size.width,
17417 height = size.height,
17418 offset = config.offset,
17419 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17421 this.controller.onBeforeCompute(root);
17423 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17427 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17429 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17430 var maxDepth = Math.min(treeDepth, levelsToShow-1);
17431 var initialDepth = startNode._depth;
17432 if(this.layout.horizontal()) {
17433 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17435 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17439 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17440 root.getPos(posType).setc(x, y);
17441 root.setData('width', width, posType);
17442 root.setData('height', height, posType);
17444 var nodeLength, prevNodeLength = 0, totalDim = 0;
17445 var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17447 if(!children.length)
17450 $.each(children, function(e) { totalDim += e.getData('dim'); });
17452 for(var i=0, l=children.length; i < l; i++) {
17453 if(this.layout.horizontal()) {
17454 nodeLength = height * children[i].getData('dim') / totalDim;
17455 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17458 nodeLength = width * children[i].getData('dim') / totalDim;
17459 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17476 Icicle space filling visualization.
17480 All <Loader> methods
17482 Constructor Options:
17484 Inherits options from
17487 - <Options.Controller>
17493 - <Options.NodeStyles>
17494 - <Options.Navigation>
17496 Additionally, there are other parameters and some default values changed
17498 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17499 offset - (number) Default's *2*. Boxes offset.
17500 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17501 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17502 animate - (boolean) Default's *false*. Whether to animate transitions.
17503 Node.type - Described in <Options.Node>. Default's *rectangle*.
17504 Label.type - Described in <Options.Label>. Default's *Native*.
17505 duration - Described in <Options.Fx>. Default's *700*.
17506 fps - Described in <Options.Fx>. Default's *45*.
17508 Instance Properties:
17510 canvas - Access a <Canvas> instance.
17511 graph - Access a <Graph> instance.
17512 op - Access a <Icicle.Op> instance.
17513 fx - Access a <Icicle.Plot> instance.
17514 labels - Access a <Icicle.Label> interface implementation.
17518 $jit.Icicle = new Class({
17519 Implements: [ Loader, Extras, Layouts.Icicle ],
17523 vertical: function(){
17524 return this.orientation == "v";
17526 horizontal: function(){
17527 return this.orientation == "h";
17529 change: function(){
17530 this.orientation = this.vertical()? "h" : "v";
17534 initialize: function(controller) {
17539 levelsToShow: Number.MAX_VALUE,
17540 constrained: false,
17555 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17556 "Events", "Navigation", "Controller", "Label");
17557 this.controller = this.config = $.merge(opts, config, controller);
17558 this.layout.orientation = this.config.orientation;
17560 var canvasConfig = this.config;
17561 if (canvasConfig.useCanvas) {
17562 this.canvas = canvasConfig.useCanvas;
17563 this.config.labelContainer = this.canvas.id + '-label';
17565 this.canvas = new Canvas(this, canvasConfig);
17566 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17569 this.graphOptions = {
17578 this.graph = new Graph(
17579 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17581 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17582 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17583 this.op = new $jit.Icicle.Op(this);
17584 this.group = new $jit.Icicle.Group(this);
17585 this.clickedNode = null;
17587 this.initializeExtras();
17593 Computes positions and plots the tree.
17595 refresh: function(){
17596 var labelType = this.config.Label.type;
17597 if(labelType != 'Native') {
17599 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17608 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
17612 this.fx.plot(this.config);
17618 Sets the node as root.
17622 node - (object) A <Graph.Node>.
17625 enter: function (node) {
17631 config = this.config;
17634 onComplete: function() {
17635 //compute positions of newly inserted nodes
17639 if(config.animate) {
17640 that.graph.nodeList.setDataset(['current', 'end'], {
17641 'alpha': [1, 0] //fade nodes
17644 Graph.Util.eachSubgraph(node, function(n) {
17645 n.setData('alpha', 1, 'end');
17650 modes:['node-property:alpha'],
17651 onComplete: function() {
17652 that.clickedNode = node;
17653 that.compute('end');
17656 modes:['linear', 'node-property:width:height'],
17658 onComplete: function() {
17660 that.clickedNode = node;
17666 that.clickedNode = node;
17673 if(config.request) {
17674 this.requestNodes(clickedNode, callback);
17676 callback.onComplete();
17683 Sets the parent node of the current selected node as root.
17691 GUtil = Graph.Util,
17692 config = this.config,
17693 graph = this.graph,
17694 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17695 parent = parents[0],
17696 clickedNode = parent,
17697 previousClickedNode = this.clickedNode;
17700 this.events.hoveredNode = false;
17707 //final plot callback
17709 onComplete: function() {
17710 that.clickedNode = parent;
17711 if(config.request) {
17712 that.requestNodes(parent, {
17713 onComplete: function() {
17727 //animate node positions
17728 if(config.animate) {
17729 this.clickedNode = clickedNode;
17730 this.compute('end');
17731 //animate the visible subtree only
17732 this.clickedNode = previousClickedNode;
17734 modes:['linear', 'node-property:width:height'],
17736 onComplete: function() {
17737 //animate the parent subtree
17738 that.clickedNode = clickedNode;
17739 //change nodes alpha
17740 graph.nodeList.setDataset(['current', 'end'], {
17743 GUtil.eachSubgraph(previousClickedNode, function(node) {
17744 node.setData('alpha', 1);
17748 modes:['node-property:alpha'],
17749 onComplete: function() {
17750 callback.onComplete();
17756 callback.onComplete();
17759 requestNodes: function(node, onComplete){
17760 var handler = $.merge(this.controller, onComplete),
17761 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17763 if (handler.request) {
17764 var leaves = [], d = node._depth;
17765 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17766 if (n.drawn && !Graph.Util.anySubnode(n)) {
17768 n._level = n._depth - d;
17769 if (this.config.constrained)
17770 n._level = levelsToShow - n._level;
17774 this.group.requestNodes(leaves, handler);
17776 handler.onComplete();
17784 Custom extension of <Graph.Op>.
17788 All <Graph.Op> methods
17795 $jit.Icicle.Op = new Class({
17797 Implements: Graph.Op
17802 * Performs operations on group of nodes.
17804 $jit.Icicle.Group = new Class({
17806 initialize: function(viz){
17808 this.canvas = viz.canvas;
17809 this.config = viz.config;
17813 * Calls the request method on the controller to request a subtree for each node.
17815 requestNodes: function(nodes, controller){
17816 var counter = 0, len = nodes.length, nodeSelected = {};
17817 var complete = function(){
17818 controller.onComplete();
17820 var viz = this.viz;
17823 for(var i = 0; i < len; i++) {
17824 nodeSelected[nodes[i].id] = nodes[i];
17825 controller.request(nodes[i].id, nodes[i]._level, {
17826 onComplete: function(nodeId, data){
17827 if (data && data.children) {
17833 if (++counter == len) {
17834 Graph.Util.computeLevels(viz.graph, viz.root, 0);
17846 Custom extension of <Graph.Plot>.
17850 All <Graph.Plot> methods
17857 $jit.Icicle.Plot = new Class({
17858 Implements: Graph.Plot,
17860 plot: function(opt, animating){
17861 opt = opt || this.viz.controller;
17862 var viz = this.viz,
17864 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17865 initialDepth = root._depth;
17867 viz.canvas.clear();
17868 this.plotTree(root, $.merge(opt, {
17869 'withLabels': true,
17870 'hideLabels': false,
17871 'plotSubtree': function(root, node) {
17872 return !viz.config.constrained ||
17873 (node._depth - initialDepth < viz.config.levelsToShow);
17880 Class: Icicle.Label
17882 Custom extension of <Graph.Label>.
17883 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17887 All <Graph.Label> methods and subclasses.
17891 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17894 $jit.Icicle.Label = {};
17897 Icicle.Label.Native
17899 Custom extension of <Graph.Label.Native>.
17903 All <Graph.Label.Native> methods
17907 <Graph.Label.Native>
17910 $jit.Icicle.Label.Native = new Class({
17911 Implements: Graph.Label.Native,
17913 renderLabel: function(canvas, node, controller) {
17914 var ctx = canvas.getCtx(),
17915 width = node.getData('width'),
17916 height = node.getData('height'),
17917 size = node.getLabelData('size'),
17918 m = ctx.measureText(node.name);
17920 // Guess as much as possible if the label will fit in the node
17921 if(height < (size * 1.5) || width < m.width)
17924 var pos = node.pos.getc(true);
17925 ctx.fillText(node.name,
17927 pos.y + height / 2);
17934 Custom extension of <Graph.Label.SVG>.
17938 All <Graph.Label.SVG> methods
17944 $jit.Icicle.Label.SVG = new Class( {
17945 Implements: Graph.Label.SVG,
17947 initialize: function(viz){
17954 Overrides abstract method placeLabel in <Graph.Plot>.
17958 tag - A DOM label element.
17959 node - A <Graph.Node>.
17960 controller - A configuration/controller object passed to the visualization.
17962 placeLabel: function(tag, node, controller){
17963 var pos = node.pos.getc(true), canvas = this.viz.canvas;
17964 var radius = canvas.getSize();
17966 x: Math.round(pos.x + radius.width / 2),
17967 y: Math.round(pos.y + radius.height / 2)
17969 tag.setAttribute('x', labelPos.x);
17970 tag.setAttribute('y', labelPos.y);
17972 controller.onPlaceLabel(tag, node);
17979 Custom extension of <Graph.Label.HTML>.
17983 All <Graph.Label.HTML> methods.
17990 $jit.Icicle.Label.HTML = new Class( {
17991 Implements: Graph.Label.HTML,
17993 initialize: function(viz){
18000 Overrides abstract method placeLabel in <Graph.Plot>.
18004 tag - A DOM label element.
18005 node - A <Graph.Node>.
18006 controller - A configuration/controller object passed to the visualization.
18008 placeLabel: function(tag, node, controller){
18009 var pos = node.pos.getc(true), canvas = this.viz.canvas;
18010 var radius = canvas.getSize();
18012 x: Math.round(pos.x + radius.width / 2),
18013 y: Math.round(pos.y + radius.height / 2)
18016 var style = tag.style;
18017 style.left = labelPos.x + 'px';
18018 style.top = labelPos.y + 'px';
18019 style.display = '';
18021 controller.onPlaceLabel(tag, node);
18026 Class: Icicle.Plot.NodeTypes
18028 This class contains a list of <Graph.Node> built-in types.
18029 Node types implemented are 'none', 'rectangle'.
18031 You can add your custom node types, customizing your visualization to the extreme.
18036 Icicle.Plot.NodeTypes.implement({
18038 'render': function(node, canvas) {
18039 //print your custom node to canvas
18042 'contains': function(node, pos) {
18043 //return true if pos is inside the node or false otherwise
18050 $jit.Icicle.Plot.NodeTypes = new Class( {
18056 'render': function(node, canvas, animating) {
18057 var config = this.viz.config;
18058 var offset = config.offset;
18059 var width = node.getData('width');
18060 var height = node.getData('height');
18061 var border = node.getData('border');
18062 var pos = node.pos.getc(true);
18063 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18064 var ctx = canvas.getCtx();
18066 if(width - offset < 2 || height - offset < 2) return;
18068 if(config.cushion) {
18069 var color = node.getData('color');
18070 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
18071 posy + (height - offset)/2, 1,
18072 posx + (width-offset)/2, posy + (height-offset)/2,
18073 width < height? height : width);
18074 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
18075 function(r) { return r * 0.3 >> 0; }));
18076 lg.addColorStop(0, color);
18077 lg.addColorStop(1, colorGrad);
18078 ctx.fillStyle = lg;
18082 ctx.strokeStyle = border;
18086 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18087 border && ctx.strokeRect(pos.x, pos.y, width, height);
18090 'contains': function(node, pos) {
18091 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18092 var npos = node.pos.getc(true),
18093 width = node.getData('width'),
18094 height = node.getData('height');
18095 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18100 $jit.Icicle.Plot.EdgeTypes = new Class( {
18107 * File: Layouts.ForceDirected.js
18112 * Class: Layouts.ForceDirected
18114 * Implements a Force Directed Layout.
18122 * Marcus Cobden <http://marcuscobden.co.uk>
18125 Layouts.ForceDirected = new Class({
18127 getOptions: function(random) {
18128 var s = this.canvas.getSize();
18129 var w = s.width, h = s.height;
18132 this.graph.eachNode(function(n) {
18135 var k2 = w * h / count, k = Math.sqrt(k2);
18136 var l = this.config.levelDistance;
18142 nodef: function(x) { return k2 / (x || 1); },
18143 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18147 compute: function(property, incremental) {
18148 var prop = $.splat(property || ['current', 'start', 'end']);
18149 var opt = this.getOptions();
18150 NodeDim.compute(this.graph, prop, this.config);
18151 this.graph.computeLevels(this.root, 0, "ignore");
18152 this.graph.eachNode(function(n) {
18153 $.each(prop, function(p) {
18154 var pos = n.getPos(p);
18155 if(pos.equals(Complex.KER)) {
18156 pos.x = opt.width/5 * (Math.random() - 0.5);
18157 pos.y = opt.height/5 * (Math.random() - 0.5);
18159 //initialize disp vector
18161 $.each(prop, function(p) {
18162 n.disp[p] = $C(0, 0);
18166 this.computePositions(prop, opt, incremental);
18169 computePositions: function(property, opt, incremental) {
18170 var times = this.config.iterations, i = 0, that = this;
18173 for(var total=incremental.iter, j=0; j<total; j++) {
18174 opt.t = opt.tstart * (1 - i++/(times -1));
18175 that.computePositionStep(property, opt);
18177 incremental.onComplete();
18181 incremental.onStep(Math.round(i / (times -1) * 100));
18182 setTimeout(iter, 1);
18185 for(; i < times; i++) {
18186 opt.t = opt.tstart * (1 - i/(times -1));
18187 this.computePositionStep(property, opt);
18192 computePositionStep: function(property, opt) {
18193 var graph = this.graph;
18194 var min = Math.min, max = Math.max;
18195 var dpos = $C(0, 0);
18196 //calculate repulsive forces
18197 graph.eachNode(function(v) {
18199 $.each(property, function(p) {
18200 v.disp[p].x = 0; v.disp[p].y = 0;
18202 graph.eachNode(function(u) {
18204 $.each(property, function(p) {
18205 var vp = v.getPos(p), up = u.getPos(p);
18206 dpos.x = vp.x - up.x;
18207 dpos.y = vp.y - up.y;
18208 var norm = dpos.norm() || 1;
18209 v.disp[p].$add(dpos
18210 .$scale(opt.nodef(norm) / norm));
18215 //calculate attractive forces
18216 var T = !!graph.getNode(this.root).visited;
18217 graph.eachNode(function(node) {
18218 node.eachAdjacency(function(adj) {
18219 var nodeTo = adj.nodeTo;
18220 if(!!nodeTo.visited === T) {
18221 $.each(property, function(p) {
18222 var vp = node.getPos(p), up = nodeTo.getPos(p);
18223 dpos.x = vp.x - up.x;
18224 dpos.y = vp.y - up.y;
18225 var norm = dpos.norm() || 1;
18226 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18227 nodeTo.disp[p].$add(dpos.$scale(-1));
18233 //arrange positions to fit the canvas
18234 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18235 graph.eachNode(function(u) {
18236 $.each(property, function(p) {
18237 var disp = u.disp[p];
18238 var norm = disp.norm() || 1;
18239 var p = u.getPos(p);
18240 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
18241 disp.y * min(Math.abs(disp.y), t) / norm));
18242 p.x = min(w2, max(-w2, p.x));
18243 p.y = min(h2, max(-h2, p.y));
18250 * File: ForceDirected.js
18254 Class: ForceDirected
18256 A visualization that lays graphs using a Force-Directed layout algorithm.
18260 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18264 All <Loader> methods
18266 Constructor Options:
18268 Inherits options from
18271 - <Options.Controller>
18277 - <Options.NodeStyles>
18278 - <Options.Navigation>
18280 Additionally, there are two parameters
18282 levelDistance - (number) Default's *50*. The natural length desired for the edges.
18283 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*.
18285 Instance Properties:
18287 canvas - Access a <Canvas> instance.
18288 graph - Access a <Graph> instance.
18289 op - Access a <ForceDirected.Op> instance.
18290 fx - Access a <ForceDirected.Plot> instance.
18291 labels - Access a <ForceDirected.Label> interface implementation.
18295 $jit.ForceDirected = new Class( {
18297 Implements: [ Loader, Extras, Layouts.ForceDirected ],
18299 initialize: function(controller) {
18300 var $ForceDirected = $jit.ForceDirected;
18307 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18308 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18310 var canvasConfig = this.config;
18311 if(canvasConfig.useCanvas) {
18312 this.canvas = canvasConfig.useCanvas;
18313 this.config.labelContainer = this.canvas.id + '-label';
18315 if(canvasConfig.background) {
18316 canvasConfig.background = $.merge({
18318 }, canvasConfig.background);
18320 this.canvas = new Canvas(this, canvasConfig);
18321 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18324 this.graphOptions = {
18332 this.graph = new Graph(this.graphOptions, this.config.Node,
18334 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18335 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18336 this.op = new $ForceDirected.Op(this);
18339 // initialize extras
18340 this.initializeExtras();
18346 Computes positions and plots the tree.
18348 refresh: function() {
18353 reposition: function() {
18354 this.compute('end');
18358 Method: computeIncremental
18360 Performs the Force Directed algorithm incrementally.
18364 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
18365 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
18366 avoiding browser messages such as "This script is taking too long to complete".
18370 opt - (object) The object properties are described below
18372 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
18373 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18375 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
18376 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
18377 computations for final animation positions then you can just choose 'end'.
18379 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
18380 parameter a percentage value.
18382 onComplete - A callback function called when the algorithm completed.
18386 In this example I calculate the end positions and then animate the graph to those positions
18389 var fd = new $jit.ForceDirected(...);
18390 fd.computeIncremental({
18393 onStep: function(perc) {
18394 Log.write("loading " + perc + "%");
18396 onComplete: function() {
18403 In this example I calculate all positions and (re)plot the graph
18406 var fd = new ForceDirected(...);
18407 fd.computeIncremental({
18409 property: ['end', 'start', 'current'],
18410 onStep: function(perc) {
18411 Log.write("loading " + perc + "%");
18413 onComplete: function() {
18421 computeIncremental: function(opt) {
18426 onComplete: $.empty
18429 this.config.onBeforeCompute(this.graph.getNode(this.root));
18430 this.compute(opt.property, opt);
18436 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18445 Animates the graph from the current positions to the 'end' node positions.
18447 animate: function(opt) {
18448 this.fx.animate($.merge( {
18449 modes: [ 'linear' ]
18454 $jit.ForceDirected.$extend = true;
18456 (function(ForceDirected) {
18459 Class: ForceDirected.Op
18461 Custom extension of <Graph.Op>.
18465 All <Graph.Op> methods
18472 ForceDirected.Op = new Class( {
18474 Implements: Graph.Op
18479 Class: ForceDirected.Plot
18481 Custom extension of <Graph.Plot>.
18485 All <Graph.Plot> methods
18492 ForceDirected.Plot = new Class( {
18494 Implements: Graph.Plot
18499 Class: ForceDirected.Label
18501 Custom extension of <Graph.Label>.
18502 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18506 All <Graph.Label> methods and subclasses.
18510 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18513 ForceDirected.Label = {};
18516 ForceDirected.Label.Native
18518 Custom extension of <Graph.Label.Native>.
18522 All <Graph.Label.Native> methods
18526 <Graph.Label.Native>
18529 ForceDirected.Label.Native = new Class( {
18530 Implements: Graph.Label.Native
18534 ForceDirected.Label.SVG
18536 Custom extension of <Graph.Label.SVG>.
18540 All <Graph.Label.SVG> methods
18547 ForceDirected.Label.SVG = new Class( {
18548 Implements: Graph.Label.SVG,
18550 initialize: function(viz) {
18557 Overrides abstract method placeLabel in <Graph.Label>.
18561 tag - A DOM label element.
18562 node - A <Graph.Node>.
18563 controller - A configuration/controller object passed to the visualization.
18566 placeLabel: function(tag, node, controller) {
18567 var pos = node.pos.getc(true),
18568 canvas = this.viz.canvas,
18569 ox = canvas.translateOffsetX,
18570 oy = canvas.translateOffsetY,
18571 sx = canvas.scaleOffsetX,
18572 sy = canvas.scaleOffsetY,
18573 radius = canvas.getSize();
18575 x: Math.round(pos.x * sx + ox + radius.width / 2),
18576 y: Math.round(pos.y * sy + oy + radius.height / 2)
18578 tag.setAttribute('x', labelPos.x);
18579 tag.setAttribute('y', labelPos.y);
18581 controller.onPlaceLabel(tag, node);
18586 ForceDirected.Label.HTML
18588 Custom extension of <Graph.Label.HTML>.
18592 All <Graph.Label.HTML> methods.
18599 ForceDirected.Label.HTML = new Class( {
18600 Implements: Graph.Label.HTML,
18602 initialize: function(viz) {
18608 Overrides abstract method placeLabel in <Graph.Plot>.
18612 tag - A DOM label element.
18613 node - A <Graph.Node>.
18614 controller - A configuration/controller object passed to the visualization.
18617 placeLabel: function(tag, node, controller) {
18618 var pos = node.pos.getc(true),
18619 canvas = this.viz.canvas,
18620 ox = canvas.translateOffsetX,
18621 oy = canvas.translateOffsetY,
18622 sx = canvas.scaleOffsetX,
18623 sy = canvas.scaleOffsetY,
18624 radius = canvas.getSize();
18626 x: Math.round(pos.x * sx + ox + radius.width / 2),
18627 y: Math.round(pos.y * sy + oy + radius.height / 2)
18629 var style = tag.style;
18630 style.left = labelPos.x + 'px';
18631 style.top = labelPos.y + 'px';
18632 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18634 controller.onPlaceLabel(tag, node);
18639 Class: ForceDirected.Plot.NodeTypes
18641 This class contains a list of <Graph.Node> built-in types.
18642 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18644 You can add your custom node types, customizing your visualization to the extreme.
18649 ForceDirected.Plot.NodeTypes.implement({
18651 'render': function(node, canvas) {
18652 //print your custom node to canvas
18655 'contains': function(node, pos) {
18656 //return true if pos is inside the node or false otherwise
18663 ForceDirected.Plot.NodeTypes = new Class({
18666 'contains': $.lambda(false)
18669 'render': function(node, canvas){
18670 var pos = node.pos.getc(true),
18671 dim = node.getData('dim');
18672 this.nodeHelper.circle.render('fill', pos, dim, canvas);
18674 'contains': function(node, pos){
18675 var npos = node.pos.getc(true),
18676 dim = node.getData('dim');
18677 return this.nodeHelper.circle.contains(npos, pos, dim);
18681 'render': function(node, canvas){
18682 var pos = node.pos.getc(true),
18683 width = node.getData('width'),
18684 height = node.getData('height');
18685 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18687 // TODO(nico): be more precise...
18688 'contains': function(node, pos){
18689 var npos = node.pos.getc(true),
18690 width = node.getData('width'),
18691 height = node.getData('height');
18692 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18696 'render': function(node, canvas){
18697 var pos = node.pos.getc(true),
18698 dim = node.getData('dim');
18699 this.nodeHelper.square.render('fill', pos, dim, canvas);
18701 'contains': function(node, pos){
18702 var npos = node.pos.getc(true),
18703 dim = node.getData('dim');
18704 return this.nodeHelper.square.contains(npos, pos, dim);
18708 'render': function(node, canvas){
18709 var pos = node.pos.getc(true),
18710 width = node.getData('width'),
18711 height = node.getData('height');
18712 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18714 'contains': function(node, pos){
18715 var npos = node.pos.getc(true),
18716 width = node.getData('width'),
18717 height = node.getData('height');
18718 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18722 'render': function(node, canvas){
18723 var pos = node.pos.getc(true),
18724 dim = node.getData('dim');
18725 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18727 'contains': function(node, pos) {
18728 var npos = node.pos.getc(true),
18729 dim = node.getData('dim');
18730 return this.nodeHelper.triangle.contains(npos, pos, dim);
18734 'render': function(node, canvas){
18735 var pos = node.pos.getc(true),
18736 dim = node.getData('dim');
18737 this.nodeHelper.star.render('fill', pos, dim, canvas);
18739 'contains': function(node, pos) {
18740 var npos = node.pos.getc(true),
18741 dim = node.getData('dim');
18742 return this.nodeHelper.star.contains(npos, pos, dim);
18748 Class: ForceDirected.Plot.EdgeTypes
18750 This class contains a list of <Graph.Adjacence> built-in types.
18751 Edge types implemented are 'none', 'line' and 'arrow'.
18753 You can add your custom edge types, customizing your visualization to the extreme.
18758 ForceDirected.Plot.EdgeTypes.implement({
18760 'render': function(adj, canvas) {
18761 //print your custom edge to canvas
18764 'contains': function(adj, pos) {
18765 //return true if pos is inside the arc or false otherwise
18772 ForceDirected.Plot.EdgeTypes = new Class({
18775 'render': function(adj, canvas) {
18776 var from = adj.nodeFrom.pos.getc(true),
18777 to = adj.nodeTo.pos.getc(true);
18778 this.edgeHelper.line.render(from, to, canvas);
18780 'contains': function(adj, pos) {
18781 var from = adj.nodeFrom.pos.getc(true),
18782 to = adj.nodeTo.pos.getc(true);
18783 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18787 'render': function(adj, canvas) {
18788 var from = adj.nodeFrom.pos.getc(true),
18789 to = adj.nodeTo.pos.getc(true),
18790 dim = adj.getData('dim'),
18791 direction = adj.data.$direction,
18792 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18793 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18795 'contains': function(adj, pos) {
18796 var from = adj.nodeFrom.pos.getc(true),
18797 to = adj.nodeTo.pos.getc(true);
18798 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18803 })($jit.ForceDirected);
18815 $jit.TM.$extend = true;
18820 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18824 All <Loader> methods
18826 Constructor Options:
18828 Inherits options from
18831 - <Options.Controller>
18837 - <Options.NodeStyles>
18838 - <Options.Navigation>
18840 Additionally, there are other parameters and some default values changed
18842 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18843 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18844 offset - (number) Default's *2*. Boxes offset.
18845 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18846 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18847 animate - (boolean) Default's *false*. Whether to animate transitions.
18848 Node.type - Described in <Options.Node>. Default's *rectangle*.
18849 duration - Described in <Options.Fx>. Default's *700*.
18850 fps - Described in <Options.Fx>. Default's *45*.
18852 Instance Properties:
18854 canvas - Access a <Canvas> instance.
18855 graph - Access a <Graph> instance.
18856 op - Access a <TM.Op> instance.
18857 fx - Access a <TM.Plot> instance.
18858 labels - Access a <TM.Label> interface implementation.
18862 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18864 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18868 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.
18874 vertical: function(){
18875 return this.orientation == "v";
18877 horizontal: function(){
18878 return this.orientation == "h";
18880 change: function(){
18881 this.orientation = this.vertical()? "h" : "v";
18885 initialize: function(controller){
18891 constrained: false,
18896 //we all know why this is not zero,
18903 textAlign: 'center',
18904 textBaseline: 'top'
18913 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18914 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18915 this.layout.orientation = this.config.orientation;
18917 var canvasConfig = this.config;
18918 if (canvasConfig.useCanvas) {
18919 this.canvas = canvasConfig.useCanvas;
18920 this.config.labelContainer = this.canvas.id + '-label';
18922 if(canvasConfig.background) {
18923 canvasConfig.background = $.merge({
18925 }, canvasConfig.background);
18927 this.canvas = new Canvas(this, canvasConfig);
18928 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18931 this.graphOptions = {
18939 this.graph = new Graph(this.graphOptions, this.config.Node,
18941 this.labels = new TM.Label[canvasConfig.Label.type](this);
18942 this.fx = new TM.Plot(this);
18943 this.op = new TM.Op(this);
18944 this.group = new TM.Group(this);
18945 this.geom = new TM.Geom(this);
18946 this.clickedNode = null;
18948 // initialize extras
18949 this.initializeExtras();
18955 Computes positions and plots the tree.
18957 refresh: function(){
18958 if(this.busy) return;
18961 if(this.config.animate) {
18962 this.compute('end');
18963 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18964 && this.clickedNode.id || this.root));
18965 this.fx.animate($.merge(this.config, {
18966 modes: ['linear', 'node-property:width:height'],
18967 onComplete: function() {
18972 var labelType = this.config.Label.type;
18973 if(labelType != 'Native') {
18975 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18979 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18980 && this.clickedNode.id || this.root));
18988 Plots the TreeMap. This is a shortcut to *fx.plot*.
18998 Returns whether the node is a leaf.
19002 n - (object) A <Graph.Node>.
19006 return n.getSubnodes([
19008 ], "ignore").length == 0;
19014 Sets the node as root.
19018 n - (object) A <Graph.Node>.
19021 enter: function(n){
19022 if(this.busy) return;
19026 config = this.config,
19027 graph = this.graph,
19029 previousClickedNode = this.clickedNode;
19032 onComplete: function() {
19033 //ensure that nodes are shown for that level
19034 if(config.levelsToShow > 0) {
19035 that.geom.setRightLevelToShow(n);
19037 //compute positions of newly inserted nodes
19038 if(config.levelsToShow > 0 || config.request) that.compute();
19039 if(config.animate) {
19041 graph.nodeList.setData('alpha', 0, 'end');
19042 n.eachSubgraph(function(n) {
19043 n.setData('alpha', 1, 'end');
19047 modes:['node-property:alpha'],
19048 onComplete: function() {
19049 //compute end positions
19050 that.clickedNode = clickedNode;
19051 that.compute('end');
19052 //animate positions
19053 //TODO(nico) commenting this line didn't seem to throw errors...
19054 that.clickedNode = previousClickedNode;
19056 modes:['linear', 'node-property:width:height'],
19058 onComplete: function() {
19060 //TODO(nico) check comment above
19061 that.clickedNode = clickedNode;
19068 that.clickedNode = n;
19073 if(config.request) {
19074 this.requestNodes(clickedNode, callback);
19076 callback.onComplete();
19083 Sets the parent node of the current selected node as root.
19087 if(this.busy) return;
19089 this.events.hoveredNode = false;
19091 config = this.config,
19092 graph = this.graph,
19093 parents = graph.getNode(this.clickedNode
19094 && this.clickedNode.id || this.root).getParents(),
19095 parent = parents[0],
19096 clickedNode = parent,
19097 previousClickedNode = this.clickedNode;
19099 //if no parents return
19104 //final plot callback
19106 onComplete: function() {
19107 that.clickedNode = parent;
19108 if(config.request) {
19109 that.requestNodes(parent, {
19110 onComplete: function() {
19124 if (config.levelsToShow > 0)
19125 this.geom.setRightLevelToShow(parent);
19126 //animate node positions
19127 if(config.animate) {
19128 this.clickedNode = clickedNode;
19129 this.compute('end');
19130 //animate the visible subtree only
19131 this.clickedNode = previousClickedNode;
19133 modes:['linear', 'node-property:width:height'],
19135 onComplete: function() {
19136 //animate the parent subtree
19137 that.clickedNode = clickedNode;
19138 //change nodes alpha
19139 graph.eachNode(function(n) {
19140 n.setDataset(['current', 'end'], {
19144 previousClickedNode.eachSubgraph(function(node) {
19145 node.setData('alpha', 1);
19149 modes:['node-property:alpha'],
19150 onComplete: function() {
19151 callback.onComplete();
19157 callback.onComplete();
19161 requestNodes: function(node, onComplete){
19162 var handler = $.merge(this.controller, onComplete),
19163 lev = this.config.levelsToShow;
19164 if (handler.request) {
19165 var leaves = [], d = node._depth;
19166 node.eachLevel(0, lev, function(n){
19167 var nodeLevel = lev - (n._depth - d);
19168 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19170 n._level = nodeLevel;
19173 this.group.requestNodes(leaves, handler);
19175 handler.onComplete();
19183 Custom extension of <Graph.Op>.
19187 All <Graph.Op> methods
19194 TM.Op = new Class({
19195 Implements: Graph.Op,
19197 initialize: function(viz){
19202 //extend level methods of Graph.Geom
19203 TM.Geom = new Class({
19204 Implements: Graph.Geom,
19206 getRightLevelToShow: function() {
19207 return this.viz.config.levelsToShow;
19210 setRightLevelToShow: function(node) {
19211 var level = this.getRightLevelToShow(),
19212 fx = this.viz.labels;
19213 node.eachLevel(0, level+1, function(n) {
19214 var d = n._depth - node._depth;
19219 fx.hideLabel(n, false);
19227 delete node.ignore;
19233 Performs operations on group of nodes.
19236 TM.Group = new Class( {
19238 initialize: function(viz){
19240 this.canvas = viz.canvas;
19241 this.config = viz.config;
19246 Calls the request method on the controller to request a subtree for each node.
19248 requestNodes: function(nodes, controller){
19249 var counter = 0, len = nodes.length, nodeSelected = {};
19250 var complete = function(){
19251 controller.onComplete();
19253 var viz = this.viz;
19256 for ( var i = 0; i < len; i++) {
19257 nodeSelected[nodes[i].id] = nodes[i];
19258 controller.request(nodes[i].id, nodes[i]._level, {
19259 onComplete: function(nodeId, data){
19260 if (data && data.children) {
19266 if (++counter == len) {
19267 viz.graph.computeLevels(viz.root, 0);
19279 Custom extension of <Graph.Plot>.
19283 All <Graph.Plot> methods
19290 TM.Plot = new Class({
19292 Implements: Graph.Plot,
19294 initialize: function(viz){
19296 this.config = viz.config;
19297 this.node = this.config.Node;
19298 this.edge = this.config.Edge;
19299 this.animation = new Animation;
19300 this.nodeTypes = new TM.Plot.NodeTypes;
19301 this.edgeTypes = new TM.Plot.EdgeTypes;
19302 this.labels = viz.labels;
19305 plot: function(opt, animating){
19306 var viz = this.viz,
19308 viz.canvas.clear();
19309 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19310 'withLabels': true,
19311 'hideLabels': false,
19312 'plotSubtree': function(n, ch){
19313 return n.anySubnode("exist");
19322 Custom extension of <Graph.Label>.
19323 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19327 All <Graph.Label> methods and subclasses.
19331 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19339 Custom extension of <Graph.Label.Native>.
19343 All <Graph.Label.Native> methods
19347 <Graph.Label.Native>
19349 TM.Label.Native = new Class({
19350 Implements: Graph.Label.Native,
19352 initialize: function(viz) {
19353 this.config = viz.config;
19354 this.leaf = viz.leaf;
19357 renderLabel: function(canvas, node, controller){
19358 if(!this.leaf(node) && !this.config.titleHeight) return;
19359 var pos = node.pos.getc(true),
19360 ctx = canvas.getCtx(),
19361 width = node.getData('width'),
19362 height = node.getData('height'),
19363 x = pos.x + width/2,
19366 ctx.fillText(node.name, x, y, width);
19373 Custom extension of <Graph.Label.SVG>.
19377 All <Graph.Label.SVG> methods
19383 TM.Label.SVG = new Class( {
19384 Implements: Graph.Label.SVG,
19386 initialize: function(viz){
19388 this.leaf = viz.leaf;
19389 this.config = viz.config;
19395 Overrides abstract method placeLabel in <Graph.Plot>.
19399 tag - A DOM label element.
19400 node - A <Graph.Node>.
19401 controller - A configuration/controller object passed to the visualization.
19404 placeLabel: function(tag, node, controller){
19405 var pos = node.pos.getc(true),
19406 canvas = this.viz.canvas,
19407 ox = canvas.translateOffsetX,
19408 oy = canvas.translateOffsetY,
19409 sx = canvas.scaleOffsetX,
19410 sy = canvas.scaleOffsetY,
19411 radius = canvas.getSize();
19413 x: Math.round(pos.x * sx + ox + radius.width / 2),
19414 y: Math.round(pos.y * sy + oy + radius.height / 2)
19416 tag.setAttribute('x', labelPos.x);
19417 tag.setAttribute('y', labelPos.y);
19419 if(!this.leaf(node) && !this.config.titleHeight) {
19420 tag.style.display = 'none';
19422 controller.onPlaceLabel(tag, node);
19429 Custom extension of <Graph.Label.HTML>.
19433 All <Graph.Label.HTML> methods.
19440 TM.Label.HTML = new Class( {
19441 Implements: Graph.Label.HTML,
19443 initialize: function(viz){
19445 this.leaf = viz.leaf;
19446 this.config = viz.config;
19452 Overrides abstract method placeLabel in <Graph.Plot>.
19456 tag - A DOM label element.
19457 node - A <Graph.Node>.
19458 controller - A configuration/controller object passed to the visualization.
19461 placeLabel: function(tag, node, controller){
19462 var pos = node.pos.getc(true),
19463 canvas = this.viz.canvas,
19464 ox = canvas.translateOffsetX,
19465 oy = canvas.translateOffsetY,
19466 sx = canvas.scaleOffsetX,
19467 sy = canvas.scaleOffsetY,
19468 radius = canvas.getSize();
19470 x: Math.round(pos.x * sx + ox + radius.width / 2),
19471 y: Math.round(pos.y * sy + oy + radius.height / 2)
19474 var style = tag.style;
19475 style.left = labelPos.x + 'px';
19476 style.top = labelPos.y + 'px';
19477 style.width = node.getData('width') * sx + 'px';
19478 style.height = node.getData('height') * sy + 'px';
19479 style.zIndex = node._depth * 100;
19480 style.display = '';
19482 if(!this.leaf(node) && !this.config.titleHeight) {
19483 tag.style.display = 'none';
19485 controller.onPlaceLabel(tag, node);
19490 Class: TM.Plot.NodeTypes
19492 This class contains a list of <Graph.Node> built-in types.
19493 Node types implemented are 'none', 'rectangle'.
19495 You can add your custom node types, customizing your visualization to the extreme.
19500 TM.Plot.NodeTypes.implement({
19502 'render': function(node, canvas) {
19503 //print your custom node to canvas
19506 'contains': function(node, pos) {
19507 //return true if pos is inside the node or false otherwise
19514 TM.Plot.NodeTypes = new Class( {
19520 'render': function(node, canvas, animating){
19521 var leaf = this.viz.leaf(node),
19522 config = this.config,
19523 offst = config.offset,
19524 titleHeight = config.titleHeight,
19525 pos = node.pos.getc(true),
19526 width = node.getData('width'),
19527 height = node.getData('height'),
19528 border = node.getData('border'),
19529 ctx = canvas.getCtx(),
19530 posx = pos.x + offst / 2,
19531 posy = pos.y + offst / 2;
19532 if(width <= offst || height <= offst) return;
19534 if(config.cushion) {
19535 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
19536 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19537 var color = node.getData('color');
19538 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
19539 function(r) { return r * 0.2 >> 0; }));
19540 lg.addColorStop(0, color);
19541 lg.addColorStop(1, colorGrad);
19542 ctx.fillStyle = lg;
19544 ctx.fillRect(posx, posy, width - offst, height - offst);
19547 ctx.strokeStyle = border;
19548 ctx.strokeRect(posx, posy, width - offst, height - offst);
19551 } else if(titleHeight > 0){
19552 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19553 titleHeight - offst);
19556 ctx.strokeStyle = border;
19557 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19563 'contains': function(node, pos) {
19564 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19565 var npos = node.pos.getc(true),
19566 width = node.getData('width'),
19567 leaf = this.viz.leaf(node),
19568 height = leaf? node.getData('height') : this.config.titleHeight;
19569 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19574 TM.Plot.EdgeTypes = new Class( {
19579 Class: TM.SliceAndDice
19581 A slice and dice TreeMap visualization.
19585 All <TM.Base> methods and properties.
19587 TM.SliceAndDice = new Class( {
19589 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19594 Class: TM.Squarified
19596 A squarified TreeMap visualization.
19600 All <TM.Base> methods and properties.
19602 TM.Squarified = new Class( {
19604 Loader, Extras, TM.Base, Layouts.TM.Squarified
19611 A strip TreeMap visualization.
19615 All <TM.Base> methods and properties.
19617 TM.Strip = new Class( {
19619 Loader, Extras, TM.Base, Layouts.TM.Strip
19632 A radial graph visualization with advanced animations.
19636 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>
19640 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.
19644 All <Loader> methods
19646 Constructor Options:
19648 Inherits options from
19651 - <Options.Controller>
19657 - <Options.NodeStyles>
19658 - <Options.Navigation>
19660 Additionally, there are other parameters and some default values changed
19662 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19663 levelDistance - (number) Default's *100*. The distance between levels of the tree.
19665 Instance Properties:
19667 canvas - Access a <Canvas> instance.
19668 graph - Access a <Graph> instance.
19669 op - Access a <RGraph.Op> instance.
19670 fx - Access a <RGraph.Plot> instance.
19671 labels - Access a <RGraph.Label> interface implementation.
19674 $jit.RGraph = new Class( {
19677 Loader, Extras, Layouts.Radial
19680 initialize: function(controller){
19681 var $RGraph = $jit.RGraph;
19684 interpolation: 'linear',
19688 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19689 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19691 var canvasConfig = this.config;
19692 if(canvasConfig.useCanvas) {
19693 this.canvas = canvasConfig.useCanvas;
19694 this.config.labelContainer = this.canvas.id + '-label';
19696 if(canvasConfig.background) {
19697 canvasConfig.background = $.merge({
19699 }, canvasConfig.background);
19701 this.canvas = new Canvas(this, canvasConfig);
19702 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19705 this.graphOptions = {
19713 this.graph = new Graph(this.graphOptions, this.config.Node,
19715 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19716 this.fx = new $RGraph.Plot(this, $RGraph);
19717 this.op = new $RGraph.Op(this);
19721 this.parent = false;
19722 // initialize extras
19723 this.initializeExtras();
19728 createLevelDistanceFunc
19730 Returns the levelDistance function used for calculating a node distance
19731 to its origin. This function returns a function that is computed
19732 per level and not per node, such that all nodes with the same depth will have the
19733 same distance to the origin. The resulting function gets the
19734 parent node as parameter and returns a float.
19737 createLevelDistanceFunc: function(){
19738 var ld = this.config.levelDistance;
19739 return function(elem){
19740 return (elem._depth + 1) * ld;
19747 Computes positions and plots the tree.
19750 refresh: function(){
19755 reposition: function(){
19756 this.compute('end');
19762 Plots the RGraph. This is a shortcut to *fx.plot*.
19768 getNodeAndParentAngle
19770 Returns the _parent_ of the given node, also calculating its angle span.
19772 getNodeAndParentAngle: function(id){
19774 var n = this.graph.getNode(id);
19775 var ps = n.getParents();
19776 var p = (ps.length > 0)? ps[0] : false;
19778 var posParent = p.pos.getc(), posChild = n.pos.getc();
19779 var newPos = posParent.add(posChild.scale(-1));
19780 theta = Math.atan2(newPos.y, newPos.x);
19782 theta += 2 * Math.PI;
19792 Enumerates the children in order to maintain child ordering (second constraint of the paper).
19794 tagChildren: function(par, id){
19795 if (par.angleSpan) {
19797 par.eachAdjacency(function(elem){
19798 adjs.push(elem.nodeTo);
19800 var len = adjs.length;
19801 for ( var i = 0; i < len && id != adjs[i].id; i++)
19803 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19804 adjs[j].dist = k++;
19811 Animates the <RGraph> to center the node specified by *id*.
19815 id - A <Graph.Node> id.
19816 opt - (optional|object) An object containing some extra properties described below
19817 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19822 rgraph.onClick('someid');
19824 rgraph.onClick('someid', {
19830 onClick: function(id, opt){
19831 if (this.root != id && !this.busy) {
19835 this.controller.onBeforeCompute(this.graph.getNode(id));
19836 var obj = this.getNodeAndParentAngle(id);
19838 // second constraint
19839 this.tagChildren(obj.parent, id);
19840 this.parent = obj.parent;
19841 this.compute('end');
19843 // first constraint
19844 var thetaDiff = obj.theta - obj.parent.endPos.theta;
19845 this.graph.eachNode(function(elem){
19846 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19849 var mode = this.config.interpolation;
19851 onComplete: $.empty
19854 this.fx.animate($.merge( {
19860 onComplete: function(){
19869 $jit.RGraph.$extend = true;
19876 Custom extension of <Graph.Op>.
19880 All <Graph.Op> methods
19887 RGraph.Op = new Class( {
19889 Implements: Graph.Op
19896 Custom extension of <Graph.Plot>.
19900 All <Graph.Plot> methods
19907 RGraph.Plot = new Class( {
19909 Implements: Graph.Plot
19914 Object: RGraph.Label
19916 Custom extension of <Graph.Label>.
19917 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19921 All <Graph.Label> methods and subclasses.
19925 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19931 RGraph.Label.Native
19933 Custom extension of <Graph.Label.Native>.
19937 All <Graph.Label.Native> methods
19941 <Graph.Label.Native>
19944 RGraph.Label.Native = new Class( {
19945 Implements: Graph.Label.Native
19951 Custom extension of <Graph.Label.SVG>.
19955 All <Graph.Label.SVG> methods
19962 RGraph.Label.SVG = new Class( {
19963 Implements: Graph.Label.SVG,
19965 initialize: function(viz){
19972 Overrides abstract method placeLabel in <Graph.Plot>.
19976 tag - A DOM label element.
19977 node - A <Graph.Node>.
19978 controller - A configuration/controller object passed to the visualization.
19981 placeLabel: function(tag, node, controller){
19982 var pos = node.pos.getc(true),
19983 canvas = this.viz.canvas,
19984 ox = canvas.translateOffsetX,
19985 oy = canvas.translateOffsetY,
19986 sx = canvas.scaleOffsetX,
19987 sy = canvas.scaleOffsetY,
19988 radius = canvas.getSize();
19990 x: Math.round(pos.x * sx + ox + radius.width / 2),
19991 y: Math.round(pos.y * sy + oy + radius.height / 2)
19993 tag.setAttribute('x', labelPos.x);
19994 tag.setAttribute('y', labelPos.y);
19996 controller.onPlaceLabel(tag, node);
20003 Custom extension of <Graph.Label.HTML>.
20007 All <Graph.Label.HTML> methods.
20014 RGraph.Label.HTML = new Class( {
20015 Implements: Graph.Label.HTML,
20017 initialize: function(viz){
20023 Overrides abstract method placeLabel in <Graph.Plot>.
20027 tag - A DOM label element.
20028 node - A <Graph.Node>.
20029 controller - A configuration/controller object passed to the visualization.
20032 placeLabel: function(tag, node, controller){
20033 var pos = node.pos.getc(true),
20034 canvas = this.viz.canvas,
20035 ox = canvas.translateOffsetX,
20036 oy = canvas.translateOffsetY,
20037 sx = canvas.scaleOffsetX,
20038 sy = canvas.scaleOffsetY,
20039 radius = canvas.getSize();
20041 x: Math.round(pos.x * sx + ox + radius.width / 2),
20042 y: Math.round(pos.y * sy + oy + radius.height / 2)
20045 var style = tag.style;
20046 style.left = labelPos.x + 'px';
20047 style.top = labelPos.y + 'px';
20048 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20050 controller.onPlaceLabel(tag, node);
20055 Class: RGraph.Plot.NodeTypes
20057 This class contains a list of <Graph.Node> built-in types.
20058 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20060 You can add your custom node types, customizing your visualization to the extreme.
20065 RGraph.Plot.NodeTypes.implement({
20067 'render': function(node, canvas) {
20068 //print your custom node to canvas
20071 'contains': function(node, pos) {
20072 //return true if pos is inside the node or false otherwise
20079 RGraph.Plot.NodeTypes = new Class({
20082 'contains': $.lambda(false)
20085 'render': function(node, canvas){
20086 var pos = node.pos.getc(true),
20087 dim = node.getData('dim');
20088 this.nodeHelper.circle.render('fill', pos, dim, canvas);
20090 'contains': function(node, pos){
20091 var npos = node.pos.getc(true),
20092 dim = node.getData('dim');
20093 return this.nodeHelper.circle.contains(npos, pos, dim);
20097 'render': function(node, canvas){
20098 var pos = node.pos.getc(true),
20099 width = node.getData('width'),
20100 height = node.getData('height');
20101 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20103 // TODO(nico): be more precise...
20104 'contains': function(node, pos){
20105 var npos = node.pos.getc(true),
20106 width = node.getData('width'),
20107 height = node.getData('height');
20108 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20112 'render': function(node, canvas){
20113 var pos = node.pos.getc(true),
20114 dim = node.getData('dim');
20115 this.nodeHelper.square.render('fill', pos, dim, canvas);
20117 'contains': function(node, pos){
20118 var npos = node.pos.getc(true),
20119 dim = node.getData('dim');
20120 return this.nodeHelper.square.contains(npos, pos, dim);
20124 'render': function(node, canvas){
20125 var pos = node.pos.getc(true),
20126 width = node.getData('width'),
20127 height = node.getData('height');
20128 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20130 'contains': function(node, pos){
20131 var npos = node.pos.getc(true),
20132 width = node.getData('width'),
20133 height = node.getData('height');
20134 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20138 'render': function(node, canvas){
20139 var pos = node.pos.getc(true),
20140 dim = node.getData('dim');
20141 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20143 'contains': function(node, pos) {
20144 var npos = node.pos.getc(true),
20145 dim = node.getData('dim');
20146 return this.nodeHelper.triangle.contains(npos, pos, dim);
20150 'render': function(node, canvas){
20151 var pos = node.pos.getc(true),
20152 dim = node.getData('dim');
20153 this.nodeHelper.star.render('fill', pos, dim, canvas);
20155 'contains': function(node, pos) {
20156 var npos = node.pos.getc(true),
20157 dim = node.getData('dim');
20158 return this.nodeHelper.star.contains(npos, pos, dim);
20164 Class: RGraph.Plot.EdgeTypes
20166 This class contains a list of <Graph.Adjacence> built-in types.
20167 Edge types implemented are 'none', 'line' and 'arrow'.
20169 You can add your custom edge types, customizing your visualization to the extreme.
20174 RGraph.Plot.EdgeTypes.implement({
20176 'render': function(adj, canvas) {
20177 //print your custom edge to canvas
20180 'contains': function(adj, pos) {
20181 //return true if pos is inside the arc or false otherwise
20188 RGraph.Plot.EdgeTypes = new Class({
20191 'render': function(adj, canvas) {
20192 var from = adj.nodeFrom.pos.getc(true),
20193 to = adj.nodeTo.pos.getc(true);
20194 this.edgeHelper.line.render(from, to, canvas);
20196 'contains': function(adj, pos) {
20197 var from = adj.nodeFrom.pos.getc(true),
20198 to = adj.nodeTo.pos.getc(true);
20199 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20203 'render': function(adj, canvas) {
20204 var from = adj.nodeFrom.pos.getc(true),
20205 to = adj.nodeTo.pos.getc(true),
20206 dim = adj.getData('dim'),
20207 direction = adj.data.$direction,
20208 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20209 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20211 'contains': function(adj, pos) {
20212 var from = adj.nodeFrom.pos.getc(true),
20213 to = adj.nodeTo.pos.getc(true);
20214 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20223 * File: Hypertree.js
20230 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
20234 moebiusTransformation
20236 Calculates a moebius transformation for this point / complex.
20237 For more information go to:
20238 http://en.wikipedia.org/wiki/Moebius_transformation.
20242 c - An initialized Complex instance representing a translation Vector.
20245 Complex.prototype.moebiusTransformation = function(c) {
20246 var num = this.add(c);
20247 var den = c.$conjugate().$prod(this);
20249 return num.$div(den);
20253 moebiusTransformation
20255 Calculates a moebius transformation for the hyperbolic tree.
20257 <http://en.wikipedia.org/wiki/Moebius_transformation>
20261 graph - A <Graph> instance.
20263 prop - A property array.
20264 theta - Rotation angle.
20265 startPos - _optional_ start position.
20267 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20268 this.eachNode(graph, function(elem) {
20269 for ( var i = 0; i < prop.length; i++) {
20270 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20271 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20279 A Hyperbolic Tree/Graph visualization.
20283 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
20284 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20288 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.
20292 All <Loader> methods
20294 Constructor Options:
20296 Inherits options from
20299 - <Options.Controller>
20305 - <Options.NodeStyles>
20306 - <Options.Navigation>
20308 Additionally, there are other parameters and some default values changed
20310 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*.
20311 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.
20312 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20313 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20314 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
20316 Instance Properties:
20318 canvas - Access a <Canvas> instance.
20319 graph - Access a <Graph> instance.
20320 op - Access a <Hypertree.Op> instance.
20321 fx - Access a <Hypertree.Plot> instance.
20322 labels - Access a <Hypertree.Label> interface implementation.
20326 $jit.Hypertree = new Class( {
20328 Implements: [ Loader, Extras, Layouts.Radial ],
20330 initialize: function(controller) {
20331 var $Hypertree = $jit.Hypertree;
20342 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20343 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20345 var canvasConfig = this.config;
20346 if(canvasConfig.useCanvas) {
20347 this.canvas = canvasConfig.useCanvas;
20348 this.config.labelContainer = this.canvas.id + '-label';
20350 if(canvasConfig.background) {
20351 canvasConfig.background = $.merge({
20353 }, canvasConfig.background);
20355 this.canvas = new Canvas(this, canvasConfig);
20356 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20359 this.graphOptions = {
20367 this.graph = new Graph(this.graphOptions, this.config.Node,
20369 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20370 this.fx = new $Hypertree.Plot(this, $Hypertree);
20371 this.op = new $Hypertree.Op(this);
20375 // initialize extras
20376 this.initializeExtras();
20381 createLevelDistanceFunc
20383 Returns the levelDistance function used for calculating a node distance
20384 to its origin. This function returns a function that is computed
20385 per level and not per node, such that all nodes with the same depth will have the
20386 same distance to the origin. The resulting function gets the
20387 parent node as parameter and returns a float.
20390 createLevelDistanceFunc: function() {
20391 // get max viz. length.
20392 var r = this.getRadius();
20394 var depth = 0, max = Math.max, config = this.config;
20395 this.graph.eachNode(function(node) {
20396 depth = max(node._depth, depth);
20399 // node distance generator
20400 var genDistFunc = function(a) {
20401 return function(node) {
20403 var d = node._depth + 1;
20404 var acum = 0, pow = Math.pow;
20406 acum += pow(a, d--);
20408 return acum - config.offset;
20411 // estimate better edge length.
20412 for ( var i = 0.51; i <= 1; i += 0.01) {
20413 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20414 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20416 return genDistFunc(0.75);
20422 Returns the current radius of the visualization. If *config.radius* is *auto* then it
20423 calculates the radius by taking the smaller size of the <Canvas> widget.
20430 getRadius: function() {
20431 var rad = this.config.radius;
20432 if (rad !== "auto") { return rad; }
20433 var s = this.canvas.getSize();
20434 return Math.min(s.width, s.height) / 2;
20440 Computes positions and plots the tree.
20444 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20447 refresh: function(reposition) {
20450 this.graph.eachNode(function(node) {
20451 node.startPos.rho = node.pos.rho = node.endPos.rho;
20452 node.startPos.theta = node.pos.theta = node.endPos.theta;
20463 Computes nodes' positions and restores the tree to its previous position.
20465 For calculating nodes' positions the root must be placed on its origin. This method does this
20466 and then attemps to restore the hypertree to its previous position.
20469 reposition: function() {
20470 this.compute('end');
20471 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20472 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20474 this.graph.eachNode(function(node) {
20476 node.endPos.rho = node.pos.rho;
20477 node.endPos.theta = node.pos.theta;
20485 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
20495 Animates the <Hypertree> to center the node specified by *id*.
20499 id - A <Graph.Node> id.
20500 opt - (optional|object) An object containing some extra properties described below
20501 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20506 ht.onClick('someid');
20508 ht.onClick('someid', {
20514 onClick: function(id, opt) {
20515 var pos = this.graph.getNode(id).pos.getc(true);
20516 this.move(pos, opt);
20522 Translates the tree to the given position.
20526 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20527 opt - This object has been defined in <Hypertree.onClick>
20532 ht.move({ x: 0, y: 0.7 }, {
20538 move: function(pos, opt) {
20539 var versor = $C(pos.x, pos.y);
20540 if (this.busy === false && versor.norm() < 1) {
20542 var root = this.graph.getClosestNodeToPos(versor), that = this;
20543 this.graph.computeLevels(root.id, 0);
20544 this.controller.onBeforeCompute(root);
20546 onComplete: $.empty
20548 this.fx.animate($.merge( {
20549 modes: [ 'moebius' ],
20552 onComplete: function() {
20561 $jit.Hypertree.$extend = true;
20563 (function(Hypertree) {
20566 Class: Hypertree.Op
20568 Custom extension of <Graph.Op>.
20572 All <Graph.Op> methods
20579 Hypertree.Op = new Class( {
20581 Implements: Graph.Op
20586 Class: Hypertree.Plot
20588 Custom extension of <Graph.Plot>.
20592 All <Graph.Plot> methods
20599 Hypertree.Plot = new Class( {
20601 Implements: Graph.Plot
20606 Object: Hypertree.Label
20608 Custom extension of <Graph.Label>.
20609 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20613 All <Graph.Label> methods and subclasses.
20617 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20620 Hypertree.Label = {};
20623 Hypertree.Label.Native
20625 Custom extension of <Graph.Label.Native>.
20629 All <Graph.Label.Native> methods
20633 <Graph.Label.Native>
20636 Hypertree.Label.Native = new Class( {
20637 Implements: Graph.Label.Native,
20639 initialize: function(viz) {
20643 renderLabel: function(canvas, node, controller) {
20644 var ctx = canvas.getCtx();
20645 var coord = node.pos.getc(true);
20646 var s = this.viz.getRadius();
20647 ctx.fillText(node.name, coord.x * s, coord.y * s);
20652 Hypertree.Label.SVG
20654 Custom extension of <Graph.Label.SVG>.
20658 All <Graph.Label.SVG> methods
20665 Hypertree.Label.SVG = new Class( {
20666 Implements: Graph.Label.SVG,
20668 initialize: function(viz) {
20675 Overrides abstract method placeLabel in <Graph.Plot>.
20679 tag - A DOM label element.
20680 node - A <Graph.Node>.
20681 controller - A configuration/controller object passed to the visualization.
20684 placeLabel: function(tag, node, controller) {
20685 var pos = node.pos.getc(true),
20686 canvas = this.viz.canvas,
20687 ox = canvas.translateOffsetX,
20688 oy = canvas.translateOffsetY,
20689 sx = canvas.scaleOffsetX,
20690 sy = canvas.scaleOffsetY,
20691 radius = canvas.getSize(),
20692 r = this.viz.getRadius();
20694 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20695 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20697 tag.setAttribute('x', labelPos.x);
20698 tag.setAttribute('y', labelPos.y);
20699 controller.onPlaceLabel(tag, node);
20704 Hypertree.Label.HTML
20706 Custom extension of <Graph.Label.HTML>.
20710 All <Graph.Label.HTML> methods.
20717 Hypertree.Label.HTML = new Class( {
20718 Implements: Graph.Label.HTML,
20720 initialize: function(viz) {
20726 Overrides abstract method placeLabel in <Graph.Plot>.
20730 tag - A DOM label element.
20731 node - A <Graph.Node>.
20732 controller - A configuration/controller object passed to the visualization.
20735 placeLabel: function(tag, node, controller) {
20736 var pos = node.pos.getc(true),
20737 canvas = this.viz.canvas,
20738 ox = canvas.translateOffsetX,
20739 oy = canvas.translateOffsetY,
20740 sx = canvas.scaleOffsetX,
20741 sy = canvas.scaleOffsetY,
20742 radius = canvas.getSize(),
20743 r = this.viz.getRadius();
20745 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20746 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20748 var style = tag.style;
20749 style.left = labelPos.x + 'px';
20750 style.top = labelPos.y + 'px';
20751 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20753 controller.onPlaceLabel(tag, node);
20758 Class: Hypertree.Plot.NodeTypes
20760 This class contains a list of <Graph.Node> built-in types.
20761 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20763 You can add your custom node types, customizing your visualization to the extreme.
20768 Hypertree.Plot.NodeTypes.implement({
20770 'render': function(node, canvas) {
20771 //print your custom node to canvas
20774 'contains': function(node, pos) {
20775 //return true if pos is inside the node or false otherwise
20782 Hypertree.Plot.NodeTypes = new Class({
20785 'contains': $.lambda(false)
20788 'render': function(node, canvas) {
20789 var nconfig = this.node,
20790 dim = node.getData('dim'),
20791 p = node.pos.getc();
20792 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20793 p.$scale(node.scale);
20795 this.nodeHelper.circle.render('fill', p, dim, canvas);
20798 'contains': function(node, pos) {
20799 var dim = node.getData('dim'),
20800 npos = node.pos.getc().$scale(node.scale);
20801 return this.nodeHelper.circle.contains(npos, pos, dim);
20805 'render': function(node, canvas) {
20806 var pos = node.pos.getc().$scale(node.scale),
20807 width = node.getData('width'),
20808 height = node.getData('height');
20809 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20811 'contains': function(node, pos) {
20812 var width = node.getData('width'),
20813 height = node.getData('height'),
20814 npos = node.pos.getc().$scale(node.scale);
20815 return this.nodeHelper.circle.contains(npos, pos, width, height);
20819 'render': function(node, canvas) {
20820 var nconfig = this.node,
20821 dim = node.getData('dim'),
20822 p = node.pos.getc();
20823 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20824 p.$scale(node.scale);
20826 this.nodeHelper.square.render('fill', p, dim, canvas);
20829 'contains': function(node, pos) {
20830 var dim = node.getData('dim'),
20831 npos = node.pos.getc().$scale(node.scale);
20832 return this.nodeHelper.square.contains(npos, pos, dim);
20836 'render': function(node, canvas) {
20837 var nconfig = this.node,
20838 width = node.getData('width'),
20839 height = node.getData('height'),
20840 pos = node.pos.getc();
20841 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20842 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20843 pos.$scale(node.scale);
20844 if (width > 0.2 && height > 0.2) {
20845 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20848 'contains': function(node, pos) {
20849 var width = node.getData('width'),
20850 height = node.getData('height'),
20851 npos = node.pos.getc().$scale(node.scale);
20852 return this.nodeHelper.square.contains(npos, pos, width, height);
20856 'render': function(node, canvas) {
20857 var nconfig = this.node,
20858 dim = node.getData('dim'),
20859 p = node.pos.getc();
20860 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20861 p.$scale(node.scale);
20863 this.nodeHelper.triangle.render('fill', p, dim, canvas);
20866 'contains': function(node, pos) {
20867 var dim = node.getData('dim'),
20868 npos = node.pos.getc().$scale(node.scale);
20869 return this.nodeHelper.triangle.contains(npos, pos, dim);
20873 'render': function(node, canvas) {
20874 var nconfig = this.node,
20875 dim = node.getData('dim'),
20876 p = node.pos.getc();
20877 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20878 p.$scale(node.scale);
20880 this.nodeHelper.star.render('fill', p, dim, canvas);
20883 'contains': function(node, pos) {
20884 var dim = node.getData('dim'),
20885 npos = node.pos.getc().$scale(node.scale);
20886 return this.nodeHelper.star.contains(npos, pos, dim);
20892 Class: Hypertree.Plot.EdgeTypes
20894 This class contains a list of <Graph.Adjacence> built-in types.
20895 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20897 You can add your custom edge types, customizing your visualization to the extreme.
20902 Hypertree.Plot.EdgeTypes.implement({
20904 'render': function(adj, canvas) {
20905 //print your custom edge to canvas
20908 'contains': function(adj, pos) {
20909 //return true if pos is inside the arc or false otherwise
20916 Hypertree.Plot.EdgeTypes = new Class({
20919 'render': function(adj, canvas) {
20920 var from = adj.nodeFrom.pos.getc(true),
20921 to = adj.nodeTo.pos.getc(true),
20922 r = adj.nodeFrom.scale;
20923 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20925 'contains': function(adj, pos) {
20926 var from = adj.nodeFrom.pos.getc(true),
20927 to = adj.nodeTo.pos.getc(true),
20928 r = adj.nodeFrom.scale;
20929 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20933 'render': function(adj, canvas) {
20934 var from = adj.nodeFrom.pos.getc(true),
20935 to = adj.nodeTo.pos.getc(true),
20936 r = adj.nodeFrom.scale,
20937 dim = adj.getData('dim'),
20938 direction = adj.data.$direction,
20939 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20940 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20942 'contains': function(adj, pos) {
20943 var from = adj.nodeFrom.pos.getc(true),
20944 to = adj.nodeTo.pos.getc(true),
20945 r = adj.nodeFrom.scale;
20946 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20950 'render': function(adj, canvas) {
20951 var from = adj.nodeFrom.pos.getc(),
20952 to = adj.nodeTo.pos.getc(),
20953 dim = this.viz.getRadius();
20954 this.edgeHelper.hyperline.render(from, to, dim, canvas);
20956 'contains': $.lambda(false)
20960 })($jit.Hypertree);