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;
11676 chartBarWidth = dimArray[i];
11677 chartBarHeight = height;
11682 yCoord = y - acum - dimArray[i];
11683 chartBarWidth = width;
11684 chartBarHeight = dimArray[i];
11686 ctx.fillRect(xCoord, yCoord, chartBarWidth, chartBarHeight);
11689 if (chartBarHeight > 0)
11691 ctx.font = label.style + ' ' + (label.size - 2) + 'px ' + label.family;
11692 labelText = valueArray[i].toString();
11693 mtxt = ctx.measureText(labelText);
11695 labelTextPaddingX = 10;
11696 labelTextPaddingY = 6;
11698 labelBoxWidth = mtxt.width + labelTextPaddingX;
11699 labelBoxHeight = label.size + labelTextPaddingY;
11701 // do NOT draw label if label box is smaller than chartBarHeight
11702 if ((horz && (labelBoxWidth < chartBarWidth)) || (!horz && (labelBoxHeight < chartBarHeight)))
11704 labelBoxX = xCoord + chartBarWidth/2 - mtxt.width/2 - labelTextPaddingX/2;
11705 labelBoxY = yCoord + chartBarHeight/2 - labelBoxHeight/2;
11707 ctx.fillStyle = "rgba(255,255,255,.2)";
11708 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "fill");
11709 ctx.fillStyle = "rgba(0,0,0,.8)";
11710 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "stroke");
11711 ctx.textAlign = 'center';
11712 ctx.fillStyle = "rgba(255,255,255,.6)";
11713 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2, labelBoxY + labelBoxHeight/2);
11714 ctx.fillStyle = "rgba(0,0,0,.6)";
11715 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2 + 1, labelBoxY + labelBoxHeight/2 + 1);
11719 if(border && border.name == stringArray[i]) {
11721 opt.dimValue = dimArray[i];
11723 acum += (dimArray[i] || 0);
11724 valAcum += (valueArray[i] || 0);
11729 ctx.strokeStyle = border.color;
11731 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11733 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11737 if(label.type == 'Native') {
11739 ctx.fillStyle = ctx.strokeStyle = label.color;
11740 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11741 ctx.textBaseline = 'middle';
11743 acumValueLabel = gvl;
11745 acumValueLabel = valAcum;
11747 if(aggregates(node.name, valAcum)) {
11749 ctx.textAlign = 'center';
11750 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11753 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11754 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11755 (label ? label.size + config.labelOffset : 0));
11756 mtxt = ctx.measureText(acumValueLabel);
11757 boxWidth = mtxt.width+10;
11759 boxHeight = label.size+6;
11761 if(boxHeight + acum + config.labelOffset > gridHeight) {
11762 bottomPadding = acum - config.labelOffset - boxHeight;
11764 bottomPadding = acum + config.labelOffset + inset;
11768 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11771 boxY = -boxHeight/2;
11773 ctx.rotate(0 * Math.PI / 180);
11774 ctx.fillStyle = "rgba(255,255,255,.8)";
11775 if(boxHeight + acum + config.labelOffset > gridHeight) {
11776 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11778 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11779 ctx.fillStyle = ctx.strokeStyle = label.color;
11780 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11785 if(showLabels(node.name, valAcum, node)) {
11790 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11793 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11794 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11795 boxWidth = mtxt.width+10;
11798 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11799 leftPadding = acum - config.labelOffset - boxWidth - inset;
11801 leftPadding = acum + config.labelOffset;
11805 ctx.textAlign = 'left';
11806 ctx.translate(x + inset + leftPadding, y + height/2);
11807 boxHeight = label.size+6;
11809 boxY = -boxHeight/2;
11810 ctx.fillStyle = "rgba(255,255,255,.8)";
11812 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11813 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11815 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11817 ctx.fillStyle = label.color;
11818 ctx.rotate(0 * Math.PI / 180);
11819 ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11823 //if the number of nodes greater than 8 rotate labels 45 degrees
11824 if(nodeCount > 8) {
11825 ctx.textAlign = 'left';
11826 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11827 ctx.rotate(45* Math.PI / 180);
11828 ctx.fillText(node.name, 0, 0);
11830 ctx.textAlign = 'center';
11831 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11839 'contains': function(node, mpos) {
11840 var pos = node.pos.getc(true),
11841 width = node.getData('width'),
11842 height = node.getData('height'),
11843 algnPos = this.getAlignedPos(pos, width, height),
11844 x = algnPos.x, y = algnPos.y,
11845 dimArray = node.getData('dimArray'),
11846 config = node.getData('config'),
11848 horz = config.orientation == 'horizontal';
11849 //bounding box check
11851 if(mpos.x < x || mpos.x > x + width
11852 || mpos.y > y + height || mpos.y < y) {
11856 if(mpos.x < x || mpos.x > x + width
11857 || mpos.y > y || mpos.y < y - height) {
11862 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11863 var dimi = dimArray[i];
11864 var url = Url.decode(node.getData('linkArray')[i]);
11867 var intersec = acum;
11868 if(mpos.x <= intersec) {
11870 'name': node.getData('stringArray')[i],
11871 'color': node.getData('colorArray')[i],
11872 'value': node.getData('valueArray')[i],
11873 'valuelabel': node.getData('valuelabelArray')[i],
11874 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11881 var intersec = acum;
11882 if(mpos.y >= intersec) {
11884 'name': node.getData('stringArray')[i],
11885 'color': node.getData('colorArray')[i],
11886 'value': node.getData('valueArray')[i],
11887 'valuelabel': node.getData('valuelabelArray')[i],
11888 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11898 'barchart-grouped' : {
11899 'render' : function(node, canvas) {
11900 var pos = node.pos.getc(true),
11901 width = node.getData('width'),
11902 height = node.getData('height'),
11903 algnPos = this.getAlignedPos(pos, width, height),
11904 x = algnPos.x, y = algnPos.y,
11905 dimArray = node.getData('dimArray'),
11906 valueArray = node.getData('valueArray'),
11907 valuelabelArray = node.getData('valuelabelArray'),
11908 linkArray = node.getData('linkArray'),
11909 valueLength = valueArray.length,
11910 colorArray = node.getData('colorArray'),
11911 colorLength = colorArray.length,
11912 stringArray = node.getData('stringArray');
11914 var ctx = canvas.getCtx(),
11915 canvasSize = canvas.getSize(),
11917 border = node.getData('border'),
11918 gradient = node.getData('gradient'),
11919 config = node.getData('config'),
11920 horz = config.orientation == 'horizontal',
11921 aggregates = config.showAggregates,
11922 showLabels = config.showLabels,
11923 label = config.Label,
11924 shadow = config.shadow,
11925 margin = config.Margin,
11926 fixedDim = (horz? height : width) / valueLength;
11930 maxValue = Math.max.apply(null, dimArray);
11934 ctx.fillStyle = "rgba(0,0,0,.2)";
11935 if (colorArray && dimArray && stringArray && shadow.enable) {
11936 shadowThickness = shadow.size;
11938 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11939 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11940 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11943 ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11948 if(nextBar && nextBar > dimArray[i]) {
11949 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11950 } else if (nextBar && nextBar < dimArray[i]){
11951 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11953 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11955 } else if (i> 0 && i<l-1) {
11956 if(nextBar && nextBar > dimArray[i]) {
11957 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11958 } else if (nextBar && nextBar < dimArray[i]){
11959 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11961 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11963 } else if (i == l-1) {
11964 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11974 if (colorArray && dimArray && stringArray) {
11975 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11976 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11980 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
11981 x + dimArray[i]/2, y + fixedDim * (i + 1));
11983 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
11984 x + fixedDim * (i + 1), y - dimArray[i]/2);
11986 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11987 function(v) { return (v * 0.8) >> 0; }));
11988 linear.addColorStop(0, color);
11989 linear.addColorStop(0.3, colorArray[i % colorLength]);
11990 linear.addColorStop(0.7, colorArray[i % colorLength]);
11991 linear.addColorStop(1, color);
11992 ctx.fillStyle = linear;
11995 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11997 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11999 if(border && border.name == stringArray[i]) {
12000 opt.acum = fixedDim * i;
12001 opt.dimValue = dimArray[i];
12003 acum += (dimArray[i] || 0);
12004 valAcum += (valueArray[i] || 0);
12005 ctx.fillStyle = ctx.strokeStyle = label.color;
12006 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12009 if(aggregates(node.name, valAcum) && label.type == 'Native') {
12010 if(valuelabelArray[i]) {
12011 acumValueLabel = valuelabelArray[i];
12013 acumValueLabel = valueArray[i];
12016 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12017 ctx.textAlign = 'left';
12018 ctx.textBaseline = 'top';
12019 ctx.fillStyle = "rgba(255,255,255,.8)";
12021 gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
12022 mtxt = ctx.measureText(acumValueLabel);
12023 boxWidth = mtxt.width+10;
12025 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12026 leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
12028 leftPadding = dimArray[i] + config.labelOffset + inset;
12030 boxHeight = label.size+6;
12031 boxX = x + leftPadding;
12032 boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
12036 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12037 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12039 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12041 ctx.fillStyle = ctx.strokeStyle = label.color;
12042 ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12049 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12051 ctx.textAlign = 'center';
12054 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12055 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12056 (label ? label.size + config.labelOffset : 0));
12058 mtxt = ctx.measureText(acumValueLabel);
12059 boxWidth = mtxt.width+10;
12060 boxHeight = label.size+6;
12061 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12062 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12064 bottomPadding = dimArray[i] + config.labelOffset + inset;
12068 ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12070 boxX = -boxWidth/2;
12071 boxY = -boxHeight/2;
12072 ctx.fillStyle = "rgba(255,255,255,.8)";
12076 //ctx.rotate(270* Math.PI / 180);
12077 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12078 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12080 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12082 ctx.fillStyle = ctx.strokeStyle = label.color;
12083 ctx.fillText(acumValueLabel, 0,0);
12092 ctx.strokeStyle = border.color;
12094 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12096 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12100 if(label.type == 'Native') {
12102 ctx.fillStyle = ctx.strokeStyle = label.color;
12103 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12104 ctx.textBaseline = 'middle';
12106 if(showLabels(node.name, valAcum, node)) {
12108 ctx.textAlign = 'center';
12109 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12110 ctx.rotate(Math.PI / 2);
12111 ctx.fillText(node.name, 0, 0);
12113 ctx.textAlign = 'center';
12114 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12121 'contains': function(node, mpos) {
12122 var pos = node.pos.getc(true),
12123 width = node.getData('width'),
12124 height = node.getData('height'),
12125 algnPos = this.getAlignedPos(pos, width, height),
12126 x = algnPos.x, y = algnPos.y,
12127 dimArray = node.getData('dimArray'),
12128 len = dimArray.length,
12129 config = node.getData('config'),
12131 horz = config.orientation == 'horizontal',
12132 fixedDim = (horz? height : width) / len;
12133 //bounding box check
12135 if(mpos.x < x || mpos.x > x + width
12136 || mpos.y > y + height || mpos.y < y) {
12140 if(mpos.x < x || mpos.x > x + width
12141 || mpos.y > y || mpos.y < y - height) {
12146 for(var i=0, l=dimArray.length; i<l; i++) {
12147 var dimi = dimArray[i];
12148 var url = Url.decode(node.getData('linkArray')[i]);
12150 var limit = y + fixedDim * i;
12151 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12153 'name': node.getData('stringArray')[i],
12154 'color': node.getData('colorArray')[i],
12155 'value': node.getData('valueArray')[i],
12156 'valuelabel': node.getData('valuelabelArray')[i],
12157 'title': node.getData('titleArray')[i],
12158 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12164 var limit = x + fixedDim * i;
12165 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12167 'name': node.getData('stringArray')[i],
12168 'color': node.getData('colorArray')[i],
12169 'value': node.getData('valueArray')[i],
12170 'valuelabel': node.getData('valuelabelArray')[i],
12171 'title': node.getData('titleArray')[i],
12172 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12182 'barchart-basic' : {
12183 'render' : function(node, canvas) {
12184 var pos = node.pos.getc(true),
12185 width = node.getData('width'),
12186 height = node.getData('height'),
12187 algnPos = this.getAlignedPos(pos, width, height),
12188 x = algnPos.x, y = algnPos.y,
12189 dimArray = node.getData('dimArray'),
12190 valueArray = node.getData('valueArray'),
12191 valuelabelArray = node.getData('valuelabelArray'),
12192 linkArray = node.getData('linkArray'),
12193 valueLength = valueArray.length,
12194 colorArray = node.getData('colorMono'),
12195 colorLength = colorArray.length,
12196 stringArray = node.getData('stringArray');
12198 var ctx = canvas.getCtx(),
12199 canvasSize = canvas.getSize(),
12201 border = node.getData('border'),
12202 gradient = node.getData('gradient'),
12203 config = node.getData('config'),
12204 horz = config.orientation == 'horizontal',
12205 aggregates = config.showAggregates,
12206 showLabels = config.showLabels,
12207 label = config.Label,
12208 fixedDim = (horz? height : width) / valueLength,
12209 margin = config.Margin;
12211 if (colorArray && dimArray && stringArray) {
12212 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12213 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12218 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
12219 x + dimArray[i]/2, y + fixedDim * (i + 1));
12221 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
12222 x + fixedDim * (i + 1), y - dimArray[i]/2);
12225 if(config.shadow.size) {
12226 shadowThickness = config.shadow.size;
12227 ctx.fillStyle = "rgba(0,0,0,.2)";
12229 ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12231 ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12235 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
12236 function(v) { return (v * 0.8) >> 0; }));
12237 linear.addColorStop(0, color);
12238 linear.addColorStop(0.3, colorArray[i % colorLength]);
12239 linear.addColorStop(0.7, colorArray[i % colorLength]);
12240 linear.addColorStop(1, color);
12241 ctx.fillStyle = linear;
12244 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12246 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12248 if(border && border.name == stringArray[i]) {
12249 opt.acum = fixedDim * i;
12250 opt.dimValue = dimArray[i];
12252 acum += (dimArray[i] || 0);
12253 valAcum += (valueArray[i] || 0);
12255 if(label.type == 'Native') {
12256 ctx.fillStyle = ctx.strokeStyle = label.color;
12257 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12258 if(aggregates(node.name, valAcum)) {
12259 if(valuelabelArray[i]) {
12260 acumValueLabel = valuelabelArray[i];
12262 acumValueLabel = valueArray[i];
12265 ctx.textAlign = 'center';
12266 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12269 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12270 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12271 (label ? label.size + config.labelOffset : 0));
12272 mtxt = ctx.measureText(acumValueLabel);
12273 boxWidth = mtxt.width+10;
12275 boxHeight = label.size+6;
12277 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12278 bottomPadding = dimArray[i] - config.labelOffset - inset;
12280 bottomPadding = dimArray[i] + config.labelOffset + inset;
12284 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12287 boxY = -boxHeight/2;
12289 //ctx.rotate(270* Math.PI / 180);
12290 ctx.fillStyle = "rgba(255,255,255,.6)";
12291 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12292 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12294 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12295 ctx.fillStyle = ctx.strokeStyle = label.color;
12296 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12305 ctx.strokeStyle = border.color;
12307 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12309 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12313 if(label.type == 'Native') {
12315 ctx.fillStyle = ctx.strokeStyle = label.color;
12316 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12317 ctx.textBaseline = 'middle';
12318 if(showLabels(node.name, valAcum, node)) {
12322 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12323 mtxt = ctx.measureText(node.name + ": " + valAcum);
12324 boxWidth = mtxt.width+10;
12327 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12328 leftPadding = acum - config.labelOffset - boxWidth - inset;
12330 leftPadding = acum + config.labelOffset;
12334 ctx.textAlign = 'left';
12335 ctx.translate(x + inset + leftPadding, y + height/2);
12336 boxHeight = label.size+6;
12338 boxY = -boxHeight/2;
12339 ctx.fillStyle = "rgba(255,255,255,.8)";
12342 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12343 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12345 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12348 ctx.fillStyle = label.color;
12349 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12353 if(stringArray.length > 8) {
12354 ctx.textAlign = 'left';
12355 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12356 ctx.rotate(45* Math.PI / 180);
12357 ctx.fillText(node.name, 0, 0);
12359 ctx.textAlign = 'center';
12360 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12369 'contains': function(node, mpos) {
12370 var pos = node.pos.getc(true),
12371 width = node.getData('width'),
12372 height = node.getData('height'),
12373 config = node.getData('config'),
12374 algnPos = this.getAlignedPos(pos, width, height),
12375 x = algnPos.x, y = algnPos.y ,
12376 dimArray = node.getData('dimArray'),
12377 len = dimArray.length,
12379 horz = config.orientation == 'horizontal',
12380 fixedDim = (horz? height : width) / len;
12382 //bounding box check
12384 if(mpos.x < x || mpos.x > x + width
12385 || mpos.y > y + height || mpos.y < y) {
12389 if(mpos.x < x || mpos.x > x + width
12390 || mpos.y > y || mpos.y < y - height) {
12395 for(var i=0, l=dimArray.length; i<l; i++) {
12396 var dimi = dimArray[i];
12397 var url = Url.decode(node.getData('linkArray')[i]);
12399 var limit = y + fixedDim * i;
12400 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12402 'name': node.getData('stringArray')[i],
12403 'color': node.getData('colorArray')[i],
12404 'value': node.getData('valueArray')[i],
12405 'valuelabel': node.getData('valuelabelArray')[i],
12406 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12412 var limit = x + fixedDim * i;
12413 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12415 'name': node.getData('stringArray')[i],
12416 'color': node.getData('colorArray')[i],
12417 'value': node.getData('valueArray')[i],
12418 'valuelabel': node.getData('valuelabelArray')[i],
12419 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12434 A visualization that displays stacked bar charts.
12436 Constructor Options:
12438 See <Options.BarChart>.
12441 $jit.BarChart = new Class({
12443 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12447 initialize: function(opt) {
12448 this.controller = this.config =
12449 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12450 Label: { type: 'Native' }
12452 //set functions for showLabels and showAggregates
12453 var showLabels = this.config.showLabels,
12454 typeLabels = $.type(showLabels),
12455 showAggregates = this.config.showAggregates,
12456 typeAggregates = $.type(showAggregates);
12457 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12458 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12459 Options.Fx.clearCanvas = false;
12460 this.initializeViz();
12463 initializeViz: function() {
12464 var config = this.config, that = this;
12465 var nodeType = config.type.split(":")[0],
12466 horz = config.orientation == 'horizontal',
12468 var st = new $jit.ST({
12469 injectInto: config.injectInto,
12470 orientation: horz? 'left' : 'bottom',
12471 background: config.background,
12472 renderBackground: config.renderBackground,
12473 backgroundColor: config.backgroundColor,
12474 colorStop1: config.colorStop1,
12475 colorStop2: config.colorStop2,
12477 nodeCount: config.nodeCount,
12478 siblingOffset: config.barsOffset,
12480 withLabels: config.Label.type != 'Native',
12481 useCanvas: config.useCanvas,
12483 type: config.Label.type
12487 type: 'barchart-' + nodeType,
12496 enable: config.Tips.enable,
12499 onShow: function(tip, node, contains) {
12500 var elem = contains;
12501 config.Tips.onShow(tip, elem, node);
12502 if(elem.link != 'undefined' && elem.link != '') {
12503 document.body.style.cursor = 'pointer';
12506 onHide: function(call) {
12507 document.body.style.cursor = 'default';
12514 onClick: function(node, eventInfo, evt) {
12515 if(!config.Events.enable) return;
12516 var elem = eventInfo.getContains();
12517 config.Events.onClick(elem, eventInfo, evt);
12519 onMouseMove: function(node, eventInfo, evt) {
12520 if(!config.hoveredColor) return;
12522 var elem = eventInfo.getContains();
12523 that.select(node.id, elem.name, elem.index);
12525 that.select(false, false, false);
12529 onCreateLabel: function(domElement, node) {
12530 var labelConf = config.Label,
12531 valueArray = node.getData('valueArray'),
12532 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12533 grouped = config.type.split(':')[0] == 'grouped',
12534 horz = config.orientation == 'horizontal';
12536 wrapper: document.createElement('div'),
12537 aggregate: document.createElement('div'),
12538 label: document.createElement('div')
12541 var wrapper = nlbs.wrapper,
12542 label = nlbs.label,
12543 aggregate = nlbs.aggregate,
12544 wrapperStyle = wrapper.style,
12545 labelStyle = label.style,
12546 aggregateStyle = aggregate.style;
12547 //store node labels
12548 nodeLabels[node.id] = nlbs;
12550 wrapper.appendChild(label);
12551 wrapper.appendChild(aggregate);
12552 if(!config.showLabels(node.name, acum, node)) {
12553 labelStyle.display = 'none';
12555 if(!config.showAggregates(node.name, acum, node)) {
12556 aggregateStyle.display = 'none';
12558 wrapperStyle.position = 'relative';
12559 wrapperStyle.overflow = 'visible';
12560 wrapperStyle.fontSize = labelConf.size + 'px';
12561 wrapperStyle.fontFamily = labelConf.family;
12562 wrapperStyle.color = labelConf.color;
12563 wrapperStyle.textAlign = 'center';
12564 aggregateStyle.position = labelStyle.position = 'absolute';
12566 domElement.style.width = node.getData('width') + 'px';
12567 domElement.style.height = node.getData('height') + 'px';
12568 aggregateStyle.left = "0px";
12569 labelStyle.left = config.labelOffset + 'px';
12570 labelStyle.whiteSpace = "nowrap";
12571 label.innerHTML = node.name;
12573 domElement.appendChild(wrapper);
12575 onPlaceLabel: function(domElement, node) {
12576 if(!nodeLabels[node.id]) return;
12577 var labels = nodeLabels[node.id],
12578 wrapperStyle = labels.wrapper.style,
12579 labelStyle = labels.label.style,
12580 aggregateStyle = labels.aggregate.style,
12581 grouped = config.type.split(':')[0] == 'grouped',
12582 horz = config.orientation == 'horizontal',
12583 dimArray = node.getData('dimArray'),
12584 valArray = node.getData('valueArray'),
12585 nodeCount = node.getData('nodeCount'),
12586 valueLength = valArray.length;
12587 valuelabelArray = node.getData('valuelabelArray'),
12588 stringArray = node.getData('stringArray'),
12589 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12590 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12591 font = parseInt(wrapperStyle.fontSize, 10),
12592 domStyle = domElement.style,
12593 fixedDim = (horz? height : width) / valueLength;
12596 if(dimArray && valArray) {
12597 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12599 aggregateStyle.width = width - config.labelOffset + "px";
12600 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12601 if(dimArray[i] > 0) {
12602 acum+= valArray[i];
12605 if(config.showLabels(node.name, acum, node)) {
12606 labelStyle.display = '';
12608 labelStyle.display = 'none';
12610 if(config.showAggregates(node.name, acum, node)) {
12611 aggregateStyle.display = '';
12613 aggregateStyle.display = 'none';
12615 if(config.orientation == 'horizontal') {
12616 aggregateStyle.textAlign = 'right';
12617 labelStyle.textAlign = 'left';
12618 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12619 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12620 domElement.style.height = wrapperStyle.height = height + 'px';
12622 aggregateStyle.top = (-font - config.labelOffset) + 'px';
12623 labelStyle.top = (config.labelOffset + height) + 'px';
12624 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12625 domElement.style.height = wrapperStyle.height = height + 'px';
12626 if(stringArray.length > 8) {
12627 labels.label.className = "rotatedLabelReverse";
12628 labelStyle.textAlign = "left";
12629 labelStyle.top = config.labelOffset + height + width/2 + "px";
12635 labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12636 labels.aggregate.innerHTML = "";
12641 maxValue = Math.max.apply(null,dimArray);
12642 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12643 valueLabelDim = 50;
12644 valueLabel = document.createElement('div');
12645 valueLabel.innerHTML = valuelabelArray[i];
12646 // valueLabel.class = "rotatedLabel";
12647 valueLabel.className = "rotatedLabel";
12648 valueLabel.style.position = "absolute";
12649 valueLabel.style.textAlign = "left";
12650 valueLabel.style.verticalAlign = "middle";
12651 valueLabel.style.height = valueLabelDim + "px";
12652 valueLabel.style.width = valueLabelDim + "px";
12653 valueLabel.style.top = (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12654 valueLabel.style.left = (fixedDim * i) + "px";
12655 labels.wrapper.appendChild(valueLabel);
12658 labels.aggregate.innerHTML = acum;
12665 var size = st.canvas.getSize(),
12666 l = config.nodeCount,
12667 margin = config.Margin;
12668 title = config.Title;
12669 subtitle = config.Subtitle,
12670 grouped = config.type.split(':')[0] == 'grouped',
12671 margin = config.Margin,
12672 ticks = config.Ticks,
12673 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12674 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12675 horz = config.orientation == 'horizontal',
12676 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12677 fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12678 whiteSpace = size.width - (marginWidth + (fixedDim * l));
12679 //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
12680 if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12682 //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12683 if(!grouped && !horz) {
12684 st.config.siblingOffset = whiteSpace/(l+1);
12691 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12692 if(config.Ticks.enable) {
12693 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;
12695 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12698 st.config.offsetY = -size.height/2 + margin.bottom
12699 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12700 if(config.Ticks.enable) {
12701 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12703 st.config.offsetX = (margin.right - margin.left)/2;
12707 this.canvas = this.st.canvas;
12712 renderTitle: function() {
12713 var canvas = this.canvas,
12714 size = canvas.getSize(),
12715 config = this.config,
12716 margin = config.Margin,
12717 label = config.Label,
12718 title = config.Title;
12719 ctx = canvas.getCtx();
12720 ctx.fillStyle = title.color;
12721 ctx.textAlign = 'left';
12722 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12723 if(label.type == 'Native') {
12724 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12728 renderSubtitle: function() {
12729 var canvas = this.canvas,
12730 size = canvas.getSize(),
12731 config = this.config,
12732 margin = config.Margin,
12733 label = config.Label,
12734 subtitle = config.Subtitle,
12735 nodeCount = config.nodeCount,
12736 horz = config.orientation == 'horizontal' ? true : false,
12737 ctx = canvas.getCtx();
12738 ctx.fillStyle = title.color;
12739 ctx.textAlign = 'left';
12740 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12741 if(label.type == 'Native') {
12742 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12746 renderScrollNote: function() {
12747 var canvas = this.canvas,
12748 size = canvas.getSize(),
12749 config = this.config,
12750 margin = config.Margin,
12751 label = config.Label,
12752 note = config.ScrollNote;
12753 ctx = canvas.getCtx();
12754 ctx.fillStyle = title.color;
12755 title = config.Title;
12756 ctx.textAlign = 'center';
12757 ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12758 if(label.type == 'Native') {
12759 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12763 renderTicks: function() {
12765 var canvas = this.canvas,
12766 size = canvas.getSize(),
12767 config = this.config,
12768 margin = config.Margin,
12769 ticks = config.Ticks,
12770 title = config.Title,
12771 subtitle = config.Subtitle,
12772 label = config.Label,
12773 shadow = config.shadow;
12774 horz = config.orientation == 'horizontal',
12775 maxValue = this.getMaxValue(),
12776 maxTickValue = Math.ceil(maxValue*.1)*10;
12777 if(maxTickValue == maxValue) {
12778 var length = maxTickValue.toString().length;
12779 maxTickValue = maxTickValue + parseInt(pad(1,length));
12781 grouped = config.type.split(':')[0] == 'grouped',
12783 labelIncrement = maxTickValue/ticks.segments,
12784 ctx = canvas.getCtx();
12785 ctx.strokeStyle = ticks.color;
12786 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12788 ctx.textAlign = 'center';
12789 ctx.textBaseline = 'middle';
12791 idLabel = canvas.id + "-label";
12793 container = document.getElementById(idLabel);
12797 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12798 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12799 segmentLength = grid/ticks.segments;
12800 ctx.fillStyle = ticks.color;
12802 (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12803 size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12805 while(axis<=grid) {
12806 ctx.fillStyle = ticks.color;
12807 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);
12808 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));
12809 ctx.fillStyle = label.color;
12811 if(label.type == 'Native' && config.showLabels) {
12812 ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12814 axis += segmentLength;
12815 labelValue += labelIncrement;
12820 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12821 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12822 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)),
12823 segmentLength = grid/ticks.segments;
12824 ctx.fillStyle = ticks.color;
12825 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));
12827 while(axis>=grid) {
12829 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12830 ctx.rotate(0 * Math.PI / 180 );
12831 ctx.fillStyle = label.color;
12832 if(config.showLabels) {
12833 if(label.type == 'Native') {
12834 ctx.fillText(labelValue, 0, 0);
12836 //html labels on y axis
12837 labelDiv = document.createElement('div');
12838 labelDiv.innerHTML = labelValue;
12839 labelDiv.className = "rotatedLabel";
12840 // labelDiv.class = "rotatedLabel";
12841 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12842 labelDiv.style.left = margin.left + "px";
12843 labelDiv.style.width = labelDim + "px";
12844 labelDiv.style.height = labelDim + "px";
12845 labelDiv.style.textAlign = "center";
12846 labelDiv.style.verticalAlign = "middle";
12847 labelDiv.style.position = "absolute";
12848 container.appendChild(labelDiv);
12852 ctx.fillStyle = ticks.color;
12853 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 );
12854 htmlOrigin += segmentLength;
12855 axis += segmentLength;
12856 labelValue += labelIncrement;
12865 renderBackground: function() {
12866 var canvas = this.canvas,
12867 config = this.config,
12868 backgroundColor = config.backgroundColor,
12869 size = canvas.getSize(),
12870 ctx = canvas.getCtx();
12871 ctx.fillStyle = backgroundColor;
12872 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12875 clear: function() {
12876 var canvas = this.canvas;
12877 var ctx = canvas.getCtx(),
12878 size = canvas.getSize();
12879 ctx.fillStyle = "rgba(255,255,255,0)";
12880 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12881 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
12883 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
12884 var canvas = this.canvas,
12885 size = canvas.getSize(),
12886 config = this.config,
12887 orgHeight = size.height,
12888 margin = config.Margin,
12890 grouped = config.type.split(':')[0] == 'grouped',
12891 horz = config.orientation == 'horizontal',
12892 ctx = canvas.getCtx();
12894 var newWindowWidth = document.body.offsetWidth;
12895 var diff = newWindowWidth - orgWindowWidth;
12896 var newWidth = orgContainerDivWidth + (diff/cols);
12897 var scale = newWidth/orgContainerDivWidth;
12898 canvas.resize(newWidth,orgHeight);
12899 if(typeof FlashCanvas == "undefined") {
12902 this.clear();// hack for flashcanvas bug not properly clearing rectangle
12905 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12908 this.loadJSON(json);
12915 Loads JSON data into the visualization.
12919 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>.
12923 var barChart = new $jit.BarChart(options);
12924 barChart.loadJSON(json);
12927 loadJSON: function(json) {
12928 if(this.busy) return;
12931 var prefix = $.time(),
12934 name = $.splat(json.label),
12935 color = $.splat(json.color || this.colors),
12936 config = this.config,
12937 gradient = !!config.type.split(":")[1],
12938 renderBackground = config.renderBackground,
12939 animate = config.animate,
12940 ticks = config.Ticks,
12941 title = config.Title,
12942 note = config.ScrollNote,
12943 subtitle = config.Subtitle,
12944 horz = config.orientation == 'horizontal',
12946 colorLength = color.length,
12947 nameLength = name.length;
12948 groupTotalValue = 0;
12949 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12950 var val = values[i];
12951 var valArray = $.splat(val.values);
12952 groupTotalValue += parseFloat(valArray.sum());
12955 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12956 var val = values[i];
12957 var valArray = $.splat(values[i].values);
12958 var valuelabelArray = $.splat(values[i].valuelabels);
12959 var linkArray = $.splat(values[i].links);
12960 var titleArray = $.splat(values[i].titles);
12961 var barTotalValue = valArray.sum();
12964 'id': prefix + val.label,
12969 '$linkArray': linkArray,
12970 '$gvl': val.gvaluelabel,
12971 '$titleArray': titleArray,
12972 '$valueArray': valArray,
12973 '$valuelabelArray': valuelabelArray,
12974 '$colorArray': color,
12975 '$colorMono': $.splat(color[i % colorLength]),
12976 '$stringArray': name,
12977 '$barTotalValue': barTotalValue,
12978 '$groupTotalValue': groupTotalValue,
12979 '$nodeCount': values.length,
12980 '$gradient': gradient,
12987 'id': prefix + '$root',
12998 this.normalizeDims();
13000 if(renderBackground) {
13001 this.renderBackground();
13004 if(!animate && ticks.enable) {
13005 this.renderTicks();
13007 if(!animate && note.text) {
13008 this.renderScrollNote();
13010 if(!animate && title.text) {
13011 this.renderTitle();
13013 if(!animate && subtitle.text) {
13014 this.renderSubtitle();
13018 st.select(st.root);
13022 modes: ['node-property:width:dimArray'],
13024 onComplete: function() {
13030 modes: ['node-property:height:dimArray'],
13032 onComplete: function() {
13045 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.
13049 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13050 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13055 barChart.updateJSON(json, {
13056 onComplete: function() {
13057 alert('update complete!');
13062 updateJSON: function(json, onComplete) {
13063 if(this.busy) return;
13067 var graph = st.graph;
13068 var values = json.values;
13069 var animate = this.config.animate;
13071 var horz = this.config.orientation == 'horizontal';
13072 $.each(values, function(v) {
13073 var n = graph.getByName(v.label);
13075 n.setData('valueArray', $.splat(v.values));
13077 n.setData('stringArray', $.splat(json.label));
13081 this.normalizeDims();
13083 st.select(st.root);
13087 modes: ['node-property:width:dimArray'],
13089 onComplete: function() {
13091 onComplete && onComplete.onComplete();
13096 modes: ['node-property:height:dimArray'],
13098 onComplete: function() {
13100 onComplete && onComplete.onComplete();
13107 //adds the little brown bar when hovering the node
13108 select: function(id, name) {
13110 if(!this.config.hoveredColor) return;
13111 var s = this.selected;
13112 if(s.id != id || s.name != name) {
13115 s.color = this.config.hoveredColor;
13116 this.st.graph.eachNode(function(n) {
13118 n.setData('border', s);
13120 n.setData('border', false);
13130 Returns an object containing as keys the legend names and as values hex strings with color values.
13135 var legend = barChart.getLegend();
13138 getLegend: function() {
13139 var legend = new Array();
13140 var name = new Array();
13141 var color = new Array();
13143 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13146 var colors = n.getData('colorArray'),
13147 len = colors.length;
13148 $.each(n.getData('stringArray'), function(s, i) {
13149 color[i] = colors[i % len];
13152 legend['name'] = name;
13153 legend['color'] = color;
13158 Method: getMaxValue
13160 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13165 var ans = barChart.getMaxValue();
13168 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13173 //will return 100 for all BarChart instances,
13174 //displaying all of them with the same scale
13175 $jit.BarChart.implement({
13176 'getMaxValue': function() {
13183 getMaxValue: function() {
13184 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13185 this.st.graph.eachNode(function(n) {
13186 var valArray = n.getData('valueArray'),
13188 if(!valArray) return;
13190 $.each(valArray, function(v) {
13194 acum = Math.max.apply(null, valArray);
13196 maxValue = maxValue>acum? maxValue:acum;
13201 setBarType: function(type) {
13202 this.config.type = type;
13203 this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13206 normalizeDims: function() {
13207 //number of elements
13208 var root = this.st.graph.getNode(this.st.root), l=0;
13209 root.eachAdjacency(function() {
13212 var maxValue = this.getMaxValue() || 1,
13213 size = this.st.canvas.getSize(),
13214 config = this.config,
13215 margin = config.Margin,
13216 ticks = config.Ticks,
13217 title = config.Title,
13218 subtitle = config.Subtitle,
13219 grouped = config.type.split(':')[0] == 'grouped',
13220 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13221 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13222 horz = config.orientation == 'horizontal',
13223 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13224 animate = config.animate,
13225 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13227 - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13228 dim1 = horz? 'height':'width',
13229 dim2 = horz? 'width':'height',
13230 basic = config.type.split(':')[0] == 'basic';
13233 var maxTickValue = Math.ceil(maxValue*.1)*10;
13234 if(maxTickValue == maxValue) {
13235 var length = maxTickValue.toString().length;
13236 maxTickValue = maxTickValue + parseInt(pad(1,length));
13239 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13242 this.st.graph.eachNode(function(n) {
13243 var acum = 0, animateValue = [];
13244 $.each(n.getData('valueArray'), function(v) {
13246 animateValue.push(0);
13250 fixedDim = animateValue.length * 40;
13252 n.setData(dim1, fixedDim);
13256 n.setData(dim2, acum * height / maxValue, 'end');
13257 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13258 return n * height / maxValue;
13260 var dimArray = n.getData('dimArray');
13262 n.setData('dimArray', animateValue);
13268 n.setData(dim2, acum * height / maxTickValue);
13269 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13270 return n * height / maxTickValue;
13273 n.setData(dim2, acum * height / maxValue);
13274 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13275 return n * height / maxValue;
13283 //funnel chart options
13286 Options.FunnelChart = {
13290 type: 'stacked', //stacked, grouped, : gradient
13291 labelOffset: 3, //label offset
13292 barsOffset: 0, //distance between bars
13293 hoveredColor: '#9fd4ff',
13294 orientation: 'vertical',
13295 showAggregates: true,
13308 $jit.ST.Plot.NodeTypes.implement({
13309 'funnelchart-basic' : {
13310 'render' : function(node, canvas) {
13311 var pos = node.pos.getc(true),
13312 width = node.getData('width'),
13313 height = node.getData('height'),
13314 algnPos = this.getAlignedPos(pos, width, height),
13315 x = algnPos.x, y = algnPos.y,
13316 dimArray = node.getData('dimArray'),
13317 valueArray = node.getData('valueArray'),
13318 valuelabelArray = node.getData('valuelabelArray'),
13319 linkArray = node.getData('linkArray'),
13320 colorArray = node.getData('colorArray'),
13321 colorLength = colorArray.length,
13322 stringArray = node.getData('stringArray');
13323 var ctx = canvas.getCtx(),
13325 border = node.getData('border'),
13326 gradient = node.getData('gradient'),
13327 config = node.getData('config'),
13328 horz = config.orientation == 'horizontal',
13329 aggregates = config.showAggregates,
13330 showLabels = config.showLabels,
13331 label = config.Label,
13332 size = canvas.getSize(),
13333 labelOffset = config.labelOffset + 10;
13334 minWidth = width * .25;
13337 if (colorArray && dimArray && stringArray) {
13340 // horizontal lines
13341 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13342 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13344 if(label.type == 'Native') {
13345 if(showLabels(node.name, valAcum, node)) {
13346 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13347 var stringValue = stringArray[i];
13348 var valueLabel = String(valuelabelArray[i]);
13349 var mV = ctx.measureText(stringValue);
13350 var mVL = ctx.measureText(valueLabel);
13351 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13352 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13353 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13354 var bottomWidth = minWidth + ((acum) * ratio);
13355 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13356 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13357 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13358 // ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13362 ctx.moveTo(bottomWidth/2,y - acum); //
13363 ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight); // top right
13364 ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight); // bottom right
13368 ctx.moveTo(-bottomWidth/2,y - acum); //
13369 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight); // top right
13370 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight); // bottom right
13375 acum += (dimArray[i] || 0);
13376 valAcum += (valueArray[i] || 0);
13383 //funnel segments and labels
13384 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13385 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13386 var colori = colorArray[i % colorLength];
13387 if(label.type == 'Native') {
13388 var stringValue = stringArray[i];
13389 var valueLabel = String(valuelabelArray[i]);
13390 var mV = ctx.measureText(stringValue);
13391 var mVL = ctx.measureText(valueLabel);
13396 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13397 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13398 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13399 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13401 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13402 var bottomWidth = minWidth + ((acum) * ratio);
13403 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13408 linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13409 var colorRgb = $.hexToRgb(colori);
13410 var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
13411 function(v) { return (v * .5) >> 0; });
13412 linear.addColorStop(0, 'rgba('+color+',1)');
13413 linear.addColorStop(0.5, 'rgba('+colorRgb+',1)');
13414 linear.addColorStop(1, 'rgba('+color+',1)');
13415 ctx.fillStyle = linear;
13419 ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13420 ctx.lineTo(topWidth/2,y - acum - dimArray[i]); // top right
13421 ctx.lineTo(bottomWidth/2,y - acum); // bottom right
13422 ctx.lineTo(-bottomWidth/2,y - acum); // bottom left
13427 if(border && border.name == stringArray[i]) {
13429 opt.dimValue = dimArray[i];
13436 ctx.strokeStyle = border.color;
13438 //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13442 if(label.type == 'Native') {
13444 ctx.fillStyle = ctx.strokeStyle = label.color;
13445 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13446 ctx.textBaseline = 'middle';
13448 acumValueLabel = valAcum;
13450 if(showLabels(node.name, valAcum, node)) {
13453 ctx.textAlign = 'left';
13454 ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13455 ctx.textAlign = 'right';
13456 ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13461 acum += (dimArray[i] || 0);
13462 valAcum += (valueArray[i] || 0);
13468 'contains': function(node, mpos) {
13469 var pos = node.pos.getc(true),
13470 width = node.getData('width'),
13471 height = node.getData('height'),
13472 algnPos = this.getAlignedPos(pos, width, height),
13473 x = algnPos.x, y = algnPos.y,
13474 dimArray = node.getData('dimArray'),
13475 config = node.getData('config'),
13476 st = node.getData('st'),
13478 horz = config.orientation == 'horizontal',
13479 minWidth = width * .25;
13481 canvas = node.getData('canvas'),
13482 size = canvas.getSize(),
13483 offsetY = st.config.offsetY;
13484 //bounding box check
13486 if(mpos.y > y || mpos.y < y - height) {
13490 var newY = Math.abs(mpos.y + offsetY);
13491 var bound = minWidth + (newY * ratio);
13492 var boundLeft = -bound/2;
13493 var boundRight = bound/2;
13494 if(mpos.x < boundLeft || mpos.x > boundRight ) {
13500 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13501 var dimi = dimArray[i];
13505 var url = Url.decode(node.getData('linkArray')[i]);
13507 var intersec = acum;
13508 if(mpos.y >= intersec) {
13510 'name': node.getData('stringArray')[i],
13511 'color': node.getData('colorArray')[i],
13512 'value': node.getData('valueArray')[i],
13513 'percentage': node.getData('percentageArray')[i],
13514 'valuelabel': node.getData('valuelabelArray')[i],
13529 A visualization that displays funnel charts.
13531 Constructor Options:
13533 See <Options.FunnelChart>.
13536 $jit.FunnelChart = new Class({
13538 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13542 initialize: function(opt) {
13543 this.controller = this.config =
13544 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13545 Label: { type: 'Native' }
13547 //set functions for showLabels and showAggregates
13548 var showLabels = this.config.showLabels,
13549 typeLabels = $.type(showLabels),
13550 showAggregates = this.config.showAggregates,
13551 typeAggregates = $.type(showAggregates);
13552 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13553 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13554 Options.Fx.clearCanvas = false;
13555 this.initializeViz();
13558 initializeViz: function() {
13559 var config = this.config, that = this;
13560 var nodeType = config.type.split(":")[0],
13561 horz = config.orientation == 'horizontal',
13563 var st = new $jit.ST({
13564 injectInto: config.injectInto,
13565 orientation: horz? 'left' : 'bottom',
13567 background: config.background,
13568 renderBackground: config.renderBackground,
13569 backgroundColor: config.backgroundColor,
13570 colorStop1: config.colorStop1,
13571 colorStop2: config.colorStop2,
13572 siblingOffset: config.segmentOffset,
13574 withLabels: config.Label.type != 'Native',
13575 useCanvas: config.useCanvas,
13577 type: config.Label.type
13581 type: 'funnelchart-' + nodeType,
13590 enable: config.Tips.enable,
13593 onShow: function(tip, node, contains) {
13594 var elem = contains;
13595 config.Tips.onShow(tip, elem, node);
13596 if(elem.link != 'undefined' && elem.link != '') {
13597 document.body.style.cursor = 'pointer';
13600 onHide: function(call) {
13601 document.body.style.cursor = 'default';
13608 onClick: function(node, eventInfo, evt) {
13609 if(!config.Events.enable) return;
13610 var elem = eventInfo.getContains();
13611 config.Events.onClick(elem, eventInfo, evt);
13613 onMouseMove: function(node, eventInfo, evt) {
13614 if(!config.hoveredColor) return;
13616 var elem = eventInfo.getContains();
13617 that.select(node.id, elem.name, elem.index);
13619 that.select(false, false, false);
13623 onCreateLabel: function(domElement, node) {
13624 var labelConf = config.Label,
13625 valueArray = node.getData('valueArray'),
13626 idArray = node.getData('idArray'),
13627 valuelabelArray = node.getData('valuelabelArray'),
13628 stringArray = node.getData('stringArray');
13629 size = st.canvas.getSize()
13632 for(var i=0, l=valueArray.length; i<l; i++) {
13634 wrapper: document.createElement('div'),
13635 valueLabel: document.createElement('div'),
13636 label: document.createElement('div')
13638 var wrapper = nlbs.wrapper,
13639 label = nlbs.label,
13640 valueLabel = nlbs.valueLabel,
13641 wrapperStyle = wrapper.style,
13642 labelStyle = label.style,
13643 valueLabelStyle = valueLabel.style;
13644 //store node labels
13645 nodeLabels[idArray[i]] = nlbs;
13647 wrapper.appendChild(label);
13648 wrapper.appendChild(valueLabel);
13650 wrapperStyle.position = 'relative';
13651 wrapperStyle.overflow = 'visible';
13652 wrapperStyle.fontSize = labelConf.size + 'px';
13653 wrapperStyle.fontFamily = labelConf.family;
13654 wrapperStyle.color = labelConf.color;
13655 wrapperStyle.textAlign = 'center';
13656 wrapperStyle.width = size.width + 'px';
13657 valueLabelStyle.position = labelStyle.position = 'absolute';
13658 valueLabelStyle.left = labelStyle.left = '0px';
13659 valueLabelStyle.width = (size.width/3) + 'px';
13660 valueLabelStyle.textAlign = 'right';
13661 label.innerHTML = stringArray[i];
13662 valueLabel.innerHTML = valuelabelArray[i];
13663 domElement.id = prefix+'funnel';
13664 domElement.style.width = size.width + 'px';
13666 domElement.appendChild(wrapper);
13670 onPlaceLabel: function(domElement, node) {
13672 var dimArray = node.getData('dimArray'),
13673 idArray = node.getData('idArray'),
13674 valueArray = node.getData('valueArray'),
13675 valuelabelArray = node.getData('valuelabelArray'),
13676 stringArray = node.getData('stringArray');
13677 size = st.canvas.getSize(),
13678 pos = node.pos.getc(true),
13679 domElement.style.left = "0px",
13680 domElement.style.top = "0px",
13681 minWidth = node.getData('width') * .25,
13683 pos = node.pos.getc(true),
13684 labelConf = config.Label;
13687 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13689 var labels = nodeLabels[idArray[i]],
13690 wrapperStyle = labels.wrapper.style,
13691 labelStyle = labels.label.style,
13692 valueLabelStyle = labels.valueLabel.style;
13693 var bottomWidth = minWidth + (acum * ratio);
13695 font = parseInt(wrapperStyle.fontSize, 10),
13696 domStyle = domElement.style;
13700 wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13701 valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13702 labelStyle.left = (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13704 acum += (dimArray[i] || 0);
13712 var size = st.canvas.getSize(),
13713 margin = config.Margin;
13714 title = config.Title;
13715 subtitle = config.Subtitle;
13718 st.config.offsetY = -size.height/2 + margin.bottom
13719 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13721 st.config.offsetX = (margin.right - margin.left)/2;
13725 this.canvas = this.st.canvas;
13728 renderTitle: function() {
13729 var canvas = this.canvas,
13730 size = canvas.getSize(),
13731 config = this.config,
13732 margin = config.Margin,
13733 label = config.Label,
13734 title = config.Title;
13735 ctx = canvas.getCtx();
13736 ctx.fillStyle = title.color;
13737 ctx.textAlign = 'left';
13738 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13739 if(label.type == 'Native') {
13740 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13744 renderSubtitle: function() {
13745 var canvas = this.canvas,
13746 size = canvas.getSize(),
13747 config = this.config,
13748 margin = config.Margin,
13749 label = config.Label,
13750 subtitle = config.Subtitle;
13751 ctx = canvas.getCtx();
13752 ctx.fillStyle = title.color;
13753 ctx.textAlign = 'left';
13754 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13755 if(label.type == 'Native') {
13756 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13761 renderDropShadow: function() {
13762 var canvas = this.canvas,
13763 size = canvas.getSize(),
13764 config = this.config,
13765 margin = config.Margin,
13766 horz = config.orientation == 'horizontal',
13767 label = config.Label,
13768 title = config.Title,
13769 shadowThickness = 4,
13770 subtitle = config.Subtitle,
13771 ctx = canvas.getCtx(),
13772 minwidth = (size.width/8) * .25,
13773 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13774 topMargin = (title.text? title.size + title.offset : 0) + margin.top,
13775 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13776 - (config.showLabels && (config.Label.size + config.labelOffset)),
13778 topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13779 topY = (-size.height/2) + topMargin - shadowThickness;
13780 bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13781 bottomWidth = minwidth + shadowThickness;
13783 ctx.fillStyle = "rgba(0,0,0,.2)";
13784 ctx.moveTo(0,topY);
13785 ctx.lineTo(-topWidth/2,topY); //top left
13786 ctx.lineTo(-bottomWidth/2,bottomY); // bottom left
13787 ctx.lineTo(bottomWidth/2,bottomY); // bottom right
13788 ctx.lineTo(topWidth/2,topY); // top right
13795 renderBackground: function() {
13796 var canvas = this.canvas,
13797 config = this.config,
13798 backgroundColor = config.backgroundColor,
13799 size = canvas.getSize(),
13800 ctx = canvas.getCtx();
13801 //ctx.globalCompositeOperation = "destination-over";
13802 ctx.fillStyle = backgroundColor;
13803 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13805 clear: function() {
13806 var canvas = this.canvas;
13807 var ctx = canvas.getCtx(),
13808 size = canvas.getSize();
13809 ctx.fillStyle = "rgba(255,255,255,0)";
13810 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13811 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13813 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
13814 var canvas = this.canvas,
13815 size = canvas.getSize(),
13816 config = this.config,
13817 orgHeight = size.height,
13818 margin = config.Margin,
13820 label = config.Label,
13821 horz = config.orientation == 'horizontal',
13822 ctx = canvas.getCtx();
13825 var newWindowWidth = document.body.offsetWidth;
13826 var diff = newWindowWidth - orgWindowWidth;
13827 var newWidth = orgContainerDivWidth + (diff/cols);
13828 canvas.resize(newWidth,orgHeight);
13830 if(typeof FlashCanvas == "undefined") {
13833 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13835 this.loadJSON(json);
13839 loadJSON: function(json) {
13840 if(this.busy) return;
13842 var prefix = $.time(),
13845 name = $.splat(json.label),
13846 color = $.splat(json.color || this.colors),
13847 config = this.config,
13848 canvas = this.canvas,
13849 gradient = !!config.type.split(":")[1],
13850 animate = config.animate,
13851 title = config.Title,
13852 subtitle = config.Subtitle,
13853 renderBackground = config.renderBackground,
13854 horz = config.orientation == 'horizontal',
13856 colorLength = color.length,
13857 nameLength = name.length,
13859 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13860 var val = values[i];
13861 var valArray = $.splat(val.values);
13862 totalValue += parseFloat(valArray.sum());
13866 var nameArray = new Array();
13867 var idArray = new Array();
13868 var valArray = new Array();
13869 var valuelabelArray = new Array();
13870 var linkArray = new Array();
13871 var titleArray = new Array();
13872 var percentageArray = new Array();
13874 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13875 var val = values[i];
13876 nameArray[i] = $.splat(val.label);
13877 idArray[i] = $.splat(prefix + val.label);
13878 valArray[i] = $.splat(val.values);
13879 valuelabelArray[i] = $.splat(val.valuelabels);
13880 linkArray[i] = $.splat(val.links);
13881 titleArray[i] = $.splat(val.titles);
13882 percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13887 nameArray.reverse();
13888 valArray.reverse();
13889 valuelabelArray.reverse();
13890 linkArray.reverse();
13891 titleArray.reverse();
13892 percentageArray.reverse();
13895 'id': prefix + val.label,
13900 '$idArray': idArray,
13901 '$linkArray': linkArray,
13902 '$titleArray': titleArray,
13903 '$valueArray': valArray,
13904 '$valuelabelArray': valuelabelArray,
13905 '$colorArray': color,
13906 '$colorMono': $.splat(color[i % colorLength]),
13907 '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
13908 '$gradient': gradient,
13910 '$percentageArray' : percentageArray,
13918 'id': prefix + '$root',
13929 this.normalizeDims();
13931 if(renderBackground) {
13932 this.renderBackground();
13934 if(!animate && title.text) {
13935 this.renderTitle();
13937 if(!animate && subtitle.text) {
13938 this.renderSubtitle();
13940 if(typeof FlashCanvas == "undefined") {
13941 this.renderDropShadow();
13944 st.select(st.root);
13948 modes: ['node-property:width:dimArray'],
13950 onComplete: function() {
13956 modes: ['node-property:height:dimArray'],
13958 onComplete: function() {
13971 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.
13975 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13976 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13981 barChart.updateJSON(json, {
13982 onComplete: function() {
13983 alert('update complete!');
13988 updateJSON: function(json, onComplete) {
13989 if(this.busy) return;
13993 var graph = st.graph;
13994 var values = json.values;
13995 var animate = this.config.animate;
13997 var horz = this.config.orientation == 'horizontal';
13998 $.each(values, function(v) {
13999 var n = graph.getByName(v.label);
14001 n.setData('valueArray', $.splat(v.values));
14003 n.setData('stringArray', $.splat(json.label));
14007 this.normalizeDims();
14009 st.select(st.root);
14013 modes: ['node-property:width:dimArray'],
14015 onComplete: function() {
14017 onComplete && onComplete.onComplete();
14022 modes: ['node-property:height:dimArray'],
14024 onComplete: function() {
14026 onComplete && onComplete.onComplete();
14033 //adds the little brown bar when hovering the node
14034 select: function(id, name) {
14036 if(!this.config.hoveredColor) return;
14037 var s = this.selected;
14038 if(s.id != id || s.name != name) {
14041 s.color = this.config.hoveredColor;
14042 this.st.graph.eachNode(function(n) {
14044 n.setData('border', s);
14046 n.setData('border', false);
14056 Returns an object containing as keys the legend names and as values hex strings with color values.
14061 var legend = barChart.getLegend();
14064 getLegend: function() {
14065 var legend = new Array();
14066 var name = new Array();
14067 var color = new Array();
14069 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14072 var colors = n.getData('colorArray'),
14073 len = colors.length;
14074 $.each(n.getData('stringArray'), function(s, i) {
14075 color[i] = colors[i % len];
14078 legend['name'] = name;
14079 legend['color'] = color;
14084 Method: getMaxValue
14086 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14091 var ans = barChart.getMaxValue();
14094 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14099 //will return 100 for all BarChart instances,
14100 //displaying all of them with the same scale
14101 $jit.BarChart.implement({
14102 'getMaxValue': function() {
14109 getMaxValue: function() {
14110 var maxValue = 0, stacked = true;
14111 this.st.graph.eachNode(function(n) {
14112 var valArray = n.getData('valueArray'),
14114 if(!valArray) return;
14116 $.each(valArray, function(v) {
14120 acum = Math.max.apply(null, valArray);
14122 maxValue = maxValue>acum? maxValue:acum;
14127 setBarType: function(type) {
14128 this.config.type = type;
14129 this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14132 normalizeDims: function() {
14133 //number of elements
14134 var root = this.st.graph.getNode(this.st.root), l=0;
14135 root.eachAdjacency(function() {
14138 var maxValue = this.getMaxValue() || 1,
14139 size = this.st.canvas.getSize(),
14140 config = this.config,
14141 margin = config.Margin,
14142 title = config.Title,
14143 subtitle = config.Subtitle,
14144 marginWidth = margin.left + margin.right,
14145 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14146 horz = config.orientation == 'horizontal',
14147 animate = config.animate,
14148 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14150 - (config.showLabels && (config.Label.size + config.labelOffset)),
14151 dim1 = horz? 'height':'width',
14152 dim2 = horz? 'width':'height';
14155 minWidth = size.width/8;
14159 this.st.graph.eachNode(function(n) {
14160 var acum = 0, animateValue = [];
14161 $.each(n.getData('valueArray'), function(v) {
14163 animateValue.push(0);
14165 n.setData(dim1, minWidth);
14168 n.setData(dim2, acum * height / maxValue, 'end');
14169 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14170 return n * height / maxValue;
14172 var dimArray = n.getData('dimArray');
14174 n.setData('dimArray', animateValue);
14177 n.setData(dim2, acum * height / maxValue);
14178 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14179 return n * height / maxValue;
14190 * File: Options.PieChart.js
14194 Object: Options.PieChart
14196 <PieChart> options.
14197 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14203 Options.PieChart = {
14209 hoveredColor: '#9fd4ff',
14211 resizeLabels: false,
14212 updateHeights: false
14221 var pie = new $jit.PieChart({
14224 type: 'stacked:gradient'
14231 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14232 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14233 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14234 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14235 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14236 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14237 showLabels - (boolean) Default's *true*. Display the name of the slots.
14238 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.
14239 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.
14242 Options.PieChart = {
14246 offset: 25, // page offset
14248 labelOffset: 3, // label offset
14249 type: 'stacked', // gradient
14251 hoveredColor: '#9fd4ff',
14262 resizeLabels: false,
14264 //only valid for mono-valued datasets
14265 updateHeights: false
14269 * Class: Layouts.Radial
14271 * Implements a Radial Layout.
14275 * <RGraph>, <Hypertree>
14278 Layouts.Radial = new Class({
14283 * Computes nodes' positions.
14287 * property - _optional_ A <Graph.Node> position property to store the new
14288 * positions. Possible values are 'pos', 'end' or 'start'.
14291 compute : function(property) {
14292 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14293 NodeDim.compute(this.graph, prop, this.config);
14294 this.graph.computeLevels(this.root, 0, "ignore");
14295 var lengthFunc = this.createLevelDistanceFunc();
14296 this.computeAngularWidths(prop);
14297 this.computePositions(prop, lengthFunc);
14303 * Performs the main algorithm for computing node positions.
14305 computePositions : function(property, getLength) {
14306 var propArray = property;
14307 var graph = this.graph;
14308 var root = graph.getNode(this.root);
14309 var parent = this.parent;
14310 var config = this.config;
14312 for ( var i=0, l=propArray.length; i < l; i++) {
14313 var pi = propArray[i];
14314 root.setPos($P(0, 0), pi);
14315 root.setData('span', Math.PI * 2, pi);
14323 graph.eachBFS(this.root, function(elem) {
14324 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14325 var angleInit = elem.angleSpan.begin;
14326 var len = getLength(elem);
14327 //Calculate the sum of all angular widths
14328 var totalAngularWidths = 0, subnodes = [], maxDim = {};
14329 elem.eachSubnode(function(sib) {
14330 totalAngularWidths += sib._treeAngularWidth;
14332 for ( var i=0, l=propArray.length; i < l; i++) {
14333 var pi = propArray[i], dim = sib.getData('dim', pi);
14334 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14336 subnodes.push(sib);
14338 //Maintain children order
14339 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14340 if (parent && parent.id == elem.id && subnodes.length > 0
14341 && subnodes[0].dist) {
14342 subnodes.sort(function(a, b) {
14343 return (a.dist >= b.dist) - (a.dist <= b.dist);
14346 //Calculate nodes positions.
14347 for (var k = 0, ls=subnodes.length; k < ls; k++) {
14348 var child = subnodes[k];
14349 if (!child._flag) {
14350 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14351 var theta = angleInit + angleProportion / 2;
14353 for ( var i=0, l=propArray.length; i < l; i++) {
14354 var pi = propArray[i];
14355 child.setPos($P(theta, len), pi);
14356 child.setData('span', angleProportion, pi);
14357 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14360 child.angleSpan = {
14362 end : angleInit + angleProportion
14364 angleInit += angleProportion;
14371 * Method: setAngularWidthForNodes
14373 * Sets nodes angular widths.
14375 setAngularWidthForNodes : function(prop) {
14376 this.graph.eachBFS(this.root, function(elem, i) {
14377 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14378 elem._angularWidth = diamValue / i;
14383 * Method: setSubtreesAngularWidth
14385 * Sets subtrees angular widths.
14387 setSubtreesAngularWidth : function() {
14389 this.graph.eachNode(function(elem) {
14390 that.setSubtreeAngularWidth(elem);
14395 * Method: setSubtreeAngularWidth
14397 * Sets the angular width for a subtree.
14399 setSubtreeAngularWidth : function(elem) {
14400 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14401 elem.eachSubnode(function(child) {
14402 that.setSubtreeAngularWidth(child);
14403 sumAW += child._treeAngularWidth;
14405 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14409 * Method: computeAngularWidths
14411 * Computes nodes and subtrees angular widths.
14413 computeAngularWidths : function(prop) {
14414 this.setAngularWidthForNodes(prop);
14415 this.setSubtreesAngularWidth();
14422 * File: Sunburst.js
14428 A radial space filling tree visualization.
14432 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14436 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.
14440 All <Loader> methods
14442 Constructor Options:
14444 Inherits options from
14447 - <Options.Controller>
14453 - <Options.NodeStyles>
14454 - <Options.Navigation>
14456 Additionally, there are other parameters and some default values changed
14458 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14459 levelDistance - (number) Default's *100*. The distance between levels of the tree.
14460 Node.type - Described in <Options.Node>. Default's to *multipie*.
14461 Node.height - Described in <Options.Node>. Default's *0*.
14462 Edge.type - Described in <Options.Edge>. Default's *none*.
14463 Label.textAlign - Described in <Options.Label>. Default's *start*.
14464 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14466 Instance Properties:
14468 canvas - Access a <Canvas> instance.
14469 graph - Access a <Graph> instance.
14470 op - Access a <Sunburst.Op> instance.
14471 fx - Access a <Sunburst.Plot> instance.
14472 labels - Access a <Sunburst.Label> interface implementation.
14476 $jit.Sunburst = new Class({
14478 Implements: [ Loader, Extras, Layouts.Radial ],
14480 initialize: function(controller) {
14481 var $Sunburst = $jit.Sunburst;
14484 interpolation: 'linear',
14485 levelDistance: 100,
14487 'type': 'multipie',
14494 textAlign: 'start',
14495 textBaseline: 'middle'
14499 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14500 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14502 var canvasConfig = this.config;
14503 if(canvasConfig.useCanvas) {
14504 this.canvas = canvasConfig.useCanvas;
14505 this.config.labelContainer = this.canvas.id + '-label';
14507 if(canvasConfig.background) {
14508 canvasConfig.background = $.merge({
14510 colorStop1: this.config.colorStop1,
14511 colorStop2: this.config.colorStop2
14512 }, canvasConfig.background);
14514 this.canvas = new Canvas(this, canvasConfig);
14515 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14518 this.graphOptions = {
14526 this.graph = new Graph(this.graphOptions, this.config.Node,
14528 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14529 this.fx = new $Sunburst.Plot(this, $Sunburst);
14530 this.op = new $Sunburst.Op(this);
14533 this.rotated = null;
14535 // initialize extras
14536 this.initializeExtras();
14541 createLevelDistanceFunc
14543 Returns the levelDistance function used for calculating a node distance
14544 to its origin. This function returns a function that is computed
14545 per level and not per node, such that all nodes with the same depth will have the
14546 same distance to the origin. The resulting function gets the
14547 parent node as parameter and returns a float.
14550 createLevelDistanceFunc: function() {
14551 var ld = this.config.levelDistance;
14552 return function(elem) {
14553 return (elem._depth + 1) * ld;
14560 Computes positions and plots the tree.
14563 refresh: function() {
14571 An alias for computing new positions to _endPos_
14578 reposition: function() {
14579 this.compute('end');
14585 Rotates the graph so that the selected node is horizontal on the right.
14589 node - (object) A <Graph.Node>.
14590 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14591 opt - (object) Configuration options merged with this visualization configuration options.
14595 <Sunburst.rotateAngle>
14598 rotate: function(node, method, opt) {
14599 var theta = node.getPos(opt.property || 'current').getp(true).theta;
14600 this.rotated = node;
14601 this.rotateAngle(-theta, method, opt);
14605 Method: rotateAngle
14607 Rotates the graph of an angle theta.
14611 node - (object) A <Graph.Node>.
14612 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14613 opt - (object) Configuration options merged with this visualization configuration options.
14620 rotateAngle: function(theta, method, opt) {
14622 var options = $.merge(this.config, opt || {}, {
14625 var prop = opt.property || (method === "animate" ? 'end' : 'current');
14626 if(method === 'animate') {
14627 this.fx.animation.pause();
14629 this.graph.eachNode(function(n) {
14630 var p = n.getPos(prop);
14633 p.theta += Math.PI * 2;
14636 if (method == 'animate') {
14637 this.fx.animate(options);
14638 } else if (method == 'replot') {
14647 Plots the Sunburst. This is a shortcut to *fx.plot*.
14654 $jit.Sunburst.$extend = true;
14656 (function(Sunburst) {
14661 Custom extension of <Graph.Op>.
14665 All <Graph.Op> methods
14672 Sunburst.Op = new Class( {
14674 Implements: Graph.Op
14679 Class: Sunburst.Plot
14681 Custom extension of <Graph.Plot>.
14685 All <Graph.Plot> methods
14692 Sunburst.Plot = new Class( {
14694 Implements: Graph.Plot
14699 Class: Sunburst.Label
14701 Custom extension of <Graph.Label>.
14702 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14706 All <Graph.Label> methods and subclasses.
14710 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14713 Sunburst.Label = {};
14716 Sunburst.Label.Native
14718 Custom extension of <Graph.Label.Native>.
14722 All <Graph.Label.Native> methods
14726 <Graph.Label.Native>
14728 Sunburst.Label.Native = new Class( {
14729 Implements: Graph.Label.Native,
14731 initialize: function(viz) {
14733 this.label = viz.config.Label;
14734 this.config = viz.config;
14737 renderLabel: function(canvas, node, controller) {
14738 var span = node.getData('span');
14739 if(span < Math.PI /2 && Math.tan(span) *
14740 this.config.levelDistance * node._depth < 10) {
14743 var ctx = canvas.getCtx();
14744 var measure = ctx.measureText(node.name);
14745 if (node.id == this.viz.root) {
14746 var x = -measure.width / 2, y = 0, thetap = 0;
14750 var ld = controller.levelDistance - indent;
14751 var clone = node.pos.clone();
14752 clone.rho += indent;
14753 var p = clone.getp(true);
14754 var ct = clone.getc(true);
14755 var x = ct.x, y = ct.y;
14756 // get angle in degrees
14758 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14759 var thetap = cond ? p.theta + pi : p.theta;
14761 x -= Math.abs(Math.cos(p.theta) * measure.width);
14762 y += Math.sin(p.theta) * measure.width;
14763 } else if (node.id == this.viz.root) {
14764 x -= measure.width / 2;
14768 ctx.translate(x, y);
14769 ctx.rotate(thetap);
14770 ctx.fillText(node.name, 0, 0);
14778 Custom extension of <Graph.Label.SVG>.
14782 All <Graph.Label.SVG> methods
14789 Sunburst.Label.SVG = new Class( {
14790 Implements: Graph.Label.SVG,
14792 initialize: function(viz) {
14799 Overrides abstract method placeLabel in <Graph.Plot>.
14803 tag - A DOM label element.
14804 node - A <Graph.Node>.
14805 controller - A configuration/controller object passed to the visualization.
14808 placeLabel: function(tag, node, controller) {
14809 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14810 var radius = canvas.getSize();
14812 x: Math.round(pos.x + radius.width / 2),
14813 y: Math.round(pos.y + radius.height / 2)
14815 tag.setAttribute('x', labelPos.x);
14816 tag.setAttribute('y', labelPos.y);
14818 var bb = tag.getBBox();
14820 // center the label
14821 var x = tag.getAttribute('x');
14822 var y = tag.getAttribute('y');
14823 // get polar coordinates
14824 var p = node.pos.getp(true);
14825 // get angle in degrees
14827 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14829 tag.setAttribute('x', x - bb.width);
14830 tag.setAttribute('y', y - bb.height);
14831 } else if (node.id == viz.root) {
14832 tag.setAttribute('x', x - bb.width / 2);
14835 var thetap = cond ? p.theta + pi : p.theta;
14837 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14841 controller.onPlaceLabel(tag, node);
14846 Sunburst.Label.HTML
14848 Custom extension of <Graph.Label.HTML>.
14852 All <Graph.Label.HTML> methods.
14859 Sunburst.Label.HTML = new Class( {
14860 Implements: Graph.Label.HTML,
14862 initialize: function(viz) {
14868 Overrides abstract method placeLabel in <Graph.Plot>.
14872 tag - A DOM label element.
14873 node - A <Graph.Node>.
14874 controller - A configuration/controller object passed to the visualization.
14877 placeLabel: function(tag, node, controller) {
14878 var pos = node.pos.clone(),
14879 canvas = this.viz.canvas,
14880 height = node.getData('height'),
14881 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14882 radius = canvas.getSize();
14884 pos = pos.getc(true);
14887 x: Math.round(pos.x + radius.width / 2),
14888 y: Math.round(pos.y + radius.height / 2)
14891 var style = tag.style;
14892 style.left = labelPos.x + 'px';
14893 style.top = labelPos.y + 'px';
14894 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14896 controller.onPlaceLabel(tag, node);
14901 Class: Sunburst.Plot.NodeTypes
14903 This class contains a list of <Graph.Node> built-in types.
14904 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14906 You can add your custom node types, customizing your visualization to the extreme.
14911 Sunburst.Plot.NodeTypes.implement({
14913 'render': function(node, canvas) {
14914 //print your custom node to canvas
14917 'contains': function(node, pos) {
14918 //return true if pos is inside the node or false otherwise
14925 Sunburst.Plot.NodeTypes = new Class( {
14928 'contains': $.lambda(false),
14929 'anglecontains': function(node, pos) {
14930 var span = node.getData('span') / 2, theta = node.pos.theta;
14931 var begin = theta - span, end = theta + span;
14933 begin += Math.PI * 2;
14934 var atan = Math.atan2(pos.y, pos.x);
14936 atan += Math.PI * 2;
14938 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14940 return atan > begin && atan < end;
14943 'anglecontainsgauge': function(node, pos) {
14944 var span = node.getData('span') / 2, theta = node.pos.theta;
14945 var config = node.getData('config');
14946 var ld = this.config.levelDistance;
14947 var yOffset = pos.y-(ld/2);
14948 var begin = ((theta - span)/2)+Math.PI,
14949 end = ((theta + span)/2)+Math.PI;
14952 begin += Math.PI * 2;
14953 var atan = Math.atan2(yOffset, pos.x);
14957 atan += Math.PI * 2;
14961 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14963 return atan > begin && atan < end;
14969 'render': function(node, canvas) {
14970 var span = node.getData('span') / 2, theta = node.pos.theta;
14971 var begin = theta - span, end = theta + span;
14972 var polarNode = node.pos.getp(true);
14973 var polar = new Polar(polarNode.rho, begin);
14974 var p1coord = polar.getc(true);
14976 var p2coord = polar.getc(true);
14978 var ctx = canvas.getCtx();
14981 ctx.lineTo(p1coord.x, p1coord.y);
14983 ctx.lineTo(p2coord.x, p2coord.y);
14985 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14989 'contains': function(node, pos) {
14990 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14991 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14992 var ld = this.config.levelDistance, d = node._depth;
14993 return (rho <= ld * d);
14999 'render': function(node, canvas) {
15000 var height = node.getData('height');
15001 var ldist = height? height : this.config.levelDistance;
15002 var span = node.getData('span') / 2, theta = node.pos.theta;
15003 var begin = theta - span, end = theta + span;
15004 var polarNode = node.pos.getp(true);
15006 var polar = new Polar(polarNode.rho, begin);
15007 var p1coord = polar.getc(true);
15010 var p2coord = polar.getc(true);
15012 polar.rho += ldist;
15013 var p3coord = polar.getc(true);
15015 polar.theta = begin;
15016 var p4coord = polar.getc(true);
15018 var ctx = canvas.getCtx();
15021 ctx.arc(0, 0, polarNode.rho, begin, end, false);
15022 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15023 ctx.moveTo(p1coord.x, p1coord.y);
15024 ctx.lineTo(p4coord.x, p4coord.y);
15025 ctx.moveTo(p2coord.x, p2coord.y);
15026 ctx.lineTo(p3coord.x, p3coord.y);
15029 if (node.collapsed) {
15034 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15040 'contains': function(node, pos) {
15041 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15042 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15043 var height = node.getData('height');
15044 var ldist = height? height : this.config.levelDistance;
15045 var ld = this.config.levelDistance, d = node._depth;
15046 return (rho >= ld * d) && (rho <= (ld * d + ldist));
15052 'gradient-multipie': {
15053 'render': function(node, canvas) {
15054 var ctx = canvas.getCtx();
15055 var height = node.getData('height');
15056 var ldist = height? height : this.config.levelDistance;
15057 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15058 0, 0, node.getPos().rho + ldist);
15060 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15061 $.each(colorArray, function(i) {
15062 ans.push(parseInt(i * 0.5, 10));
15064 var endColor = $.rgbToHex(ans);
15065 radialGradient.addColorStop(0, endColor);
15066 radialGradient.addColorStop(1, node.getData('color'));
15067 ctx.fillStyle = radialGradient;
15068 this.nodeTypes['multipie'].render.call(this, node, canvas);
15070 'contains': function(node, pos) {
15071 return this.nodeTypes['multipie'].contains.call(this, node, pos);
15076 'render': function(node, canvas) {
15077 var ctx = canvas.getCtx();
15078 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15081 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15082 $.each(colorArray, function(i) {
15083 ans.push(parseInt(i * 0.5, 10));
15085 var endColor = $.rgbToHex(ans);
15086 radialGradient.addColorStop(1, endColor);
15087 radialGradient.addColorStop(0, node.getData('color'));
15088 ctx.fillStyle = radialGradient;
15089 this.nodeTypes['pie'].render.call(this, node, canvas);
15091 'contains': function(node, pos) {
15092 return this.nodeTypes['pie'].contains.call(this, node, pos);
15098 Class: Sunburst.Plot.EdgeTypes
15100 This class contains a list of <Graph.Adjacence> built-in types.
15101 Edge types implemented are 'none', 'line' and 'arrow'.
15103 You can add your custom edge types, customizing your visualization to the extreme.
15108 Sunburst.Plot.EdgeTypes.implement({
15110 'render': function(adj, canvas) {
15111 //print your custom edge to canvas
15114 'contains': function(adj, pos) {
15115 //return true if pos is inside the arc or false otherwise
15122 Sunburst.Plot.EdgeTypes = new Class({
15125 'render': function(adj, canvas) {
15126 var from = adj.nodeFrom.pos.getc(true),
15127 to = adj.nodeTo.pos.getc(true);
15128 this.edgeHelper.line.render(from, to, canvas);
15130 'contains': function(adj, pos) {
15131 var from = adj.nodeFrom.pos.getc(true),
15132 to = adj.nodeTo.pos.getc(true);
15133 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15137 'render': function(adj, canvas) {
15138 var from = adj.nodeFrom.pos.getc(true),
15139 to = adj.nodeTo.pos.getc(true),
15140 dim = adj.getData('dim'),
15141 direction = adj.data.$direction,
15142 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15143 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15145 'contains': function(adj, pos) {
15146 var from = adj.nodeFrom.pos.getc(true),
15147 to = adj.nodeTo.pos.getc(true);
15148 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15152 'render': function(adj, canvas) {
15153 var from = adj.nodeFrom.pos.getc(),
15154 to = adj.nodeTo.pos.getc(),
15155 dim = Math.max(from.norm(), to.norm());
15156 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15158 'contains': $.lambda(false) //TODO(nico): Implement this!
15166 * File: PieChart.js
15170 $jit.Sunburst.Plot.NodeTypes.implement({
15171 'piechart-stacked' : {
15172 'render' : function(node, canvas) {
15173 var pos = node.pos.getp(true),
15174 dimArray = node.getData('dimArray'),
15175 valueArray = node.getData('valueArray'),
15176 colorArray = node.getData('colorArray'),
15177 colorLength = colorArray.length,
15178 stringArray = node.getData('stringArray'),
15179 span = node.getData('span') / 2,
15180 theta = node.pos.theta,
15181 begin = theta - span,
15182 end = theta + span,
15185 var ctx = canvas.getCtx(),
15187 gradient = node.getData('gradient'),
15188 border = node.getData('border'),
15189 config = node.getData('config'),
15190 showLabels = config.showLabels,
15191 resizeLabels = config.resizeLabels,
15192 label = config.Label;
15194 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15195 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15197 if (colorArray && dimArray && stringArray) {
15198 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15199 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15200 if(dimi <= 0) continue;
15201 ctx.fillStyle = ctx.strokeStyle = colori;
15202 if(gradient && dimi) {
15203 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15204 xpos, ypos, acum + dimi + config.sliceOffset);
15205 var colorRgb = $.hexToRgb(colori),
15206 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15207 endColor = $.rgbToHex(ans);
15209 radialGradient.addColorStop(0, colori);
15210 radialGradient.addColorStop(0.5, colori);
15211 radialGradient.addColorStop(1, endColor);
15212 ctx.fillStyle = radialGradient;
15215 polar.rho = acum + config.sliceOffset;
15216 polar.theta = begin;
15217 var p1coord = polar.getc(true);
15219 var p2coord = polar.getc(true);
15221 var p3coord = polar.getc(true);
15222 polar.theta = begin;
15223 var p4coord = polar.getc(true);
15226 //fixing FF arc method + fill
15227 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15228 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15230 if(border && border.name == stringArray[i]) {
15232 opt.dimValue = dimArray[i];
15236 acum += (dimi || 0);
15237 valAcum += (valueArray[i] || 0);
15241 ctx.globalCompositeOperation = "source-over";
15243 ctx.strokeStyle = border.color;
15244 var s = begin < end? 1 : -1;
15246 //fixing FF arc method + fill
15247 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15248 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15253 if(showLabels && label.type == 'Native') {
15255 ctx.fillStyle = ctx.strokeStyle = label.color;
15256 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15257 fontSize = (label.size * scale) >> 0;
15258 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15260 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15261 ctx.textBaseline = 'middle';
15262 ctx.textAlign = 'center';
15264 polar.rho = acum + config.labelOffset + config.sliceOffset;
15265 polar.theta = node.pos.theta;
15266 var cart = polar.getc(true);
15268 ctx.fillText(node.name, cart.x, cart.y);
15273 'contains': function(node, pos) {
15274 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15275 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15276 var ld = this.config.levelDistance, d = node._depth;
15277 var config = node.getData('config');
15278 if(rho <=ld * d + config.sliceOffset) {
15279 var dimArray = node.getData('dimArray');
15280 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15281 var dimi = dimArray[i];
15282 if(rho >= acum && rho <= acum + dimi) {
15284 name: node.getData('stringArray')[i],
15285 color: node.getData('colorArray')[i],
15286 value: node.getData('valueArray')[i],
15299 'piechart-basic' : {
15300 'render' : function(node, canvas) {
15301 var pos = node.pos.getp(true),
15302 dimArray = node.getData('dimArray'),
15303 valueArray = node.getData('valueArray'),
15304 colorArray = node.getData('colorMono'),
15305 colorLength = colorArray.length,
15306 stringArray = node.getData('stringArray'),
15307 percentage = node.getData('percentage'),
15308 iteration = node.getData('iteration'),
15309 span = node.getData('span') / 2,
15310 theta = node.pos.theta,
15311 begin = theta - span,
15312 end = theta + span,
15315 var ctx = canvas.getCtx(),
15317 gradient = node.getData('gradient'),
15318 border = node.getData('border'),
15319 config = node.getData('config'),
15320 renderSubtitle = node.getData('renderSubtitle'),
15321 renderBackground = config.renderBackground,
15322 showLabels = config.showLabels,
15323 resizeLabels = config.resizeLabels,
15324 label = config.Label;
15326 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15327 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15328 //background rendering for IE
15329 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15330 backgroundColor = config.backgroundColor,
15331 size = canvas.getSize();
15333 ctx.fillStyle = backgroundColor;
15334 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15338 var margin = config.Margin,
15339 title = config.Title,
15340 subtitle = config.Subtitle;
15341 ctx.fillStyle = title.color;
15342 ctx.textAlign = 'left';
15344 if(title.text != "") {
15345 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15347 if(label.type == 'Native') {
15348 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15352 if(subtitle.text != "") {
15353 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15354 if(label.type == 'Native') {
15355 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15360 if (colorArray && dimArray && stringArray) {
15361 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15362 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15363 if(dimi <= 0) continue;
15364 ctx.fillStyle = ctx.strokeStyle = colori;
15366 polar.rho = acum + config.sliceOffset;
15367 polar.theta = begin;
15368 var p1coord = polar.getc(true);
15370 var p2coord = polar.getc(true);
15372 var p3coord = polar.getc(true);
15373 polar.theta = begin;
15374 var p4coord = polar.getc(true);
15376 if(typeof FlashCanvas == "undefined") {
15379 ctx.fillStyle = "rgba(0,0,0,.2)";
15380 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15381 ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);
15384 if(gradient && dimi) {
15385 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15386 xpos, ypos, acum + dimi + config.sliceOffset);
15387 var colorRgb = $.hexToRgb(colori),
15388 endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15389 endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15391 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15392 radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15393 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15394 radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15395 ctx.fillStyle = radialGradient;
15400 //fixing FF arc method + fill
15402 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15403 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15405 if(border && border.name == stringArray[i]) {
15407 opt.dimValue = dimArray[i];
15410 opt.sliceValue = valueArray[i];
15412 acum += (dimi || 0);
15413 valAcum += (valueArray[i] || 0);
15417 ctx.globalCompositeOperation = "source-over";
15419 ctx.strokeStyle = border.color;
15420 var s = begin < end? 1 : -1;
15422 //fixing FF arc method + fill
15423 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15424 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15429 if(showLabels && label.type == 'Native') {
15431 ctx.fillStyle = ctx.strokeStyle = label.color;
15432 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15433 fontSize = (label.size * scale) >> 0;
15434 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15436 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15437 ctx.textBaseline = 'middle';
15438 ctx.textAlign = 'center';
15440 angle = theta * 360 / (2 * pi);
15441 polar.rho = acum + config.labelOffset + config.sliceOffset;
15442 polar.theta = node.pos.theta;
15443 var cart = polar.getc(true);
15444 if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15447 if(config.labelType == 'name') {
15448 ctx.fillText(node.name, cart.x, cart.y);
15450 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15457 'contains': function(node, pos) {
15458 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15459 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15460 var ld = this.config.levelDistance, d = node._depth;
15461 var config = node.getData('config');
15463 if(rho <=ld * d + config.sliceOffset) {
15464 var dimArray = node.getData('dimArray');
15465 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15466 var dimi = dimArray[i];
15467 if(rho >= acum && rho <= acum + dimi) {
15468 var url = Url.decode(node.getData('linkArray')[i]);
15470 name: node.getData('stringArray')[i],
15472 color: node.getData('colorArray')[i],
15473 value: node.getData('valueArray')[i],
15474 percentage: node.getData('percentage'),
15475 valuelabel: node.getData('valuelabelsArray')[i],
15493 A visualization that displays stacked bar charts.
15495 Constructor Options:
15497 See <Options.PieChart>.
15500 $jit.PieChart = new Class({
15502 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15506 initialize: function(opt) {
15507 this.controller = this.config =
15508 $.merge(Options("Canvas", "PieChart", "Label"), {
15509 Label: { type: 'Native' }
15511 this.initializeViz();
15514 initializeViz: function() {
15515 var config = this.config, that = this;
15516 var nodeType = config.type.split(":")[0];
15517 var sb = new $jit.Sunburst({
15518 injectInto: config.injectInto,
15519 useCanvas: config.useCanvas,
15520 withLabels: config.Label.type != 'Native',
15521 background: config.background,
15522 renderBackground: config.renderBackground,
15523 backgroundColor: config.backgroundColor,
15524 colorStop1: config.colorStop1,
15525 colorStop2: config.colorStop2,
15527 type: config.Label.type
15531 type: 'piechart-' + nodeType,
15539 enable: config.Tips.enable,
15542 onShow: function(tip, node, contains) {
15543 var elem = contains;
15544 config.Tips.onShow(tip, elem, node);
15545 if(elem.link != 'undefined' && elem.link != '') {
15546 document.body.style.cursor = 'pointer';
15549 onHide: function() {
15550 document.body.style.cursor = 'default';
15556 onClick: function(node, eventInfo, evt) {
15557 if(!config.Events.enable) return;
15558 var elem = eventInfo.getContains();
15559 config.Events.onClick(elem, eventInfo, evt);
15561 onMouseMove: function(node, eventInfo, evt) {
15562 if(!config.hoveredColor) return;
15564 var elem = eventInfo.getContains();
15565 that.select(node.id, elem.name, elem.index);
15567 that.select(false, false, false);
15571 onCreateLabel: function(domElement, node) {
15572 var labelConf = config.Label;
15573 if(config.showLabels) {
15574 var style = domElement.style;
15575 style.fontSize = labelConf.size + 'px';
15576 style.fontFamily = labelConf.family;
15577 style.color = labelConf.color;
15578 style.textAlign = 'center';
15579 if(config.labelType == 'name') {
15580 domElement.innerHTML = node.name;
15582 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15584 domElement.style.width = '400px';
15587 onPlaceLabel: function(domElement, node) {
15588 if(!config.showLabels) return;
15589 var pos = node.pos.getp(true),
15590 dimArray = node.getData('dimArray'),
15591 span = node.getData('span') / 2,
15592 theta = node.pos.theta,
15593 begin = theta - span,
15594 end = theta + span,
15597 var showLabels = config.showLabels,
15598 resizeLabels = config.resizeLabels,
15599 label = config.Label;
15602 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15603 acum += dimArray[i];
15605 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15606 fontSize = (label.size * scale) >> 0;
15607 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15608 domElement.style.fontSize = fontSize + 'px';
15609 polar.rho = acum + config.labelOffset + config.sliceOffset;
15610 polar.theta = (begin + end) / 2;
15611 var pos = polar.getc(true);
15612 var radius = that.canvas.getSize();
15614 x: Math.round(pos.x + radius.width / 2),
15615 y: Math.round(pos.y + radius.height / 2)
15617 domElement.style.left = (labelPos.x - 200) + 'px';
15618 domElement.style.top = labelPos.y + 'px';
15623 var size = sb.canvas.getSize(),
15625 sb.config.levelDistance = min(size.width, size.height)/2
15626 - config.offset - config.sliceOffset;
15628 this.canvas = this.sb.canvas;
15629 this.canvas.getCtx().globalCompositeOperation = 'lighter';
15631 renderBackground: function() {
15632 var canvas = this.canvas,
15633 config = this.config,
15634 backgroundColor = config.backgroundColor,
15635 size = canvas.getSize(),
15636 ctx = canvas.getCtx();
15637 ctx.globalCompositeOperation = "destination-over";
15638 ctx.fillStyle = backgroundColor;
15639 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15641 renderTitle: function() {
15642 var canvas = this.canvas,
15643 size = canvas.getSize(),
15644 config = this.config,
15645 margin = config.Margin,
15646 radius = this.sb.config.levelDistance,
15647 title = config.Title,
15648 label = config.Label,
15649 subtitle = config.Subtitle;
15650 ctx = canvas.getCtx();
15651 ctx.fillStyle = title.color;
15652 ctx.textAlign = 'left';
15653 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15655 if(label.type == 'Native') {
15656 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15659 renderSubtitle: function() {
15660 var canvas = this.canvas,
15661 size = canvas.getSize(),
15662 config = this.config,
15663 margin = config.Margin,
15664 radius = this.sb.config.levelDistance,
15665 title = config.Title,
15666 label = config.Label,
15667 subtitle = config.Subtitle;
15668 ctx = canvas.getCtx();
15669 ctx.fillStyle = title.color;
15670 ctx.textAlign = 'left';
15671 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15673 if(label.type == 'Native') {
15674 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15677 clear: function() {
15678 var canvas = this.canvas;
15679 var ctx = canvas.getCtx(),
15680 size = canvas.getSize();
15681 ctx.fillStyle = "rgba(255,255,255,0)";
15682 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15683 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15685 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
15686 var canvas = this.canvas,
15687 size = canvas.getSize(),
15688 config = this.config,
15689 orgHeight = size.height,
15690 margin = config.Margin,
15692 horz = config.orientation == 'horizontal';
15695 var newWindowWidth = document.body.offsetWidth;
15696 var diff = newWindowWidth - orgWindowWidth;
15697 var newWidth = orgContainerDivWidth + (diff/cols);
15698 var scale = newWidth/orgContainerDivWidth;
15699 canvas.resize(newWidth,orgHeight);
15700 if(typeof FlashCanvas == "undefined") {
15703 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15705 this.loadJSON(json);
15711 Loads JSON data into the visualization.
15715 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>.
15719 var pieChart = new $jit.PieChart(options);
15720 pieChart.loadJSON(json);
15723 loadJSON: function(json) {
15724 var prefix = $.time(),
15727 name = $.splat(json.label),
15728 nameLength = name.length,
15729 color = $.splat(json.color || this.colors),
15730 colorLength = color.length,
15731 config = this.config,
15732 renderBackground = config.renderBackground,
15733 title = config.Title,
15734 subtitle = config.Subtitle,
15735 gradient = !!config.type.split(":")[1],
15736 animate = config.animate,
15737 mono = nameLength == 1;
15739 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15740 var val = values[i];
15741 var valArray = $.splat(val.values);
15742 totalValue += parseFloat(valArray.sum());
15745 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15746 var val = values[i];
15747 var valArray = $.splat(val.values);
15748 var percentage = (valArray.sum()/totalValue) * 100;
15750 var linkArray = $.splat(val.links);
15751 var valuelabelsArray = $.splat(val.valuelabels);
15755 'id': prefix + val.label,
15759 'valuelabel': valuelabelsArray,
15760 '$linkArray': linkArray,
15761 '$valuelabelsArray': valuelabelsArray,
15762 '$valueArray': valArray,
15763 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15764 '$colorMono': $.splat(color[i % colorLength]),
15765 '$stringArray': name,
15766 '$gradient': gradient,
15769 '$percentage': percentage.toFixed(1),
15770 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15776 'id': prefix + '$root',
15788 this.normalizeDims();
15792 if(title.text != "") {
15793 this.renderTitle();
15796 if(subtitle.text != "") {
15797 this.renderSubtitle();
15799 if(renderBackground && typeof FlashCanvas == "undefined") {
15800 this.renderBackground();
15805 modes: ['node-property:dimArray'],
15814 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.
15818 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15819 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15824 pieChart.updateJSON(json, {
15825 onComplete: function() {
15826 alert('update complete!');
15831 updateJSON: function(json, onComplete) {
15832 if(this.busy) return;
15836 var graph = sb.graph;
15837 var values = json.values;
15838 var animate = this.config.animate;
15840 $.each(values, function(v) {
15841 var n = graph.getByName(v.label),
15842 vals = $.splat(v.values);
15844 n.setData('valueArray', vals);
15845 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15847 n.setData('stringArray', $.splat(json.label));
15851 this.normalizeDims();
15855 modes: ['node-property:dimArray:span', 'linear'],
15857 onComplete: function() {
15859 onComplete && onComplete.onComplete();
15867 //adds the little brown bar when hovering the node
15868 select: function(id, name) {
15869 if(!this.config.hoveredColor) return;
15870 var s = this.selected;
15871 if(s.id != id || s.name != name) {
15874 s.color = this.config.hoveredColor;
15875 this.sb.graph.eachNode(function(n) {
15877 n.setData('border', s);
15879 n.setData('border', false);
15889 Returns an object containing as keys the legend names and as values hex strings with color values.
15894 var legend = pieChart.getLegend();
15897 getLegend: function() {
15898 var legend = new Array();
15899 var name = new Array();
15900 var color = new Array();
15902 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15905 var colors = n.getData('colorArray'),
15906 len = colors.length;
15907 $.each(n.getData('stringArray'), function(s, i) {
15908 color[i] = colors[i % len];
15911 legend['name'] = name;
15912 legend['color'] = color;
15917 Method: getMaxValue
15919 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15924 var ans = pieChart.getMaxValue();
15927 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15932 //will return 100 for all PieChart instances,
15933 //displaying all of them with the same scale
15934 $jit.PieChart.implement({
15935 'getMaxValue': function() {
15942 getMaxValue: function() {
15944 this.sb.graph.eachNode(function(n) {
15945 var valArray = n.getData('valueArray'),
15947 $.each(valArray, function(v) {
15950 maxValue = maxValue>acum? maxValue:acum;
15955 normalizeDims: function() {
15956 //number of elements
15957 var root = this.sb.graph.getNode(this.sb.root), l=0;
15958 root.eachAdjacency(function() {
15961 var maxValue = this.getMaxValue() || 1,
15962 config = this.config,
15963 animate = config.animate,
15964 rho = this.sb.config.levelDistance;
15965 this.sb.graph.eachNode(function(n) {
15966 var acum = 0, animateValue = [];
15967 $.each(n.getData('valueArray'), function(v) {
15969 animateValue.push(1);
15971 var stat = (animateValue.length == 1) && !config.updateHeights;
15973 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15974 return stat? rho: (n * rho / maxValue);
15976 var dimArray = n.getData('dimArray');
15978 n.setData('dimArray', animateValue);
15981 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15982 return stat? rho : (n * rho / maxValue);
15985 n.setData('normalizedDim', acum / maxValue);
15993 Options.GaugeChart = {
15997 offset: 25, // page offset
15999 labelOffset: 3, // label offset
16000 type: 'stacked', // gradient
16002 hoveredColor: '#9fd4ff',
16013 resizeLabels: false,
16015 //only valid for mono-valued datasets
16016 updateHeights: false
16021 $jit.Sunburst.Plot.NodeTypes.implement({
16022 'gaugechart-basic' : {
16023 'render' : function(node, canvas) {
16024 var pos = node.pos.getp(true),
16025 dimArray = node.getData('dimArray'),
16026 valueArray = node.getData('valueArray'),
16027 valuelabelsArray = node.getData('valuelabelsArray'),
16028 gaugeTarget = node.getData('gaugeTarget'),
16029 nodeIteration = node.getData('nodeIteration'),
16030 nodeLength = node.getData('nodeLength'),
16031 colorArray = node.getData('colorMono'),
16032 colorLength = colorArray.length,
16033 stringArray = node.getData('stringArray'),
16034 span = node.getData('span') / 2,
16035 theta = node.pos.theta,
16036 begin = ((theta - span)/2)+Math.PI,
16037 end = ((theta + span)/2)+Math.PI,
16041 var ctx = canvas.getCtx(),
16043 gradient = node.getData('gradient'),
16044 border = node.getData('border'),
16045 config = node.getData('config'),
16046 showLabels = config.showLabels,
16047 resizeLabels = config.resizeLabels,
16048 label = config.Label;
16050 var xpos = Math.cos((begin + end) /2);
16051 var ypos = Math.sin((begin + end) /2);
16053 if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16054 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16055 var dimi = dimArray[i], colori = colorArray[i % colorLength];
16056 if(dimi <= 0) continue;
16057 ctx.fillStyle = ctx.strokeStyle = colori;
16058 if(gradient && dimi) {
16059 var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16060 xpos, (ypos + dimi/2), acum + dimi);
16061 var colorRgb = $.hexToRgb(colori),
16062 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16063 endColor = $.rgbToHex(ans);
16065 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16066 radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16067 radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16068 radialGradient.addColorStop(1, 'rgba('+ans+',1)');
16069 ctx.fillStyle = radialGradient;
16073 polar.theta = begin;
16074 var p1coord = polar.getc(true);
16076 var p2coord = polar.getc(true);
16078 var p3coord = polar.getc(true);
16079 polar.theta = begin;
16080 var p4coord = polar.getc(true);
16084 //fixing FF arc method + fill
16085 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16086 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16090 acum += (dimi || 0);
16091 valAcum += (valueArray[i] || 0);
16094 if(showLabels && label.type == 'Native') {
16096 ctx.fillStyle = ctx.strokeStyle = label.color;
16099 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16100 ctx.textBaseline = 'bottom';
16101 ctx.textAlign = 'center';
16103 polar.rho = acum * .65;
16104 polar.theta = begin;
16105 var cart = polar.getc(true);
16107 //changes y pos of first label
16108 if(nodeIteration == 1) {
16109 textY = cart.y - (label.size/2) + acum /2;
16111 textY = cart.y + acum/2;
16114 if(config.labelType == 'name') {
16115 ctx.fillText(node.name, cart.x, textY);
16117 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16121 if(nodeIteration == nodeLength) {
16123 var cart = polar.getc(true);
16124 if(config.labelType == 'name') {
16125 ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16127 ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16136 'contains': function(node, pos) {
16140 if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16141 var config = node.getData('config');
16142 var ld = this.config.levelDistance , d = node._depth;
16143 var yOffset = pos.y - (ld/2);
16144 var xOffset = pos.x;
16145 var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16146 if(rho <=parseInt(ld * d)) {
16147 var dimArray = node.getData('dimArray');
16148 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16149 var dimi = dimArray[i];
16150 if(rho >= ld * .8 && rho <= acum + dimi) {
16152 var url = Url.decode(node.getData('linkArray')[i]);
16154 name: node.getData('stringArray')[i],
16156 color: node.getData('colorArray')[i],
16157 value: node.getData('valueArray')[i],
16158 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16178 A visualization that displays gauge charts
16180 Constructor Options:
16182 See <Options.Gauge>.
16185 $jit.GaugeChart = new Class({
16187 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16191 initialize: function(opt) {
16192 this.controller = this.config =
16193 $.merge(Options("Canvas", "GaugeChart", "Label"), {
16194 Label: { type: 'Native' }
16196 this.initializeViz();
16199 initializeViz: function() {
16200 var config = this.config, that = this;
16201 var nodeType = config.type.split(":")[0];
16202 var sb = new $jit.Sunburst({
16203 injectInto: config.injectInto,
16204 useCanvas: config.useCanvas,
16205 withLabels: config.Label.type != 'Native',
16206 background: config.background,
16207 renderBackground: config.renderBackground,
16208 backgroundColor: config.backgroundColor,
16209 colorStop1: config.colorStop1,
16210 colorStop2: config.colorStop2,
16212 type: config.Label.type
16216 type: 'gaugechart-' + nodeType,
16224 enable: config.Tips.enable,
16227 onShow: function(tip, node, contains) {
16228 var elem = contains;
16229 config.Tips.onShow(tip, elem, node);
16230 if(elem.link != 'undefined' && elem.link != '') {
16231 document.body.style.cursor = 'pointer';
16234 onHide: function() {
16235 document.body.style.cursor = 'default';
16241 onClick: function(node, eventInfo, evt) {
16242 if(!config.Events.enable) return;
16243 var elem = eventInfo.getContains();
16244 config.Events.onClick(elem, eventInfo, evt);
16247 onCreateLabel: function(domElement, node) {
16248 var labelConf = config.Label;
16249 if(config.showLabels) {
16250 var style = domElement.style;
16251 style.fontSize = labelConf.size + 'px';
16252 style.fontFamily = labelConf.family;
16253 style.color = labelConf.color;
16254 style.textAlign = 'center';
16255 valuelabelsArray = node.getData('valuelabelsArray'),
16256 nodeIteration = node.getData('nodeIteration'),
16257 nodeLength = node.getData('nodeLength'),
16258 canvas = sb.canvas,
16261 if(config.labelType == 'name') {
16262 domElement.innerHTML = node.name;
16264 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16267 domElement.style.width = '400px';
16270 if(nodeIteration == nodeLength && nodeLength != 0) {
16271 idLabel = canvas.id + "-label";
16272 container = document.getElementById(idLabel);
16273 finalLabel = document.createElement('div');
16274 finalLabelStyle = finalLabel.style;
16275 finalLabel.id = prefix + "finalLabel";
16276 finalLabelStyle.position = "absolute";
16277 finalLabelStyle.width = "400px";
16278 finalLabelStyle.left = "0px";
16279 container.appendChild(finalLabel);
16280 if(config.labelType == 'name') {
16281 finalLabel.innerHTML = node.name;
16283 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16289 onPlaceLabel: function(domElement, node) {
16290 if(!config.showLabels) return;
16291 var pos = node.pos.getp(true),
16292 dimArray = node.getData('dimArray'),
16293 nodeIteration = node.getData('nodeIteration'),
16294 nodeLength = node.getData('nodeLength'),
16295 span = node.getData('span') / 2,
16296 theta = node.pos.theta,
16297 begin = ((theta - span)/2)+Math.PI,
16298 end = ((theta + span)/2)+Math.PI,
16301 var showLabels = config.showLabels,
16302 resizeLabels = config.resizeLabels,
16303 label = config.Label,
16304 radiusOffset = sb.config.levelDistance;
16307 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16308 acum += dimArray[i];
16310 var scale = resizeLabels? node.getData('normalizedDim') : 1,
16311 fontSize = (label.size * scale) >> 0;
16312 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16313 domElement.style.fontSize = fontSize + 'px';
16314 polar.rho = acum * .65;
16315 polar.theta = begin;
16316 var pos = polar.getc(true);
16317 var radius = that.canvas.getSize();
16319 x: Math.round(pos.x + radius.width / 2),
16320 y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16325 domElement.style.left = (labelPos.x - 200) + 'px';
16326 domElement.style.top = labelPos.y + 'px';
16328 //reposition first label
16329 if(nodeIteration == 1) {
16330 domElement.style.top = labelPos.y - label.size + 'px';
16334 //position final label
16335 if(nodeIteration == nodeLength && nodeLength != 0) {
16337 var final = polar.getc(true);
16339 x: Math.round(final.x + radius.width / 2),
16340 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16342 finalLabel.style.left = (finalPos.x - 200) + "px";
16343 finalLabel.style.top = finalPos.y - label.size + "px";
16351 this.canvas = this.sb.canvas;
16352 var size = sb.canvas.getSize(),
16354 sb.config.levelDistance = min(size.width, size.height)/2
16355 - config.offset - config.sliceOffset;
16360 renderBackground: function() {
16361 var canvas = this.sb.canvas,
16362 config = this.config,
16363 style = config.gaugeStyle,
16364 ctx = canvas.getCtx(),
16365 size = canvas.getSize(),
16366 radius = this.sb.config.levelDistance,
16367 startAngle = (Math.PI/180)*1,
16368 endAngle = (Math.PI/180)*179;
16371 //background border
16372 ctx.fillStyle = style.borderColor;
16374 ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true);
16378 var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16379 radialGradient.addColorStop(0, '#ffffff');
16380 radialGradient.addColorStop(0.3, style.backgroundColor);
16381 radialGradient.addColorStop(0.6, style.backgroundColor);
16382 radialGradient.addColorStop(1, '#FFFFFF');
16383 ctx.fillStyle = radialGradient;
16386 startAngle = (Math.PI/180)*0;
16387 endAngle = (Math.PI/180)*180;
16389 ctx.arc(0,radius/2,radius,startAngle,endAngle, true);
16397 renderNeedle: function(gaugePosition,target) {
16398 var canvas = this.sb.canvas,
16399 config = this.config,
16400 style = config.gaugeStyle,
16401 ctx = canvas.getCtx(),
16402 size = canvas.getSize(),
16403 radius = this.sb.config.levelDistance;
16404 gaugeCenter = (radius/2);
16406 endAngle = (Math.PI/180)*180;
16410 ctx.fillStyle = style.needleColor;
16411 var segments = 180/target;
16412 needleAngle = gaugePosition * segments;
16413 ctx.translate(0, gaugeCenter);
16415 ctx.rotate(needleAngle * Math.PI / 180);
16419 ctx.lineTo(-radius*.9,-1);
16420 ctx.lineTo(-radius*.9,1);
16430 ctx.strokeStyle = '#aa0000';
16432 ctx.rotate(needleAngle * Math.PI / 180);
16436 ctx.lineTo(-radius*.8,-1);
16437 ctx.lineTo(-radius*.8,1);
16445 ctx.fillStyle = "#000000";
16446 ctx.lineWidth = style.borderSize;
16447 ctx.strokeStyle = style.borderColor;
16448 var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16449 radialGradient.addColorStop(0, '#666666');
16450 radialGradient.addColorStop(0.8, '#444444');
16451 radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
16452 ctx.fillStyle = radialGradient;
16453 ctx.translate(0,5);
16456 ctx.arc(0,0,radius*.2,startAngle,endAngle, true);
16463 renderTicks: function(values) {
16464 var canvas = this.sb.canvas,
16465 config = this.config,
16466 style = config.gaugeStyle,
16467 ctx = canvas.getCtx(),
16468 size = canvas.getSize(),
16469 radius = this.sb.config.levelDistance,
16470 gaugeCenter = (radius/2);
16473 ctx.strokeStyle = style.borderColor;
16475 ctx.lineCap = "round";
16476 for(var i=0, total = 0, l=values.length; i<l; i++) {
16477 var val = values[i];
16478 if(val.label != 'GaugePosition') {
16479 total += (parseInt(val.values) || 0);
16483 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16484 var val = values[i];
16485 if(val.label != 'GaugePosition') {
16486 acum += (parseInt(val.values) || 0);
16488 var segments = 180/total;
16489 angle = acum * segments;
16493 ctx.translate(0, gaugeCenter);
16495 ctx.rotate(angle * (Math.PI/180));
16496 ctx.moveTo(-radius,0);
16497 ctx.lineTo(-radius*.75,0);
16505 renderPositionLabel: function(position) {
16506 var canvas = this.sb.canvas,
16507 config = this.config,
16508 label = config.Label,
16509 style = config.gaugeStyle,
16510 ctx = canvas.getCtx(),
16511 size = canvas.getSize(),
16512 radius = this.sb.config.levelDistance,
16513 gaugeCenter = (radius/2);
16514 ctx.textBaseline = 'middle';
16515 ctx.textAlign = 'center';
16516 ctx.font = style.positionFontSize + 'px ' + label.family;
16517 ctx.fillStyle = "#ffffff";
16519 height = style.positionFontSize + 10,
16521 idLabel = canvas.id + "-label";
16522 container = document.getElementById(idLabel);
16523 if(label.type == 'Native') {
16524 var m = ctx.measureText(position),
16525 width = m.width + 40;
16529 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16530 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16531 if(label.type == 'Native') {
16532 ctx.fillStyle = label.color;
16533 ctx.fillText(position, 0, (height/2) + style.positionOffset);
16535 var labelDiv = document.createElement('div');
16536 labelDivStyle = labelDiv.style;
16537 labelDivStyle.color = label.color;
16538 labelDivStyle.fontSize = style.positionFontSize + "px";
16539 labelDivStyle.position = "absolute";
16540 labelDivStyle.width = width + "px";
16541 labelDivStyle.left = (size.width/2) - (width/2) + "px";
16542 labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16543 labelDiv.innerHTML = position;
16544 container.appendChild(labelDiv);
16549 renderSubtitle: function() {
16550 var canvas = this.canvas,
16551 size = canvas.getSize(),
16552 config = this.config,
16553 margin = config.Margin,
16554 radius = this.sb.config.levelDistance,
16555 title = config.Title,
16556 label = config.Label,
16557 subtitle = config.Subtitle;
16558 ctx = canvas.getCtx();
16559 ctx.fillStyle = title.color;
16560 ctx.textAlign = 'left';
16561 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16563 if(label.type == 'Native') {
16564 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2));
16568 renderChartBackground: function() {
16569 var canvas = this.canvas,
16570 config = this.config,
16571 backgroundColor = config.backgroundColor,
16572 size = canvas.getSize(),
16573 ctx = canvas.getCtx();
16574 //ctx.globalCompositeOperation = "destination-over";
16575 ctx.fillStyle = backgroundColor;
16576 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16578 clear: function() {
16579 var canvas = this.canvas;
16580 var ctx = canvas.getCtx(),
16581 size = canvas.getSize();
16582 ctx.fillStyle = "rgba(255,255,255,0)";
16583 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16584 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16586 resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
16587 var canvas = this.canvas,
16588 size = canvas.getSize(),
16589 config = this.config,
16590 orgHeight = size.height,
16591 margin = config.Margin,
16593 horz = config.orientation == 'horizontal';
16596 var newWindowWidth = document.body.offsetWidth;
16597 var diff = newWindowWidth - orgWindowWidth;
16598 var newWidth = orgContainerDivWidth + (diff/cols);
16599 canvas.resize(newWidth,orgHeight);
16600 if(typeof FlashCanvas == "undefined") {
16603 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16605 this.loadJSON(json);
16608 loadJSON: function(json) {
16610 var prefix = $.time(),
16613 name = $.splat(json.label),
16614 nameLength = name.length,
16615 color = $.splat(json.color || this.colors),
16616 colorLength = color.length,
16617 config = this.config,
16618 renderBackground = config.renderBackground,
16619 gradient = !!config.type.split(":")[1],
16620 animate = config.animate,
16621 mono = nameLength == 1;
16622 var props = $.splat(json.properties)[0];
16624 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16626 var val = values[i];
16627 if(val.label != 'GaugePosition') {
16628 var valArray = $.splat(val.values);
16629 var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16630 var valuelabelsArray = $.splat(val.valuelabels);
16633 'id': prefix + val.label,
16637 'valuelabel': valuelabelsArray,
16638 '$linkArray': linkArray,
16639 '$valuelabelsArray': valuelabelsArray,
16640 '$valueArray': valArray,
16641 '$nodeIteration': i,
16642 '$nodeLength': l-1,
16643 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16644 '$colorMono': $.splat(color[i % colorLength]),
16645 '$stringArray': name,
16646 '$gradient': gradient,
16648 '$gaugeTarget': props['gaugeTarget'],
16649 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16654 var gaugePosition = val.gvalue;
16655 var gaugePositionLabel = val.gvaluelabel;
16659 'id': prefix + '$root',
16672 if(renderBackground) {
16673 this.renderChartBackground();
16676 this.renderBackground();
16677 this.renderSubtitle();
16679 this.normalizeDims();
16684 modes: ['node-property:dimArray'],
16690 this.renderPositionLabel(gaugePositionLabel);
16691 if (props['gaugeTarget'] != 0) {
16692 this.renderTicks(json.values);
16693 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16700 updateJSON: function(json, onComplete) {
16701 if(this.busy) return;
16705 var graph = sb.graph;
16706 var values = json.values;
16707 var animate = this.config.animate;
16709 $.each(values, function(v) {
16710 var n = graph.getByName(v.label),
16711 vals = $.splat(v.values);
16713 n.setData('valueArray', vals);
16714 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16716 n.setData('stringArray', $.splat(json.label));
16720 this.normalizeDims();
16724 modes: ['node-property:dimArray:span', 'linear'],
16726 onComplete: function() {
16728 onComplete && onComplete.onComplete();
16736 //adds the little brown bar when hovering the node
16737 select: function(id, name) {
16738 if(!this.config.hoveredColor) return;
16739 var s = this.selected;
16740 if(s.id != id || s.name != name) {
16743 s.color = this.config.hoveredColor;
16744 this.sb.graph.eachNode(function(n) {
16746 n.setData('border', s);
16748 n.setData('border', false);
16758 Returns an object containing as keys the legend names and as values hex strings with color values.
16763 var legend = pieChart.getLegend();
16766 getLegend: function() {
16767 var legend = new Array();
16768 var name = new Array();
16769 var color = new Array();
16771 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16774 var colors = n.getData('colorArray'),
16775 len = colors.length;
16776 $.each(n.getData('stringArray'), function(s, i) {
16777 color[i] = colors[i % len];
16780 legend['name'] = name;
16781 legend['color'] = color;
16786 Method: getMaxValue
16788 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16793 var ans = pieChart.getMaxValue();
16796 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16801 //will return 100 for all PieChart instances,
16802 //displaying all of them with the same scale
16803 $jit.PieChart.implement({
16804 'getMaxValue': function() {
16811 getMaxValue: function() {
16813 this.sb.graph.eachNode(function(n) {
16814 var valArray = n.getData('valueArray'),
16816 $.each(valArray, function(v) {
16819 maxValue = maxValue>acum? maxValue:acum;
16824 normalizeDims: function() {
16825 //number of elements
16826 var root = this.sb.graph.getNode(this.sb.root), l=0;
16827 root.eachAdjacency(function() {
16830 var maxValue = this.getMaxValue() || 1,
16831 config = this.config,
16832 animate = config.animate,
16833 rho = this.sb.config.levelDistance;
16834 this.sb.graph.eachNode(function(n) {
16835 var acum = 0, animateValue = [];
16836 $.each(n.getData('valueArray'), function(v) {
16838 animateValue.push(1);
16840 var stat = (animateValue.length == 1) && !config.updateHeights;
16842 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16843 return stat? rho: (n * rho / maxValue);
16845 var dimArray = n.getData('dimArray');
16847 n.setData('dimArray', animateValue);
16850 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16851 return stat? rho : (n * rho / maxValue);
16854 n.setData('normalizedDim', acum / maxValue);
16861 * Class: Layouts.TM
16863 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16872 Layouts.TM.SliceAndDice = new Class({
16873 compute: function(prop) {
16874 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16875 this.controller.onBeforeCompute(root);
16876 var size = this.canvas.getSize(),
16877 config = this.config,
16878 width = size.width,
16879 height = size.height;
16880 this.graph.computeLevels(this.root, 0, "ignore");
16881 //set root position and dimensions
16882 root.getPos(prop).setc(-width/2, -height/2);
16883 root.setData('width', width, prop);
16884 root.setData('height', height + config.titleHeight, prop);
16885 this.computePositions(root, root, this.layout.orientation, prop);
16886 this.controller.onAfterCompute(root);
16889 computePositions: function(par, ch, orn, prop) {
16890 //compute children areas
16892 par.eachSubnode(function(n) {
16893 totalArea += n.getData('area', prop);
16896 var config = this.config,
16897 offst = config.offset,
16898 width = par.getData('width', prop),
16899 height = par.getData('height', prop) - config.titleHeight,
16900 fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16902 var otherSize, size, dim, pos, pos2, posth, pos2th;
16903 var horizontal = (orn == "h");
16906 otherSize = height;
16907 size = width * fact;
16911 posth = config.titleHeight;
16915 otherSize = height * fact;
16921 pos2th = config.titleHeight;
16923 var cpos = ch.getPos(prop);
16924 ch.setData('width', size, prop);
16925 ch.setData('height', otherSize, prop);
16926 var offsetSize = 0, tm = this;
16927 ch.eachSubnode(function(n) {
16928 var p = n.getPos(prop);
16929 p[pos] = offsetSize + cpos[pos] + posth;
16930 p[pos2] = cpos[pos2] + pos2th;
16931 tm.computePositions(ch, n, orn, prop);
16932 offsetSize += n.getData(dim, prop);
16938 Layouts.TM.Area = {
16942 Called by loadJSON to calculate recursively all node positions and lay out the tree.
16946 json - A JSON tree. See also <Loader.loadJSON>.
16947 coord - A coordinates object specifying width, height, left and top style properties.
16949 compute: function(prop) {
16950 prop = prop || "current";
16951 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16952 this.controller.onBeforeCompute(root);
16953 var config = this.config,
16954 size = this.canvas.getSize(),
16955 width = size.width,
16956 height = size.height,
16957 offst = config.offset,
16958 offwdth = width - offst,
16959 offhght = height - offst;
16960 this.graph.computeLevels(this.root, 0, "ignore");
16961 //set root position and dimensions
16962 root.getPos(prop).setc(-width/2, -height/2);
16963 root.setData('width', width, prop);
16964 root.setData('height', height, prop);
16965 //create a coordinates object
16967 'top': -height/2 + config.titleHeight,
16970 'height': offhght - config.titleHeight
16972 this.computePositions(root, coord, prop);
16973 this.controller.onAfterCompute(root);
16979 Computes dimensions and positions of a group of nodes
16980 according to a custom layout row condition.
16984 tail - An array of nodes.
16985 initElem - An array of nodes (containing the initial node to be laid).
16986 w - A fixed dimension where nodes will be layed out.
16987 coord - A coordinates object specifying width, height, left and top style properties.
16988 comp - A custom comparison function
16990 computeDim: function(tail, initElem, w, coord, comp, prop) {
16991 if(tail.length + initElem.length == 1) {
16992 var l = (tail.length == 1)? tail : initElem;
16993 this.layoutLast(l, w, coord, prop);
16996 if(tail.length >= 2 && initElem.length == 0) {
16997 initElem = [tail.shift()];
16999 if(tail.length == 0) {
17000 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17004 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17005 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17007 var newCoords = this.layoutRow(initElem, w, coord, prop);
17008 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17014 Method: worstAspectRatio
17016 Calculates the worst aspect ratio of a group of rectangles.
17020 <http://en.wikipedia.org/wiki/Aspect_ratio>
17024 ch - An array of nodes.
17025 w - The fixed dimension where rectangles are being laid out.
17029 The worst aspect ratio.
17033 worstAspectRatio: function(ch, w) {
17034 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17035 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17036 for(var i=0, l=ch.length; i<l; i++) {
17037 var area = ch[i]._area;
17039 minArea = minArea < area? minArea : area;
17040 maxArea = maxArea > area? maxArea : area;
17042 var sqw = w * w, sqAreaSum = areaSum * areaSum;
17043 return Math.max(sqw * maxArea / sqAreaSum,
17044 sqAreaSum / (sqw * minArea));
17048 Method: avgAspectRatio
17050 Calculates the average aspect ratio of a group of rectangles.
17054 <http://en.wikipedia.org/wiki/Aspect_ratio>
17058 ch - An array of nodes.
17059 w - The fixed dimension where rectangles are being laid out.
17063 The average aspect ratio.
17067 avgAspectRatio: function(ch, w) {
17068 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17070 for(var i=0, l=ch.length; i<l; i++) {
17071 var area = ch[i]._area;
17073 arSum += w > h? w / h : h / w;
17081 Performs the layout of the last computed sibling.
17085 ch - An array of nodes.
17086 w - A fixed dimension where nodes will be layed out.
17087 coord - A coordinates object specifying width, height, left and top style properties.
17089 layoutLast: function(ch, w, coord, prop) {
17091 child.getPos(prop).setc(coord.left, coord.top);
17092 child.setData('width', coord.width, prop);
17093 child.setData('height', coord.height, prop);
17098 Layouts.TM.Squarified = new Class({
17099 Implements: Layouts.TM.Area,
17101 computePositions: function(node, coord, prop) {
17102 var config = this.config;
17104 if (coord.width >= coord.height)
17105 this.layout.orientation = 'h';
17107 this.layout.orientation = 'v';
17109 var ch = node.getSubnodes([1, 1], "ignore");
17110 if(ch.length > 0) {
17111 this.processChildrenLayout(node, ch, coord, prop);
17112 for(var i=0, l=ch.length; i<l; i++) {
17114 var offst = config.offset,
17115 height = chi.getData('height', prop) - offst - config.titleHeight,
17116 width = chi.getData('width', prop) - offst;
17117 var chipos = chi.getPos(prop);
17121 'top': chipos.y + config.titleHeight,
17124 this.computePositions(chi, coord, prop);
17130 Method: processChildrenLayout
17132 Computes children real areas and other useful parameters for performing the Squarified algorithm.
17136 par - The parent node of the json subtree.
17137 ch - An Array of nodes
17138 coord - A coordinates object specifying width, height, left and top style properties.
17140 processChildrenLayout: function(par, ch, coord, prop) {
17141 //compute children real areas
17142 var parentArea = coord.width * coord.height;
17143 var i, l=ch.length, totalChArea=0, chArea = [];
17144 for(i=0; i<l; i++) {
17145 chArea[i] = parseFloat(ch[i].getData('area', prop));
17146 totalChArea += chArea[i];
17148 for(i=0; i<l; i++) {
17149 ch[i]._area = parentArea * chArea[i] / totalChArea;
17151 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17152 ch.sort(function(a, b) {
17153 var diff = b._area - a._area;
17154 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
17156 var initElem = [ch[0]];
17157 var tail = ch.slice(1);
17158 this.squarify(tail, initElem, minimumSideValue, coord, prop);
17164 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17168 tail - An array of nodes.
17169 initElem - An array of nodes, containing the initial node to be laid out.
17170 w - A fixed dimension where nodes will be laid out.
17171 coord - A coordinates object specifying width, height, left and top style properties.
17173 squarify: function(tail, initElem, w, coord, prop) {
17174 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17180 Performs the layout of an array of nodes.
17184 ch - An array of nodes.
17185 w - A fixed dimension where nodes will be laid out.
17186 coord - A coordinates object specifying width, height, left and top style properties.
17188 layoutRow: function(ch, w, coord, prop) {
17189 if(this.layout.horizontal()) {
17190 return this.layoutV(ch, w, coord, prop);
17192 return this.layoutH(ch, w, coord, prop);
17196 layoutV: function(ch, w, coord, prop) {
17197 var totalArea = 0, rnd = function(x) { return x; };
17198 $.each(ch, function(elem) { totalArea += elem._area; });
17199 var width = rnd(totalArea / w), top = 0;
17200 for(var i=0, l=ch.length; i<l; i++) {
17201 var h = rnd(ch[i]._area / width);
17203 chi.getPos(prop).setc(coord.left, coord.top + top);
17204 chi.setData('width', width, prop);
17205 chi.setData('height', h, prop);
17209 'height': coord.height,
17210 'width': coord.width - width,
17212 'left': coord.left + width
17214 //take minimum side value.
17215 ans.dim = Math.min(ans.width, ans.height);
17216 if(ans.dim != ans.height) this.layout.change();
17220 layoutH: function(ch, w, coord, prop) {
17222 $.each(ch, function(elem) { totalArea += elem._area; });
17223 var height = totalArea / w,
17227 for(var i=0, l=ch.length; i<l; i++) {
17229 var w = chi._area / height;
17230 chi.getPos(prop).setc(coord.left + left, top);
17231 chi.setData('width', w, prop);
17232 chi.setData('height', height, prop);
17236 'height': coord.height - height,
17237 'width': coord.width,
17238 'top': coord.top + height,
17241 ans.dim = Math.min(ans.width, ans.height);
17242 if(ans.dim != ans.width) this.layout.change();
17247 Layouts.TM.Strip = new Class({
17248 Implements: Layouts.TM.Area,
17253 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17257 json - A JSON subtree. See also <Loader.loadJSON>.
17258 coord - A coordinates object specifying width, height, left and top style properties.
17260 computePositions: function(node, coord, prop) {
17261 var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17262 if(ch.length > 0) {
17263 this.processChildrenLayout(node, ch, coord, prop);
17264 for(var i=0, l=ch.length; i<l; i++) {
17266 var offst = config.offset,
17267 height = chi.getData('height', prop) - offst - config.titleHeight,
17268 width = chi.getData('width', prop) - offst;
17269 var chipos = chi.getPos(prop);
17273 'top': chipos.y + config.titleHeight,
17276 this.computePositions(chi, coord, prop);
17282 Method: processChildrenLayout
17284 Computes children real areas and other useful parameters for performing the Strip algorithm.
17288 par - The parent node of the json subtree.
17289 ch - An Array of nodes
17290 coord - A coordinates object specifying width, height, left and top style properties.
17292 processChildrenLayout: function(par, ch, coord, prop) {
17293 //compute children real areas
17294 var parentArea = coord.width * coord.height;
17295 var i, l=ch.length, totalChArea=0, chArea = [];
17296 for(i=0; i<l; i++) {
17297 chArea[i] = +ch[i].getData('area', prop);
17298 totalChArea += chArea[i];
17300 for(i=0; i<l; i++) {
17301 ch[i]._area = parentArea * chArea[i] / totalChArea;
17303 var side = this.layout.horizontal()? coord.width : coord.height;
17304 var initElem = [ch[0]];
17305 var tail = ch.slice(1);
17306 this.stripify(tail, initElem, side, coord, prop);
17312 Performs an heuristic method to calculate div elements sizes in order to have
17313 a good compromise between aspect ratio and order.
17317 tail - An array of nodes.
17318 initElem - An array of nodes.
17319 w - A fixed dimension where nodes will be layed out.
17320 coord - A coordinates object specifying width, height, left and top style properties.
17322 stripify: function(tail, initElem, w, coord, prop) {
17323 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17329 Performs the layout of an array of nodes.
17333 ch - An array of nodes.
17334 w - A fixed dimension where nodes will be laid out.
17335 coord - A coordinates object specifying width, height, left and top style properties.
17337 layoutRow: function(ch, w, coord, prop) {
17338 if(this.layout.horizontal()) {
17339 return this.layoutH(ch, w, coord, prop);
17341 return this.layoutV(ch, w, coord, prop);
17345 layoutV: function(ch, w, coord, prop) {
17347 $.each(ch, function(elem) { totalArea += elem._area; });
17348 var width = totalArea / w, top = 0;
17349 for(var i=0, l=ch.length; i<l; i++) {
17351 var h = chi._area / width;
17352 chi.getPos(prop).setc(coord.left,
17353 coord.top + (w - h - top));
17354 chi.setData('width', width, prop);
17355 chi.setData('height', h, prop);
17360 'height': coord.height,
17361 'width': coord.width - width,
17363 'left': coord.left + width,
17368 layoutH: function(ch, w, coord, prop) {
17370 $.each(ch, function(elem) { totalArea += elem._area; });
17371 var height = totalArea / w,
17372 top = coord.height - height,
17375 for(var i=0, l=ch.length; i<l; i++) {
17377 var s = chi._area / height;
17378 chi.getPos(prop).setc(coord.left + left, coord.top + top);
17379 chi.setData('width', s, prop);
17380 chi.setData('height', height, prop);
17384 'height': coord.height - height,
17385 'width': coord.width,
17387 'left': coord.left,
17394 * Class: Layouts.Icicle
17396 * Implements the icicle tree layout.
17404 Layouts.Icicle = new Class({
17408 * Called by loadJSON to calculate all node positions.
17412 * posType - The nodes' position to compute. Either "start", "end" or
17413 * "current". Defaults to "current".
17415 compute: function(posType) {
17416 posType = posType || "current";
17417 var root = this.graph.getNode(this.root),
17418 config = this.config,
17419 size = this.canvas.getSize(),
17420 width = size.width,
17421 height = size.height,
17422 offset = config.offset,
17423 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17425 this.controller.onBeforeCompute(root);
17427 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17431 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17433 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17434 var maxDepth = Math.min(treeDepth, levelsToShow-1);
17435 var initialDepth = startNode._depth;
17436 if(this.layout.horizontal()) {
17437 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17439 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17443 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17444 root.getPos(posType).setc(x, y);
17445 root.setData('width', width, posType);
17446 root.setData('height', height, posType);
17448 var nodeLength, prevNodeLength = 0, totalDim = 0;
17449 var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17451 if(!children.length)
17454 $.each(children, function(e) { totalDim += e.getData('dim'); });
17456 for(var i=0, l=children.length; i < l; i++) {
17457 if(this.layout.horizontal()) {
17458 nodeLength = height * children[i].getData('dim') / totalDim;
17459 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17462 nodeLength = width * children[i].getData('dim') / totalDim;
17463 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17480 Icicle space filling visualization.
17484 All <Loader> methods
17486 Constructor Options:
17488 Inherits options from
17491 - <Options.Controller>
17497 - <Options.NodeStyles>
17498 - <Options.Navigation>
17500 Additionally, there are other parameters and some default values changed
17502 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17503 offset - (number) Default's *2*. Boxes offset.
17504 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17505 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17506 animate - (boolean) Default's *false*. Whether to animate transitions.
17507 Node.type - Described in <Options.Node>. Default's *rectangle*.
17508 Label.type - Described in <Options.Label>. Default's *Native*.
17509 duration - Described in <Options.Fx>. Default's *700*.
17510 fps - Described in <Options.Fx>. Default's *45*.
17512 Instance Properties:
17514 canvas - Access a <Canvas> instance.
17515 graph - Access a <Graph> instance.
17516 op - Access a <Icicle.Op> instance.
17517 fx - Access a <Icicle.Plot> instance.
17518 labels - Access a <Icicle.Label> interface implementation.
17522 $jit.Icicle = new Class({
17523 Implements: [ Loader, Extras, Layouts.Icicle ],
17527 vertical: function(){
17528 return this.orientation == "v";
17530 horizontal: function(){
17531 return this.orientation == "h";
17533 change: function(){
17534 this.orientation = this.vertical()? "h" : "v";
17538 initialize: function(controller) {
17543 levelsToShow: Number.MAX_VALUE,
17544 constrained: false,
17559 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17560 "Events", "Navigation", "Controller", "Label");
17561 this.controller = this.config = $.merge(opts, config, controller);
17562 this.layout.orientation = this.config.orientation;
17564 var canvasConfig = this.config;
17565 if (canvasConfig.useCanvas) {
17566 this.canvas = canvasConfig.useCanvas;
17567 this.config.labelContainer = this.canvas.id + '-label';
17569 this.canvas = new Canvas(this, canvasConfig);
17570 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17573 this.graphOptions = {
17582 this.graph = new Graph(
17583 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17585 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17586 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17587 this.op = new $jit.Icicle.Op(this);
17588 this.group = new $jit.Icicle.Group(this);
17589 this.clickedNode = null;
17591 this.initializeExtras();
17597 Computes positions and plots the tree.
17599 refresh: function(){
17600 var labelType = this.config.Label.type;
17601 if(labelType != 'Native') {
17603 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17612 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
17616 this.fx.plot(this.config);
17622 Sets the node as root.
17626 node - (object) A <Graph.Node>.
17629 enter: function (node) {
17635 config = this.config;
17638 onComplete: function() {
17639 //compute positions of newly inserted nodes
17643 if(config.animate) {
17644 that.graph.nodeList.setDataset(['current', 'end'], {
17645 'alpha': [1, 0] //fade nodes
17648 Graph.Util.eachSubgraph(node, function(n) {
17649 n.setData('alpha', 1, 'end');
17654 modes:['node-property:alpha'],
17655 onComplete: function() {
17656 that.clickedNode = node;
17657 that.compute('end');
17660 modes:['linear', 'node-property:width:height'],
17662 onComplete: function() {
17664 that.clickedNode = node;
17670 that.clickedNode = node;
17677 if(config.request) {
17678 this.requestNodes(clickedNode, callback);
17680 callback.onComplete();
17687 Sets the parent node of the current selected node as root.
17695 GUtil = Graph.Util,
17696 config = this.config,
17697 graph = this.graph,
17698 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17699 parent = parents[0],
17700 clickedNode = parent,
17701 previousClickedNode = this.clickedNode;
17704 this.events.hoveredNode = false;
17711 //final plot callback
17713 onComplete: function() {
17714 that.clickedNode = parent;
17715 if(config.request) {
17716 that.requestNodes(parent, {
17717 onComplete: function() {
17731 //animate node positions
17732 if(config.animate) {
17733 this.clickedNode = clickedNode;
17734 this.compute('end');
17735 //animate the visible subtree only
17736 this.clickedNode = previousClickedNode;
17738 modes:['linear', 'node-property:width:height'],
17740 onComplete: function() {
17741 //animate the parent subtree
17742 that.clickedNode = clickedNode;
17743 //change nodes alpha
17744 graph.nodeList.setDataset(['current', 'end'], {
17747 GUtil.eachSubgraph(previousClickedNode, function(node) {
17748 node.setData('alpha', 1);
17752 modes:['node-property:alpha'],
17753 onComplete: function() {
17754 callback.onComplete();
17760 callback.onComplete();
17763 requestNodes: function(node, onComplete){
17764 var handler = $.merge(this.controller, onComplete),
17765 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17767 if (handler.request) {
17768 var leaves = [], d = node._depth;
17769 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17770 if (n.drawn && !Graph.Util.anySubnode(n)) {
17772 n._level = n._depth - d;
17773 if (this.config.constrained)
17774 n._level = levelsToShow - n._level;
17778 this.group.requestNodes(leaves, handler);
17780 handler.onComplete();
17788 Custom extension of <Graph.Op>.
17792 All <Graph.Op> methods
17799 $jit.Icicle.Op = new Class({
17801 Implements: Graph.Op
17806 * Performs operations on group of nodes.
17808 $jit.Icicle.Group = new Class({
17810 initialize: function(viz){
17812 this.canvas = viz.canvas;
17813 this.config = viz.config;
17817 * Calls the request method on the controller to request a subtree for each node.
17819 requestNodes: function(nodes, controller){
17820 var counter = 0, len = nodes.length, nodeSelected = {};
17821 var complete = function(){
17822 controller.onComplete();
17824 var viz = this.viz;
17827 for(var i = 0; i < len; i++) {
17828 nodeSelected[nodes[i].id] = nodes[i];
17829 controller.request(nodes[i].id, nodes[i]._level, {
17830 onComplete: function(nodeId, data){
17831 if (data && data.children) {
17837 if (++counter == len) {
17838 Graph.Util.computeLevels(viz.graph, viz.root, 0);
17850 Custom extension of <Graph.Plot>.
17854 All <Graph.Plot> methods
17861 $jit.Icicle.Plot = new Class({
17862 Implements: Graph.Plot,
17864 plot: function(opt, animating){
17865 opt = opt || this.viz.controller;
17866 var viz = this.viz,
17868 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17869 initialDepth = root._depth;
17871 viz.canvas.clear();
17872 this.plotTree(root, $.merge(opt, {
17873 'withLabels': true,
17874 'hideLabels': false,
17875 'plotSubtree': function(root, node) {
17876 return !viz.config.constrained ||
17877 (node._depth - initialDepth < viz.config.levelsToShow);
17884 Class: Icicle.Label
17886 Custom extension of <Graph.Label>.
17887 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17891 All <Graph.Label> methods and subclasses.
17895 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17898 $jit.Icicle.Label = {};
17901 Icicle.Label.Native
17903 Custom extension of <Graph.Label.Native>.
17907 All <Graph.Label.Native> methods
17911 <Graph.Label.Native>
17914 $jit.Icicle.Label.Native = new Class({
17915 Implements: Graph.Label.Native,
17917 renderLabel: function(canvas, node, controller) {
17918 var ctx = canvas.getCtx(),
17919 width = node.getData('width'),
17920 height = node.getData('height'),
17921 size = node.getLabelData('size'),
17922 m = ctx.measureText(node.name);
17924 // Guess as much as possible if the label will fit in the node
17925 if(height < (size * 1.5) || width < m.width)
17928 var pos = node.pos.getc(true);
17929 ctx.fillText(node.name,
17931 pos.y + height / 2);
17938 Custom extension of <Graph.Label.SVG>.
17942 All <Graph.Label.SVG> methods
17948 $jit.Icicle.Label.SVG = new Class( {
17949 Implements: Graph.Label.SVG,
17951 initialize: function(viz){
17958 Overrides abstract method placeLabel in <Graph.Plot>.
17962 tag - A DOM label element.
17963 node - A <Graph.Node>.
17964 controller - A configuration/controller object passed to the visualization.
17966 placeLabel: function(tag, node, controller){
17967 var pos = node.pos.getc(true), canvas = this.viz.canvas;
17968 var radius = canvas.getSize();
17970 x: Math.round(pos.x + radius.width / 2),
17971 y: Math.round(pos.y + radius.height / 2)
17973 tag.setAttribute('x', labelPos.x);
17974 tag.setAttribute('y', labelPos.y);
17976 controller.onPlaceLabel(tag, node);
17983 Custom extension of <Graph.Label.HTML>.
17987 All <Graph.Label.HTML> methods.
17994 $jit.Icicle.Label.HTML = new Class( {
17995 Implements: Graph.Label.HTML,
17997 initialize: function(viz){
18004 Overrides abstract method placeLabel in <Graph.Plot>.
18008 tag - A DOM label element.
18009 node - A <Graph.Node>.
18010 controller - A configuration/controller object passed to the visualization.
18012 placeLabel: function(tag, node, controller){
18013 var pos = node.pos.getc(true), canvas = this.viz.canvas;
18014 var radius = canvas.getSize();
18016 x: Math.round(pos.x + radius.width / 2),
18017 y: Math.round(pos.y + radius.height / 2)
18020 var style = tag.style;
18021 style.left = labelPos.x + 'px';
18022 style.top = labelPos.y + 'px';
18023 style.display = '';
18025 controller.onPlaceLabel(tag, node);
18030 Class: Icicle.Plot.NodeTypes
18032 This class contains a list of <Graph.Node> built-in types.
18033 Node types implemented are 'none', 'rectangle'.
18035 You can add your custom node types, customizing your visualization to the extreme.
18040 Icicle.Plot.NodeTypes.implement({
18042 'render': function(node, canvas) {
18043 //print your custom node to canvas
18046 'contains': function(node, pos) {
18047 //return true if pos is inside the node or false otherwise
18054 $jit.Icicle.Plot.NodeTypes = new Class( {
18060 'render': function(node, canvas, animating) {
18061 var config = this.viz.config;
18062 var offset = config.offset;
18063 var width = node.getData('width');
18064 var height = node.getData('height');
18065 var border = node.getData('border');
18066 var pos = node.pos.getc(true);
18067 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18068 var ctx = canvas.getCtx();
18070 if(width - offset < 2 || height - offset < 2) return;
18072 if(config.cushion) {
18073 var color = node.getData('color');
18074 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
18075 posy + (height - offset)/2, 1,
18076 posx + (width-offset)/2, posy + (height-offset)/2,
18077 width < height? height : width);
18078 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
18079 function(r) { return r * 0.3 >> 0; }));
18080 lg.addColorStop(0, color);
18081 lg.addColorStop(1, colorGrad);
18082 ctx.fillStyle = lg;
18086 ctx.strokeStyle = border;
18090 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18091 border && ctx.strokeRect(pos.x, pos.y, width, height);
18094 'contains': function(node, pos) {
18095 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18096 var npos = node.pos.getc(true),
18097 width = node.getData('width'),
18098 height = node.getData('height');
18099 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18104 $jit.Icicle.Plot.EdgeTypes = new Class( {
18111 * File: Layouts.ForceDirected.js
18116 * Class: Layouts.ForceDirected
18118 * Implements a Force Directed Layout.
18126 * Marcus Cobden <http://marcuscobden.co.uk>
18129 Layouts.ForceDirected = new Class({
18131 getOptions: function(random) {
18132 var s = this.canvas.getSize();
18133 var w = s.width, h = s.height;
18136 this.graph.eachNode(function(n) {
18139 var k2 = w * h / count, k = Math.sqrt(k2);
18140 var l = this.config.levelDistance;
18146 nodef: function(x) { return k2 / (x || 1); },
18147 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18151 compute: function(property, incremental) {
18152 var prop = $.splat(property || ['current', 'start', 'end']);
18153 var opt = this.getOptions();
18154 NodeDim.compute(this.graph, prop, this.config);
18155 this.graph.computeLevels(this.root, 0, "ignore");
18156 this.graph.eachNode(function(n) {
18157 $.each(prop, function(p) {
18158 var pos = n.getPos(p);
18159 if(pos.equals(Complex.KER)) {
18160 pos.x = opt.width/5 * (Math.random() - 0.5);
18161 pos.y = opt.height/5 * (Math.random() - 0.5);
18163 //initialize disp vector
18165 $.each(prop, function(p) {
18166 n.disp[p] = $C(0, 0);
18170 this.computePositions(prop, opt, incremental);
18173 computePositions: function(property, opt, incremental) {
18174 var times = this.config.iterations, i = 0, that = this;
18177 for(var total=incremental.iter, j=0; j<total; j++) {
18178 opt.t = opt.tstart * (1 - i++/(times -1));
18179 that.computePositionStep(property, opt);
18181 incremental.onComplete();
18185 incremental.onStep(Math.round(i / (times -1) * 100));
18186 setTimeout(iter, 1);
18189 for(; i < times; i++) {
18190 opt.t = opt.tstart * (1 - i/(times -1));
18191 this.computePositionStep(property, opt);
18196 computePositionStep: function(property, opt) {
18197 var graph = this.graph;
18198 var min = Math.min, max = Math.max;
18199 var dpos = $C(0, 0);
18200 //calculate repulsive forces
18201 graph.eachNode(function(v) {
18203 $.each(property, function(p) {
18204 v.disp[p].x = 0; v.disp[p].y = 0;
18206 graph.eachNode(function(u) {
18208 $.each(property, function(p) {
18209 var vp = v.getPos(p), up = u.getPos(p);
18210 dpos.x = vp.x - up.x;
18211 dpos.y = vp.y - up.y;
18212 var norm = dpos.norm() || 1;
18213 v.disp[p].$add(dpos
18214 .$scale(opt.nodef(norm) / norm));
18219 //calculate attractive forces
18220 var T = !!graph.getNode(this.root).visited;
18221 graph.eachNode(function(node) {
18222 node.eachAdjacency(function(adj) {
18223 var nodeTo = adj.nodeTo;
18224 if(!!nodeTo.visited === T) {
18225 $.each(property, function(p) {
18226 var vp = node.getPos(p), up = nodeTo.getPos(p);
18227 dpos.x = vp.x - up.x;
18228 dpos.y = vp.y - up.y;
18229 var norm = dpos.norm() || 1;
18230 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18231 nodeTo.disp[p].$add(dpos.$scale(-1));
18237 //arrange positions to fit the canvas
18238 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18239 graph.eachNode(function(u) {
18240 $.each(property, function(p) {
18241 var disp = u.disp[p];
18242 var norm = disp.norm() || 1;
18243 var p = u.getPos(p);
18244 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
18245 disp.y * min(Math.abs(disp.y), t) / norm));
18246 p.x = min(w2, max(-w2, p.x));
18247 p.y = min(h2, max(-h2, p.y));
18254 * File: ForceDirected.js
18258 Class: ForceDirected
18260 A visualization that lays graphs using a Force-Directed layout algorithm.
18264 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18268 All <Loader> methods
18270 Constructor Options:
18272 Inherits options from
18275 - <Options.Controller>
18281 - <Options.NodeStyles>
18282 - <Options.Navigation>
18284 Additionally, there are two parameters
18286 levelDistance - (number) Default's *50*. The natural length desired for the edges.
18287 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*.
18289 Instance Properties:
18291 canvas - Access a <Canvas> instance.
18292 graph - Access a <Graph> instance.
18293 op - Access a <ForceDirected.Op> instance.
18294 fx - Access a <ForceDirected.Plot> instance.
18295 labels - Access a <ForceDirected.Label> interface implementation.
18299 $jit.ForceDirected = new Class( {
18301 Implements: [ Loader, Extras, Layouts.ForceDirected ],
18303 initialize: function(controller) {
18304 var $ForceDirected = $jit.ForceDirected;
18311 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18312 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18314 var canvasConfig = this.config;
18315 if(canvasConfig.useCanvas) {
18316 this.canvas = canvasConfig.useCanvas;
18317 this.config.labelContainer = this.canvas.id + '-label';
18319 if(canvasConfig.background) {
18320 canvasConfig.background = $.merge({
18322 }, canvasConfig.background);
18324 this.canvas = new Canvas(this, canvasConfig);
18325 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18328 this.graphOptions = {
18336 this.graph = new Graph(this.graphOptions, this.config.Node,
18338 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18339 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18340 this.op = new $ForceDirected.Op(this);
18343 // initialize extras
18344 this.initializeExtras();
18350 Computes positions and plots the tree.
18352 refresh: function() {
18357 reposition: function() {
18358 this.compute('end');
18362 Method: computeIncremental
18364 Performs the Force Directed algorithm incrementally.
18368 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
18369 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
18370 avoiding browser messages such as "This script is taking too long to complete".
18374 opt - (object) The object properties are described below
18376 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
18377 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18379 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
18380 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
18381 computations for final animation positions then you can just choose 'end'.
18383 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
18384 parameter a percentage value.
18386 onComplete - A callback function called when the algorithm completed.
18390 In this example I calculate the end positions and then animate the graph to those positions
18393 var fd = new $jit.ForceDirected(...);
18394 fd.computeIncremental({
18397 onStep: function(perc) {
18398 Log.write("loading " + perc + "%");
18400 onComplete: function() {
18407 In this example I calculate all positions and (re)plot the graph
18410 var fd = new ForceDirected(...);
18411 fd.computeIncremental({
18413 property: ['end', 'start', 'current'],
18414 onStep: function(perc) {
18415 Log.write("loading " + perc + "%");
18417 onComplete: function() {
18425 computeIncremental: function(opt) {
18430 onComplete: $.empty
18433 this.config.onBeforeCompute(this.graph.getNode(this.root));
18434 this.compute(opt.property, opt);
18440 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18449 Animates the graph from the current positions to the 'end' node positions.
18451 animate: function(opt) {
18452 this.fx.animate($.merge( {
18453 modes: [ 'linear' ]
18458 $jit.ForceDirected.$extend = true;
18460 (function(ForceDirected) {
18463 Class: ForceDirected.Op
18465 Custom extension of <Graph.Op>.
18469 All <Graph.Op> methods
18476 ForceDirected.Op = new Class( {
18478 Implements: Graph.Op
18483 Class: ForceDirected.Plot
18485 Custom extension of <Graph.Plot>.
18489 All <Graph.Plot> methods
18496 ForceDirected.Plot = new Class( {
18498 Implements: Graph.Plot
18503 Class: ForceDirected.Label
18505 Custom extension of <Graph.Label>.
18506 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18510 All <Graph.Label> methods and subclasses.
18514 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18517 ForceDirected.Label = {};
18520 ForceDirected.Label.Native
18522 Custom extension of <Graph.Label.Native>.
18526 All <Graph.Label.Native> methods
18530 <Graph.Label.Native>
18533 ForceDirected.Label.Native = new Class( {
18534 Implements: Graph.Label.Native
18538 ForceDirected.Label.SVG
18540 Custom extension of <Graph.Label.SVG>.
18544 All <Graph.Label.SVG> methods
18551 ForceDirected.Label.SVG = new Class( {
18552 Implements: Graph.Label.SVG,
18554 initialize: function(viz) {
18561 Overrides abstract method placeLabel in <Graph.Label>.
18565 tag - A DOM label element.
18566 node - A <Graph.Node>.
18567 controller - A configuration/controller object passed to the visualization.
18570 placeLabel: function(tag, node, controller) {
18571 var pos = node.pos.getc(true),
18572 canvas = this.viz.canvas,
18573 ox = canvas.translateOffsetX,
18574 oy = canvas.translateOffsetY,
18575 sx = canvas.scaleOffsetX,
18576 sy = canvas.scaleOffsetY,
18577 radius = canvas.getSize();
18579 x: Math.round(pos.x * sx + ox + radius.width / 2),
18580 y: Math.round(pos.y * sy + oy + radius.height / 2)
18582 tag.setAttribute('x', labelPos.x);
18583 tag.setAttribute('y', labelPos.y);
18585 controller.onPlaceLabel(tag, node);
18590 ForceDirected.Label.HTML
18592 Custom extension of <Graph.Label.HTML>.
18596 All <Graph.Label.HTML> methods.
18603 ForceDirected.Label.HTML = new Class( {
18604 Implements: Graph.Label.HTML,
18606 initialize: function(viz) {
18612 Overrides abstract method placeLabel in <Graph.Plot>.
18616 tag - A DOM label element.
18617 node - A <Graph.Node>.
18618 controller - A configuration/controller object passed to the visualization.
18621 placeLabel: function(tag, node, controller) {
18622 var pos = node.pos.getc(true),
18623 canvas = this.viz.canvas,
18624 ox = canvas.translateOffsetX,
18625 oy = canvas.translateOffsetY,
18626 sx = canvas.scaleOffsetX,
18627 sy = canvas.scaleOffsetY,
18628 radius = canvas.getSize();
18630 x: Math.round(pos.x * sx + ox + radius.width / 2),
18631 y: Math.round(pos.y * sy + oy + radius.height / 2)
18633 var style = tag.style;
18634 style.left = labelPos.x + 'px';
18635 style.top = labelPos.y + 'px';
18636 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18638 controller.onPlaceLabel(tag, node);
18643 Class: ForceDirected.Plot.NodeTypes
18645 This class contains a list of <Graph.Node> built-in types.
18646 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18648 You can add your custom node types, customizing your visualization to the extreme.
18653 ForceDirected.Plot.NodeTypes.implement({
18655 'render': function(node, canvas) {
18656 //print your custom node to canvas
18659 'contains': function(node, pos) {
18660 //return true if pos is inside the node or false otherwise
18667 ForceDirected.Plot.NodeTypes = new Class({
18670 'contains': $.lambda(false)
18673 'render': function(node, canvas){
18674 var pos = node.pos.getc(true),
18675 dim = node.getData('dim');
18676 this.nodeHelper.circle.render('fill', pos, dim, canvas);
18678 'contains': function(node, pos){
18679 var npos = node.pos.getc(true),
18680 dim = node.getData('dim');
18681 return this.nodeHelper.circle.contains(npos, pos, dim);
18685 'render': function(node, canvas){
18686 var pos = node.pos.getc(true),
18687 width = node.getData('width'),
18688 height = node.getData('height');
18689 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18691 // TODO(nico): be more precise...
18692 'contains': function(node, pos){
18693 var npos = node.pos.getc(true),
18694 width = node.getData('width'),
18695 height = node.getData('height');
18696 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18700 'render': function(node, canvas){
18701 var pos = node.pos.getc(true),
18702 dim = node.getData('dim');
18703 this.nodeHelper.square.render('fill', pos, dim, canvas);
18705 'contains': function(node, pos){
18706 var npos = node.pos.getc(true),
18707 dim = node.getData('dim');
18708 return this.nodeHelper.square.contains(npos, pos, dim);
18712 'render': function(node, canvas){
18713 var pos = node.pos.getc(true),
18714 width = node.getData('width'),
18715 height = node.getData('height');
18716 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18718 'contains': function(node, pos){
18719 var npos = node.pos.getc(true),
18720 width = node.getData('width'),
18721 height = node.getData('height');
18722 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18726 'render': function(node, canvas){
18727 var pos = node.pos.getc(true),
18728 dim = node.getData('dim');
18729 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18731 'contains': function(node, pos) {
18732 var npos = node.pos.getc(true),
18733 dim = node.getData('dim');
18734 return this.nodeHelper.triangle.contains(npos, pos, dim);
18738 'render': function(node, canvas){
18739 var pos = node.pos.getc(true),
18740 dim = node.getData('dim');
18741 this.nodeHelper.star.render('fill', pos, dim, canvas);
18743 'contains': function(node, pos) {
18744 var npos = node.pos.getc(true),
18745 dim = node.getData('dim');
18746 return this.nodeHelper.star.contains(npos, pos, dim);
18752 Class: ForceDirected.Plot.EdgeTypes
18754 This class contains a list of <Graph.Adjacence> built-in types.
18755 Edge types implemented are 'none', 'line' and 'arrow'.
18757 You can add your custom edge types, customizing your visualization to the extreme.
18762 ForceDirected.Plot.EdgeTypes.implement({
18764 'render': function(adj, canvas) {
18765 //print your custom edge to canvas
18768 'contains': function(adj, pos) {
18769 //return true if pos is inside the arc or false otherwise
18776 ForceDirected.Plot.EdgeTypes = new Class({
18779 'render': function(adj, canvas) {
18780 var from = adj.nodeFrom.pos.getc(true),
18781 to = adj.nodeTo.pos.getc(true);
18782 this.edgeHelper.line.render(from, to, canvas);
18784 'contains': function(adj, pos) {
18785 var from = adj.nodeFrom.pos.getc(true),
18786 to = adj.nodeTo.pos.getc(true);
18787 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18791 'render': function(adj, canvas) {
18792 var from = adj.nodeFrom.pos.getc(true),
18793 to = adj.nodeTo.pos.getc(true),
18794 dim = adj.getData('dim'),
18795 direction = adj.data.$direction,
18796 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18797 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18799 'contains': function(adj, pos) {
18800 var from = adj.nodeFrom.pos.getc(true),
18801 to = adj.nodeTo.pos.getc(true);
18802 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18807 })($jit.ForceDirected);
18819 $jit.TM.$extend = true;
18824 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18828 All <Loader> methods
18830 Constructor Options:
18832 Inherits options from
18835 - <Options.Controller>
18841 - <Options.NodeStyles>
18842 - <Options.Navigation>
18844 Additionally, there are other parameters and some default values changed
18846 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18847 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18848 offset - (number) Default's *2*. Boxes offset.
18849 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18850 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18851 animate - (boolean) Default's *false*. Whether to animate transitions.
18852 Node.type - Described in <Options.Node>. Default's *rectangle*.
18853 duration - Described in <Options.Fx>. Default's *700*.
18854 fps - Described in <Options.Fx>. Default's *45*.
18856 Instance Properties:
18858 canvas - Access a <Canvas> instance.
18859 graph - Access a <Graph> instance.
18860 op - Access a <TM.Op> instance.
18861 fx - Access a <TM.Plot> instance.
18862 labels - Access a <TM.Label> interface implementation.
18866 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18868 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18872 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.
18878 vertical: function(){
18879 return this.orientation == "v";
18881 horizontal: function(){
18882 return this.orientation == "h";
18884 change: function(){
18885 this.orientation = this.vertical()? "h" : "v";
18889 initialize: function(controller){
18895 constrained: false,
18900 //we all know why this is not zero,
18907 textAlign: 'center',
18908 textBaseline: 'top'
18917 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18918 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18919 this.layout.orientation = this.config.orientation;
18921 var canvasConfig = this.config;
18922 if (canvasConfig.useCanvas) {
18923 this.canvas = canvasConfig.useCanvas;
18924 this.config.labelContainer = this.canvas.id + '-label';
18926 if(canvasConfig.background) {
18927 canvasConfig.background = $.merge({
18929 }, canvasConfig.background);
18931 this.canvas = new Canvas(this, canvasConfig);
18932 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18935 this.graphOptions = {
18943 this.graph = new Graph(this.graphOptions, this.config.Node,
18945 this.labels = new TM.Label[canvasConfig.Label.type](this);
18946 this.fx = new TM.Plot(this);
18947 this.op = new TM.Op(this);
18948 this.group = new TM.Group(this);
18949 this.geom = new TM.Geom(this);
18950 this.clickedNode = null;
18952 // initialize extras
18953 this.initializeExtras();
18959 Computes positions and plots the tree.
18961 refresh: function(){
18962 if(this.busy) return;
18965 if(this.config.animate) {
18966 this.compute('end');
18967 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18968 && this.clickedNode.id || this.root));
18969 this.fx.animate($.merge(this.config, {
18970 modes: ['linear', 'node-property:width:height'],
18971 onComplete: function() {
18976 var labelType = this.config.Label.type;
18977 if(labelType != 'Native') {
18979 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18983 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18984 && this.clickedNode.id || this.root));
18992 Plots the TreeMap. This is a shortcut to *fx.plot*.
19002 Returns whether the node is a leaf.
19006 n - (object) A <Graph.Node>.
19010 return n.getSubnodes([
19012 ], "ignore").length == 0;
19018 Sets the node as root.
19022 n - (object) A <Graph.Node>.
19025 enter: function(n){
19026 if(this.busy) return;
19030 config = this.config,
19031 graph = this.graph,
19033 previousClickedNode = this.clickedNode;
19036 onComplete: function() {
19037 //ensure that nodes are shown for that level
19038 if(config.levelsToShow > 0) {
19039 that.geom.setRightLevelToShow(n);
19041 //compute positions of newly inserted nodes
19042 if(config.levelsToShow > 0 || config.request) that.compute();
19043 if(config.animate) {
19045 graph.nodeList.setData('alpha', 0, 'end');
19046 n.eachSubgraph(function(n) {
19047 n.setData('alpha', 1, 'end');
19051 modes:['node-property:alpha'],
19052 onComplete: function() {
19053 //compute end positions
19054 that.clickedNode = clickedNode;
19055 that.compute('end');
19056 //animate positions
19057 //TODO(nico) commenting this line didn't seem to throw errors...
19058 that.clickedNode = previousClickedNode;
19060 modes:['linear', 'node-property:width:height'],
19062 onComplete: function() {
19064 //TODO(nico) check comment above
19065 that.clickedNode = clickedNode;
19072 that.clickedNode = n;
19077 if(config.request) {
19078 this.requestNodes(clickedNode, callback);
19080 callback.onComplete();
19087 Sets the parent node of the current selected node as root.
19091 if(this.busy) return;
19093 this.events.hoveredNode = false;
19095 config = this.config,
19096 graph = this.graph,
19097 parents = graph.getNode(this.clickedNode
19098 && this.clickedNode.id || this.root).getParents(),
19099 parent = parents[0],
19100 clickedNode = parent,
19101 previousClickedNode = this.clickedNode;
19103 //if no parents return
19108 //final plot callback
19110 onComplete: function() {
19111 that.clickedNode = parent;
19112 if(config.request) {
19113 that.requestNodes(parent, {
19114 onComplete: function() {
19128 if (config.levelsToShow > 0)
19129 this.geom.setRightLevelToShow(parent);
19130 //animate node positions
19131 if(config.animate) {
19132 this.clickedNode = clickedNode;
19133 this.compute('end');
19134 //animate the visible subtree only
19135 this.clickedNode = previousClickedNode;
19137 modes:['linear', 'node-property:width:height'],
19139 onComplete: function() {
19140 //animate the parent subtree
19141 that.clickedNode = clickedNode;
19142 //change nodes alpha
19143 graph.eachNode(function(n) {
19144 n.setDataset(['current', 'end'], {
19148 previousClickedNode.eachSubgraph(function(node) {
19149 node.setData('alpha', 1);
19153 modes:['node-property:alpha'],
19154 onComplete: function() {
19155 callback.onComplete();
19161 callback.onComplete();
19165 requestNodes: function(node, onComplete){
19166 var handler = $.merge(this.controller, onComplete),
19167 lev = this.config.levelsToShow;
19168 if (handler.request) {
19169 var leaves = [], d = node._depth;
19170 node.eachLevel(0, lev, function(n){
19171 var nodeLevel = lev - (n._depth - d);
19172 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19174 n._level = nodeLevel;
19177 this.group.requestNodes(leaves, handler);
19179 handler.onComplete();
19187 Custom extension of <Graph.Op>.
19191 All <Graph.Op> methods
19198 TM.Op = new Class({
19199 Implements: Graph.Op,
19201 initialize: function(viz){
19206 //extend level methods of Graph.Geom
19207 TM.Geom = new Class({
19208 Implements: Graph.Geom,
19210 getRightLevelToShow: function() {
19211 return this.viz.config.levelsToShow;
19214 setRightLevelToShow: function(node) {
19215 var level = this.getRightLevelToShow(),
19216 fx = this.viz.labels;
19217 node.eachLevel(0, level+1, function(n) {
19218 var d = n._depth - node._depth;
19223 fx.hideLabel(n, false);
19231 delete node.ignore;
19237 Performs operations on group of nodes.
19240 TM.Group = new Class( {
19242 initialize: function(viz){
19244 this.canvas = viz.canvas;
19245 this.config = viz.config;
19250 Calls the request method on the controller to request a subtree for each node.
19252 requestNodes: function(nodes, controller){
19253 var counter = 0, len = nodes.length, nodeSelected = {};
19254 var complete = function(){
19255 controller.onComplete();
19257 var viz = this.viz;
19260 for ( var i = 0; i < len; i++) {
19261 nodeSelected[nodes[i].id] = nodes[i];
19262 controller.request(nodes[i].id, nodes[i]._level, {
19263 onComplete: function(nodeId, data){
19264 if (data && data.children) {
19270 if (++counter == len) {
19271 viz.graph.computeLevels(viz.root, 0);
19283 Custom extension of <Graph.Plot>.
19287 All <Graph.Plot> methods
19294 TM.Plot = new Class({
19296 Implements: Graph.Plot,
19298 initialize: function(viz){
19300 this.config = viz.config;
19301 this.node = this.config.Node;
19302 this.edge = this.config.Edge;
19303 this.animation = new Animation;
19304 this.nodeTypes = new TM.Plot.NodeTypes;
19305 this.edgeTypes = new TM.Plot.EdgeTypes;
19306 this.labels = viz.labels;
19309 plot: function(opt, animating){
19310 var viz = this.viz,
19312 viz.canvas.clear();
19313 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19314 'withLabels': true,
19315 'hideLabels': false,
19316 'plotSubtree': function(n, ch){
19317 return n.anySubnode("exist");
19326 Custom extension of <Graph.Label>.
19327 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19331 All <Graph.Label> methods and subclasses.
19335 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19343 Custom extension of <Graph.Label.Native>.
19347 All <Graph.Label.Native> methods
19351 <Graph.Label.Native>
19353 TM.Label.Native = new Class({
19354 Implements: Graph.Label.Native,
19356 initialize: function(viz) {
19357 this.config = viz.config;
19358 this.leaf = viz.leaf;
19361 renderLabel: function(canvas, node, controller){
19362 if(!this.leaf(node) && !this.config.titleHeight) return;
19363 var pos = node.pos.getc(true),
19364 ctx = canvas.getCtx(),
19365 width = node.getData('width'),
19366 height = node.getData('height'),
19367 x = pos.x + width/2,
19370 ctx.fillText(node.name, x, y, width);
19377 Custom extension of <Graph.Label.SVG>.
19381 All <Graph.Label.SVG> methods
19387 TM.Label.SVG = new Class( {
19388 Implements: Graph.Label.SVG,
19390 initialize: function(viz){
19392 this.leaf = viz.leaf;
19393 this.config = viz.config;
19399 Overrides abstract method placeLabel in <Graph.Plot>.
19403 tag - A DOM label element.
19404 node - A <Graph.Node>.
19405 controller - A configuration/controller object passed to the visualization.
19408 placeLabel: function(tag, node, controller){
19409 var pos = node.pos.getc(true),
19410 canvas = this.viz.canvas,
19411 ox = canvas.translateOffsetX,
19412 oy = canvas.translateOffsetY,
19413 sx = canvas.scaleOffsetX,
19414 sy = canvas.scaleOffsetY,
19415 radius = canvas.getSize();
19417 x: Math.round(pos.x * sx + ox + radius.width / 2),
19418 y: Math.round(pos.y * sy + oy + radius.height / 2)
19420 tag.setAttribute('x', labelPos.x);
19421 tag.setAttribute('y', labelPos.y);
19423 if(!this.leaf(node) && !this.config.titleHeight) {
19424 tag.style.display = 'none';
19426 controller.onPlaceLabel(tag, node);
19433 Custom extension of <Graph.Label.HTML>.
19437 All <Graph.Label.HTML> methods.
19444 TM.Label.HTML = new Class( {
19445 Implements: Graph.Label.HTML,
19447 initialize: function(viz){
19449 this.leaf = viz.leaf;
19450 this.config = viz.config;
19456 Overrides abstract method placeLabel in <Graph.Plot>.
19460 tag - A DOM label element.
19461 node - A <Graph.Node>.
19462 controller - A configuration/controller object passed to the visualization.
19465 placeLabel: function(tag, node, controller){
19466 var pos = node.pos.getc(true),
19467 canvas = this.viz.canvas,
19468 ox = canvas.translateOffsetX,
19469 oy = canvas.translateOffsetY,
19470 sx = canvas.scaleOffsetX,
19471 sy = canvas.scaleOffsetY,
19472 radius = canvas.getSize();
19474 x: Math.round(pos.x * sx + ox + radius.width / 2),
19475 y: Math.round(pos.y * sy + oy + radius.height / 2)
19478 var style = tag.style;
19479 style.left = labelPos.x + 'px';
19480 style.top = labelPos.y + 'px';
19481 style.width = node.getData('width') * sx + 'px';
19482 style.height = node.getData('height') * sy + 'px';
19483 style.zIndex = node._depth * 100;
19484 style.display = '';
19486 if(!this.leaf(node) && !this.config.titleHeight) {
19487 tag.style.display = 'none';
19489 controller.onPlaceLabel(tag, node);
19494 Class: TM.Plot.NodeTypes
19496 This class contains a list of <Graph.Node> built-in types.
19497 Node types implemented are 'none', 'rectangle'.
19499 You can add your custom node types, customizing your visualization to the extreme.
19504 TM.Plot.NodeTypes.implement({
19506 'render': function(node, canvas) {
19507 //print your custom node to canvas
19510 'contains': function(node, pos) {
19511 //return true if pos is inside the node or false otherwise
19518 TM.Plot.NodeTypes = new Class( {
19524 'render': function(node, canvas, animating){
19525 var leaf = this.viz.leaf(node),
19526 config = this.config,
19527 offst = config.offset,
19528 titleHeight = config.titleHeight,
19529 pos = node.pos.getc(true),
19530 width = node.getData('width'),
19531 height = node.getData('height'),
19532 border = node.getData('border'),
19533 ctx = canvas.getCtx(),
19534 posx = pos.x + offst / 2,
19535 posy = pos.y + offst / 2;
19536 if(width <= offst || height <= offst) return;
19538 if(config.cushion) {
19539 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
19540 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19541 var color = node.getData('color');
19542 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
19543 function(r) { return r * 0.2 >> 0; }));
19544 lg.addColorStop(0, color);
19545 lg.addColorStop(1, colorGrad);
19546 ctx.fillStyle = lg;
19548 ctx.fillRect(posx, posy, width - offst, height - offst);
19551 ctx.strokeStyle = border;
19552 ctx.strokeRect(posx, posy, width - offst, height - offst);
19555 } else if(titleHeight > 0){
19556 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19557 titleHeight - offst);
19560 ctx.strokeStyle = border;
19561 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19567 'contains': function(node, pos) {
19568 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19569 var npos = node.pos.getc(true),
19570 width = node.getData('width'),
19571 leaf = this.viz.leaf(node),
19572 height = leaf? node.getData('height') : this.config.titleHeight;
19573 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19578 TM.Plot.EdgeTypes = new Class( {
19583 Class: TM.SliceAndDice
19585 A slice and dice TreeMap visualization.
19589 All <TM.Base> methods and properties.
19591 TM.SliceAndDice = new Class( {
19593 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19598 Class: TM.Squarified
19600 A squarified TreeMap visualization.
19604 All <TM.Base> methods and properties.
19606 TM.Squarified = new Class( {
19608 Loader, Extras, TM.Base, Layouts.TM.Squarified
19615 A strip TreeMap visualization.
19619 All <TM.Base> methods and properties.
19621 TM.Strip = new Class( {
19623 Loader, Extras, TM.Base, Layouts.TM.Strip
19636 A radial graph visualization with advanced animations.
19640 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>
19644 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.
19648 All <Loader> methods
19650 Constructor Options:
19652 Inherits options from
19655 - <Options.Controller>
19661 - <Options.NodeStyles>
19662 - <Options.Navigation>
19664 Additionally, there are other parameters and some default values changed
19666 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19667 levelDistance - (number) Default's *100*. The distance between levels of the tree.
19669 Instance Properties:
19671 canvas - Access a <Canvas> instance.
19672 graph - Access a <Graph> instance.
19673 op - Access a <RGraph.Op> instance.
19674 fx - Access a <RGraph.Plot> instance.
19675 labels - Access a <RGraph.Label> interface implementation.
19678 $jit.RGraph = new Class( {
19681 Loader, Extras, Layouts.Radial
19684 initialize: function(controller){
19685 var $RGraph = $jit.RGraph;
19688 interpolation: 'linear',
19692 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19693 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19695 var canvasConfig = this.config;
19696 if(canvasConfig.useCanvas) {
19697 this.canvas = canvasConfig.useCanvas;
19698 this.config.labelContainer = this.canvas.id + '-label';
19700 if(canvasConfig.background) {
19701 canvasConfig.background = $.merge({
19703 }, canvasConfig.background);
19705 this.canvas = new Canvas(this, canvasConfig);
19706 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19709 this.graphOptions = {
19717 this.graph = new Graph(this.graphOptions, this.config.Node,
19719 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19720 this.fx = new $RGraph.Plot(this, $RGraph);
19721 this.op = new $RGraph.Op(this);
19725 this.parent = false;
19726 // initialize extras
19727 this.initializeExtras();
19732 createLevelDistanceFunc
19734 Returns the levelDistance function used for calculating a node distance
19735 to its origin. This function returns a function that is computed
19736 per level and not per node, such that all nodes with the same depth will have the
19737 same distance to the origin. The resulting function gets the
19738 parent node as parameter and returns a float.
19741 createLevelDistanceFunc: function(){
19742 var ld = this.config.levelDistance;
19743 return function(elem){
19744 return (elem._depth + 1) * ld;
19751 Computes positions and plots the tree.
19754 refresh: function(){
19759 reposition: function(){
19760 this.compute('end');
19766 Plots the RGraph. This is a shortcut to *fx.plot*.
19772 getNodeAndParentAngle
19774 Returns the _parent_ of the given node, also calculating its angle span.
19776 getNodeAndParentAngle: function(id){
19778 var n = this.graph.getNode(id);
19779 var ps = n.getParents();
19780 var p = (ps.length > 0)? ps[0] : false;
19782 var posParent = p.pos.getc(), posChild = n.pos.getc();
19783 var newPos = posParent.add(posChild.scale(-1));
19784 theta = Math.atan2(newPos.y, newPos.x);
19786 theta += 2 * Math.PI;
19796 Enumerates the children in order to maintain child ordering (second constraint of the paper).
19798 tagChildren: function(par, id){
19799 if (par.angleSpan) {
19801 par.eachAdjacency(function(elem){
19802 adjs.push(elem.nodeTo);
19804 var len = adjs.length;
19805 for ( var i = 0; i < len && id != adjs[i].id; i++)
19807 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19808 adjs[j].dist = k++;
19815 Animates the <RGraph> to center the node specified by *id*.
19819 id - A <Graph.Node> id.
19820 opt - (optional|object) An object containing some extra properties described below
19821 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19826 rgraph.onClick('someid');
19828 rgraph.onClick('someid', {
19834 onClick: function(id, opt){
19835 if (this.root != id && !this.busy) {
19839 this.controller.onBeforeCompute(this.graph.getNode(id));
19840 var obj = this.getNodeAndParentAngle(id);
19842 // second constraint
19843 this.tagChildren(obj.parent, id);
19844 this.parent = obj.parent;
19845 this.compute('end');
19847 // first constraint
19848 var thetaDiff = obj.theta - obj.parent.endPos.theta;
19849 this.graph.eachNode(function(elem){
19850 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19853 var mode = this.config.interpolation;
19855 onComplete: $.empty
19858 this.fx.animate($.merge( {
19864 onComplete: function(){
19873 $jit.RGraph.$extend = true;
19880 Custom extension of <Graph.Op>.
19884 All <Graph.Op> methods
19891 RGraph.Op = new Class( {
19893 Implements: Graph.Op
19900 Custom extension of <Graph.Plot>.
19904 All <Graph.Plot> methods
19911 RGraph.Plot = new Class( {
19913 Implements: Graph.Plot
19918 Object: RGraph.Label
19920 Custom extension of <Graph.Label>.
19921 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19925 All <Graph.Label> methods and subclasses.
19929 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19935 RGraph.Label.Native
19937 Custom extension of <Graph.Label.Native>.
19941 All <Graph.Label.Native> methods
19945 <Graph.Label.Native>
19948 RGraph.Label.Native = new Class( {
19949 Implements: Graph.Label.Native
19955 Custom extension of <Graph.Label.SVG>.
19959 All <Graph.Label.SVG> methods
19966 RGraph.Label.SVG = new Class( {
19967 Implements: Graph.Label.SVG,
19969 initialize: function(viz){
19976 Overrides abstract method placeLabel in <Graph.Plot>.
19980 tag - A DOM label element.
19981 node - A <Graph.Node>.
19982 controller - A configuration/controller object passed to the visualization.
19985 placeLabel: function(tag, node, controller){
19986 var pos = node.pos.getc(true),
19987 canvas = this.viz.canvas,
19988 ox = canvas.translateOffsetX,
19989 oy = canvas.translateOffsetY,
19990 sx = canvas.scaleOffsetX,
19991 sy = canvas.scaleOffsetY,
19992 radius = canvas.getSize();
19994 x: Math.round(pos.x * sx + ox + radius.width / 2),
19995 y: Math.round(pos.y * sy + oy + radius.height / 2)
19997 tag.setAttribute('x', labelPos.x);
19998 tag.setAttribute('y', labelPos.y);
20000 controller.onPlaceLabel(tag, node);
20007 Custom extension of <Graph.Label.HTML>.
20011 All <Graph.Label.HTML> methods.
20018 RGraph.Label.HTML = new Class( {
20019 Implements: Graph.Label.HTML,
20021 initialize: function(viz){
20027 Overrides abstract method placeLabel in <Graph.Plot>.
20031 tag - A DOM label element.
20032 node - A <Graph.Node>.
20033 controller - A configuration/controller object passed to the visualization.
20036 placeLabel: function(tag, node, controller){
20037 var pos = node.pos.getc(true),
20038 canvas = this.viz.canvas,
20039 ox = canvas.translateOffsetX,
20040 oy = canvas.translateOffsetY,
20041 sx = canvas.scaleOffsetX,
20042 sy = canvas.scaleOffsetY,
20043 radius = canvas.getSize();
20045 x: Math.round(pos.x * sx + ox + radius.width / 2),
20046 y: Math.round(pos.y * sy + oy + radius.height / 2)
20049 var style = tag.style;
20050 style.left = labelPos.x + 'px';
20051 style.top = labelPos.y + 'px';
20052 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20054 controller.onPlaceLabel(tag, node);
20059 Class: RGraph.Plot.NodeTypes
20061 This class contains a list of <Graph.Node> built-in types.
20062 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20064 You can add your custom node types, customizing your visualization to the extreme.
20069 RGraph.Plot.NodeTypes.implement({
20071 'render': function(node, canvas) {
20072 //print your custom node to canvas
20075 'contains': function(node, pos) {
20076 //return true if pos is inside the node or false otherwise
20083 RGraph.Plot.NodeTypes = new Class({
20086 'contains': $.lambda(false)
20089 'render': function(node, canvas){
20090 var pos = node.pos.getc(true),
20091 dim = node.getData('dim');
20092 this.nodeHelper.circle.render('fill', pos, dim, canvas);
20094 'contains': function(node, pos){
20095 var npos = node.pos.getc(true),
20096 dim = node.getData('dim');
20097 return this.nodeHelper.circle.contains(npos, pos, dim);
20101 'render': function(node, canvas){
20102 var pos = node.pos.getc(true),
20103 width = node.getData('width'),
20104 height = node.getData('height');
20105 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20107 // TODO(nico): be more precise...
20108 'contains': function(node, pos){
20109 var npos = node.pos.getc(true),
20110 width = node.getData('width'),
20111 height = node.getData('height');
20112 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20116 'render': function(node, canvas){
20117 var pos = node.pos.getc(true),
20118 dim = node.getData('dim');
20119 this.nodeHelper.square.render('fill', pos, dim, canvas);
20121 'contains': function(node, pos){
20122 var npos = node.pos.getc(true),
20123 dim = node.getData('dim');
20124 return this.nodeHelper.square.contains(npos, pos, dim);
20128 'render': function(node, canvas){
20129 var pos = node.pos.getc(true),
20130 width = node.getData('width'),
20131 height = node.getData('height');
20132 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20134 'contains': function(node, pos){
20135 var npos = node.pos.getc(true),
20136 width = node.getData('width'),
20137 height = node.getData('height');
20138 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20142 'render': function(node, canvas){
20143 var pos = node.pos.getc(true),
20144 dim = node.getData('dim');
20145 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20147 'contains': function(node, pos) {
20148 var npos = node.pos.getc(true),
20149 dim = node.getData('dim');
20150 return this.nodeHelper.triangle.contains(npos, pos, dim);
20154 'render': function(node, canvas){
20155 var pos = node.pos.getc(true),
20156 dim = node.getData('dim');
20157 this.nodeHelper.star.render('fill', pos, dim, canvas);
20159 'contains': function(node, pos) {
20160 var npos = node.pos.getc(true),
20161 dim = node.getData('dim');
20162 return this.nodeHelper.star.contains(npos, pos, dim);
20168 Class: RGraph.Plot.EdgeTypes
20170 This class contains a list of <Graph.Adjacence> built-in types.
20171 Edge types implemented are 'none', 'line' and 'arrow'.
20173 You can add your custom edge types, customizing your visualization to the extreme.
20178 RGraph.Plot.EdgeTypes.implement({
20180 'render': function(adj, canvas) {
20181 //print your custom edge to canvas
20184 'contains': function(adj, pos) {
20185 //return true if pos is inside the arc or false otherwise
20192 RGraph.Plot.EdgeTypes = new Class({
20195 'render': function(adj, canvas) {
20196 var from = adj.nodeFrom.pos.getc(true),
20197 to = adj.nodeTo.pos.getc(true);
20198 this.edgeHelper.line.render(from, to, canvas);
20200 'contains': function(adj, pos) {
20201 var from = adj.nodeFrom.pos.getc(true),
20202 to = adj.nodeTo.pos.getc(true);
20203 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20207 'render': function(adj, canvas) {
20208 var from = adj.nodeFrom.pos.getc(true),
20209 to = adj.nodeTo.pos.getc(true),
20210 dim = adj.getData('dim'),
20211 direction = adj.data.$direction,
20212 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20213 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20215 'contains': function(adj, pos) {
20216 var from = adj.nodeFrom.pos.getc(true),
20217 to = adj.nodeTo.pos.getc(true);
20218 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20227 * File: Hypertree.js
20234 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
20238 moebiusTransformation
20240 Calculates a moebius transformation for this point / complex.
20241 For more information go to:
20242 http://en.wikipedia.org/wiki/Moebius_transformation.
20246 c - An initialized Complex instance representing a translation Vector.
20249 Complex.prototype.moebiusTransformation = function(c) {
20250 var num = this.add(c);
20251 var den = c.$conjugate().$prod(this);
20253 return num.$div(den);
20257 moebiusTransformation
20259 Calculates a moebius transformation for the hyperbolic tree.
20261 <http://en.wikipedia.org/wiki/Moebius_transformation>
20265 graph - A <Graph> instance.
20267 prop - A property array.
20268 theta - Rotation angle.
20269 startPos - _optional_ start position.
20271 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20272 this.eachNode(graph, function(elem) {
20273 for ( var i = 0; i < prop.length; i++) {
20274 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20275 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20283 A Hyperbolic Tree/Graph visualization.
20287 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
20288 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20292 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.
20296 All <Loader> methods
20298 Constructor Options:
20300 Inherits options from
20303 - <Options.Controller>
20309 - <Options.NodeStyles>
20310 - <Options.Navigation>
20312 Additionally, there are other parameters and some default values changed
20314 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*.
20315 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.
20316 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20317 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20318 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
20320 Instance Properties:
20322 canvas - Access a <Canvas> instance.
20323 graph - Access a <Graph> instance.
20324 op - Access a <Hypertree.Op> instance.
20325 fx - Access a <Hypertree.Plot> instance.
20326 labels - Access a <Hypertree.Label> interface implementation.
20330 $jit.Hypertree = new Class( {
20332 Implements: [ Loader, Extras, Layouts.Radial ],
20334 initialize: function(controller) {
20335 var $Hypertree = $jit.Hypertree;
20346 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20347 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20349 var canvasConfig = this.config;
20350 if(canvasConfig.useCanvas) {
20351 this.canvas = canvasConfig.useCanvas;
20352 this.config.labelContainer = this.canvas.id + '-label';
20354 if(canvasConfig.background) {
20355 canvasConfig.background = $.merge({
20357 }, canvasConfig.background);
20359 this.canvas = new Canvas(this, canvasConfig);
20360 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20363 this.graphOptions = {
20371 this.graph = new Graph(this.graphOptions, this.config.Node,
20373 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20374 this.fx = new $Hypertree.Plot(this, $Hypertree);
20375 this.op = new $Hypertree.Op(this);
20379 // initialize extras
20380 this.initializeExtras();
20385 createLevelDistanceFunc
20387 Returns the levelDistance function used for calculating a node distance
20388 to its origin. This function returns a function that is computed
20389 per level and not per node, such that all nodes with the same depth will have the
20390 same distance to the origin. The resulting function gets the
20391 parent node as parameter and returns a float.
20394 createLevelDistanceFunc: function() {
20395 // get max viz. length.
20396 var r = this.getRadius();
20398 var depth = 0, max = Math.max, config = this.config;
20399 this.graph.eachNode(function(node) {
20400 depth = max(node._depth, depth);
20403 // node distance generator
20404 var genDistFunc = function(a) {
20405 return function(node) {
20407 var d = node._depth + 1;
20408 var acum = 0, pow = Math.pow;
20410 acum += pow(a, d--);
20412 return acum - config.offset;
20415 // estimate better edge length.
20416 for ( var i = 0.51; i <= 1; i += 0.01) {
20417 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20418 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20420 return genDistFunc(0.75);
20426 Returns the current radius of the visualization. If *config.radius* is *auto* then it
20427 calculates the radius by taking the smaller size of the <Canvas> widget.
20434 getRadius: function() {
20435 var rad = this.config.radius;
20436 if (rad !== "auto") { return rad; }
20437 var s = this.canvas.getSize();
20438 return Math.min(s.width, s.height) / 2;
20444 Computes positions and plots the tree.
20448 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20451 refresh: function(reposition) {
20454 this.graph.eachNode(function(node) {
20455 node.startPos.rho = node.pos.rho = node.endPos.rho;
20456 node.startPos.theta = node.pos.theta = node.endPos.theta;
20467 Computes nodes' positions and restores the tree to its previous position.
20469 For calculating nodes' positions the root must be placed on its origin. This method does this
20470 and then attemps to restore the hypertree to its previous position.
20473 reposition: function() {
20474 this.compute('end');
20475 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20476 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20478 this.graph.eachNode(function(node) {
20480 node.endPos.rho = node.pos.rho;
20481 node.endPos.theta = node.pos.theta;
20489 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
20499 Animates the <Hypertree> to center the node specified by *id*.
20503 id - A <Graph.Node> id.
20504 opt - (optional|object) An object containing some extra properties described below
20505 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20510 ht.onClick('someid');
20512 ht.onClick('someid', {
20518 onClick: function(id, opt) {
20519 var pos = this.graph.getNode(id).pos.getc(true);
20520 this.move(pos, opt);
20526 Translates the tree to the given position.
20530 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20531 opt - This object has been defined in <Hypertree.onClick>
20536 ht.move({ x: 0, y: 0.7 }, {
20542 move: function(pos, opt) {
20543 var versor = $C(pos.x, pos.y);
20544 if (this.busy === false && versor.norm() < 1) {
20546 var root = this.graph.getClosestNodeToPos(versor), that = this;
20547 this.graph.computeLevels(root.id, 0);
20548 this.controller.onBeforeCompute(root);
20550 onComplete: $.empty
20552 this.fx.animate($.merge( {
20553 modes: [ 'moebius' ],
20556 onComplete: function() {
20565 $jit.Hypertree.$extend = true;
20567 (function(Hypertree) {
20570 Class: Hypertree.Op
20572 Custom extension of <Graph.Op>.
20576 All <Graph.Op> methods
20583 Hypertree.Op = new Class( {
20585 Implements: Graph.Op
20590 Class: Hypertree.Plot
20592 Custom extension of <Graph.Plot>.
20596 All <Graph.Plot> methods
20603 Hypertree.Plot = new Class( {
20605 Implements: Graph.Plot
20610 Object: Hypertree.Label
20612 Custom extension of <Graph.Label>.
20613 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20617 All <Graph.Label> methods and subclasses.
20621 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20624 Hypertree.Label = {};
20627 Hypertree.Label.Native
20629 Custom extension of <Graph.Label.Native>.
20633 All <Graph.Label.Native> methods
20637 <Graph.Label.Native>
20640 Hypertree.Label.Native = new Class( {
20641 Implements: Graph.Label.Native,
20643 initialize: function(viz) {
20647 renderLabel: function(canvas, node, controller) {
20648 var ctx = canvas.getCtx();
20649 var coord = node.pos.getc(true);
20650 var s = this.viz.getRadius();
20651 ctx.fillText(node.name, coord.x * s, coord.y * s);
20656 Hypertree.Label.SVG
20658 Custom extension of <Graph.Label.SVG>.
20662 All <Graph.Label.SVG> methods
20669 Hypertree.Label.SVG = new Class( {
20670 Implements: Graph.Label.SVG,
20672 initialize: function(viz) {
20679 Overrides abstract method placeLabel in <Graph.Plot>.
20683 tag - A DOM label element.
20684 node - A <Graph.Node>.
20685 controller - A configuration/controller object passed to the visualization.
20688 placeLabel: function(tag, node, controller) {
20689 var pos = node.pos.getc(true),
20690 canvas = this.viz.canvas,
20691 ox = canvas.translateOffsetX,
20692 oy = canvas.translateOffsetY,
20693 sx = canvas.scaleOffsetX,
20694 sy = canvas.scaleOffsetY,
20695 radius = canvas.getSize(),
20696 r = this.viz.getRadius();
20698 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20699 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20701 tag.setAttribute('x', labelPos.x);
20702 tag.setAttribute('y', labelPos.y);
20703 controller.onPlaceLabel(tag, node);
20708 Hypertree.Label.HTML
20710 Custom extension of <Graph.Label.HTML>.
20714 All <Graph.Label.HTML> methods.
20721 Hypertree.Label.HTML = new Class( {
20722 Implements: Graph.Label.HTML,
20724 initialize: function(viz) {
20730 Overrides abstract method placeLabel in <Graph.Plot>.
20734 tag - A DOM label element.
20735 node - A <Graph.Node>.
20736 controller - A configuration/controller object passed to the visualization.
20739 placeLabel: function(tag, node, controller) {
20740 var pos = node.pos.getc(true),
20741 canvas = this.viz.canvas,
20742 ox = canvas.translateOffsetX,
20743 oy = canvas.translateOffsetY,
20744 sx = canvas.scaleOffsetX,
20745 sy = canvas.scaleOffsetY,
20746 radius = canvas.getSize(),
20747 r = this.viz.getRadius();
20749 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20750 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20752 var style = tag.style;
20753 style.left = labelPos.x + 'px';
20754 style.top = labelPos.y + 'px';
20755 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20757 controller.onPlaceLabel(tag, node);
20762 Class: Hypertree.Plot.NodeTypes
20764 This class contains a list of <Graph.Node> built-in types.
20765 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20767 You can add your custom node types, customizing your visualization to the extreme.
20772 Hypertree.Plot.NodeTypes.implement({
20774 'render': function(node, canvas) {
20775 //print your custom node to canvas
20778 'contains': function(node, pos) {
20779 //return true if pos is inside the node or false otherwise
20786 Hypertree.Plot.NodeTypes = new Class({
20789 'contains': $.lambda(false)
20792 'render': function(node, canvas) {
20793 var nconfig = this.node,
20794 dim = node.getData('dim'),
20795 p = node.pos.getc();
20796 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20797 p.$scale(node.scale);
20799 this.nodeHelper.circle.render('fill', p, dim, canvas);
20802 'contains': function(node, pos) {
20803 var dim = node.getData('dim'),
20804 npos = node.pos.getc().$scale(node.scale);
20805 return this.nodeHelper.circle.contains(npos, pos, dim);
20809 'render': function(node, canvas) {
20810 var pos = node.pos.getc().$scale(node.scale),
20811 width = node.getData('width'),
20812 height = node.getData('height');
20813 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20815 'contains': function(node, pos) {
20816 var width = node.getData('width'),
20817 height = node.getData('height'),
20818 npos = node.pos.getc().$scale(node.scale);
20819 return this.nodeHelper.circle.contains(npos, pos, width, height);
20823 'render': function(node, canvas) {
20824 var nconfig = this.node,
20825 dim = node.getData('dim'),
20826 p = node.pos.getc();
20827 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20828 p.$scale(node.scale);
20830 this.nodeHelper.square.render('fill', p, dim, canvas);
20833 'contains': function(node, pos) {
20834 var dim = node.getData('dim'),
20835 npos = node.pos.getc().$scale(node.scale);
20836 return this.nodeHelper.square.contains(npos, pos, dim);
20840 'render': function(node, canvas) {
20841 var nconfig = this.node,
20842 width = node.getData('width'),
20843 height = node.getData('height'),
20844 pos = node.pos.getc();
20845 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20846 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20847 pos.$scale(node.scale);
20848 if (width > 0.2 && height > 0.2) {
20849 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20852 'contains': function(node, pos) {
20853 var width = node.getData('width'),
20854 height = node.getData('height'),
20855 npos = node.pos.getc().$scale(node.scale);
20856 return this.nodeHelper.square.contains(npos, pos, width, height);
20860 'render': function(node, canvas) {
20861 var nconfig = this.node,
20862 dim = node.getData('dim'),
20863 p = node.pos.getc();
20864 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20865 p.$scale(node.scale);
20867 this.nodeHelper.triangle.render('fill', p, dim, canvas);
20870 'contains': function(node, pos) {
20871 var dim = node.getData('dim'),
20872 npos = node.pos.getc().$scale(node.scale);
20873 return this.nodeHelper.triangle.contains(npos, pos, dim);
20877 'render': function(node, canvas) {
20878 var nconfig = this.node,
20879 dim = node.getData('dim'),
20880 p = node.pos.getc();
20881 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20882 p.$scale(node.scale);
20884 this.nodeHelper.star.render('fill', p, dim, canvas);
20887 'contains': function(node, pos) {
20888 var dim = node.getData('dim'),
20889 npos = node.pos.getc().$scale(node.scale);
20890 return this.nodeHelper.star.contains(npos, pos, dim);
20896 Class: Hypertree.Plot.EdgeTypes
20898 This class contains a list of <Graph.Adjacence> built-in types.
20899 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20901 You can add your custom edge types, customizing your visualization to the extreme.
20906 Hypertree.Plot.EdgeTypes.implement({
20908 'render': function(adj, canvas) {
20909 //print your custom edge to canvas
20912 'contains': function(adj, pos) {
20913 //return true if pos is inside the arc or false otherwise
20920 Hypertree.Plot.EdgeTypes = new Class({
20923 'render': function(adj, canvas) {
20924 var from = adj.nodeFrom.pos.getc(true),
20925 to = adj.nodeTo.pos.getc(true),
20926 r = adj.nodeFrom.scale;
20927 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20929 'contains': function(adj, pos) {
20930 var from = adj.nodeFrom.pos.getc(true),
20931 to = adj.nodeTo.pos.getc(true),
20932 r = adj.nodeFrom.scale;
20933 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20937 'render': function(adj, canvas) {
20938 var from = adj.nodeFrom.pos.getc(true),
20939 to = adj.nodeTo.pos.getc(true),
20940 r = adj.nodeFrom.scale,
20941 dim = adj.getData('dim'),
20942 direction = adj.data.$direction,
20943 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20944 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20946 'contains': function(adj, pos) {
20947 var from = adj.nodeFrom.pos.getc(true),
20948 to = adj.nodeTo.pos.getc(true),
20949 r = adj.nodeFrom.scale;
20950 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20954 'render': function(adj, canvas) {
20955 var from = adj.nodeFrom.pos.getc(),
20956 to = adj.nodeTo.pos.getc(),
20957 dim = this.viz.getRadius();
20958 this.edgeHelper.hyperline.render(from, to, dim, canvas);
20960 'contains': $.lambda(false)
20964 })($jit.Hypertree);