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);
221 var handleSuccess = function(o){
225 success:handleSuccess,
226 failure:handleFailure,
227 argument: { foo:'foo', bar:''}
229 var path = "index.php?action=DynamicAction&DynamicAction=saveImage&module=Charts&to_pdf=1";
230 var postData = "imageStr=" + strDataURI + "&filename=" + filename;
231 var request = YAHOO.util.Connect.asyncRequest('POST', path, callback, postData);
235 $.saveImageTest = function (id,jsonfilename,imageExt) {
236 if(typeof FlashCanvas != "undefined") {
237 setTimeout(function(){$.saveImageFile(id,jsonfilename,imageExt)},10000);
239 $.saveImageFile(id,jsonfilename,imageExt);
245 Augment an object by appending another object's properties.
249 original - (object) The object to be extended.
250 extended - (object) An object which properties are going to be appended to the original object.
254 $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
257 $.extend = function(original, extended) {
258 for ( var key in (extended || {}))
259 original[key] = extended[key];
263 $.lambda = function(value) {
264 return (typeof value == 'function') ? value : function() {
269 $.time = Date.now || function() {
276 Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
280 obj - (mixed) The object to be wrapped in an array.
284 $jit.util.splat(3); //[3]
285 $jit.util.splat([3]); //[3]
288 $.splat = function(obj) {
289 var type = $.type(obj);
290 return type ? ((type != 'array') ? [ obj ] : obj) : [];
293 $.type = function(elem) {
294 var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
295 if(type != 'object') return type;
296 if(elem && elem.$$family) return elem.$$family;
297 return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
299 $.type.s = Object.prototype.toString;
304 Iterates through an iterable applying *f*.
308 iterable - (array) The original array.
309 fn - (function) The function to apply to the array elements.
313 $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
316 $.each = function(iterable, fn) {
317 var type = $.type(iterable);
318 if (type == 'object') {
319 for ( var key in iterable)
320 fn(iterable[key], key);
322 for ( var i = 0, l = iterable.length; i < l; i++)
327 $.indexOf = function(array, item) {
328 if(Array.indexOf) return array.indexOf(item);
329 for(var i=0,l=array.length; i<l; i++) {
330 if(array[i] === item) return i;
338 Maps or collects an array by applying *f*.
342 array - (array) The original array.
343 f - (function) The function to apply to the array elements.
347 $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
350 $.map = function(array, f) {
352 $.each(array, function(elem, i) {
353 ans.push(f(elem, i));
361 Iteratively applies the binary function *f* storing the result in an accumulator.
365 array - (array) The original array.
366 f - (function) The function to apply to the array elements.
367 opt - (optional|mixed) The starting value for the acumulator.
371 $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
374 $.reduce = function(array, f, opt) {
375 var l = array.length;
377 var acum = arguments.length == 3? opt : array[--l];
379 acum = f(acum, array[l]);
387 Merges n-objects and their sub-objects creating a new, fresh object.
391 An arbitrary number of objects.
395 $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
398 $.merge = function() {
400 for ( var i = 0, l = arguments.length; i < l; i++) {
401 var object = arguments[i];
402 if ($.type(object) != 'object')
404 for ( var key in object) {
405 var op = object[key], mp = mix[key];
406 mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
407 .merge(mp, op) : $.unlink(op);
413 $.unlink = function(object) {
415 switch ($.type(object)) {
418 for ( var p in object)
419 unlinked[p] = $.unlink(object[p]);
423 for ( var i = 0, l = object.length; i < l; i++)
424 unlinked[i] = $.unlink(object[i]);
433 if(arguments.length === 0) return [];
434 for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
435 for(var i=0, row=[]; i<l; i++) {
436 row.push(arguments[i][j]);
446 Converts an RGB array into a Hex string.
450 srcArray - (array) An array with R, G and B values
454 $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
457 $.rgbToHex = function(srcArray, array) {
458 if (srcArray.length < 3)
460 if (srcArray.length == 4 && srcArray[3] == 0 && !array)
461 return 'transparent';
463 for ( var i = 0; i < 3; i++) {
464 var bit = (srcArray[i] - 0).toString(16);
465 hex.push(bit.length == 1 ? '0' + bit : bit);
467 return array ? hex : '#' + hex.join('');
473 Converts an Hex color string into an RGB array.
477 hex - (string) A color hex string.
481 $jit.util.hexToRgb('#fff'); //[255, 255, 255]
484 $.hexToRgb = function(hex) {
485 if (hex.length != 7) {
486 hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
491 for ( var i = 0; i < 3; i++) {
493 if (value.length == 1)
495 rgb.push(parseInt(value, 16));
499 hex = parseInt(hex.slice(1), 16);
500 return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
504 $.destroy = function(elem) {
507 elem.parentNode.removeChild(elem);
508 if (elem.clearAttributes)
509 elem.clearAttributes();
512 $.clean = function(elem) {
513 for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
521 Cross-browser add event listener.
525 obj - (obj) The Element to attach the listener to.
526 type - (string) The listener type. For example 'click', or 'mousemove'.
527 fn - (function) The callback function to be used when the event is fired.
531 $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
534 $.addEvent = function(obj, type, fn) {
535 if (obj.addEventListener)
536 obj.addEventListener(type, fn, false);
538 obj.attachEvent('on' + type, fn);
541 $.addEvents = function(obj, typeObj) {
542 for(var type in typeObj) {
543 $.addEvent(obj, type, typeObj[type]);
547 $.hasClass = function(obj, klass) {
548 return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
551 $.addClass = function(obj, klass) {
552 if (!$.hasClass(obj, klass))
553 obj.className = (obj.className + " " + klass);
556 $.removeClass = function(obj, klass) {
557 obj.className = obj.className.replace(new RegExp(
558 '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
561 $.getPos = function(elem) {
562 var offset = getOffsets(elem);
563 var scroll = getScrolls(elem);
565 x: offset.x - scroll.x,
566 y: offset.y - scroll.y
569 function getOffsets(elem) {
574 while (elem && !isBody(elem)) {
575 position.x += elem.offsetLeft;
576 position.y += elem.offsetTop;
577 elem = elem.offsetParent;
582 function getScrolls(elem) {
587 while (elem && !isBody(elem)) {
588 position.x += elem.scrollLeft;
589 position.y += elem.scrollTop;
590 elem = elem.parentNode;
595 function isBody(element) {
596 return (/^(?:body|html)$/i).test(element.tagName);
601 get: function(e, win) {
603 return e || win.event;
605 getWheel: function(e) {
606 return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
608 isRightClick: function(e) {
609 return (e.which == 3 || e.button == 2);
611 getPos: function(e, win) {
612 // get mouse position
615 var doc = win.document;
616 doc = doc.documentElement || doc.body;
617 //TODO(nico): make touch event handling better
618 if(e.touches && e.touches.length) {
622 x: e.pageX || (e.clientX + doc.scrollLeft),
623 y: e.pageY || (e.clientY + doc.scrollTop)
628 if (e.stopPropagation) e.stopPropagation();
629 e.cancelBubble = true;
630 if (e.preventDefault) e.preventDefault();
631 else e.returnValue = false;
635 $jit.util = $jit.id = $;
637 var Class = function(properties) {
638 properties = properties || {};
639 var klass = function() {
640 for ( var key in this) {
641 if (typeof this[key] != 'function')
642 this[key] = $.unlink(this[key]);
644 this.constructor = klass;
645 if (Class.prototyping)
647 var instance = this.initialize ? this.initialize.apply(this, arguments)
650 this.$$family = 'class';
654 for ( var mutator in Class.Mutators) {
655 if (!properties[mutator])
657 properties = Class.Mutators[mutator](properties, properties[mutator]);
658 delete properties[mutator];
661 $.extend(klass, this);
662 klass.constructor = Class;
663 klass.prototype = properties;
669 Implements: function(self, klasses) {
670 $.each($.splat(klasses), function(klass) {
671 Class.prototyping = klass;
672 var instance = (typeof klass == 'function') ? new klass : klass;
673 for ( var prop in instance) {
674 if (!(prop in self)) {
675 self[prop] = instance[prop];
678 delete Class.prototyping;
687 inherit: function(object, properties) {
688 for ( var key in properties) {
689 var override = properties[key];
690 var previous = object[key];
691 var type = $.type(override);
692 if (previous && type == 'function') {
693 if (override != previous) {
694 Class.override(object, key, override);
696 } else if (type == 'object') {
697 object[key] = $.merge(previous, override);
699 object[key] = override;
705 override: function(object, name, method) {
706 var parent = Class.prototyping;
707 if (parent && object[name] != parent[name])
709 var override = function() {
710 var previous = this.parent;
711 this.parent = parent ? parent[name] : object[name];
712 var value = method.apply(this, arguments);
713 this.parent = previous;
716 object[name] = override;
721 Class.prototype.implement = function() {
722 var proto = this.prototype;
723 $.each(Array.prototype.slice.call(arguments || []), function(properties) {
724 Class.inherit(proto, properties);
734 Provides JSON utility functions.
736 Most of these functions are JSON-tree traversal and manipulation functions.
742 Clears all tree nodes having depth greater than maxLevel.
746 tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
747 maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
750 prune: function(tree, maxLevel) {
751 this.each(tree, function(elem, i) {
752 if (i == maxLevel && elem.children) {
753 delete elem.children;
761 Returns the parent node of the node having _id_ as id.
765 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
766 id - (string) The _id_ of the child node whose parent will be returned.
770 A tree JSON node if any, or false otherwise.
773 getParent: function(tree, id) {
776 var ch = tree.children;
777 if (ch && ch.length > 0) {
778 for ( var i = 0; i < ch.length; i++) {
782 var ans = this.getParent(ch[i], id);
793 Returns the subtree that matches the given id.
797 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
798 id - (string) A node *unique* identifier.
802 A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
805 getSubtree: function(tree, id) {
808 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
809 var t = this.getSubtree(ch[i], id);
818 Iterates on tree nodes with relative depth less or equal than a specified level.
822 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
823 initLevel - (number) An integer specifying the initial relative level. Usually zero.
824 toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
825 action - (function) A function that receives a node and an integer specifying the actual level of the node.
829 $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
830 alert(node.name + ' ' + depth);
834 eachLevel: function(tree, initLevel, toLevel, action) {
835 if (initLevel <= toLevel) {
836 action(tree, initLevel);
837 if(!tree.children) return;
838 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
839 this.eachLevel(ch[i], initLevel + 1, toLevel, action);
846 A JSON tree iterator.
850 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
851 action - (function) A function that receives a node.
855 $jit.json.each(tree, function(node) {
861 each: function(tree, action) {
862 this.eachLevel(tree, 0, Number.MAX_VALUE, action);
868 An object containing multiple type of transformations.
879 var Trans = $jit.Trans;
883 var makeTrans = function(transition, params){
884 params = $.splat(params);
885 return $.extend(transition, {
886 easeIn: function(pos){
887 return transition(pos, params);
889 easeOut: function(pos){
890 return 1 - transition(1 - pos, params);
892 easeInOut: function(pos){
893 return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
894 2 * (1 - pos), params)) / 2;
902 return Math.pow(p, x[0] || 6);
906 return Math.pow(2, 8 * (p - 1));
910 return 1 - Math.sin(Math.acos(p));
914 return 1 - Math.sin((1 - p) * Math.PI / 2);
917 Back: function(p, x){
919 return Math.pow(p, 2) * ((x + 1) * p - x);
924 for ( var a = 0, b = 1; 1; a += b, b /= 2) {
925 if (p >= (7 - 4 * a) / 11) {
926 value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
933 Elastic: function(p, x){
934 return Math.pow(2, 10 * --p)
935 * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
940 $.each(transitions, function(val, key){
941 Trans[key] = makeTrans(val);
945 'Quad', 'Cubic', 'Quart', 'Quint'
946 ], function(elem, i){
947 Trans[elem] = makeTrans(function(p){
957 A Class that can perform animations for generic objects.
959 If you are looking for animation transitions please take a look at the <Trans> object.
967 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>.
971 var Animation = new Class( {
973 initialize: function(options){
974 this.setOptions(options);
977 setOptions: function(options){
981 transition: Trans.Quart.easeInOut,
986 this.opt = $.merge(opt, options || {});
991 var time = $.time(), opt = this.opt;
992 if (time < this.time + opt.duration) {
993 var delta = opt.transition((time - this.time) / opt.duration);
996 this.timer = clearInterval(this.timer);
1010 startTimer: function(){
1011 var that = this, fps = this.opt.fps;
1014 this.time = $.time() - this.time;
1015 this.timer = setInterval((function(){
1017 }), Math.round(1000 / fps));
1031 stopTimer: function(){
1034 this.time = $.time() - this.time;
1035 this.timer = clearInterval(this.timer);
1042 if (this.opt.link == 'cancel') {
1051 var Options = function() {
1052 var args = arguments;
1053 for(var i=0, l=args.length, ans={}; i<l; i++) {
1054 var opt = Options[args[i]];
1065 * File: Options.AreaChart.js
1070 Object: Options.AreaChart
1072 <AreaChart> options.
1073 Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1079 Options.AreaChart = {
1083 selectOnHover: true,
1084 showAggregates: true,
1086 filterOnClick: false,
1087 restoreOnRightClick: false
1096 var areaChart = new $jit.AreaChart({
1098 type: 'stacked:gradient',
1099 selectOnHover: true,
1100 filterOnClick: true,
1101 restoreOnRightClick: true
1108 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1109 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1110 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1111 selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1112 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1113 showLabels - (boolean) Default's *true*. Display the name of the slots.
1114 filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1115 restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1119 Options.AreaChart = {
1123 labelOffset: 3, // label offset
1124 type: 'stacked', // gradient
1134 selectOnHover: true,
1135 showAggregates: true,
1137 filterOnClick: false,
1138 restoreOnRightClick: false
1142 * File: Options.Margin.js
1147 Object: Options.Margin
1149 Canvas drawing margins.
1168 var viz = new $jit.Viz({
1179 top - (number) Default's *0*. Top margin.
1180 left - (number) Default's *0*. Left margin.
1181 right - (number) Default's *0*. Right margin.
1182 bottom - (number) Default's *0*. Bottom margin.
1196 * File: Options.Canvas.js
1201 Object: Options.Canvas
1203 These are Canvas general options, like where to append it in the DOM, its dimensions, background,
1204 and other more advanced options.
1223 var viz = new $jit.Viz({
1224 injectInto: 'someContainerId',
1232 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.
1233 width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1234 height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1235 useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1236 withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1237 background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1249 colorStop1: 'rgba(255,255,255,1)',
1250 colorStop2: 'rgba(255,255,255,0)'
1254 * File: Options.Tree.js
1259 Object: Options.Tree
1261 Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1267 orientation: "left",
1279 var st = new $jit.ST({
1280 orientation: 'left',
1289 subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1290 siblingOffset - (number) Default's 5. Separation offset between siblings.
1291 orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1292 align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1293 indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1294 multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1300 orientation: "left",
1310 * File: Options.Node.js
1315 Object: Options.Node
1317 Provides Node rendering options for Tree and Graph based visualizations.
1344 var viz = new $jit.Viz({
1356 overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1357 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.
1358 color - (string) Default's *#ccb*. Node color.
1359 alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1360 dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1361 height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1362 width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1363 autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1364 autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1365 lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1366 transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1367 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.
1368 angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1369 span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1370 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.
1390 //Raw canvas styles to be
1391 //applied to the context instance
1392 //before plotting a node
1398 * File: Options.Edge.js
1403 Object: Options.Edge
1405 Provides Edge rendering options for Tree and Graph based visualizations.
1424 var viz = new $jit.Viz({
1430 shadowColor: '#ccc',
1439 overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1440 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.
1441 color - (string) Default's '#ccb'. Edge color.
1442 lineWidth - (number) Default's *1*. Line/Edge width.
1443 alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1444 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.
1445 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*.
1446 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.
1450 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.
1463 //Raw canvas styles to be
1464 //applied to the context instance
1465 //before plotting an edge
1471 * File: Options.Fx.js
1478 Provides animation options like duration of the animations, frames per second and animation transitions.
1486 transition: $jit.Trans.Quart.easeInOut,
1494 var viz = new $jit.Viz({
1497 transition: $jit.Trans.linear
1503 clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1504 duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1505 fps - (number) Default's *40*. Frames per second.
1506 transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1510 This object is used for specifying different animation transitions in all visualizations.
1512 There are many different type of animation transitions.
1516 Displays a linear transition
1524 Displays a Quadratic transition.
1528 >Trans.Quad.easeInOut
1534 Displays a Cubic transition.
1537 >Trans.Cubic.easeOut
1538 >Trans.Cubic.easeInOut
1544 Displays a Quartetic transition.
1547 >Trans.Quart.easeOut
1548 >Trans.Quart.easeInOut
1554 Displays a Quintic transition.
1557 >Trans.Quint.easeOut
1558 >Trans.Quint.easeInOut
1564 Displays an Exponential transition.
1568 >Trans.Expo.easeInOut
1574 Displays a Circular transition.
1578 >Trans.Circ.easeInOut
1584 Displays a Sineousidal transition.
1588 >Trans.Sine.easeInOut
1596 >Trans.Back.easeInOut
1604 >Trans.Bounce.easeIn
1605 >Trans.Bounce.easeOut
1606 >Trans.Bounce.easeInOut
1614 >Trans.Elastic.easeIn
1615 >Trans.Elastic.easeOut
1616 >Trans.Elastic.easeInOut
1622 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>.
1631 transition: $jit.Trans.Quart.easeInOut,
1636 * File: Options.Label.js
1640 Object: Options.Label
1642 Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.
1649 type: 'HTML', //'SVG', 'Native'
1652 family: 'sans-serif',
1653 textAlign: 'center',
1654 textBaseline: 'alphabetic',
1662 var viz = new $jit.Viz({
1673 overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1674 type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1675 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.
1676 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.
1677 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.
1678 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.
1684 type: 'HTML', //'SVG', 'Native'
1687 family: 'sans-serif',
1688 textAlign: 'center',
1689 textBaseline: 'alphabetic',
1695 * File: Options.Tips.js
1700 Object: Options.Tips
1720 var viz = new $jit.Viz({
1726 onShow: function(tip, node) {
1727 tip.innerHTML = node.name;
1735 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.
1736 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.
1737 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.
1738 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.
1739 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.
1740 onHide() - This callack is used when hiding a tooltip.
1757 * File: Options.NodeStyles.js
1762 Object: Options.NodeStyles
1764 Apply different styles when a node is hovered or selected.
1769 Options.NodeStyles = {
1780 var viz = new $jit.Viz({
1795 enable - (boolean) Default's *false*. Whether to enable this option.
1796 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>.
1797 stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1798 stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1801 Options.NodeStyles = {
1812 * File: Options.Events.js
1817 Object: Options.Events
1819 Configuration for adding mouse/touch event handlers to Nodes.
1826 enableForEdges: false,
1829 onRightClick: $.empty,
1830 onMouseMove: $.empty,
1831 onMouseEnter: $.empty,
1832 onMouseLeave: $.empty,
1833 onDragStart: $.empty,
1834 onDragMove: $.empty,
1835 onDragCancel: $.empty,
1837 onTouchStart: $.empty,
1838 onTouchMove: $.empty,
1839 onTouchEnd: $.empty,
1840 onTouchCancel: $.empty,
1841 onMouseWheel: $.empty
1848 var viz = new $jit.Viz({
1851 onClick: function(node, eventInfo, e) {
1854 onMouseEnter: function(node, eventInfo, e) {
1855 viz.canvas.getElement().style.cursor = 'pointer';
1857 onMouseLeave: function(node, eventInfo, e) {
1858 viz.canvas.getElement().style.cursor = '';
1866 enable - (boolean) Default's *false*. Whether to enable the Event system.
1867 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*.
1868 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.
1869 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.
1870 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.
1871 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.
1872 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.
1873 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.
1874 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.
1875 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.
1876 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.
1877 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.
1878 onTouchStart(node, eventInfo, e) - Behaves just like onDragStart.
1879 onTouchMove(node, eventInfo, e) - Behaves just like onDragMove.
1880 onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd.
1881 onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1882 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.
1889 enableForEdges: false,
1892 onRightClick: $.empty,
1893 onMouseMove: $.empty,
1894 onMouseEnter: $.empty,
1895 onMouseLeave: $.empty,
1896 onDragStart: $.empty,
1897 onDragMove: $.empty,
1898 onDragCancel: $.empty,
1900 onTouchStart: $.empty,
1901 onTouchMove: $.empty,
1902 onTouchEnd: $.empty,
1903 onMouseWheel: $.empty
1907 * File: Options.Navigation.js
1912 Object: Options.Navigation
1914 Panning and zooming options for Graph/Tree based visualizations. These options are implemented
1915 by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1921 Options.Navigation = {
1924 panning: false, //true, 'avoid nodes'
1933 var viz = new $jit.Viz({
1936 panning: 'avoid nodes',
1944 enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1945 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>.
1946 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.
1950 Options.Navigation = {
1955 panning: false, //true | 'avoid nodes'
1960 * File: Options.Controller.js
1965 Object: Options.Controller
1967 Provides controller methods. Controller methods are callback functions that get called at different stages
1968 of the animation, computing or plotting of the visualization.
1972 All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1978 Options.Controller = {
1979 onBeforeCompute: $.empty,
1980 onAfterCompute: $.empty,
1981 onCreateLabel: $.empty,
1982 onPlaceLabel: $.empty,
1983 onComplete: $.empty,
1984 onBeforePlotLine:$.empty,
1985 onAfterPlotLine: $.empty,
1986 onBeforePlotNode:$.empty,
1987 onAfterPlotNode: $.empty,
1996 var viz = new $jit.Viz({
1997 onBeforePlotNode: function(node) {
1999 node.setData('color', '#ffc');
2001 node.removeData('color');
2004 onBeforePlotLine: function(adj) {
2005 if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2006 adj.setData('color', '#ffc');
2008 adj.removeData('color');
2011 onAfterCompute: function() {
2019 onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2020 onAfterCompute() - This method is triggered after all animations or computations ended.
2021 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.
2022 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.
2023 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.
2024 onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2025 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.
2026 onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2028 *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2030 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.
2033 Options.Controller = {
2036 onBeforeCompute: $.empty,
2037 onAfterCompute: $.empty,
2038 onCreateLabel: $.empty,
2039 onPlaceLabel: $.empty,
2040 onComplete: $.empty,
2041 onBeforePlotLine:$.empty,
2042 onAfterPlotLine: $.empty,
2043 onBeforePlotNode:$.empty,
2044 onAfterPlotNode: $.empty,
2052 * Provides Extras such as Tips and Style Effects.
2056 * Provides the <Tips> and <NodeStyles> classes and functions.
2061 * Manager for mouse events (clicking and mouse moving).
2063 * This class is used for registering objects implementing onClick
2064 * and onMousemove methods. These methods are called when clicking or
2065 * moving the mouse around the Canvas.
2066 * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2069 var ExtrasInitializer = {
2070 initialize: function(className, viz) {
2072 this.canvas = viz.canvas;
2073 this.config = viz.config[className];
2074 this.nodeTypes = viz.fx.nodeTypes;
2075 var type = this.config.type;
2076 this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2077 this.labelContainer = this.dom && viz.labels.getLabelContainer();
2078 this.isEnabled() && this.initializePost();
2080 initializePost: $.empty,
2081 setAsProperty: $.lambda(false),
2082 isEnabled: function() {
2083 return this.config.enable;
2085 isLabel: function(e, win) {
2086 e = $.event.get(e, win);
2087 var labelContainer = this.labelContainer,
2088 target = e.target || e.srcElement;
2089 if(target && target.parentNode == labelContainer)
2095 var EventsInterface = {
2097 onMouseDown: $.empty,
2098 onMouseMove: $.empty,
2099 onMouseOver: $.empty,
2100 onMouseOut: $.empty,
2101 onMouseWheel: $.empty,
2102 onTouchStart: $.empty,
2103 onTouchMove: $.empty,
2104 onTouchEnd: $.empty,
2105 onTouchCancel: $.empty
2108 var MouseEventsManager = new Class({
2109 initialize: function(viz) {
2111 this.canvas = viz.canvas;
2114 this.registeredObjects = [];
2115 this.attachEvents();
2118 attachEvents: function() {
2119 var htmlCanvas = this.canvas.getElement(),
2121 htmlCanvas.oncontextmenu = $.lambda(false);
2122 $.addEvents(htmlCanvas, {
2123 'mouseup': function(e, win) {
2124 var event = $.event.get(e, win);
2125 that.handleEvent('MouseUp', e, win,
2126 that.makeEventObject(e, win),
2127 $.event.isRightClick(event));
2129 'mousedown': function(e, win) {
2130 var event = $.event.get(e, win);
2131 that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win),
2132 $.event.isRightClick(event));
2134 'mousemove': function(e, win) {
2135 that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2137 'mouseover': function(e, win) {
2138 that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2140 'mouseout': function(e, win) {
2141 that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2143 'touchstart': function(e, win) {
2144 that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2146 'touchmove': function(e, win) {
2147 that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2149 'touchend': function(e, win) {
2150 that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2153 //attach mousewheel event
2154 var handleMouseWheel = function(e, win) {
2155 var event = $.event.get(e, win);
2156 var wheel = $.event.getWheel(event);
2157 that.handleEvent('MouseWheel', e, win, wheel);
2159 //TODO(nico): this is a horrible check for non-gecko browsers!
2160 if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2161 $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2163 htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2167 register: function(obj) {
2168 this.registeredObjects.push(obj);
2171 handleEvent: function() {
2172 var args = Array.prototype.slice.call(arguments),
2173 type = args.shift();
2174 for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2175 regs[i]['on' + type].apply(regs[i], args);
2179 makeEventObject: function(e, win) {
2181 graph = this.viz.graph,
2183 ntypes = fx.nodeTypes,
2184 etypes = fx.edgeTypes;
2190 getNodeCalled: false,
2191 getEdgeCalled: false,
2192 getPos: function() {
2193 //TODO(nico): check why this can't be cache anymore when using edge detection
2194 //if(this.pos) return this.pos;
2195 var canvas = that.viz.canvas,
2196 s = canvas.getSize(),
2197 p = canvas.getPos(),
2198 ox = canvas.translateOffsetX,
2199 oy = canvas.translateOffsetY,
2200 sx = canvas.scaleOffsetX,
2201 sy = canvas.scaleOffsetY,
2202 pos = $.event.getPos(e, win);
2204 x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2205 y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2209 getNode: function() {
2210 if(this.getNodeCalled) return this.node;
2211 this.getNodeCalled = true;
2212 for(var id in graph.nodes) {
2213 var n = graph.nodes[id],
2214 geom = n && ntypes[n.getData('type')],
2215 contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2217 this.contains = contains;
2218 return that.node = this.node = n;
2221 return that.node = this.node = false;
2223 getEdge: function() {
2224 if(this.getEdgeCalled) return this.edge;
2225 this.getEdgeCalled = true;
2227 for(var id in graph.edges) {
2228 var edgeFrom = graph.edges[id];
2230 for(var edgeId in edgeFrom) {
2231 if(edgeId in hashset) continue;
2232 var e = edgeFrom[edgeId],
2233 geom = e && etypes[e.getData('type')],
2234 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2236 this.contains = contains;
2237 return that.edge = this.edge = e;
2241 return that.edge = this.edge = false;
2243 getContains: function() {
2244 if(this.getNodeCalled) return this.contains;
2246 return this.contains;
2253 * Provides the initialization function for <NodeStyles> and <Tips> implemented
2254 * by all main visualizations.
2258 initializeExtras: function() {
2259 var mem = new MouseEventsManager(this), that = this;
2260 $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2261 var obj = new Extras.Classes[k](k, that);
2262 if(obj.isEnabled()) {
2265 if(obj.setAsProperty()) {
2266 that[k.toLowerCase()] = obj;
2272 Extras.Classes = {};
2276 This class defines an Event API to be accessed by the user.
2277 The methods implemented are the ones defined in the <Options.Events> object.
2280 Extras.Classes.Events = new Class({
2281 Implements: [ExtrasInitializer, EventsInterface],
2283 initializePost: function() {
2284 this.fx = this.viz.fx;
2285 this.ntypes = this.viz.fx.nodeTypes;
2286 this.etypes = this.viz.fx.edgeTypes;
2288 this.hovered = false;
2289 this.pressed = false;
2290 this.touched = false;
2292 this.touchMoved = false;
2297 setAsProperty: $.lambda(true),
2299 onMouseUp: function(e, win, event, isRightClick) {
2300 var evt = $.event.get(e, win);
2303 this.config.onRightClick(this.hovered, event, evt);
2305 this.config.onClick(this.pressed, event, evt);
2310 this.config.onDragEnd(this.pressed, event, evt);
2312 this.config.onDragCancel(this.pressed, event, evt);
2314 this.pressed = this.moved = false;
2318 onMouseOut: function(e, win, event) {
2320 var evt = $.event.get(e, win), label;
2321 if(this.dom && (label = this.isLabel(e, win))) {
2322 this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2324 this.hovered = false;
2328 var rt = evt.relatedTarget,
2329 canvasWidget = this.canvas.getElement();
2330 while(rt && rt.parentNode) {
2331 if(canvasWidget == rt.parentNode) return;
2335 this.config.onMouseLeave(this.hovered,
2337 this.hovered = false;
2341 onMouseOver: function(e, win, event) {
2343 var evt = $.event.get(e, win), label;
2344 if(this.dom && (label = this.isLabel(e, win))) {
2345 this.hovered = this.viz.graph.getNode(label.id);
2346 this.config.onMouseEnter(this.hovered,
2351 onMouseMove: function(e, win, event) {
2352 var label, evt = $.event.get(e, win);
2355 this.config.onDragMove(this.pressed, event, evt);
2359 this.config.onMouseMove(this.hovered,
2363 var hn = this.hovered;
2364 var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2365 var contains = geom && geom.contains
2366 && geom.contains.call(this.fx, hn, event.getPos());
2368 this.config.onMouseMove(hn, event, evt);
2371 this.config.onMouseLeave(hn, event, evt);
2372 this.hovered = false;
2375 if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2376 this.config.onMouseEnter(this.hovered, event, evt);
2378 this.config.onMouseMove(false, event, evt);
2383 onMouseWheel: function(e, win, delta) {
2384 this.config.onMouseWheel(delta, $.event.get(e, win));
2387 onMouseDown: function(e, win, event) {
2388 var evt = $.event.get(e, win);
2389 this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2390 this.config.onDragStart(this.pressed, event, evt);
2393 onTouchStart: function(e, win, event) {
2394 var evt = $.event.get(e, win);
2395 this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2396 this.config.onTouchStart(this.touched, event, evt);
2399 onTouchMove: function(e, win, event) {
2400 var evt = $.event.get(e, win);
2402 this.touchMoved = true;
2403 this.config.onTouchMove(this.touched, event, evt);
2407 onTouchEnd: function(e, win, event) {
2408 var evt = $.event.get(e, win);
2410 if(this.touchMoved) {
2411 this.config.onTouchEnd(this.touched, event, evt);
2413 this.config.onTouchCancel(this.touched, event, evt);
2415 this.touched = this.touchMoved = false;
2423 A class containing tip related functions. This class is used internally.
2427 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2434 Extras.Classes.Tips = new Class({
2435 Implements: [ExtrasInitializer, EventsInterface],
2437 initializePost: function() {
2440 var tip = $('_tooltip') || document.createElement('div');
2441 tip.id = '_tooltip';
2442 tip.className = 'tip';
2443 $.extend(tip.style, {
2444 position: 'absolute',
2448 document.body.appendChild(tip);
2454 setAsProperty: $.lambda(true),
2456 onMouseOut: function(e, win) {
2458 if(this.dom && this.isLabel(e, win)) {
2463 var rt = e.relatedTarget,
2464 canvasWidget = this.canvas.getElement();
2465 while(rt && rt.parentNode) {
2466 if(canvasWidget == rt.parentNode) return;
2472 onMouseOver: function(e, win) {
2475 if(this.dom && (label = this.isLabel(e, win))) {
2476 this.node = this.viz.graph.getNode(label.id);
2477 this.config.onShow(this.tip, this.node, label);
2481 onMouseMove: function(e, win, opt) {
2482 if(this.dom && this.isLabel(e, win)) {
2483 this.setTooltipPosition($.event.getPos(e, win));
2486 var node = opt.getNode();
2491 if(this.config.force || !this.node || this.node.id != node.id) {
2493 this.config.onShow(this.tip, node, opt.getContains());
2495 this.setTooltipPosition($.event.getPos(e, win));
2499 setTooltipPosition: function(pos) {
2504 //get window dimensions
2506 'height': document.body.clientHeight,
2507 'width': document.body.clientWidth
2509 //get tooltip dimensions
2511 'width': tip.offsetWidth,
2512 'height': tip.offsetHeight
2514 //set tooltip position
2515 var x = cont.offsetX, y = cont.offsetY;
2516 style.top = ((pos.y + y + obj.height > win.height)?
2517 (pos.y - obj.height - y) : pos.y + y) + 'px';
2518 style.left = ((pos.x + obj.width + x > win.width)?
2519 (pos.x - obj.width - x) : pos.x + x) + 'px';
2522 hide: function(triggerCallback) {
2523 if(!SUGAR.util.isTouchScreen()) {
2524 this.tip.style.display = 'none';
2526 triggerCallback && this.config.onHide();
2533 Change node styles when clicking or hovering a node. This class is used internally.
2537 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2541 <Options.NodeStyles>
2543 Extras.Classes.NodeStyles = new Class({
2544 Implements: [ExtrasInitializer, EventsInterface],
2546 initializePost: function() {
2547 this.fx = this.viz.fx;
2548 this.types = this.viz.fx.nodeTypes;
2549 this.nStyles = this.config;
2550 this.nodeStylesOnHover = this.nStyles.stylesHover;
2551 this.nodeStylesOnClick = this.nStyles.stylesClick;
2552 this.hoveredNode = false;
2553 this.fx.nodeFxAnimation = new Animation();
2559 onMouseOut: function(e, win) {
2560 this.down = this.move = false;
2561 if(!this.hoveredNode) return;
2563 if(this.dom && this.isLabel(e, win)) {
2564 this.toggleStylesOnHover(this.hoveredNode, false);
2567 var rt = e.relatedTarget,
2568 canvasWidget = this.canvas.getElement();
2569 while(rt && rt.parentNode) {
2570 if(canvasWidget == rt.parentNode) return;
2573 this.toggleStylesOnHover(this.hoveredNode, false);
2574 this.hoveredNode = false;
2577 onMouseOver: function(e, win) {
2580 if(this.dom && (label = this.isLabel(e, win))) {
2581 var node = this.viz.graph.getNode(label.id);
2582 if(node.selected) return;
2583 this.hoveredNode = node;
2584 this.toggleStylesOnHover(this.hoveredNode, true);
2588 onMouseDown: function(e, win, event, isRightClick) {
2589 if(isRightClick) return;
2591 if(this.dom && (label = this.isLabel(e, win))) {
2592 this.down = this.viz.graph.getNode(label.id);
2593 } else if(!this.dom) {
2594 this.down = event.getNode();
2599 onMouseUp: function(e, win, event, isRightClick) {
2600 if(isRightClick) return;
2602 this.onClick(event.getNode());
2604 this.down = this.move = false;
2607 getRestoredStyles: function(node, type) {
2608 var restoredStyles = {},
2609 nStyles = this['nodeStylesOn' + type];
2610 for(var prop in nStyles) {
2611 restoredStyles[prop] = node.styles['$' + prop];
2613 return restoredStyles;
2616 toggleStylesOnHover: function(node, set) {
2617 if(this.nodeStylesOnHover) {
2618 this.toggleStylesOn('Hover', node, set);
2622 toggleStylesOnClick: function(node, set) {
2623 if(this.nodeStylesOnClick) {
2624 this.toggleStylesOn('Click', node, set);
2628 toggleStylesOn: function(type, node, set) {
2630 var nStyles = this.nStyles;
2634 node.styles = $.merge(node.data, {});
2636 for(var s in this['nodeStylesOn' + type]) {
2638 if(!($s in node.styles)) {
2639 node.styles[$s] = node.getData(s);
2642 viz.fx.nodeFx($.extend({
2645 'properties': that['nodeStylesOn' + type]
2647 transition: Trans.Quart.easeOut,
2652 var restoredStyles = this.getRestoredStyles(node, type);
2653 viz.fx.nodeFx($.extend({
2656 'properties': restoredStyles
2658 transition: Trans.Quart.easeOut,
2665 onClick: function(node) {
2667 var nStyles = this.nodeStylesOnClick;
2668 if(!nStyles) return;
2669 //if the node is selected then unselect it
2671 this.toggleStylesOnClick(node, false);
2672 delete node.selected;
2674 //unselect all selected nodes...
2675 this.viz.graph.eachNode(function(n) {
2677 for(var s in nStyles) {
2678 n.setData(s, n.styles['$' + s], 'end');
2683 //select clicked node
2684 this.toggleStylesOnClick(node, true);
2685 node.selected = true;
2686 delete node.hovered;
2687 this.hoveredNode = false;
2691 onMouseMove: function(e, win, event) {
2692 //if mouse button is down and moving set move=true
2693 if(this.down) this.move = true;
2694 //already handled by mouseover/out
2695 if(this.dom && this.isLabel(e, win)) return;
2696 var nStyles = this.nodeStylesOnHover;
2697 if(!nStyles) return;
2700 if(this.hoveredNode) {
2701 var geom = this.types[this.hoveredNode.getData('type')];
2702 var contains = geom && geom.contains && geom.contains.call(this.fx,
2703 this.hoveredNode, event.getPos());
2704 if(contains) return;
2706 var node = event.getNode();
2707 //if no node is being hovered then just exit
2708 if(!this.hoveredNode && !node) return;
2709 //if the node is hovered then exit
2710 if(node.hovered) return;
2711 //select hovered node
2712 if(node && !node.selected) {
2713 //check if an animation is running and exit it
2714 this.fx.nodeFxAnimation.stopTimer();
2715 //unselect all hovered nodes...
2716 this.viz.graph.eachNode(function(n) {
2717 if(n.hovered && !n.selected) {
2718 for(var s in nStyles) {
2719 n.setData(s, n.styles['$' + s], 'end');
2724 //select hovered node
2725 node.hovered = true;
2726 this.hoveredNode = node;
2727 this.toggleStylesOnHover(node, true);
2728 } else if(this.hoveredNode && !this.hoveredNode.selected) {
2729 //check if an animation is running and exit it
2730 this.fx.nodeFxAnimation.stopTimer();
2731 //unselect hovered node
2732 this.toggleStylesOnHover(this.hoveredNode, false);
2733 delete this.hoveredNode.hovered;
2734 this.hoveredNode = false;
2740 Extras.Classes.Navigation = new Class({
2741 Implements: [ExtrasInitializer, EventsInterface],
2743 initializePost: function() {
2745 this.pressed = false;
2748 onMouseWheel: function(e, win, scroll) {
2749 if(!this.config.zooming) return;
2750 $.event.stop($.event.get(e, win));
2751 var val = this.config.zooming / 1000,
2752 ans = 1 + scroll * val;
2753 this.canvas.scale(ans, ans);
2756 onMouseDown: function(e, win, eventInfo) {
2757 if(!this.config.panning) return;
2758 if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2759 this.pressed = true;
2760 this.pos = eventInfo.getPos();
2761 var canvas = this.canvas,
2762 ox = canvas.translateOffsetX,
2763 oy = canvas.translateOffsetY,
2764 sx = canvas.scaleOffsetX,
2765 sy = canvas.scaleOffsetY;
2772 onMouseMove: function(e, win, eventInfo) {
2773 if(!this.config.panning) return;
2774 if(!this.pressed) return;
2775 if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2776 var thispos = this.pos,
2777 currentPos = eventInfo.getPos(),
2778 canvas = this.canvas,
2779 ox = canvas.translateOffsetX,
2780 oy = canvas.translateOffsetY,
2781 sx = canvas.scaleOffsetX,
2782 sy = canvas.scaleOffsetY;
2787 var x = currentPos.x - thispos.x,
2788 y = currentPos.y - thispos.y;
2789 this.pos = currentPos;
2790 this.canvas.translate(x * 1/sx, y * 1/sy);
2793 onMouseUp: function(e, win, eventInfo, isRightClick) {
2794 if(!this.config.panning) return;
2795 this.pressed = false;
2808 A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to
2809 know more about <Canvas> options take a look at <Options.Canvas>.
2811 A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior
2812 across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2816 Suppose we have this HTML
2819 <div id="infovis"></div>
2822 Now we create a new Visualization
2825 var viz = new $jit.Viz({
2826 //Where to inject the canvas. Any div container will do.
2827 'injectInto':'infovis',
2828 //width and height for canvas.
2829 //Default's to the container offsetWidth and Height.
2835 The generated HTML will look like this
2839 <div id="infovis-canvaswidget" style="position:relative;">
2840 <canvas id="infovis-canvas" width=900 height=500
2841 style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2842 <div id="infovis-label"
2843 style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2849 As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2850 of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2855 //check for native canvas support
2856 var canvasType = typeof HTMLCanvasElement,
2857 supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2858 //create element function
2859 function $E(tag, props) {
2860 var elem = document.createElement(tag);
2861 for(var p in props) {
2862 if(typeof props[p] == "object") {
2863 $.extend(elem[p], props[p]);
2868 if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2869 elem = G_vmlCanvasManager.initElement(elem);
2873 //canvas widget which we will call just Canvas
2874 $jit.Canvas = Canvas = new Class({
2878 labelContainer: false,
2879 translateOffsetX: 0,
2880 translateOffsetY: 0,
2884 initialize: function(viz, opt) {
2887 var id = $.type(opt.injectInto) == 'string'?
2888 opt.injectInto:opt.injectInto.id,
2889 idLabel = id + "-label",
2891 width = opt.width || wrapper.offsetWidth,
2892 height = opt.height || wrapper.offsetHeight;
2895 var canvasOptions = {
2900 //create main wrapper
2901 this.element = $E('div', {
2902 'id': id + '-canvaswidget',
2904 'position': 'relative',
2905 'width': width + 'px',
2906 'height': height + 'px'
2909 //create label container
2910 this.labelContainer = this.createLabelContainer(opt.Label.type,
2911 idLabel, canvasOptions);
2912 //create primary canvas
2913 this.canvases.push(new Canvas.Base({
2914 config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2915 plot: function(base) {
2918 resize: function() {
2922 //create secondary canvas
2923 var back = opt.background;
2925 var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2926 this.canvases.push(new Canvas.Base(backCanvas));
2929 var len = this.canvases.length;
2931 this.element.appendChild(this.canvases[len].canvas);
2933 this.canvases[len].plot();
2936 this.element.appendChild(this.labelContainer);
2937 wrapper.appendChild(this.element);
2938 //Update canvas position when the page is scrolled.
2939 var timer = null, that = this;
2940 $.addEvent(window, 'scroll', function() {
2941 clearTimeout(timer);
2942 timer = setTimeout(function() {
2943 that.getPos(true); //update canvas position
2946 $.addEvent(window, 'click', function() {
2947 clearTimeout(timer);
2948 timer = setTimeout(function() {
2949 that.getPos(true); //update canvas position
2952 sb = document.getElementById('sb'+id);
2953 $.addEvent(sb, 'scroll', function() {
2954 clearTimeout(timer);
2955 timer = setTimeout(function() {
2956 that.getPos(true); //update canvas position
2963 Returns the main canvas context object
2968 var ctx = canvas.getCtx();
2969 //Now I can use the native canvas context
2970 //and for example change some canvas styles
2971 ctx.globalAlpha = 1;
2974 getCtx: function(i) {
2975 return this.canvases[i || 0].getCtx();
2980 Returns the current Configuration for this Canvas Widget.
2985 var config = canvas.getConfig();
2988 getConfig: function() {
2994 Returns the main Canvas DOM wrapper
2999 var wrapper = canvas.getElement();
3000 //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3003 getElement: function() {
3004 return this.element;
3009 Returns canvas dimensions.
3013 An object with *width* and *height* properties.
3017 canvas.getSize(); //returns { width: 900, height: 500 }
3020 getSize: function(i) {
3021 return this.canvases[i || 0].getSize();
3030 width - New canvas width.
3031 height - New canvas height.
3036 canvas.resize(width, height);
3040 resize: function(width, height) {
3042 this.translateOffsetX = this.translateOffsetY = 0;
3043 this.scaleOffsetX = this.scaleOffsetY = 1;
3044 for(var i=0, l=this.canvases.length; i<l; i++) {
3045 this.canvases[i].resize(width, height);
3047 var style = this.element.style;
3048 style.width = width + 'px';
3049 style.height = height + 'px';
3050 if(this.labelContainer)
3051 this.labelContainer.style.width = width + 'px';
3056 Applies a translation to the canvas.
3060 x - (number) x offset.
3061 y - (number) y offset.
3062 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3067 canvas.translate(30, 30);
3071 translate: function(x, y, disablePlot) {
3072 this.translateOffsetX += x*this.scaleOffsetX;
3073 this.translateOffsetY += y*this.scaleOffsetY;
3074 for(var i=0, l=this.canvases.length; i<l; i++) {
3075 this.canvases[i].translate(x, y, disablePlot);
3085 x - (number) scale value.
3086 y - (number) scale value.
3087 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3092 canvas.scale(0.5, 0.5);
3096 scale: function(x, y, disablePlot) {
3097 var px = this.scaleOffsetX * x,
3098 py = this.scaleOffsetY * y;
3099 var dx = this.translateOffsetX * (x -1) / px,
3100 dy = this.translateOffsetY * (y -1) / py;
3101 this.scaleOffsetX = px;
3102 this.scaleOffsetY = py;
3103 for(var i=0, l=this.canvases.length; i<l; i++) {
3104 this.canvases[i].scale(x, y, true);
3106 this.translate(dx, dy, false);
3111 Returns the canvas position as an *x, y* object.
3115 force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3119 An object with *x* and *y* properties.
3123 canvas.getPos(true); //returns { x: 900, y: 500 }
3126 getPos: function(force){
3127 if(force || !this.pos) {
3128 return this.pos = $.getPos(this.getElement());
3138 this.canvases[i||0].clear();
3141 path: function(type, action){
3142 var ctx = this.canvases[0].getCtx();
3149 createLabelContainer: function(type, idLabel, dim) {
3150 var NS = 'http://www.w3.org/2000/svg';
3151 if(type == 'HTML' || type == 'Native') {
3155 'overflow': 'visible',
3156 'position': 'absolute',
3159 'width': dim.width + 'px',
3163 } else if(type == 'SVG') {
3164 var svgContainer = document.createElementNS(NS, 'svg:svg');
3165 svgContainer.setAttribute("width", dim.width);
3166 svgContainer.setAttribute('height', dim.height);
3167 var style = svgContainer.style;
3168 style.position = 'absolute';
3169 style.left = style.top = '0px';
3170 var labelContainer = document.createElementNS(NS, 'svg:g');
3171 labelContainer.setAttribute('width', dim.width);
3172 labelContainer.setAttribute('height', dim.height);
3173 labelContainer.setAttribute('x', 0);
3174 labelContainer.setAttribute('y', 0);
3175 labelContainer.setAttribute('id', idLabel);
3176 svgContainer.appendChild(labelContainer);
3177 return svgContainer;
3181 //base canvas wrapper
3182 Canvas.Base = new Class({
3183 translateOffsetX: 0,
3184 translateOffsetY: 0,
3188 initialize: function(viz) {
3190 this.opt = viz.config;
3192 this.createCanvas();
3193 this.translateToCenter();
3195 createCanvas: function() {
3198 height = opt.height;
3199 this.canvas = $E('canvas', {
3200 'id': opt.injectInto + opt.idSuffix,
3204 'position': 'absolute',
3207 'width': width + 'px',
3208 'height': height + 'px'
3212 getCtx: function() {
3214 return this.ctx = this.canvas.getContext('2d');
3217 getSize: function() {
3218 if(this.size) return this.size;
3219 var canvas = this.canvas;
3220 return this.size = {
3221 width: canvas.width,
3222 height: canvas.height
3225 translateToCenter: function(ps) {
3226 var size = this.getSize(),
3227 width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3228 height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3229 var ctx = this.getCtx();
3230 ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3231 ctx.translate(width/2, height/2);
3233 resize: function(width, height) {
3234 var size = this.getSize(),
3235 canvas = this.canvas,
3236 styles = canvas.style;
3238 canvas.width = width;
3239 canvas.height = height;
3240 styles.width = width + "px";
3241 styles.height = height + "px";
3242 //small ExCanvas fix
3243 if(!supportsCanvas) {
3244 this.translateToCenter(size);
3246 this.translateToCenter();
3248 this.translateOffsetX =
3249 this.translateOffsetY = 0;
3251 this.scaleOffsetY = 1;
3253 this.viz.resize(width, height, this);
3255 translate: function(x, y, disablePlot) {
3256 var sx = this.scaleOffsetX,
3257 sy = this.scaleOffsetY;
3258 this.translateOffsetX += x*sx;
3259 this.translateOffsetY += y*sy;
3260 this.getCtx().translate(x, y);
3261 !disablePlot && this.plot();
3263 scale: function(x, y, disablePlot) {
3264 this.scaleOffsetX *= x;
3265 this.scaleOffsetY *= y;
3266 this.getCtx().scale(x, y);
3267 !disablePlot && this.plot();
3270 var size = this.getSize(),
3271 ox = this.translateOffsetX,
3272 oy = this.translateOffsetY,
3273 sx = this.scaleOffsetX,
3274 sy = this.scaleOffsetY;
3275 this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx,
3276 (-size.height / 2 - oy) * 1/sy,
3277 size.width * 1/sx, size.height * 1/sy);
3281 this.viz.plot(this);
3284 //background canvases
3285 //TODO(nico): document this!
3286 Canvas.Background = {};
3287 Canvas.Background.Circles = new Class({
3288 initialize: function(viz, options) {
3290 this.config = $.merge({
3291 idSuffix: '-bkcanvas',
3298 resize: function(width, height, base) {
3301 plot: function(base) {
3302 var canvas = base.canvas,
3303 ctx = base.getCtx(),
3305 styles = conf.CanvasStyles;
3307 for(var s in styles) ctx[s] = styles[s];
3308 var n = conf.numberOfCircles,
3309 rho = conf.levelDistance;
3310 for(var i=1; i<=n; i++) {
3312 ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3316 //TODO(nico): print labels too!
3319 Canvas.Background.Fade = new Class({
3320 initialize: function(viz, options) {
3322 this.config = $.merge({
3323 idSuffix: '-bkcanvas',
3328 resize: function(width, height, base) {
3331 plot: function(base) {
3332 var canvas = base.canvas,
3333 ctx = base.getCtx(),
3335 styles = conf.CanvasStyles,
3336 size = base.getSize();
3337 ctx.fillStyle = 'rgb(255,255,255)';
3338 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3339 //TODO(nico): print labels too!
3348 * Defines the <Polar> class.
3352 * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3356 * <http://en.wikipedia.org/wiki/Polar_coordinates>
3363 A multi purpose polar representation.
3367 The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3371 <http://en.wikipedia.org/wiki/Polar_coordinates>
3379 var Polar = function(theta, rho) {
3390 Returns a complex number.
3394 simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3400 getc: function(simple) {
3401 return this.toComplex(simple);
3407 Returns a <Polar> representation.
3411 A variable in polar coordinates.
3425 v - A <Complex> or <Polar> instance.
3430 this.theta = v.theta; this.rho = v.rho;
3436 Sets a <Complex> number.
3440 x - A <Complex> number real part.
3441 y - A <Complex> number imaginary part.
3444 setc: function(x, y) {
3445 this.rho = Math.sqrt(x * x + y * y);
3446 this.theta = Math.atan2(y, x);
3447 if(this.theta < 0) this.theta += Math.PI * 2;
3453 Sets a polar number.
3457 theta - A <Polar> number angle property.
3458 rho - A <Polar> number rho property.
3461 setp: function(theta, rho) {
3469 Returns a copy of the current object.
3473 A copy of the real object.
3476 return new Polar(this.theta, this.rho);
3482 Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3486 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*.
3490 A new <Complex> instance.
3492 toComplex: function(simple) {
3493 var x = Math.cos(this.theta) * this.rho;
3494 var y = Math.sin(this.theta) * this.rho;
3495 if(simple) return { 'x': x, 'y': y};
3496 return new Complex(x, y);
3502 Adds two <Polar> instances.
3506 polar - A <Polar> number.
3510 A new Polar instance.
3512 add: function(polar) {
3513 return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3519 Scales a polar norm.
3523 number - A scale factor.
3527 A new Polar instance.
3529 scale: function(number) {
3530 return new Polar(this.theta, this.rho * number);
3538 Returns *true* if the theta and rho properties are equal.
3542 c - A <Polar> number.
3546 *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3548 equals: function(c) {
3549 return this.theta == c.theta && this.rho == c.rho;
3555 Adds two <Polar> instances affecting the current object.
3559 polar - A <Polar> instance.
3565 $add: function(polar) {
3566 this.theta = this.theta + polar.theta; this.rho += polar.rho;
3573 Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3577 polar - A <Polar> instance.
3583 $madd: function(polar) {
3584 this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3592 Scales a polar instance affecting the object.
3596 number - A scaling factor.
3602 $scale: function(number) {
3610 Calculates a polar interpolation between two points at a given delta moment.
3614 elem - A <Polar> instance.
3615 delta - A delta factor ranging [0, 1].
3619 A new <Polar> instance representing an interpolation between _this_ and _elem_
3621 interpolate: function(elem, delta) {
3622 var pi = Math.PI, pi2 = pi * 2;
3623 var ch = function(t) {
3624 var a = (t < 0)? (t % pi2) + pi2 : t % pi2;
3627 var tt = this.theta, et = elem.theta;
3628 var sum, diff = Math.abs(tt - et);
3631 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3633 sum = ch((et - pi2 + (tt - (et)) * delta));
3635 } else if(diff >= pi) {
3637 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3639 sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3642 sum = ch((et + (tt - et) * delta)) ;
3644 var r = (this.rho - elem.rho) * delta + elem.rho;
3653 var $P = function(a, b) { return new Polar(a, b); };
3655 Polar.KER = $P(0, 0);
3662 * Defines the <Complex> class.
3666 * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3670 * <http://en.wikipedia.org/wiki/Complex_number>
3677 A multi-purpose Complex Class with common methods.
3681 The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3685 <http://en.wikipedia.org/wiki/Complex_number>
3689 x - _optional_ A Complex number real part.
3690 y - _optional_ A Complex number imaginary part.
3694 var Complex = function(x, y) {
3699 $jit.Complex = Complex;
3701 Complex.prototype = {
3705 Returns a complex number.
3718 Returns a <Polar> representation of this number.
3722 simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3726 A variable in <Polar> coordinates.
3728 getp: function(simple) {
3729 return this.toPolar(simple);
3740 c - A <Complex> or <Polar> instance.
3752 Sets a complex number.
3756 x - A <Complex> number Real part.
3757 y - A <Complex> number Imaginary part.
3760 setc: function(x, y) {
3768 Sets a polar number.
3772 theta - A <Polar> number theta property.
3773 rho - A <Polar> number rho property.
3776 setp: function(theta, rho) {
3777 this.x = Math.cos(theta) * rho;
3778 this.y = Math.sin(theta) * rho;
3784 Returns a copy of the current object.
3788 A copy of the real object.
3791 return new Complex(this.x, this.y);
3797 Transforms cartesian to polar coordinates.
3801 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*.
3805 A new <Polar> instance.
3808 toPolar: function(simple) {
3809 var rho = this.norm();
3810 var atan = Math.atan2(this.y, this.x);
3811 if(atan < 0) atan += Math.PI * 2;
3812 if(simple) return { 'theta': atan, 'rho': rho };
3813 return new Polar(atan, rho);
3818 Calculates a <Complex> number norm.
3822 A real number representing the complex norm.
3825 return Math.sqrt(this.squaredNorm());
3831 Calculates a <Complex> number squared norm.
3835 A real number representing the complex squared norm.
3837 squaredNorm: function () {
3838 return this.x*this.x + this.y*this.y;
3844 Returns the result of adding two complex numbers.
3846 Does not alter the original object.
3850 pos - A <Complex> instance.
3854 The result of adding two complex numbers.
3856 add: function(pos) {
3857 return new Complex(this.x + pos.x, this.y + pos.y);
3863 Returns the result of multiplying two <Complex> numbers.
3865 Does not alter the original object.
3869 pos - A <Complex> instance.
3873 The result of multiplying two complex numbers.
3875 prod: function(pos) {
3876 return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3882 Returns the conjugate of this <Complex> number.
3884 Does not alter the original object.
3888 The conjugate of this <Complex> number.
3890 conjugate: function() {
3891 return new Complex(this.x, -this.y);
3898 Returns the result of scaling a <Complex> instance.
3900 Does not alter the original object.
3904 factor - A scale factor.
3908 The result of scaling this complex to a factor.
3910 scale: function(factor) {
3911 return new Complex(this.x * factor, this.y * factor);
3919 Returns *true* if both real and imaginary parts are equal.
3923 c - A <Complex> instance.
3927 A boolean instance indicating if both <Complex> numbers are equal.
3929 equals: function(c) {
3930 return this.x == c.x && this.y == c.y;
3936 Returns the result of adding two <Complex> numbers.
3938 Alters the original object.
3942 pos - A <Complex> instance.
3946 The result of adding two complex numbers.
3948 $add: function(pos) {
3949 this.x += pos.x; this.y += pos.y;
3956 Returns the result of multiplying two <Complex> numbers.
3958 Alters the original object.
3962 pos - A <Complex> instance.
3966 The result of multiplying two complex numbers.
3968 $prod:function(pos) {
3969 var x = this.x, y = this.y;
3970 this.x = x*pos.x - y*pos.y;
3971 this.y = y*pos.x + x*pos.y;
3978 Returns the conjugate for this <Complex>.
3980 Alters the original object.
3984 The conjugate for this complex.
3986 $conjugate: function() {
3994 Returns the result of scaling a <Complex> instance.
3996 Alters the original object.
4000 factor - A scale factor.
4004 The result of scaling this complex to a factor.
4006 $scale: function(factor) {
4007 this.x *= factor; this.y *= factor;
4014 Returns the division of two <Complex> numbers.
4016 Alters the original object.
4020 pos - A <Complex> number.
4024 The result of scaling this complex to a factor.
4026 $div: function(pos) {
4027 var x = this.x, y = this.y;
4028 var sq = pos.squaredNorm();
4029 this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4030 return this.$scale(1 / sq);
4034 var $C = function(a, b) { return new Complex(a, b); };
4036 Complex.KER = $C(0, 0);
4048 A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4050 An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4055 //create new visualization
4056 var viz = new $jit.Viz(options);
4060 viz.graph; //<Graph> instance
4065 The following <Graph.Util> methods are implemented in <Graph>
4067 - <Graph.Util.getNode>
4068 - <Graph.Util.eachNode>
4069 - <Graph.Util.computeLevels>
4070 - <Graph.Util.eachBFS>
4071 - <Graph.Util.clean>
4072 - <Graph.Util.getClosestNodeToPos>
4073 - <Graph.Util.getClosestNodeToOrigin>
4077 $jit.Graph = new Class({
4079 initialize: function(opt, Node, Edge, Label) {
4080 var innerOptions = {
4087 this.opt = $.merge(innerOptions, opt || {});
4091 //add nodeList methods
4094 for(var p in Accessors) {
4095 that.nodeList[p] = (function(p) {
4097 var args = Array.prototype.slice.call(arguments);
4098 that.eachNode(function(n) {
4099 n[p].apply(n, args);
4110 Returns a <Graph.Node> by *id*.
4114 id - (string) A <Graph.Node> id.
4119 var node = graph.getNode('nodeId');
4122 getNode: function(id) {
4123 if(this.hasNode(id)) return this.nodes[id];
4130 Returns a <Graph.Node> by *name*.
4134 name - (string) A <Graph.Node> name.
4139 var node = graph.getByName('someName');
4142 getByName: function(name) {
4143 for(var id in this.nodes) {
4144 var n = this.nodes[id];
4145 if(n.name == name) return n;
4151 Method: getAdjacence
4153 Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4157 id - (string) A <Graph.Node> id.
4158 id2 - (string) A <Graph.Node> id.
4160 getAdjacence: function (id, id2) {
4161 if(id in this.edges) {
4162 return this.edges[id][id2];
4174 obj - An object with the properties described below
4176 id - (string) A node id
4177 name - (string) A node's name
4178 data - (object) A node's data hash
4184 addNode: function(obj) {
4185 if(!this.nodes[obj.id]) {
4186 var edges = this.edges[obj.id] = {};
4187 this.nodes[obj.id] = new Graph.Node($.extend({
4190 'data': $.merge(obj.data || {}, {}),
4191 'adjacencies': edges
4198 return this.nodes[obj.id];
4202 Method: addAdjacence
4204 Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4208 obj - (object) A <Graph.Node> object.
4209 obj2 - (object) Another <Graph.Node> object.
4210 data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4214 <Graph.Node>, <Graph.Adjacence>
4216 addAdjacence: function (obj, obj2, data) {
4217 if(!this.hasNode(obj.id)) { this.addNode(obj); }
4218 if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4219 obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4220 if(!obj.adjacentTo(obj2)) {
4221 var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4222 var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4223 adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4224 return adjsObj[obj2.id];
4226 return this.edges[obj.id][obj2.id];
4232 Removes a <Graph.Node> matching the specified *id*.
4236 id - (string) A node's id.
4239 removeNode: function(id) {
4240 if(this.hasNode(id)) {
4241 delete this.nodes[id];
4242 var adjs = this.edges[id];
4243 for(var to in adjs) {
4244 delete this.edges[to][id];
4246 delete this.edges[id];
4251 Method: removeAdjacence
4253 Removes a <Graph.Adjacence> matching *id1* and *id2*.
4257 id1 - (string) A <Graph.Node> id.
4258 id2 - (string) A <Graph.Node> id.
4260 removeAdjacence: function(id1, id2) {
4261 delete this.edges[id1][id2];
4262 delete this.edges[id2][id1];
4268 Returns a boolean indicating if the node belongs to the <Graph> or not.
4272 id - (string) Node id.
4274 hasNode: function(id) {
4275 return id in this.nodes;
4284 empty: function() { this.nodes = {}; this.edges = {};}
4288 var Graph = $jit.Graph;
4293 Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4299 var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4301 type = type || 'current';
4302 prefix = "$" + (prefix ? prefix + "-" : "");
4304 if(type == 'current') {
4306 } else if(type == 'start') {
4307 data = this.startData;
4308 } else if(type == 'end') {
4309 data = this.endData;
4312 var dollar = prefix + prop;
4315 return data[dollar];
4318 if(!this.Config.overridable)
4319 return prefixConfig[prop] || 0;
4321 return (dollar in data) ?
4322 data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4325 var setDataInternal = function(prefix, prop, value, type) {
4326 type = type || 'current';
4327 prefix = '$' + (prefix ? prefix + '-' : '');
4331 if(type == 'current') {
4333 } else if(type == 'start') {
4334 data = this.startData;
4335 } else if(type == 'end') {
4336 data = this.endData;
4339 data[prefix + prop] = value;
4342 var removeDataInternal = function(prefix, properties) {
4343 prefix = '$' + (prefix ? prefix + '-' : '');
4345 $.each(properties, function(prop) {
4346 var pref = prefix + prop;
4347 delete that.data[pref];
4348 delete that.endData[pref];
4349 delete that.startData[pref];
4357 Returns the specified data value property.
4358 This is useful for querying special/reserved <Graph.Node> data properties
4359 (i.e dollar prefixed properties).
4363 prop - (string) The name of the property. The dollar sign is not needed. For
4364 example *getData(width)* will return *data.$width*.
4365 type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4366 data properties also. These properties are used when making animations.
4367 force - (boolean) Whether to obtain the true value of the property (equivalent to
4368 *data.$prop*) or to check for *node.overridable = true* first.
4372 The value of the dollar prefixed property or the global Node/Edge property
4373 value if *overridable=false*
4377 node.getData('width'); //will return node.data.$width if Node.overridable=true;
4380 getData: function(prop, type, force) {
4381 return getDataInternal.call(this, "", prop, type, force, this.Config);
4388 Sets the current data property with some specific value.
4389 This method is only useful for reserved (dollar prefixed) properties.
4393 prop - (string) The name of the property. The dollar sign is not necessary. For
4394 example *setData(width)* will set *data.$width*.
4395 value - (mixed) The value to store.
4396 type - (string) The type of the data property to store. Default's "current" but
4397 can also be "start" or "end".
4402 node.setData('width', 30);
4405 If we were to make an animation of a node/edge width then we could do
4408 var node = viz.getNode('nodeId');
4409 //set start and end values
4410 node.setData('width', 10, 'start');
4411 node.setData('width', 30, 'end');
4412 //will animate nodes width property
4414 modes: ['node-property:width'],
4419 setData: function(prop, value, type) {
4420 setDataInternal.call(this, "", prop, value, type);
4426 Convenience method to set multiple data values at once.
4430 types - (array|string) A set of 'current', 'end' or 'start' values.
4431 obj - (object) A hash containing the names and values of the properties to be altered.
4435 node.setDataset(['current', 'end'], {
4437 'color': ['#fff', '#ccc']
4440 node.setDataset('end', {
4451 setDataset: function(types, obj) {
4452 types = $.splat(types);
4453 for(var attr in obj) {
4454 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4455 this.setData(attr, val[i], types[i]);
4463 Remove data properties.
4467 One or more property names as arguments. The dollar sign is not needed.
4471 node.removeData('width'); //now the default width value is returned
4474 removeData: function() {
4475 removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4479 Method: getCanvasStyle
4481 Returns the specified canvas style data value property. This is useful for
4482 querying special/reserved <Graph.Node> canvas style data properties (i.e.
4483 dollar prefixed properties that match with $canvas-<name of canvas style>).
4487 prop - (string) The name of the property. The dollar sign is not needed. For
4488 example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4489 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4490 data properties also.
4494 node.getCanvasStyle('shadowBlur');
4501 getCanvasStyle: function(prop, type, force) {
4502 return getDataInternal.call(
4503 this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4507 Method: setCanvasStyle
4509 Sets the canvas style data property with some specific value.
4510 This method is only useful for reserved (dollar prefixed) properties.
4514 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4515 value - (mixed) The value to set to the property.
4516 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4521 node.setCanvasStyle('shadowBlur', 30);
4524 If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4527 var node = viz.getNode('nodeId');
4528 //set start and end values
4529 node.setCanvasStyle('shadowBlur', 10, 'start');
4530 node.setCanvasStyle('shadowBlur', 30, 'end');
4531 //will animate nodes canvas style property for nodes
4533 modes: ['node-style:shadowBlur'],
4540 <Accessors.setData>.
4542 setCanvasStyle: function(prop, value, type) {
4543 setDataInternal.call(this, 'canvas', prop, value, type);
4547 Method: setCanvasStyles
4549 Convenience method to set multiple styles at once.
4553 types - (array|string) A set of 'current', 'end' or 'start' values.
4554 obj - (object) A hash containing the names and values of the properties to be altered.
4558 <Accessors.setDataset>.
4560 setCanvasStyles: function(types, obj) {
4561 types = $.splat(types);
4562 for(var attr in obj) {
4563 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4564 this.setCanvasStyle(attr, val[i], types[i]);
4570 Method: removeCanvasStyle
4572 Remove canvas style properties from data.
4576 A variable number of canvas style strings.
4580 <Accessors.removeData>.
4582 removeCanvasStyle: function() {
4583 removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4587 Method: getLabelData
4589 Returns the specified label data value property. This is useful for
4590 querying special/reserved <Graph.Node> label options (i.e.
4591 dollar prefixed properties that match with $label-<name of label style>).
4595 prop - (string) The name of the property. The dollar sign prefix is not needed. For
4596 example *getLabelData(size)* will return *data[$label-size]*.
4597 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4598 data properties also.
4602 <Accessors.getData>.
4604 getLabelData: function(prop, type, force) {
4605 return getDataInternal.call(
4606 this, 'label', prop, type, force, this.Label);
4610 Method: setLabelData
4612 Sets the current label data with some specific value.
4613 This method is only useful for reserved (dollar prefixed) properties.
4617 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4618 value - (mixed) The value to set to the property.
4619 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4624 node.setLabelData('size', 30);
4627 If we were to make an animation of a node label size then we could do
4630 var node = viz.getNode('nodeId');
4631 //set start and end values
4632 node.setLabelData('size', 10, 'start');
4633 node.setLabelData('size', 30, 'end');
4634 //will animate nodes label size
4636 modes: ['label-property:size'],
4643 <Accessors.setData>.
4645 setLabelData: function(prop, value, type) {
4646 setDataInternal.call(this, 'label', prop, value, type);
4650 Method: setLabelDataset
4652 Convenience function to set multiple label data at once.
4656 types - (array|string) A set of 'current', 'end' or 'start' values.
4657 obj - (object) A hash containing the names and values of the properties to be altered.
4661 <Accessors.setDataset>.
4663 setLabelDataset: function(types, obj) {
4664 types = $.splat(types);
4665 for(var attr in obj) {
4666 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4667 this.setLabelData(attr, val[i], types[i]);
4673 Method: removeLabelData
4675 Remove label properties from data.
4679 A variable number of label property strings.
4683 <Accessors.removeData>.
4685 removeLabelData: function() {
4686 removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4698 <Accessors> methods.
4700 The following <Graph.Util> methods are implemented by <Graph.Node>
4702 - <Graph.Util.eachAdjacency>
4703 - <Graph.Util.eachLevel>
4704 - <Graph.Util.eachSubgraph>
4705 - <Graph.Util.eachSubnode>
4706 - <Graph.Util.anySubnode>
4707 - <Graph.Util.getSubnodes>
4708 - <Graph.Util.getParents>
4709 - <Graph.Util.isDescendantOf>
4711 Graph.Node = new Class({
4713 initialize: function(opt, complex, Node, Edge, Label) {
4714 var innerOptions = {
4731 'pos': (complex && $C(0, 0)) || $P(0, 0),
4732 'startPos': (complex && $C(0, 0)) || $P(0, 0),
4733 'endPos': (complex && $C(0, 0)) || $P(0, 0)
4736 $.extend(this, $.extend(innerOptions, opt));
4737 this.Config = this.Node = Node;
4745 Indicates if the node is adjacent to the node specified by id
4749 id - (string) A node id.
4753 node.adjacentTo('nodeId') == true;
4756 adjacentTo: function(node) {
4757 return node.id in this.adjacencies;
4761 Method: getAdjacency
4763 Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4767 id - (string) A node id.
4769 getAdjacency: function(id) {
4770 return this.adjacencies[id];
4776 Returns the position of the node.
4780 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4784 A <Complex> or <Polar> instance.
4788 var pos = node.getPos('end');
4791 getPos: function(type) {
4792 type = type || "current";
4793 if(type == "current") {
4795 } else if(type == "end") {
4797 } else if(type == "start") {
4798 return this.startPos;
4804 Sets the node's position.
4808 value - (object) A <Complex> or <Polar> instance.
4809 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4813 node.setPos(new $jit.Complex(0, 0), 'end');
4816 setPos: function(value, type) {
4817 type = type || "current";
4819 if(type == "current") {
4821 } else if(type == "end") {
4823 } else if(type == "start") {
4824 pos = this.startPos;
4830 Graph.Node.implement(Accessors);
4833 Class: Graph.Adjacence
4835 A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4839 <Accessors> methods.
4843 <Graph>, <Graph.Node>
4847 nodeFrom - A <Graph.Node> connected by this edge.
4848 nodeTo - Another <Graph.Node> connected by this edge.
4849 data - Node data property containing a hash (i.e {}) with custom options.
4851 Graph.Adjacence = new Class({
4853 initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4854 this.nodeFrom = nodeFrom;
4855 this.nodeTo = nodeTo;
4856 this.data = data || {};
4857 this.startData = {};
4859 this.Config = this.Edge = Edge;
4864 Graph.Adjacence.implement(Accessors);
4869 <Graph> traversal and processing utility object.
4873 For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4879 For internal use only. Provides a filtering function based on flags.
4881 filter: function(param) {
4882 if(!param || !($.type(param) == 'string')) return function() { return true; };
4883 var props = param.split(" ");
4884 return function(elem) {
4885 for(var i=0; i<props.length; i++) {
4886 if(elem[props[i]]) {
4896 Returns a <Graph.Node> by *id*.
4898 Also implemented by:
4904 graph - (object) A <Graph> instance.
4905 id - (string) A <Graph.Node> id.
4910 $jit.Graph.Util.getNode(graph, 'nodeid');
4912 graph.getNode('nodeid');
4915 getNode: function(graph, id) {
4916 return graph.nodes[id];
4922 Iterates over <Graph> nodes performing an *action*.
4924 Also implemented by:
4930 graph - (object) A <Graph> instance.
4931 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4935 $jit.Graph.Util.eachNode(graph, function(node) {
4939 graph.eachNode(function(node) {
4944 eachNode: function(graph, action, flags) {
4945 var filter = this.filter(flags);
4946 for(var i in graph.nodes) {
4947 if(filter(graph.nodes[i])) action(graph.nodes[i]);
4952 Method: eachAdjacency
4954 Iterates over <Graph.Node> adjacencies applying the *action* function.
4956 Also implemented by:
4962 node - (object) A <Graph.Node>.
4963 action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4967 $jit.Graph.Util.eachAdjacency(node, function(adj) {
4968 alert(adj.nodeTo.name);
4971 node.eachAdjacency(function(adj) {
4972 alert(adj.nodeTo.name);
4976 eachAdjacency: function(node, action, flags) {
4977 var adj = node.adjacencies, filter = this.filter(flags);
4978 for(var id in adj) {
4981 if(a.nodeFrom != node) {
4982 var tmp = a.nodeFrom;
4983 a.nodeFrom = a.nodeTo;
4992 Method: computeLevels
4994 Performs a BFS traversal setting the correct depth for each node.
4996 Also implemented by:
5002 The depth of each node can then be accessed by
5007 graph - (object) A <Graph>.
5008 id - (string) A starting node id for the BFS traversal.
5009 startDepth - (optional|number) A minimum depth value. Default's 0.
5012 computeLevels: function(graph, id, startDepth, flags) {
5013 startDepth = startDepth || 0;
5014 var filter = this.filter(flags);
5015 this.eachNode(graph, function(elem) {
5019 var root = graph.getNode(id);
5020 root._depth = startDepth;
5022 while(queue.length != 0) {
5023 var node = queue.pop();
5025 this.eachAdjacency(node, function(adj) {
5027 if(n._flag == false && filter(n)) {
5028 if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5038 Performs a BFS traversal applying *action* to each <Graph.Node>.
5040 Also implemented by:
5046 graph - (object) A <Graph>.
5047 id - (string) A starting node id for the BFS traversal.
5048 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5052 $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5056 graph.eachBFS('mynodeid', function(node) {
5061 eachBFS: function(graph, id, action, flags) {
5062 var filter = this.filter(flags);
5064 var queue = [graph.getNode(id)];
5065 while(queue.length != 0) {
5066 var node = queue.pop();
5068 action(node, node._depth);
5069 this.eachAdjacency(node, function(adj) {
5071 if(n._flag == false && filter(n)) {
5082 Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5084 Also implemented by:
5090 node - (object) A <Graph.Node>.
5091 levelBegin - (number) A relative level value.
5092 levelEnd - (number) A relative level value.
5093 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5096 eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5097 var d = node._depth, filter = this.filter(flags), that = this;
5098 levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5099 (function loopLevel(node, levelBegin, levelEnd) {
5100 var d = node._depth;
5101 if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5103 that.eachAdjacency(node, function(adj) {
5105 if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5108 })(node, levelBegin + d, levelEnd + d);
5112 Method: eachSubgraph
5114 Iterates over a node's children recursively.
5116 Also implemented by:
5121 node - (object) A <Graph.Node>.
5122 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5126 $jit.Graph.Util.eachSubgraph(node, function(node) {
5130 node.eachSubgraph(function(node) {
5135 eachSubgraph: function(node, action, flags) {
5136 this.eachLevel(node, 0, false, action, flags);
5142 Iterates over a node's children (without deeper recursion).
5144 Also implemented by:
5149 node - (object) A <Graph.Node>.
5150 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5154 $jit.Graph.Util.eachSubnode(node, function(node) {
5158 node.eachSubnode(function(node) {
5163 eachSubnode: function(node, action, flags) {
5164 this.eachLevel(node, 1, 1, action, flags);
5170 Returns *true* if any subnode matches the given condition.
5172 Also implemented by:
5177 node - (object) A <Graph.Node>.
5178 cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5182 $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5184 node.anySubnode(function(node) { return node.name == 'mynodename'; });
5187 anySubnode: function(node, cond, flags) {
5189 cond = cond || $.lambda(true);
5190 var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5191 this.eachSubnode(node, function(elem) {
5192 if(c(elem)) flag = true;
5200 Collects all subnodes for a specified node.
5201 The *level* parameter filters nodes having relative depth of *level* from the root node.
5203 Also implemented by:
5208 node - (object) A <Graph.Node>.
5209 level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5215 getSubnodes: function(node, level, flags) {
5216 var ans = [], that = this;
5218 var levelStart, levelEnd;
5219 if($.type(level) == 'array') {
5220 levelStart = level[0];
5221 levelEnd = level[1];
5224 levelEnd = Number.MAX_VALUE - node._depth;
5226 this.eachLevel(node, levelStart, levelEnd, function(n) {
5236 Returns an Array of <Graph.Nodes> which are parents of the given node.
5238 Also implemented by:
5243 node - (object) A <Graph.Node>.
5246 An Array of <Graph.Nodes>.
5250 var pars = $jit.Graph.Util.getParents(node);
5252 var pars = node.getParents();
5254 if(pars.length > 0) {
5255 //do stuff with parents
5259 getParents: function(node) {
5261 this.eachAdjacency(node, function(adj) {
5263 if(n._depth < node._depth) ans.push(n);
5269 Method: isDescendantOf
5271 Returns a boolean indicating if some node is descendant of the node with the given id.
5273 Also implemented by:
5279 node - (object) A <Graph.Node>.
5280 id - (string) A <Graph.Node> id.
5284 $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5286 node.isDescendantOf('nodeid');//true|false
5289 isDescendantOf: function(node, id) {
5290 if(node.id == id) return true;
5291 var pars = this.getParents(node), ans = false;
5292 for ( var i = 0; !ans && i < pars.length; i++) {
5293 ans = ans || this.isDescendantOf(pars[i], id);
5301 Cleans flags from nodes.
5303 Also implemented by:
5308 graph - A <Graph> instance.
5310 clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5313 Method: getClosestNodeToOrigin
5315 Returns the closest node to the center of canvas.
5317 Also implemented by:
5323 graph - (object) A <Graph> instance.
5324 prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5327 getClosestNodeToOrigin: function(graph, prop, flags) {
5328 return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5332 Method: getClosestNodeToPos
5334 Returns the closest node to the given position.
5336 Also implemented by:
5342 graph - (object) A <Graph> instance.
5343 pos - (object) A <Complex> or <Polar> instance.
5344 prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5347 getClosestNodeToPos: function(graph, pos, prop, flags) {
5349 prop = prop || 'current';
5350 pos = pos && pos.getc(true) || Complex.KER;
5351 var distance = function(a, b) {
5352 var d1 = a.x - b.x, d2 = a.y - b.y;
5353 return d1 * d1 + d2 * d2;
5355 this.eachNode(graph, function(elem) {
5356 node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5357 node.getPos(prop).getc(true), pos)) ? elem : node;
5363 //Append graph methods to <Graph>
5364 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5365 Graph.prototype[m] = function() {
5366 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5370 //Append node methods to <Graph.Node>
5371 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5372 Graph.Node.prototype[m] = function() {
5373 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5385 Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5386 morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5398 initialize: function(viz) {
5405 Removes one or more <Graph.Nodes> from the visualization.
5406 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5410 node - (string|array) The node's id. Can also be an array having many ids.
5411 opt - (object) Animation options. It's an object with optional properties described below
5412 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5413 duration - Described in <Options.Fx>.
5414 fps - Described in <Options.Fx>.
5415 transition - Described in <Options.Fx>.
5416 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5420 var viz = new $jit.Viz(options);
5421 viz.op.removeNode('nodeId', {
5425 transition: $jit.Trans.Quart.easeOut
5428 viz.op.removeNode(['someId', 'otherId'], {
5435 removeNode: function(node, opt) {
5437 var options = $.merge(this.options, viz.controller, opt);
5438 var n = $.splat(node);
5439 var i, that, nodeObj;
5440 switch(options.type) {
5442 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5446 this.removeNode(n, { type: 'nothing' });
5447 viz.labels.clearLabels();
5451 case 'fade:seq': case 'fade':
5453 //set alpha to 0 for nodes to remove.
5454 for(i=0; i<n.length; i++) {
5455 nodeObj = viz.graph.getNode(n[i]);
5456 nodeObj.setData('alpha', 0, 'end');
5458 viz.fx.animate($.merge(options, {
5459 modes: ['node-property:alpha'],
5460 onComplete: function() {
5461 that.removeNode(n, { type: 'nothing' });
5462 viz.labels.clearLabels();
5464 viz.fx.animate($.merge(options, {
5473 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5474 for(i=0; i<n.length; i++) {
5475 nodeObj = viz.graph.getNode(n[i]);
5476 nodeObj.setData('alpha', 0, 'end');
5477 nodeObj.ignore = true;
5480 viz.fx.animate($.merge(options, {
5481 modes: ['node-property:alpha', 'linear'],
5482 onComplete: function() {
5483 that.removeNode(n, { type: 'nothing' });
5491 condition: function() { return n.length != 0; },
5492 step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5493 onComplete: function() { options.onComplete(); },
5494 duration: Math.ceil(options.duration / n.length)
5498 default: this.doError();
5505 Removes one or more <Graph.Adjacences> from the visualization.
5506 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5510 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'], ...]).
5511 opt - (object) Animation options. It's an object with optional properties described below
5512 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5513 duration - Described in <Options.Fx>.
5514 fps - Described in <Options.Fx>.
5515 transition - Described in <Options.Fx>.
5516 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5520 var viz = new $jit.Viz(options);
5521 viz.op.removeEdge(['nodeId', 'otherId'], {
5525 transition: $jit.Trans.Quart.easeOut
5528 viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5535 removeEdge: function(vertex, opt) {
5537 var options = $.merge(this.options, viz.controller, opt);
5538 var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5540 switch(options.type) {
5542 for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5546 this.removeEdge(v, { type: 'nothing' });
5550 case 'fade:seq': case 'fade':
5552 //set alpha to 0 for edges to remove.
5553 for(i=0; i<v.length; i++) {
5554 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5556 adj.setData('alpha', 0,'end');
5559 viz.fx.animate($.merge(options, {
5560 modes: ['edge-property:alpha'],
5561 onComplete: function() {
5562 that.removeEdge(v, { type: 'nothing' });
5564 viz.fx.animate($.merge(options, {
5573 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5574 for(i=0; i<v.length; i++) {
5575 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5577 adj.setData('alpha',0 ,'end');
5582 viz.fx.animate($.merge(options, {
5583 modes: ['edge-property:alpha', 'linear'],
5584 onComplete: function() {
5585 that.removeEdge(v, { type: 'nothing' });
5593 condition: function() { return v.length != 0; },
5594 step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5595 onComplete: function() { options.onComplete(); },
5596 duration: Math.ceil(options.duration / v.length)
5600 default: this.doError();
5607 Adds a new graph to the visualization.
5608 The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5609 The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5613 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5614 opt - (object) Animation options. It's an object with optional properties described below
5615 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5616 duration - Described in <Options.Fx>.
5617 fps - Described in <Options.Fx>.
5618 transition - Described in <Options.Fx>.
5619 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5623 //...json contains a tree or graph structure...
5625 var viz = new $jit.Viz(options);
5630 transition: $jit.Trans.Quart.easeOut
5640 sum: function(json, opt) {
5642 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5644 viz.root = opt.id || viz.root;
5645 switch(options.type) {
5647 graph = viz.construct(json);
5648 graph.eachNode(function(elem) {
5649 elem.eachAdjacency(function(adj) {
5650 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5657 this.sum(json, { type: 'nothing' });
5661 case 'fade:seq': case 'fade': case 'fade:con':
5663 graph = viz.construct(json);
5665 //set alpha to 0 for nodes to add.
5666 var fadeEdges = this.preprocessSum(graph);
5667 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5669 if(options.type != 'fade:con') {
5670 viz.fx.animate($.merge(options, {
5672 onComplete: function() {
5673 viz.fx.animate($.merge(options, {
5675 onComplete: function() {
5676 options.onComplete();
5682 viz.graph.eachNode(function(elem) {
5683 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5684 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5687 viz.fx.animate($.merge(options, {
5688 modes: ['linear'].concat(modes)
5693 default: this.doError();
5700 This method will transform the current visualized graph into the new JSON representation passed in the method.
5701 The JSON object must at least have the root node in common with the current visualized graph.
5705 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5706 opt - (object) Animation options. It's an object with optional properties described below
5707 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5708 duration - Described in <Options.Fx>.
5709 fps - Described in <Options.Fx>.
5710 transition - Described in <Options.Fx>.
5711 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5712 id - (string) The shared <Graph.Node> id between both graphs.
5714 extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5715 *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5716 For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5717 properties as values, just like specified in <Graph.Plot.animate>.
5721 //...json contains a tree or graph structure...
5723 var viz = new $jit.Viz(options);
5724 viz.op.morph(json, {
5728 transition: $jit.Trans.Quart.easeOut
5731 viz.op.morph(json, {
5735 //if the json data contains dollar prefixed params
5736 //like $width or $height these too can be animated
5737 viz.op.morph(json, {
5741 'node-property': ['width', 'height']
5746 morph: function(json, opt, extraModes) {
5748 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5750 //TODO(nico) this hack makes morphing work with the Hypertree.
5751 //Need to check if it has been solved and this can be removed.
5752 viz.root = opt.id || viz.root;
5753 switch(options.type) {
5755 graph = viz.construct(json);
5756 graph.eachNode(function(elem) {
5757 var nodeExists = viz.graph.hasNode(elem.id);
5758 elem.eachAdjacency(function(adj) {
5759 var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5760 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5761 //Update data properties if the node existed
5763 var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5764 for(var prop in (adj.data || {})) {
5765 addedAdj.data[prop] = adj.data[prop];
5769 //Update data properties if the node existed
5771 var addedNode = viz.graph.getNode(elem.id);
5772 for(var prop in (elem.data || {})) {
5773 addedNode.data[prop] = elem.data[prop];
5777 viz.graph.eachNode(function(elem) {
5778 elem.eachAdjacency(function(adj) {
5779 if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5780 viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5783 if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5789 viz.labels.clearLabels(true);
5790 this.morph(json, { type: 'nothing' });
5795 case 'fade:seq': case 'fade': case 'fade:con':
5797 graph = viz.construct(json);
5798 //preprocessing for nodes to delete.
5799 //get node property modes to interpolate
5800 var nodeModes = extraModes && ('node-property' in extraModes)
5801 && $.map($.splat(extraModes['node-property']),
5802 function(n) { return '$' + n; });
5803 viz.graph.eachNode(function(elem) {
5804 var graphNode = graph.getNode(elem.id);
5806 elem.setData('alpha', 1);
5807 elem.setData('alpha', 1, 'start');
5808 elem.setData('alpha', 0, 'end');
5811 //Update node data information
5812 var graphNodeData = graphNode.data;
5813 for(var prop in graphNodeData) {
5814 if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5815 elem.endData[prop] = graphNodeData[prop];
5817 elem.data[prop] = graphNodeData[prop];
5822 viz.graph.eachNode(function(elem) {
5823 if(elem.ignore) return;
5824 elem.eachAdjacency(function(adj) {
5825 if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5826 var nodeFrom = graph.getNode(adj.nodeFrom.id);
5827 var nodeTo = graph.getNode(adj.nodeTo.id);
5828 if(!nodeFrom.adjacentTo(nodeTo)) {
5829 var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5831 adj.setData('alpha', 1);
5832 adj.setData('alpha', 1, 'start');
5833 adj.setData('alpha', 0, 'end');
5837 //preprocessing for adding nodes.
5838 var fadeEdges = this.preprocessSum(graph);
5840 var modes = !fadeEdges? ['node-property:alpha'] :
5841 ['node-property:alpha',
5842 'edge-property:alpha'];
5843 //Append extra node-property animations (if any)
5844 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))?
5845 (':' + $.splat(extraModes['node-property']).join(':')) : '');
5846 //Append extra edge-property animations (if any)
5847 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))?
5848 (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5849 //Add label-property animations (if any)
5850 if(extraModes && ('label-property' in extraModes)) {
5851 modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5854 viz.graph.eachNode(function(elem) {
5855 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5856 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5859 viz.fx.animate($.merge(options, {
5860 modes: ['polar'].concat(modes),
5861 onComplete: function() {
5862 viz.graph.eachNode(function(elem) {
5863 if(elem.ignore) viz.graph.removeNode(elem.id);
5865 viz.graph.eachNode(function(elem) {
5866 elem.eachAdjacency(function(adj) {
5867 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5870 options.onComplete();
5883 Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5887 node - (object) A <Graph.Node>.
5888 opt - (object) An object containing options described below
5889 type - (string) Whether to 'replot' or 'animate' the contraction.
5891 There are also a number of Animation options. For more information see <Options.Fx>.
5895 var viz = new $jit.Viz(options);
5896 viz.op.contract(node, {
5900 transition: $jit.Trans.Quart.easeOut
5905 contract: function(node, opt) {
5907 if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5908 opt = $.merge(this.options, viz.config, opt || {}, {
5909 'modes': ['node-property:alpha:span', 'linear']
5911 node.collapsed = true;
5913 n.eachSubnode(function(ch) {
5915 ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5919 if(opt.type == 'animate') {
5922 viz.rotate(viz.rotated, 'none', {
5927 n.eachSubnode(function(ch) {
5928 ch.setPos(node.getPos('end'), 'end');
5932 viz.fx.animate(opt);
5933 } else if(opt.type == 'replot'){
5941 Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5945 node - (object) A <Graph.Node>.
5946 opt - (object) An object containing options described below
5947 type - (string) Whether to 'replot' or 'animate'.
5949 There are also a number of Animation options. For more information see <Options.Fx>.
5953 var viz = new $jit.Viz(options);
5954 viz.op.expand(node, {
5958 transition: $jit.Trans.Quart.easeOut
5963 expand: function(node, opt) {
5964 if(!('collapsed' in node)) return;
5966 opt = $.merge(this.options, viz.config, opt || {}, {
5967 'modes': ['node-property:alpha:span', 'linear']
5969 delete node.collapsed;
5971 n.eachSubnode(function(ch) {
5973 ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5977 if(opt.type == 'animate') {
5980 viz.rotate(viz.rotated, 'none', {
5984 viz.fx.animate(opt);
5985 } else if(opt.type == 'replot'){
5990 preprocessSum: function(graph) {
5992 graph.eachNode(function(elem) {
5993 if(!viz.graph.hasNode(elem.id)) {
5994 viz.graph.addNode(elem);
5995 var n = viz.graph.getNode(elem.id);
5996 n.setData('alpha', 0);
5997 n.setData('alpha', 0, 'start');
5998 n.setData('alpha', 1, 'end');
6001 var fadeEdges = false;
6002 graph.eachNode(function(elem) {
6003 elem.eachAdjacency(function(adj) {
6004 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6005 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6006 if(!nodeFrom.adjacentTo(nodeTo)) {
6007 var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6008 if(nodeFrom.startAlpha == nodeFrom.endAlpha
6009 && nodeTo.startAlpha == nodeTo.endAlpha) {
6011 adj.setData('alpha', 0);
6012 adj.setData('alpha', 0, 'start');
6013 adj.setData('alpha', 1, 'end');
6027 Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6028 Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6029 position is over the rendered shape.
6031 Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
6032 *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6036 //implement a new node type
6037 $jit.Viz.Plot.NodeTypes.implement({
6039 'render': function(node, canvas) {
6040 this.nodeHelper.circle.render ...
6042 'contains': function(node, pos) {
6043 this.nodeHelper.circle.contains ...
6047 //implement an edge type
6048 $jit.Viz.Plot.EdgeTypes.implement({
6050 'render': function(node, canvas) {
6051 this.edgeHelper.circle.render ...
6054 'contains': function(node, pos) {
6055 this.edgeHelper.circle.contains ...
6066 Contains rendering and other type of primitives for simple shapes.
6071 'contains': $.lambda(false)
6074 Object: NodeHelper.circle
6080 Renders a circle into the canvas.
6084 type - (string) Possible options are 'fill' or 'stroke'.
6085 pos - (object) An *x*, *y* object with the position of the center of the circle.
6086 radius - (number) The radius of the circle to be rendered.
6087 canvas - (object) A <Canvas> instance.
6091 NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6094 'render': function(type, pos, radius, canvas){
6095 var ctx = canvas.getCtx();
6097 ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6104 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6108 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6109 pos - (object) An *x*, *y* object with the position to check.
6110 radius - (number) The radius of the rendered circle.
6114 NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6117 'contains': function(npos, pos, radius){
6118 var diffx = npos.x - pos.x,
6119 diffy = npos.y - pos.y,
6120 diff = diffx * diffx + diffy * diffy;
6121 return diff <= radius * radius;
6125 Object: NodeHelper.ellipse
6131 Renders an ellipse into the canvas.
6135 type - (string) Possible options are 'fill' or 'stroke'.
6136 pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6137 width - (number) The width of the ellipse.
6138 height - (number) The height of the ellipse.
6139 canvas - (object) A <Canvas> instance.
6143 NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6146 'render': function(type, pos, width, height, canvas){
6147 var ctx = canvas.getCtx();
6151 ctx.scale(width / height, height / width);
6153 ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6162 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6166 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6167 pos - (object) An *x*, *y* object with the position to check.
6168 width - (number) The width of the rendered ellipse.
6169 height - (number) The height of the rendered ellipse.
6173 NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6176 'contains': function(npos, pos, width, height){
6177 // TODO(nico): be more precise...
6180 var dist = (width + height) / 2,
6181 diffx = npos.x - pos.x,
6182 diffy = npos.y - pos.y,
6183 diff = diffx * diffx + diffy * diffy;
6184 return diff <= dist * dist;
6188 Object: NodeHelper.square
6194 Renders a square into the canvas.
6198 type - (string) Possible options are 'fill' or 'stroke'.
6199 pos - (object) An *x*, *y* object with the position of the center of the square.
6200 dim - (number) The radius (or half-diameter) of the square.
6201 canvas - (object) A <Canvas> instance.
6205 NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6208 'render': function(type, pos, dim, canvas){
6209 canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6214 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6218 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6219 pos - (object) An *x*, *y* object with the position to check.
6220 dim - (number) The radius (or half-diameter) of the square.
6224 NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6227 'contains': function(npos, pos, dim){
6228 return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6232 Object: NodeHelper.rectangle
6238 Renders a rectangle into the canvas.
6242 type - (string) Possible options are 'fill' or 'stroke'.
6243 pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6244 width - (number) The width of the rectangle.
6245 height - (number) The height of the rectangle.
6246 canvas - (object) A <Canvas> instance.
6250 NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6253 'render': function(type, pos, width, height, canvas){
6254 canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6260 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6264 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6265 pos - (object) An *x*, *y* object with the position to check.
6266 width - (number) The width of the rendered rectangle.
6267 height - (number) The height of the rendered rectangle.
6271 NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6274 'contains': function(npos, pos, width, height){
6275 return Math.abs(pos.x - npos.x) <= width / 2
6276 && Math.abs(pos.y - npos.y) <= height / 2;
6280 Object: NodeHelper.triangle
6286 Renders a triangle into the canvas.
6290 type - (string) Possible options are 'fill' or 'stroke'.
6291 pos - (object) An *x*, *y* object with the position of the center of the triangle.
6292 dim - (number) The dimension of the triangle.
6293 canvas - (object) A <Canvas> instance.
6297 NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6300 'render': function(type, pos, dim, canvas){
6301 var ctx = canvas.getCtx(),
6309 ctx.moveTo(c1x, c1y);
6310 ctx.lineTo(c2x, c2y);
6311 ctx.lineTo(c3x, c3y);
6318 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6322 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6323 pos - (object) An *x*, *y* object with the position to check.
6324 dim - (number) The dimension of the shape.
6328 NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6331 'contains': function(npos, pos, dim) {
6332 return NodeHelper.circle.contains(npos, pos, dim);
6336 Object: NodeHelper.star
6342 Renders a star into the canvas.
6346 type - (string) Possible options are 'fill' or 'stroke'.
6347 pos - (object) An *x*, *y* object with the position of the center of the star.
6348 dim - (number) The dimension of the star.
6349 canvas - (object) A <Canvas> instance.
6353 NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6356 'render': function(type, pos, dim, canvas){
6357 var ctx = canvas.getCtx(),
6360 ctx.translate(pos.x, pos.y);
6363 for (var i = 0; i < 9; i++) {
6366 ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6378 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6382 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6383 pos - (object) An *x*, *y* object with the position to check.
6384 dim - (number) The dimension of the shape.
6388 NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6391 'contains': function(npos, pos, dim) {
6392 return NodeHelper.circle.contains(npos, pos, dim);
6400 Contains rendering primitives for simple edge shapes.
6404 Object: EdgeHelper.line
6410 Renders a line into the canvas.
6414 from - (object) An *x*, *y* object with the starting position of the line.
6415 to - (object) An *x*, *y* object with the ending position of the line.
6416 canvas - (object) A <Canvas> instance.
6420 EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6423 'render': function(from, to, canvas){
6424 var ctx = canvas.getCtx();
6426 ctx.moveTo(from.x, from.y);
6427 ctx.lineTo(to.x, to.y);
6433 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6437 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6438 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6439 pos - (object) An *x*, *y* object with the position to check.
6440 epsilon - (number) The dimension of the shape.
6444 EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6447 'contains': function(posFrom, posTo, pos, epsilon) {
6450 minPosX = min(posFrom.x, posTo.x),
6451 maxPosX = max(posFrom.x, posTo.x),
6452 minPosY = min(posFrom.y, posTo.y),
6453 maxPosY = max(posFrom.y, posTo.y);
6455 if(pos.x >= minPosX && pos.x <= maxPosX
6456 && pos.y >= minPosY && pos.y <= maxPosY) {
6457 if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6460 var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6461 return Math.abs(dist - pos.y) <= epsilon;
6467 Object: EdgeHelper.arrow
6473 Renders an arrow into the canvas.
6477 from - (object) An *x*, *y* object with the starting position of the arrow.
6478 to - (object) An *x*, *y* object with the ending position of the arrow.
6479 dim - (number) The dimension of the arrow.
6480 swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6481 canvas - (object) A <Canvas> instance.
6485 EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6488 'render': function(from, to, dim, swap, canvas){
6489 var ctx = canvas.getCtx();
6490 // invert edge direction
6496 var vect = new Complex(to.x - from.x, to.y - from.y);
6497 vect.$scale(dim / vect.norm());
6498 var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6499 normal = new Complex(-vect.y / 2, vect.x / 2),
6500 v1 = intermediatePoint.add(normal),
6501 v2 = intermediatePoint.$add(normal.$scale(-1));
6504 ctx.moveTo(from.x, from.y);
6505 ctx.lineTo(to.x, to.y);
6508 ctx.moveTo(v1.x, v1.y);
6509 ctx.lineTo(v2.x, v2.y);
6510 ctx.lineTo(to.x, to.y);
6517 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6521 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6522 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6523 pos - (object) An *x*, *y* object with the position to check.
6524 epsilon - (number) The dimension of the shape.
6528 EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6531 'contains': function(posFrom, posTo, pos, epsilon) {
6532 return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6536 Object: EdgeHelper.hyperline
6542 Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6546 from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6547 to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6548 r - (number) The scaling factor.
6549 canvas - (object) A <Canvas> instance.
6553 EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6556 'render': function(from, to, r, canvas){
6557 var ctx = canvas.getCtx();
6558 var centerOfCircle = computeArcThroughTwoPoints(from, to);
6559 if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6560 || centerOfCircle.ratio < 0) {
6562 ctx.moveTo(from.x * r, from.y * r);
6563 ctx.lineTo(to.x * r, to.y * r);
6566 var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6567 - centerOfCircle.x);
6568 var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6569 - centerOfCircle.x);
6570 var sense = sense(angleBegin, angleEnd);
6572 ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6573 * r, angleBegin, angleEnd, sense);
6577 Calculates the arc parameters through two points.
6579 More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6583 p1 - A <Complex> instance.
6584 p2 - A <Complex> instance.
6585 scale - The Disk's diameter.
6589 An object containing some arc properties.
6591 function computeArcThroughTwoPoints(p1, p2){
6592 var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6593 var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6594 // Fall back to a straight line
6602 var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6603 var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6606 var squaredRatio = (a * a + b * b) / 4 - 1;
6607 // Fall back to a straight line
6608 if (squaredRatio < 0)
6614 var ratio = Math.sqrt(squaredRatio);
6618 ratio: ratio > 1000? -1 : ratio,
6626 Sets angle direction to clockwise (true) or counterclockwise (false).
6630 angleBegin - Starting angle for drawing the arc.
6631 angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6635 A Boolean instance describing the sense for drawing the HyperLine.
6637 function sense(angleBegin, angleEnd){
6638 return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6639 : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6647 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6651 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6652 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6653 pos - (object) An *x*, *y* object with the position to check.
6654 epsilon - (number) The dimension of the shape.
6658 EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6661 'contains': $.lambda(false)
6667 * File: Graph.Plot.js
6673 <Graph> rendering and animation methods.
6677 nodeHelper - <NodeHelper> object.
6678 edgeHelper - <EdgeHelper> object.
6681 //Default intializer
6682 initialize: function(viz, klass){
6684 this.config = viz.config;
6685 this.node = viz.config.Node;
6686 this.edge = viz.config.Edge;
6687 this.animation = new Animation;
6688 this.nodeTypes = new klass.Plot.NodeTypes;
6689 this.edgeTypes = new klass.Plot.EdgeTypes;
6690 this.labels = viz.labels;
6694 nodeHelper: NodeHelper,
6695 edgeHelper: EdgeHelper,
6698 //node/edge property parsers
6706 'lineWidth': 'number',
6707 'angularWidth':'number',
6709 'valueArray':'array-number',
6710 'dimArray':'array-number'
6711 //'colorArray':'array-color'
6714 //canvas specific parsers
6716 'globalAlpha': 'number',
6717 'fillStyle': 'color',
6718 'strokeStyle': 'color',
6719 'lineWidth': 'number',
6720 'shadowBlur': 'number',
6721 'shadowColor': 'color',
6722 'shadowOffsetX': 'number',
6723 'shadowOffsetY': 'number',
6724 'miterLimit': 'number'
6733 //Number interpolator
6734 'compute': function(from, to, delta) {
6735 return from + (to - from) * delta;
6738 //Position interpolators
6739 'moebius': function(elem, props, delta, vector) {
6740 var v = vector.scale(-delta);
6742 var x = v.x, y = v.y;
6743 var ans = elem.startPos
6744 .getc().moebiusTransformation(v);
6745 elem.pos.setc(ans.x, ans.y);
6750 'linear': function(elem, props, delta) {
6751 var from = elem.startPos.getc(true);
6752 var to = elem.endPos.getc(true);
6753 elem.pos.setc(this.compute(from.x, to.x, delta),
6754 this.compute(from.y, to.y, delta));
6757 'polar': function(elem, props, delta) {
6758 var from = elem.startPos.getp(true);
6759 var to = elem.endPos.getp();
6760 var ans = to.interpolate(from, delta);
6761 elem.pos.setp(ans.theta, ans.rho);
6764 //Graph's Node/Edge interpolators
6765 'number': function(elem, prop, delta, getter, setter) {
6766 var from = elem[getter](prop, 'start');
6767 var to = elem[getter](prop, 'end');
6768 elem[setter](prop, this.compute(from, to, delta));
6771 'color': function(elem, prop, delta, getter, setter) {
6772 var from = $.hexToRgb(elem[getter](prop, 'start'));
6773 var to = $.hexToRgb(elem[getter](prop, 'end'));
6774 var comp = this.compute;
6775 var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6776 parseInt(comp(from[1], to[1], delta)),
6777 parseInt(comp(from[2], to[2], delta))]);
6779 elem[setter](prop, val);
6782 'array-number': function(elem, prop, delta, getter, setter) {
6783 var from = elem[getter](prop, 'start'),
6784 to = elem[getter](prop, 'end'),
6786 for(var i=0, l=from.length; i<l; i++) {
6787 var fromi = from[i], toi = to[i];
6789 for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6790 curi.push(this.compute(fromi[j], toi[j], delta));
6794 cur.push(this.compute(fromi, toi, delta));
6797 elem[setter](prop, cur);
6800 'node': function(elem, props, delta, map, getter, setter) {
6803 var len = props.length;
6804 for(var i=0; i<len; i++) {
6806 this[map[pi]](elem, pi, delta, getter, setter);
6809 for(var pi in map) {
6810 this[map[pi]](elem, pi, delta, getter, setter);
6815 'edge': function(elem, props, delta, mapKey, getter, setter) {
6816 var adjs = elem.adjacencies;
6817 for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6820 'node-property': function(elem, props, delta) {
6821 this['node'](elem, props, delta, 'map', 'getData', 'setData');
6824 'edge-property': function(elem, props, delta) {
6825 this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6828 'label-property': function(elem, props, delta) {
6829 this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6832 'node-style': function(elem, props, delta) {
6833 this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6836 'edge-style': function(elem, props, delta) {
6837 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6845 Iteratively performs an action while refreshing the state of the visualization.
6849 options - (object) An object containing some sequence options described below
6850 condition - (function) A function returning a boolean instance in order to stop iterations.
6851 step - (function) A function to execute on each step of the iteration.
6852 onComplete - (function) A function to execute when the sequence finishes.
6853 duration - (number) Duration (in milliseconds) of each step.
6857 var rg = new $jit.RGraph(options);
6860 condition: function() {
6866 onComplete: function() {
6873 sequence: function(options) {
6876 condition: $.lambda(false),
6878 onComplete: $.empty,
6882 var interval = setInterval(function() {
6883 if(options.condition()) {
6886 clearInterval(interval);
6887 options.onComplete();
6889 that.viz.refresh(true);
6890 }, options.duration);
6896 Prepare graph position and other attribute values before performing an Animation.
6897 This method is used internally by the Toolkit.
6901 <Animation>, <Graph.Plot.animate>
6904 prepare: function(modes) {
6905 var graph = this.viz.graph,
6908 'getter': 'getData',
6912 'getter': 'getData',
6916 'getter': 'getCanvasStyle',
6917 'setter': 'setCanvasStyle'
6920 'getter': 'getCanvasStyle',
6921 'setter': 'setCanvasStyle'
6927 if($.type(modes) == 'array') {
6928 for(var i=0, len=modes.length; i < len; i++) {
6929 var elems = modes[i].split(':');
6930 m[elems.shift()] = elems;
6933 for(var p in modes) {
6934 if(p == 'position') {
6935 m[modes.position] = [];
6937 m[p] = $.splat(modes[p]);
6942 graph.eachNode(function(node) {
6943 node.startPos.set(node.pos);
6944 $.each(['node-property', 'node-style'], function(p) {
6947 for(var i=0, l=prop.length; i < l; i++) {
6948 node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6952 $.each(['edge-property', 'edge-style'], function(p) {
6955 node.eachAdjacency(function(adj) {
6956 for(var i=0, l=prop.length; i < l; i++) {
6957 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6969 Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6973 opt - (object) Animation options. The object properties are described below
6974 duration - (optional) Described in <Options.Fx>.
6975 fps - (optional) Described in <Options.Fx>.
6976 hideLabels - (optional|boolean) Whether to hide labels during the animation.
6977 modes - (required|object) An object with animation modes (described below).
6981 Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6982 They are represented by an object that has as keys main categories of properties to animate and as values a list
6983 of these specific properties. The properties are described below
6985 position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6986 node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6987 edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6988 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.
6989 node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6990 edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6994 var viz = new $jit.Viz(options);
6995 //...tweak some Data, CanvasStyles or LabelData properties...
6998 'position': 'linear',
6999 'node-property': ['width', 'height'],
7000 'node-style': 'shadowColor',
7001 'label-property': 'size'
7005 //...can also be written like this...
7008 'node-property:width:height',
7009 'node-style:shadowColor',
7010 'label-property:size'],
7015 animate: function(opt, versor) {
7016 opt = $.merge(this.viz.config, opt || {});
7020 interp = this.Interpolator,
7021 animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7022 //prepare graph values
7023 var m = this.prepare(opt.modes);
7026 if(opt.hideLabels) this.labels.hideLabels(true);
7027 animation.setOptions($.merge(opt, {
7029 compute: function(delta) {
7030 graph.eachNode(function(node) {
7032 interp[p](node, m[p], delta, versor);
7035 that.plot(opt, this.$animating, delta);
7036 this.$animating = true;
7038 complete: function() {
7039 if(opt.hideLabels) that.labels.hideLabels(false);
7042 opt.onAfterCompute();
7050 Apply animation to node properties like color, width, height, dim, etc.
7054 options - Animation options. This object properties is described below
7055 elements - The Elements to be transformed. This is an object that has a properties
7059 //can also be an array of ids
7060 'id': 'id-of-node-to-transform',
7061 //properties to be modified. All properties are optional.
7063 'color': '#ccc', //some color
7064 'width': 10, //some width
7065 'height': 10, //some height
7066 'dim': 20, //some dim
7067 'lineWidth': 10 //some line width
7072 - _reposition_ Whether to recalculate positions and add a motion animation.
7073 This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7075 - _onComplete_ A method that is called when the animation completes.
7077 ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7081 var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7088 'transition': Trans.Quart.easeOut
7093 nodeFx: function(opt) {
7096 animation = this.nodeFxAnimation,
7097 options = $.merge(this.viz.config, {
7104 opt = $.merge(options, opt || {}, {
7105 onBeforeCompute: $.empty,
7106 onAfterCompute: $.empty
7108 //check if an animation is running
7109 animation.stopTimer();
7110 var props = opt.elements.properties;
7111 //set end values for nodes
7112 if(!opt.elements.id) {
7113 graph.eachNode(function(n) {
7114 for(var prop in props) {
7115 n.setData(prop, props[prop], 'end');
7119 var ids = $.splat(opt.elements.id);
7120 $.each(ids, function(id) {
7121 var n = graph.getNode(id);
7123 for(var prop in props) {
7124 n.setData(prop, props[prop], 'end');
7131 for(var prop in props) propnames.push(prop);
7132 //add node properties modes
7133 var modes = ['node-property:' + propnames.join(':')];
7134 //set new node positions
7135 if(opt.reposition) {
7136 modes.push('linear');
7140 this.animate($.merge(opt, {
7154 opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7159 var viz = new $jit.Viz(options);
7164 plot: function(opt, animating) {
7167 canvas = viz.canvas,
7170 ctx = canvas.getCtx(),
7172 opt = opt || this.viz.controller;
7173 opt.clearCanvas && canvas.clear();
7175 var root = aGraph.getNode(id);
7178 var T = !!root.visited;
7179 aGraph.eachNode(function(node) {
7180 var nodeAlpha = node.getData('alpha');
7181 node.eachAdjacency(function(adj) {
7182 var nodeTo = adj.nodeTo;
7183 if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7184 !animating && opt.onBeforePlotLine(adj);
7186 ctx.globalAlpha = min(nodeAlpha,
7187 nodeTo.getData('alpha'),
7188 adj.getData('alpha'));
7189 that.plotLine(adj, canvas, animating);
7191 !animating && opt.onAfterPlotLine(adj);
7196 !animating && opt.onBeforePlotNode(node);
7197 that.plotNode(node, canvas, animating);
7198 !animating && opt.onAfterPlotNode(node);
7200 if(!that.labelsHidden && opt.withLabels) {
7201 if(node.drawn && nodeAlpha >= 0.95) {
7202 that.labels.plotLabel(canvas, node, opt);
7204 that.labels.hideLabel(node, false);
7215 plotTree: function(node, opt, animating) {
7218 canvas = viz.canvas,
7219 config = this.config,
7220 ctx = canvas.getCtx();
7221 var nodeAlpha = node.getData('alpha');
7222 node.eachSubnode(function(elem) {
7223 if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7224 var adj = node.getAdjacency(elem.id);
7225 !animating && opt.onBeforePlotLine(adj);
7226 ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7227 that.plotLine(adj, canvas, animating);
7228 !animating && opt.onAfterPlotLine(adj);
7229 that.plotTree(elem, opt, animating);
7233 !animating && opt.onBeforePlotNode(node);
7234 this.plotNode(node, canvas, animating);
7235 !animating && opt.onAfterPlotNode(node);
7236 if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7237 this.labels.plotLabel(canvas, node, opt);
7239 this.labels.hideLabel(node, false);
7241 this.labels.hideLabel(node, true);
7248 Plots a <Graph.Node>.
7252 node - (object) A <Graph.Node>.
7253 canvas - (object) A <Canvas> element.
7256 plotNode: function(node, canvas, animating) {
7257 var f = node.getData('type'),
7258 ctxObj = this.node.CanvasStyles;
7260 var width = node.getData('lineWidth'),
7261 color = node.getData('color'),
7262 alpha = node.getData('alpha'),
7263 ctx = canvas.getCtx();
7265 ctx.lineWidth = width;
7266 ctx.fillStyle = ctx.strokeStyle = color;
7267 ctx.globalAlpha = alpha;
7269 for(var s in ctxObj) {
7270 ctx[s] = node.getCanvasStyle(s);
7273 this.nodeTypes[f].render.call(this, node, canvas, animating);
7280 Plots a <Graph.Adjacence>.
7284 adj - (object) A <Graph.Adjacence>.
7285 canvas - (object) A <Canvas> instance.
7288 plotLine: function(adj, canvas, animating) {
7289 var f = adj.getData('type'),
7290 ctxObj = this.edge.CanvasStyles;
7292 var width = adj.getData('lineWidth'),
7293 color = adj.getData('color'),
7294 ctx = canvas.getCtx();
7296 ctx.lineWidth = width;
7297 ctx.fillStyle = ctx.strokeStyle = color;
7299 for(var s in ctxObj) {
7300 ctx[s] = adj.getCanvasStyle(s);
7303 this.edgeTypes[f].render.call(this, adj, canvas, animating);
7312 * File: Graph.Label.js
7319 An interface for plotting/hiding/showing labels.
7323 This is a generic interface for plotting/hiding/showing labels.
7324 The <Graph.Label> interface is implemented in multiple ways to provide
7325 different label types.
7327 For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7328 HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7329 The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7331 All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7337 Class: Graph.Label.Native
7339 Implements labels natively, using the Canvas text API.
7341 Graph.Label.Native = new Class({
7345 Plots a label for a given node.
7349 canvas - (object) A <Canvas> instance.
7350 node - (object) A <Graph.Node>.
7351 controller - (object) A configuration object.
7356 var viz = new $jit.Viz(options);
7357 var node = viz.graph.getNode('nodeId');
7358 viz.labels.plotLabel(viz.canvas, node, viz.config);
7361 plotLabel: function(canvas, node, controller) {
7362 var ctx = canvas.getCtx();
7363 var pos = node.pos.getc(true);
7365 ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7366 ctx.textAlign = node.getLabelData('textAlign');
7367 ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7368 ctx.textBaseline = node.getLabelData('textBaseline');
7370 this.renderLabel(canvas, node, controller);
7376 Does the actual rendering of the label in the canvas. The default
7377 implementation renders the label close to the position of the node, this
7378 method should be overriden to position the labels differently.
7382 canvas - A <Canvas> instance.
7383 node - A <Graph.Node>.
7384 controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7386 renderLabel: function(canvas, node, controller) {
7387 var ctx = canvas.getCtx();
7388 var pos = node.pos.getc(true);
7389 ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7397 Class: Graph.Label.DOM
7399 Abstract Class implementing some DOM label methods.
7403 <Graph.Label.HTML> and <Graph.Label.SVG>.
7406 Graph.Label.DOM = new Class({
7407 //A flag value indicating if node labels are being displayed or not.
7408 labelsHidden: false,
7410 labelContainer: false,
7411 //Label elements hash.
7415 Method: getLabelContainer
7417 Lazy fetcher for the label container.
7421 The label container DOM element.
7426 var viz = new $jit.Viz(options);
7427 var labelContainer = viz.labels.getLabelContainer();
7428 alert(labelContainer.innerHTML);
7431 getLabelContainer: function() {
7432 return this.labelContainer ?
7433 this.labelContainer :
7434 this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7440 Lazy fetcher for the label element.
7444 id - (string) The label id (which is also a <Graph.Node> id).
7453 var viz = new $jit.Viz(options);
7454 var label = viz.labels.getLabel('someid');
7455 alert(label.innerHTML);
7459 getLabel: function(id) {
7460 return (id in this.labels && this.labels[id] != null) ?
7462 this.labels[id] = document.getElementById(id);
7468 Hides all labels (by hiding the label container).
7472 hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7476 var viz = new $jit.Viz(options);
7477 rg.labels.hideLabels(true);
7481 hideLabels: function (hide) {
7482 var container = this.getLabelContainer();
7484 container.style.display = 'none';
7486 container.style.display = '';
7487 this.labelsHidden = hide;
7493 Clears the label container.
7495 Useful when using a new visualization with the same canvas element/widget.
7499 force - (boolean) Forces deletion of all labels.
7503 var viz = new $jit.Viz(options);
7504 viz.labels.clearLabels();
7507 clearLabels: function(force) {
7508 for(var id in this.labels) {
7509 if (force || !this.viz.graph.hasNode(id)) {
7510 this.disposeLabel(id);
7511 delete this.labels[id];
7517 Method: disposeLabel
7523 id - (string) A label id (which generally is also a <Graph.Node> id).
7527 var viz = new $jit.Viz(options);
7528 viz.labels.disposeLabel('labelid');
7531 disposeLabel: function(id) {
7532 var elem = this.getLabel(id);
7533 if(elem && elem.parentNode) {
7534 elem.parentNode.removeChild(elem);
7541 Hides the corresponding <Graph.Node> label.
7545 node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7546 show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7550 var rg = new $jit.Viz(options);
7551 viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7554 hideLabel: function(node, show) {
7555 node = $.splat(node);
7556 var st = show ? "" : "none", lab, that = this;
7557 $.each(node, function(n) {
7558 var lab = that.getLabel(n.id);
7560 lab.style.display = st;
7568 Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7572 pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7573 canvas - A <Canvas> instance.
7577 A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7580 fitsInCanvas: function(pos, canvas) {
7581 var size = canvas.getSize();
7582 if(pos.x >= size.width || pos.x < 0
7583 || pos.y >= size.height || pos.y < 0) return false;
7589 Class: Graph.Label.HTML
7591 Implements HTML labels.
7595 All <Graph.Label.DOM> methods.
7598 Graph.Label.HTML = new Class({
7599 Implements: Graph.Label.DOM,
7604 Plots a label for a given node.
7608 canvas - (object) A <Canvas> instance.
7609 node - (object) A <Graph.Node>.
7610 controller - (object) A configuration object.
7615 var viz = new $jit.Viz(options);
7616 var node = viz.graph.getNode('nodeId');
7617 viz.labels.plotLabel(viz.canvas, node, viz.config);
7622 plotLabel: function(canvas, node, controller) {
7623 var id = node.id, tag = this.getLabel(id);
7625 if(!tag && !(tag = document.getElementById(id))) {
7626 tag = document.createElement('div');
7627 var container = this.getLabelContainer();
7629 tag.className = 'node';
7630 tag.style.position = 'absolute';
7631 controller.onCreateLabel(tag, node);
7632 container.appendChild(tag);
7633 this.labels[node.id] = tag;
7636 this.placeLabel(tag, node, controller);
7641 Class: Graph.Label.SVG
7643 Implements SVG labels.
7647 All <Graph.Label.DOM> methods.
7649 Graph.Label.SVG = new Class({
7650 Implements: Graph.Label.DOM,
7655 Plots a label for a given node.
7659 canvas - (object) A <Canvas> instance.
7660 node - (object) A <Graph.Node>.
7661 controller - (object) A configuration object.
7666 var viz = new $jit.Viz(options);
7667 var node = viz.graph.getNode('nodeId');
7668 viz.labels.plotLabel(viz.canvas, node, viz.config);
7673 plotLabel: function(canvas, node, controller) {
7674 var id = node.id, tag = this.getLabel(id);
7675 if(!tag && !(tag = document.getElementById(id))) {
7676 var ns = 'http://www.w3.org/2000/svg';
7677 tag = document.createElementNS(ns, 'svg:text');
7678 var tspan = document.createElementNS(ns, 'svg:tspan');
7679 tag.appendChild(tspan);
7680 var container = this.getLabelContainer();
7681 tag.setAttribute('id', id);
7682 tag.setAttribute('class', 'node');
7683 container.appendChild(tag);
7684 controller.onCreateLabel(tag, node);
7685 this.labels[node.id] = tag;
7687 this.placeLabel(tag, node, controller);
7693 Graph.Geom = new Class({
7695 initialize: function(viz) {
7697 this.config = viz.config;
7698 this.node = viz.config.Node;
7699 this.edge = viz.config.Edge;
7702 Applies a translation to the tree.
7706 pos - A <Complex> number specifying translation vector.
7707 prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7712 st.geom.translate(new Complex(300, 100), 'end');
7715 translate: function(pos, prop) {
7716 prop = $.splat(prop);
7717 this.viz.graph.eachNode(function(elem) {
7718 $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7722 Hides levels of the tree until it properly fits in canvas.
7724 setRightLevelToShow: function(node, canvas, callback) {
7725 var level = this.getRightLevelToShow(node, canvas),
7726 fx = this.viz.labels,
7733 node.eachLevel(0, this.config.levelsToShow, function(n) {
7734 var d = n._depth - node._depth;
7740 fx.hideLabel(n, false);
7752 Returns the right level to show for the current tree in order to fit in canvas.
7754 getRightLevelToShow: function(node, canvas) {
7755 var config = this.config;
7756 var level = config.levelsToShow;
7757 var constrained = config.constrained;
7758 if(!constrained) return level;
7759 while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7772 Provides methods for loading and serving JSON data.
7775 construct: function(json) {
7776 var isGraph = ($.type(json) == 'array');
7777 var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7780 (function (ans, json) {
7783 for(var i=0, ch = json.children; i<ch.length; i++) {
7784 ans.addAdjacence(json, ch[i]);
7785 arguments.callee(ans, ch[i]);
7791 (function (ans, json) {
7792 var getNode = function(id) {
7793 for(var i=0, l=json.length; i<l; i++) {
7794 if(json[i].id == id) {
7798 // The node was not defined in the JSON
7804 return ans.addNode(newNode);
7807 for(var i=0, l=json.length; i<l; i++) {
7808 ans.addNode(json[i]);
7809 var adj = json[i].adjacencies;
7811 for(var j=0, lj=adj.length; j<lj; j++) {
7812 var node = adj[j], data = {};
7813 if(typeof adj[j] != 'string') {
7814 data = $.merge(node.data, {});
7817 ans.addAdjacence(json[i], getNode(node), data);
7829 Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7831 A JSON tree or graph structure consists of nodes, each having as properties
7833 id - (string) A unique identifier for the node
7834 name - (string) A node's name
7835 data - (object) The data optional property contains a hash (i.e {})
7836 where you can store all the information you want about this node.
7838 For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7844 "id": "aUniqueIdentifier",
7845 "name": "usually a nodes name",
7847 "some key": "some value",
7848 "some other key": "some other value"
7850 "children": [ *other nodes or empty* ]
7854 JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7855 For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7857 There are two types of *Graph* structures, *simple* and *extended* graph structures.
7859 For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7860 id of the node connected to the main node.
7867 "id": "aUniqueIdentifier",
7868 "name": "usually a nodes name",
7870 "some key": "some value",
7871 "some other key": "some other value"
7873 "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7876 'other nodes go here...'
7880 For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7882 nodeTo - (string) The other node connected by this adjacency.
7883 data - (object) A data property, where we can store custom key/value information.
7890 "id": "aUniqueIdentifier",
7891 "name": "usually a nodes name",
7893 "some key": "some value",
7894 "some other key": "some other value"
7899 data: {} //put whatever you want here
7901 'other adjacencies go here...'
7904 'other nodes go here...'
7908 About the data property:
7910 As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
7911 You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
7912 have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7914 For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
7915 <Options.Node> will override the general value for that option with that particular value. For this to work
7916 however, you do have to set *overridable = true* in <Options.Node>.
7918 The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
7919 if <Options.Edge> has *overridable = true*.
7921 When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
7922 since this is the value which will be taken into account when creating the layout.
7923 The same thing goes for the *$color* parameter.
7925 In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
7926 *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
7927 canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
7928 to the *shadowBlur* property.
7930 These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
7931 by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7933 Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
7934 information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7936 loadJSON Parameters:
7938 json - A JSON Tree or Graph structure.
7939 i - For Graph structures only. Sets the indexed node as root for the visualization.
7942 loadJSON: function(json, i) {
7944 //if they're canvas labels erase them.
7945 if(this.labels && this.labels.clearLabels) {
7946 this.labels.clearLabels(true);
7948 this.graph = this.construct(json);
7949 if($.type(json) != 'array'){
7950 this.root = json.id;
7952 this.root = json[i? i : 0].id;
7959 Returns a JSON tree/graph structure from the visualization's <Graph>.
7960 See <Loader.loadJSON> for the graph formats available.
7968 type - (string) Default's "tree". The type of the JSON structure to be returned.
7969 Possible options are "tree" or "graph".
7971 toJSON: function(type) {
7972 type = type || "tree";
7973 if(type == 'tree') {
7975 var rootNode = this.graph.getNode(this.root);
7976 var ans = (function recTree(node) {
7979 ans.name = node.name;
7980 ans.data = node.data;
7982 node.eachSubnode(function(n) {
7983 ch.push(recTree(n));
7991 var T = !!this.graph.getNode(this.root).visited;
7992 this.graph.eachNode(function(node) {
7994 ansNode.id = node.id;
7995 ansNode.name = node.name;
7996 ansNode.data = node.data;
7998 node.eachAdjacency(function(adj) {
7999 var nodeTo = adj.nodeTo;
8000 if(!!nodeTo.visited === T) {
8002 ansAdj.nodeTo = nodeTo.id;
8003 ansAdj.data = adj.data;
8007 ansNode.adjacencies = adjs;
8021 * Implements base Tree and Graph layouts.
8025 * Implements base Tree and Graph layouts like Radial, Tree, etc.
8032 * Parent object for common layouts.
8035 var Layouts = $jit.Layouts = {};
8038 //Some util shared layout functions are defined here.
8042 compute: function(graph, prop, opt) {
8043 this.initializeLabel(opt);
8044 var label = this.label, style = label.style;
8045 graph.eachNode(function(n) {
8046 var autoWidth = n.getData('autoWidth'),
8047 autoHeight = n.getData('autoHeight');
8048 if(autoWidth || autoHeight) {
8049 //delete dimensions since these are
8050 //going to be overridden now.
8051 delete n.data.$width;
8052 delete n.data.$height;
8055 var width = n.getData('width'),
8056 height = n.getData('height');
8057 //reset label dimensions
8058 style.width = autoWidth? 'auto' : width + 'px';
8059 style.height = autoHeight? 'auto' : height + 'px';
8061 //TODO(nico) should let the user choose what to insert here.
8062 label.innerHTML = n.name;
8064 var offsetWidth = label.offsetWidth,
8065 offsetHeight = label.offsetHeight;
8066 var type = n.getData('type');
8067 if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8068 n.setData('width', offsetWidth);
8069 n.setData('height', offsetHeight);
8071 var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8072 n.setData('width', dim);
8073 n.setData('height', dim);
8074 n.setData('dim', dim);
8080 initializeLabel: function(opt) {
8082 this.label = document.createElement('div');
8083 document.body.appendChild(this.label);
8085 this.setLabelStyles(opt);
8088 setLabelStyles: function(opt) {
8089 $.extend(this.label.style, {
8090 'visibility': 'hidden',
8091 'position': 'absolute',
8095 this.label.className = 'jit-autoadjust-label';
8101 * Class: Layouts.Tree
8103 * Implements a Tree Layout.
8111 * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8114 Layouts.Tree = (function() {
8116 var slice = Array.prototype.slice;
8119 Calculates the max width and height nodes for a tree level
8121 function getBoundaries(graph, config, level, orn, prop) {
8122 var dim = config.Node;
8123 var multitree = config.multitree;
8124 if (dim.overridable) {
8126 graph.eachNode(function(n) {
8127 if (n._depth == level
8128 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8129 var dw = n.getData('width', prop);
8130 var dh = n.getData('height', prop);
8131 w = (w < dw) ? dw : w;
8132 h = (h < dh) ? dh : h;
8136 'width' : w < 0 ? dim.width : w,
8137 'height' : h < 0 ? dim.height : h
8145 function movetree(node, prop, val, orn) {
8146 var p = (orn == "left" || orn == "right") ? "y" : "x";
8147 node.getPos(prop)[p] += val;
8151 function moveextent(extent, val) {
8153 $.each(extent, function(elem) {
8154 elem = slice.call(elem);
8163 function merge(ps, qs) {
8168 var p = ps.shift(), q = qs.shift();
8169 return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8173 function mergelist(ls, def) {
8178 return mergelist(ls, merge(ps, def));
8182 function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8183 if (ext1.length <= i || ext2.length <= i)
8186 var p = ext1[i][1], q = ext2[i][0];
8187 return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8188 + subtreeOffset, p - q + siblingOffset);
8192 function fitlistl(es, subtreeOffset, siblingOffset) {
8193 function $fitlistl(acc, es, i) {
8196 var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8197 return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8200 return $fitlistl( [], es, 0);
8204 function fitlistr(es, subtreeOffset, siblingOffset) {
8205 function $fitlistr(acc, es, i) {
8208 var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8209 return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8212 es = slice.call(es);
8213 var ans = $fitlistr( [], es.reverse(), 0);
8214 return ans.reverse();
8218 function fitlist(es, subtreeOffset, siblingOffset, align) {
8219 var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8220 subtreeOffset, siblingOffset);
8222 if (align == "left")
8224 else if (align == "right")
8227 for ( var i = 0, ans = []; i < esl.length; i++) {
8228 ans[i] = (esl[i] + esr[i]) / 2;
8234 function design(graph, node, prop, config, orn) {
8235 var multitree = config.multitree;
8236 var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8237 var ind = +(orn == "left" || orn == "right");
8238 var p = auxp[ind], notp = auxp[1 - ind];
8240 var cnode = config.Node;
8241 var s = auxs[ind], nots = auxs[1 - ind];
8243 var siblingOffset = config.siblingOffset;
8244 var subtreeOffset = config.subtreeOffset;
8245 var align = config.align;
8247 function $design(node, maxsize, acum) {
8248 var sval = node.getData(s, prop);
8249 var notsval = maxsize
8250 || (node.getData(nots, prop));
8252 var trees = [], extents = [], chmaxsize = false;
8253 var chacum = notsval + config.levelDistance;
8254 node.eachSubnode(function(n) {
8256 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8259 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8261 var s = $design(n, chmaxsize[nots], acum + chacum);
8263 extents.push(s.extent);
8266 var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8267 for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8268 movetree(trees[i], prop, positions[i], orn);
8269 pextents.push(moveextent(extents[i], positions[i]));
8271 var resultextent = [ [ -sval / 2, sval / 2 ] ]
8272 .concat(mergelist(pextents));
8273 node.getPos(prop)[p] = 0;
8275 if (orn == "top" || orn == "left") {
8276 node.getPos(prop)[notp] = acum;
8278 node.getPos(prop)[notp] = -acum;
8283 extent : resultextent
8287 $design(node, false, 0);
8295 Computes nodes' positions.
8298 compute : function(property, computeLevels) {
8299 var prop = property || 'start';
8300 var node = this.graph.getNode(this.root);
8306 NodeDim.compute(this.graph, prop, this.config);
8307 if (!!computeLevels || !("_depth" in node)) {
8308 this.graph.computeLevels(this.root, 0, "ignore");
8311 this.computePositions(node, prop);
8314 computePositions : function(node, prop) {
8315 var config = this.config;
8316 var multitree = config.multitree;
8317 var align = config.align;
8318 var indent = align !== 'center' && config.indent;
8319 var orn = config.orientation;
8320 var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8322 $.each(orns, function(orn) {
8324 design(that.graph, node, prop, that.config, orn, prop);
8325 var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8327 (function red(node) {
8328 node.eachSubnode(function(n) {
8330 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8332 n.getPos(prop)[i] += node.getPos(prop)[i];
8334 n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8347 * File: Spacetree.js
8353 A Tree layout with advanced contraction and expansion animations.
8357 SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8358 <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8360 Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8364 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.
8368 All <Loader> methods
8370 Constructor Options:
8372 Inherits options from
8375 - <Options.Controller>
8382 - <Options.NodeStyles>
8383 - <Options.Navigation>
8385 Additionally, there are other parameters and some default values changed
8387 constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8388 levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8389 levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8390 Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8391 offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8392 offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8393 duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8395 Instance Properties:
8397 canvas - Access a <Canvas> instance.
8398 graph - Access a <Graph> instance.
8399 op - Access a <ST.Op> instance.
8400 fx - Access a <ST.Plot> instance.
8401 labels - Access a <ST.Label> interface implementation.
8405 $jit.ST= (function() {
8406 // Define some private methods first...
8408 var nodesInPath = [];
8409 // Nodes to contract
8410 function getNodesToHide(node) {
8411 node = node || this.clickedNode;
8412 if(!this.config.constrained) {
8415 var Geom = this.geom;
8416 var graph = this.graph;
8417 var canvas = this.canvas;
8418 var level = node._depth, nodeArray = [];
8419 graph.eachNode(function(n) {
8420 if(n.exist && !n.selected) {
8421 if(n.isDescendantOf(node.id)) {
8422 if(n._depth <= level) nodeArray.push(n);
8428 var leafLevel = Geom.getRightLevelToShow(node, canvas);
8429 node.eachLevel(leafLevel, leafLevel, function(n) {
8430 if(n.exist && !n.selected) nodeArray.push(n);
8433 for (var i = 0; i < nodesInPath.length; i++) {
8434 var n = this.graph.getNode(nodesInPath[i]);
8435 if(!n.isDescendantOf(node.id)) {
8442 function getNodesToShow(node) {
8443 var nodeArray = [], config = this.config;
8444 node = node || this.clickedNode;
8445 this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8446 if(config.multitree && !('$orn' in n.data)
8447 && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8449 } else if(n.drawn && !n.anySubnode("drawn")) {
8455 // Now define the actual class.
8458 Implements: [Loader, Extras, Layouts.Tree],
8460 initialize: function(controller) {
8475 this.controller = this.config = $.merge(
8476 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8477 "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8479 var canvasConfig = this.config;
8480 if(canvasConfig.useCanvas) {
8481 this.canvas = canvasConfig.useCanvas;
8482 this.config.labelContainer = this.canvas.id + '-label';
8484 if(canvasConfig.background) {
8485 canvasConfig.background = $.merge({
8487 colorStop1: this.config.colorStop1,
8488 colorStop2: this.config.colorStop2
8489 }, canvasConfig.background);
8491 this.canvas = new Canvas(this, canvasConfig);
8492 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8495 this.graphOptions = {
8498 this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8499 this.labels = new $ST.Label[canvasConfig.Label.type](this);
8500 this.fx = new $ST.Plot(this, $ST);
8501 this.op = new $ST.Op(this);
8502 this.group = new $ST.Group(this);
8503 this.geom = new $ST.Geom(this);
8504 this.clickedNode= null;
8505 // initialize extras
8506 this.initializeExtras();
8512 Plots the <ST>. This is a shortcut to *fx.plot*.
8515 plot: function() { this.fx.plot(this.controller); },
8519 Method: switchPosition
8521 Switches the tree orientation.
8525 pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8526 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.
8527 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8532 st.switchPosition("right", "animate", {
8533 onComplete: function() {
8534 alert('completed!');
8539 switchPosition: function(pos, method, onComplete) {
8540 var Geom = this.geom, Plot = this.fx, that = this;
8544 onComplete: function() {
8545 Geom.switchOrientation(pos);
8546 that.compute('end', false);
8548 if(method == 'animate') {
8549 that.onClick(that.clickedNode.id, onComplete);
8550 } else if(method == 'replot') {
8551 that.select(that.clickedNode.id, onComplete);
8559 Method: switchAlignment
8561 Switches the tree alignment.
8565 align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8566 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.
8567 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8572 st.switchAlignment("right", "animate", {
8573 onComplete: function() {
8574 alert('completed!');
8579 switchAlignment: function(align, method, onComplete) {
8580 this.config.align = align;
8581 if(method == 'animate') {
8582 this.select(this.clickedNode.id, onComplete);
8583 } else if(method == 'replot') {
8584 this.onClick(this.clickedNode.id, onComplete);
8589 Method: addNodeInPath
8591 Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8596 id - (string) A <Graph.Node> id.
8601 st.addNodeInPath("nodeId");
8604 addNodeInPath: function(id) {
8605 nodesInPath.push(id);
8606 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8610 Method: clearNodesInPath
8612 Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8621 st.clearNodesInPath();
8624 clearNodesInPath: function(id) {
8625 nodesInPath.length = 0;
8626 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8632 Computes positions and plots the tree.
8635 refresh: function() {
8637 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8640 reposition: function() {
8641 this.graph.computeLevels(this.root, 0, "ignore");
8642 this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8643 this.graph.eachNode(function(n) {
8644 if(n.exist) n.drawn = true;
8646 this.compute('end');
8649 requestNodes: function(node, onComplete) {
8650 var handler = $.merge(this.controller, onComplete),
8651 lev = this.config.levelsToShow;
8652 if(handler.request) {
8653 var leaves = [], d = node._depth;
8654 node.eachLevel(0, lev, function(n) {
8658 n._level = lev - (n._depth - d);
8661 this.group.requestNodes(leaves, handler);
8664 handler.onComplete();
8667 contract: function(onComplete, switched) {
8668 var orn = this.config.orientation;
8669 var Geom = this.geom, Group = this.group;
8670 if(switched) Geom.switchOrientation(switched);
8671 var nodes = getNodesToHide.call(this);
8672 if(switched) Geom.switchOrientation(orn);
8673 Group.contract(nodes, $.merge(this.controller, onComplete));
8676 move: function(node, onComplete) {
8677 this.compute('end', false);
8678 var move = onComplete.Move, offset = {
8683 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8685 this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8688 expand: function (node, onComplete) {
8689 var nodeArray = getNodesToShow.call(this, node);
8690 this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8693 selectPath: function(node) {
8695 this.graph.eachNode(function(n) { n.selected = false; });
8696 function path(node) {
8697 if(node == null || node.selected) return;
8698 node.selected = true;
8699 $.each(that.group.getSiblings([node])[node.id],
8704 var parents = node.getParents();
8705 parents = (parents.length > 0)? parents[0] : null;
8708 for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8709 path(this.graph.getNode(ns[i]));
8716 Switches the current root node. Changes the topology of the Tree.
8719 id - (string) The id of the node to be set as root.
8720 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.
8721 onComplete - (optional|object) An action to perform after the animation (if any).
8726 st.setRoot('nodeId', 'animate', {
8727 onComplete: function() {
8733 setRoot: function(id, method, onComplete) {
8734 if(this.busy) return;
8736 var that = this, canvas = this.canvas;
8737 var rootNode = this.graph.getNode(this.root);
8738 var clickedNode = this.graph.getNode(id);
8739 function $setRoot() {
8740 if(this.config.multitree && clickedNode.data.$orn) {
8741 var orn = clickedNode.data.$orn;
8748 rootNode.data.$orn = opp;
8749 (function tag(rootNode) {
8750 rootNode.eachSubnode(function(n) {
8757 delete clickedNode.data.$orn;
8760 this.clickedNode = clickedNode;
8761 this.graph.computeLevels(this.root, 0, "ignore");
8762 this.geom.setRightLevelToShow(clickedNode, canvas, {
8764 onShow: function(node) {
8767 node.setData('alpha', 1, 'end');
8768 node.setData('alpha', 0);
8769 node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8773 this.compute('end');
8776 modes: ['linear', 'node-property:alpha'],
8777 onComplete: function() {
8780 onComplete: function() {
8781 onComplete && onComplete.onComplete();
8788 // delete previous orientations (if any)
8789 delete rootNode.data.$orns;
8791 if(method == 'animate') {
8792 $setRoot.call(this);
8793 that.selectPath(clickedNode);
8794 } else if(method == 'replot') {
8795 $setRoot.call(this);
8796 this.select(this.root);
8806 subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8807 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.
8808 onComplete - (optional|object) An action to perform after the animation (if any).
8813 st.addSubtree(json, 'animate', {
8814 onComplete: function() {
8820 addSubtree: function(subtree, method, onComplete) {
8821 if(method == 'replot') {
8822 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8823 } else if (method == 'animate') {
8824 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8829 Method: removeSubtree
8834 id - (string) The _id_ of the subtree to be removed.
8835 removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8836 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.
8837 onComplete - (optional|object) An action to perform after the animation (if any).
8842 st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8843 onComplete: function() {
8850 removeSubtree: function(id, removeRoot, method, onComplete) {
8851 var node = this.graph.getNode(id), subids = [];
8852 node.eachLevel(+!removeRoot, false, function(n) {
8855 if(method == 'replot') {
8856 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8857 } else if (method == 'animate') {
8858 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8865 Selects a node in the <ST> without performing an animation. Useful when selecting
8866 nodes which are currently hidden or deep inside the tree.
8869 id - (string) The id of the node to select.
8870 onComplete - (optional|object) an onComplete callback.
8874 st.select('mynodeid', {
8875 onComplete: function() {
8881 select: function(id, onComplete) {
8882 var group = this.group, geom = this.geom;
8883 var node= this.graph.getNode(id), canvas = this.canvas;
8884 var root = this.graph.getNode(this.root);
8885 var complete = $.merge(this.controller, onComplete);
8888 complete.onBeforeCompute(node);
8889 this.selectPath(node);
8890 this.clickedNode= node;
8891 this.requestNodes(node, {
8892 onComplete: function(){
8893 group.hide(group.prepare(getNodesToHide.call(that)), complete);
8894 geom.setRightLevelToShow(node, canvas);
8895 that.compute("current");
8896 that.graph.eachNode(function(n) {
8897 var pos = n.pos.getc(true);
8898 n.startPos.setc(pos.x, pos.y);
8899 n.endPos.setc(pos.x, pos.y);
8902 var offset = { x: complete.offsetX, y: complete.offsetY };
8903 that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8904 group.show(getNodesToShow.call(that));
8906 complete.onAfterCompute(that.clickedNode);
8907 complete.onComplete();
8915 Animates the <ST> to center the node specified by *id*.
8919 id - (string) A node id.
8920 options - (optional|object) A group of options and callbacks described below.
8921 onComplete - (object) An object callback called when the animation finishes.
8922 Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8927 st.onClick('mynodeid', {
8933 onComplete: function() {
8940 onClick: function (id, options) {
8941 var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8942 var innerController = {
8945 offsetX: config.offsetX || 0,
8946 offsetY: config.offsetY || 0
8948 setRightLevelToShowConfig: false,
8949 onBeforeRequest: $.empty,
8950 onBeforeContract: $.empty,
8951 onBeforeMove: $.empty,
8952 onBeforeExpand: $.empty
8954 var complete = $.merge(this.controller, innerController, options);
8958 var node = this.graph.getNode(id);
8959 this.selectPath(node, this.clickedNode);
8960 this.clickedNode = node;
8961 complete.onBeforeCompute(node);
8962 complete.onBeforeRequest(node);
8963 this.requestNodes(node, {
8964 onComplete: function() {
8965 complete.onBeforeContract(node);
8967 onComplete: function() {
8968 Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8969 complete.onBeforeMove(node);
8971 Move: complete.Move,
8972 onComplete: function() {
8973 complete.onBeforeExpand(node);
8975 onComplete: function() {
8977 complete.onAfterCompute(id);
8978 complete.onComplete();
8993 $jit.ST.$extend = true;
8998 Custom extension of <Graph.Op>.
9002 All <Graph.Op> methods
9009 $jit.ST.Op = new Class({
9011 Implements: Graph.Op
9017 Performs operations on group of nodes.
9020 $jit.ST.Group = new Class({
9022 initialize: function(viz) {
9024 this.canvas = viz.canvas;
9025 this.config = viz.config;
9026 this.animation = new Animation;
9032 Calls the request method on the controller to request a subtree for each node.
9034 requestNodes: function(nodes, controller) {
9035 var counter = 0, len = nodes.length, nodeSelected = {};
9036 var complete = function() { controller.onComplete(); };
9038 if(len == 0) complete();
9039 for(var i=0; i<len; i++) {
9040 nodeSelected[nodes[i].id] = nodes[i];
9041 controller.request(nodes[i].id, nodes[i]._level, {
9042 onComplete: function(nodeId, data) {
9043 if(data && data.children) {
9045 viz.op.sum(data, { type: 'nothing' });
9047 if(++counter == len) {
9048 viz.graph.computeLevels(viz.root, 0);
9058 Collapses group of nodes.
9060 contract: function(nodes, controller) {
9064 nodes = this.prepare(nodes);
9065 this.animation.setOptions($.merge(controller, {
9067 compute: function(delta) {
9068 if(delta == 1) delta = 0.99;
9069 that.plotStep(1 - delta, controller, this.$animating);
9070 this.$animating = 'contract';
9073 complete: function() {
9074 that.hide(nodes, controller);
9079 hide: function(nodes, controller) {
9081 for(var i=0; i<nodes.length; i++) {
9082 // TODO nodes are requested on demand, but not
9083 // deleted when hidden. Would that be a good feature?
9084 // Currently that feature is buggy, so I'll turn it off
9085 // Actually this feature is buggy because trimming should take
9086 // place onAfterCompute and not right after collapsing nodes.
9087 if (true || !controller || !controller.request) {
9088 nodes[i].eachLevel(1, false, function(elem){
9098 nodes[i].eachLevel(1, false, function(n) {
9101 viz.op.removeNode(ids, { 'type': 'nothing' });
9102 viz.labels.clearLabels();
9105 controller.onComplete();
9110 Expands group of nodes.
9112 expand: function(nodes, controller) {
9115 this.animation.setOptions($.merge(controller, {
9117 compute: function(delta) {
9118 that.plotStep(delta, controller, this.$animating);
9119 this.$animating = 'expand';
9122 complete: function() {
9123 that.plotStep(undefined, controller, false);
9124 controller.onComplete();
9130 show: function(nodes) {
9131 var config = this.config;
9132 this.prepare(nodes);
9133 $.each(nodes, function(n) {
9134 // check for root nodes if multitree
9135 if(config.multitree && !('$orn' in n.data)) {
9136 delete n.data.$orns;
9138 n.eachSubnode(function(ch) {
9139 if(('$orn' in ch.data)
9140 && orns.indexOf(ch.data.$orn) < 0
9141 && ch.exist && !ch.drawn) {
9142 orns += ch.data.$orn + ' ';
9145 n.data.$orns = orns;
9147 n.eachLevel(0, config.levelsToShow, function(n) {
9148 if(n.exist) n.drawn = true;
9153 prepare: function(nodes) {
9154 this.nodes = this.getNodesWithChildren(nodes);
9159 Filters an array of nodes leaving only nodes with children.
9161 getNodesWithChildren: function(nodes) {
9162 var ans = [], config = this.config, root = this.viz.root;
9163 nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9164 for(var i=0; i<nodes.length; i++) {
9165 if(nodes[i].anySubnode("exist")) {
9166 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9167 if(!config.multitree || '$orn' in nodes[j].data) {
9168 desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9171 if(!desc) ans.push(nodes[i]);
9177 plotStep: function(delta, controller, animating) {
9179 config = this.config,
9180 canvas = viz.canvas,
9181 ctx = canvas.getCtx(),
9184 // hide nodes that are meant to be collapsed/expanded
9186 for(i=0; i<nodes.length; i++) {
9189 var root = config.multitree && !('$orn' in node.data);
9190 var orns = root && node.data.$orns;
9191 node.eachSubgraph(function(n) {
9192 // TODO(nico): Cleanup
9193 // special check for root node subnodes when
9194 // multitree is checked.
9195 if(root && orns && orns.indexOf(n.data.$orn) > 0
9198 nds[node.id].push(n);
9199 } else if((!root || !orns) && n.drawn) {
9201 nds[node.id].push(n);
9206 // plot the whole (non-scaled) tree
9207 if(nodes.length > 0) viz.fx.plot();
9208 // show nodes that were previously hidden
9210 $.each(nds[i], function(n) { n.drawn = true; });
9212 // plot each scaled subtree
9213 for(i=0; i<nodes.length; i++) {
9216 viz.fx.plotSubtree(node, controller, delta, animating);
9221 getSiblings: function(nodes) {
9223 $.each(nodes, function(n) {
9224 var par = n.getParents();
9225 if (par.length == 0) {
9226 siblings[n.id] = [n];
9229 par[0].eachSubnode(function(sn) {
9232 siblings[n.id] = ans;
9242 Performs low level geometrical computations.
9246 This instance can be accessed with the _geom_ parameter of the st instance created.
9251 var st = new ST(canvas, config);
9252 st.geom.translate //or can also call any other <ST.Geom> method
9257 $jit.ST.Geom = new Class({
9258 Implements: Graph.Geom,
9260 Changes the tree current orientation to the one specified.
9262 You should usually use <ST.switchPosition> instead.
9264 switchOrientation: function(orn) {
9265 this.config.orientation = orn;
9269 Makes a value dispatch according to the current layout
9270 Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9272 dispatch: function() {
9273 // TODO(nico) should store Array.prototype.slice.call somewhere.
9274 var args = Array.prototype.slice.call(arguments);
9275 var s = args.shift(), len = args.length;
9276 var val = function(a) { return typeof a == 'function'? a() : a; };
9278 return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9279 } else if(len == 4) {
9281 case "top": return val(args[0]);
9282 case "right": return val(args[1]);
9283 case "bottom": return val(args[2]);
9284 case "left": return val(args[3]);
9291 Returns label height or with, depending on the tree current orientation.
9293 getSize: function(n, invert) {
9294 var data = n.data, config = this.config;
9295 var siblingOffset = config.siblingOffset;
9296 var s = (config.multitree
9298 && data.$orn) || config.orientation;
9299 var w = n.getData('width') + siblingOffset;
9300 var h = n.getData('height') + siblingOffset;
9302 return this.dispatch(s, h, w);
9304 return this.dispatch(s, w, h);
9308 Calculates a subtree base size. This is an utility function used by _getBaseSize_
9310 getTreeBaseSize: function(node, level, leaf) {
9311 var size = this.getSize(node, true), baseHeight = 0, that = this;
9312 if(leaf(level, node)) return size;
9313 if(level === 0) return 0;
9314 node.eachSubnode(function(elem) {
9315 baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9317 return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9324 Returns a Complex instance with the begin or end position of the edge to be plotted.
9328 node - A <Graph.Node> that is connected to this edge.
9329 type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9333 A <Complex> number specifying the begin or end position.
9335 getEdge: function(node, type, s) {
9336 var $C = function(a, b) {
9338 return node.pos.add(new Complex(a, b));
9341 var dim = this.node;
9342 var w = node.getData('width');
9343 var h = node.getData('height');
9345 if(type == 'begin') {
9346 if(dim.align == "center") {
9347 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9348 $C(0, -h/2),$C(w/2, 0));
9349 } else if(dim.align == "left") {
9350 return this.dispatch(s, $C(0, h), $C(0, 0),
9351 $C(0, 0), $C(w, 0));
9352 } else if(dim.align == "right") {
9353 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9354 $C(0, -h),$C(0, 0));
9355 } else throw "align: not implemented";
9358 } else if(type == 'end') {
9359 if(dim.align == "center") {
9360 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9361 $C(0, h/2), $C(-w/2, 0));
9362 } else if(dim.align == "left") {
9363 return this.dispatch(s, $C(0, 0), $C(w, 0),
9364 $C(0, h), $C(0, 0));
9365 } else if(dim.align == "right") {
9366 return this.dispatch(s, $C(0, -h),$C(0, 0),
9367 $C(0, 0), $C(-w, 0));
9368 } else throw "align: not implemented";
9373 Adjusts the tree position due to canvas scaling or translation.
9375 getScaledTreePosition: function(node, scale) {
9376 var dim = this.node;
9377 var w = node.getData('width');
9378 var h = node.getData('height');
9379 var s = (this.config.multitree
9380 && ('$orn' in node.data)
9381 && node.data.$orn) || this.config.orientation;
9383 var $C = function(a, b) {
9385 return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9388 if(dim.align == "left") {
9389 return this.dispatch(s, $C(0, h), $C(0, 0),
9390 $C(0, 0), $C(w, 0));
9391 } else if(dim.align == "center") {
9392 return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9393 $C(0, -h / 2),$C(w / 2, 0));
9394 } else if(dim.align == "right") {
9395 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9396 $C(0, -h),$C(0, 0));
9397 } else throw "align: not implemented";
9403 Returns a Boolean if the current subtree fits in canvas.
9407 node - A <Graph.Node> which is the current root of the subtree.
9408 canvas - The <Canvas> object.
9409 level - The depth of the subtree to be considered.
9411 treeFitsInCanvas: function(node, canvas, level) {
9412 var csize = canvas.getSize();
9413 var s = (this.config.multitree
9414 && ('$orn' in node.data)
9415 && node.data.$orn) || this.config.orientation;
9417 var size = this.dispatch(s, csize.width, csize.height);
9418 var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9419 return level === 0 || !node.anySubnode();
9421 return (baseSize < size);
9428 Custom extension of <Graph.Plot>.
9432 All <Graph.Plot> methods
9439 $jit.ST.Plot = new Class({
9441 Implements: Graph.Plot,
9444 Plots a subtree from the spacetree.
9446 plotSubtree: function(node, opt, scale, animating) {
9447 var viz = this.viz, canvas = viz.canvas, config = viz.config;
9448 scale = Math.min(Math.max(0.001, scale), 1);
9451 var ctx = canvas.getCtx();
9452 var diff = viz.geom.getScaledTreePosition(node, scale);
9453 ctx.translate(diff.x, diff.y);
9454 ctx.scale(scale, scale);
9456 this.plotTree(node, $.merge(opt, {
9458 'hideLabels': !!scale,
9459 'plotSubtree': function(n, ch) {
9460 var root = config.multitree && !('$orn' in node.data);
9461 var orns = root && node.getData('orns');
9462 return !root || orns.indexOf(elem.getData('orn')) > -1;
9465 if(scale >= 0) node.drawn = true;
9469 Method: getAlignedPos
9471 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9475 pos - (object) A <Graph.Node> position.
9476 width - (number) The width of the node.
9477 height - (number) The height of the node.
9480 getAlignedPos: function(pos, width, height) {
9481 var nconfig = this.node;
9483 if(nconfig.align == "center") {
9485 x: pos.x - width / 2,
9486 y: pos.y - height / 2
9488 } else if (nconfig.align == "left") {
9489 orn = this.config.orientation;
9490 if(orn == "bottom" || orn == "top") {
9492 x: pos.x - width / 2,
9498 y: pos.y - height / 2
9501 } else if(nconfig.align == "right") {
9502 orn = this.config.orientation;
9503 if(orn == "bottom" || orn == "top") {
9505 x: pos.x - width / 2,
9511 y: pos.y - height / 2
9514 } else throw "align: not implemented";
9519 getOrientation: function(adj) {
9520 var config = this.config;
9521 var orn = config.orientation;
9523 if(config.multitree) {
9524 var nodeFrom = adj.nodeFrom;
9525 var nodeTo = adj.nodeTo;
9526 orn = (('$orn' in nodeFrom.data)
9527 && nodeFrom.data.$orn)
9528 || (('$orn' in nodeTo.data)
9529 && nodeTo.data.$orn);
9539 Custom extension of <Graph.Label>.
9540 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9544 All <Graph.Label> methods and subclasses.
9548 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9555 Custom extension of <Graph.Label.Native>.
9559 All <Graph.Label.Native> methods
9563 <Graph.Label.Native>
9565 $jit.ST.Label.Native = new Class({
9566 Implements: Graph.Label.Native,
9568 renderLabel: function(canvas, node, controller) {
9569 var ctx = canvas.getCtx();
9570 var coord = node.pos.getc(true);
9571 ctx.fillText(node.name, coord.x, coord.y);
9575 $jit.ST.Label.DOM = new Class({
9576 Implements: Graph.Label.DOM,
9581 Overrides abstract method placeLabel in <Graph.Plot>.
9585 tag - A DOM label element.
9586 node - A <Graph.Node>.
9587 controller - A configuration/controller object passed to the visualization.
9590 placeLabel: function(tag, node, controller) {
9591 var pos = node.pos.getc(true),
9592 config = this.viz.config,
9594 canvas = this.viz.canvas,
9595 w = node.getData('width'),
9596 h = node.getData('height'),
9597 radius = canvas.getSize(),
9600 var ox = canvas.translateOffsetX,
9601 oy = canvas.translateOffsetY,
9602 sx = canvas.scaleOffsetX,
9603 sy = canvas.scaleOffsetY,
9604 posx = pos.x * sx + ox,
9605 posy = pos.y * sy + oy;
9607 if(dim.align == "center") {
9609 x: Math.round(posx - w / 2 + radius.width/2),
9610 y: Math.round(posy - h / 2 + radius.height/2)
9612 } else if (dim.align == "left") {
9613 orn = config.orientation;
9614 if(orn == "bottom" || orn == "top") {
9616 x: Math.round(posx - w / 2 + radius.width/2),
9617 y: Math.round(posy + radius.height/2)
9621 x: Math.round(posx + radius.width/2),
9622 y: Math.round(posy - h / 2 + radius.height/2)
9625 } else if(dim.align == "right") {
9626 orn = config.orientation;
9627 if(orn == "bottom" || orn == "top") {
9629 x: Math.round(posx - w / 2 + radius.width/2),
9630 y: Math.round(posy - h + radius.height/2)
9634 x: Math.round(posx - w + radius.width/2),
9635 y: Math.round(posy - h / 2 + radius.height/2)
9638 } else throw "align: not implemented";
9640 var style = tag.style;
9641 style.left = labelPos.x + 'px';
9642 style.top = labelPos.y + 'px';
9643 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9644 controller.onPlaceLabel(tag, node);
9651 Custom extension of <Graph.Label.SVG>.
9655 All <Graph.Label.SVG> methods
9661 $jit.ST.Label.SVG = new Class({
9662 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9664 initialize: function(viz) {
9672 Custom extension of <Graph.Label.HTML>.
9676 All <Graph.Label.HTML> methods.
9683 $jit.ST.Label.HTML = new Class({
9684 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9686 initialize: function(viz) {
9693 Class: ST.Plot.NodeTypes
9695 This class contains a list of <Graph.Node> built-in types.
9696 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9698 You can add your custom node types, customizing your visualization to the extreme.
9703 ST.Plot.NodeTypes.implement({
9705 'render': function(node, canvas) {
9706 //print your custom node to canvas
9709 'contains': function(node, pos) {
9710 //return true if pos is inside the node or false otherwise
9717 $jit.ST.Plot.NodeTypes = new Class({
9720 'contains': $.lambda(false)
9723 'render': function(node, canvas) {
9724 var dim = node.getData('dim'),
9725 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9727 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9729 'contains': function(node, pos) {
9730 var dim = node.getData('dim'),
9731 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9733 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9737 'render': function(node, canvas) {
9738 var dim = node.getData('dim'),
9740 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9741 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9743 'contains': function(node, pos) {
9744 var dim = node.getData('dim'),
9745 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9747 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9751 'render': function(node, canvas) {
9752 var width = node.getData('width'),
9753 height = node.getData('height'),
9754 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9755 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9757 'contains': function(node, pos) {
9758 var width = node.getData('width'),
9759 height = node.getData('height'),
9760 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9761 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9765 'render': function(node, canvas) {
9766 var width = node.getData('width'),
9767 height = node.getData('height'),
9768 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9769 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9771 'contains': function(node, pos) {
9772 var width = node.getData('width'),
9773 height = node.getData('height'),
9774 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9775 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9781 Class: ST.Plot.EdgeTypes
9783 This class contains a list of <Graph.Adjacence> built-in types.
9784 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9786 You can add your custom edge types, customizing your visualization to the extreme.
9791 ST.Plot.EdgeTypes.implement({
9793 'render': function(adj, canvas) {
9794 //print your custom edge to canvas
9797 'contains': function(adj, pos) {
9798 //return true if pos is inside the arc or false otherwise
9805 $jit.ST.Plot.EdgeTypes = new Class({
9808 'render': function(adj, canvas) {
9809 var orn = this.getOrientation(adj),
9810 nodeFrom = adj.nodeFrom,
9811 nodeTo = adj.nodeTo,
9812 rel = nodeFrom._depth < nodeTo._depth,
9813 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9814 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9815 this.edgeHelper.line.render(from, to, canvas);
9817 'contains': function(adj, pos) {
9818 var orn = this.getOrientation(adj),
9819 nodeFrom = adj.nodeFrom,
9820 nodeTo = adj.nodeTo,
9821 rel = nodeFrom._depth < nodeTo._depth,
9822 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9823 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9824 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9828 'render': function(adj, canvas) {
9829 var orn = this.getOrientation(adj),
9830 node = adj.nodeFrom,
9832 dim = adj.getData('dim'),
9833 from = this.viz.geom.getEdge(node, 'begin', orn),
9834 to = this.viz.geom.getEdge(child, 'end', orn),
9835 direction = adj.data.$direction,
9836 inv = (direction && direction.length>1 && direction[0] != node.id);
9837 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9839 'contains': function(adj, pos) {
9840 var orn = this.getOrientation(adj),
9841 nodeFrom = adj.nodeFrom,
9842 nodeTo = adj.nodeTo,
9843 rel = nodeFrom._depth < nodeTo._depth,
9844 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9845 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9846 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9849 'quadratic:begin': {
9850 'render': function(adj, canvas) {
9851 var orn = this.getOrientation(adj);
9852 var nodeFrom = adj.nodeFrom,
9853 nodeTo = adj.nodeTo,
9854 rel = nodeFrom._depth < nodeTo._depth,
9855 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9856 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9857 dim = adj.getData('dim'),
9858 ctx = canvas.getCtx();
9860 ctx.moveTo(begin.x, begin.y);
9863 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9866 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9869 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9872 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9879 'render': function(adj, canvas) {
9880 var orn = this.getOrientation(adj);
9881 var nodeFrom = adj.nodeFrom,
9882 nodeTo = adj.nodeTo,
9883 rel = nodeFrom._depth < nodeTo._depth,
9884 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9885 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9886 dim = adj.getData('dim'),
9887 ctx = canvas.getCtx();
9889 ctx.moveTo(begin.x, begin.y);
9892 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9895 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9898 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9901 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9908 'render': function(adj, canvas) {
9909 var orn = this.getOrientation(adj),
9910 nodeFrom = adj.nodeFrom,
9911 nodeTo = adj.nodeTo,
9912 rel = nodeFrom._depth < nodeTo._depth,
9913 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9914 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9915 dim = adj.getData('dim'),
9916 ctx = canvas.getCtx();
9918 ctx.moveTo(begin.x, begin.y);
9921 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9924 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9927 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9930 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9939 Options.LineChart = {
9943 labelOffset: 3, // label offset
9944 type: 'basic', // gradient
9960 selectOnHover: true,
9961 showAggregates: true,
9963 filterOnClick: false,
9964 restoreOnRightClick: false
9969 * File: LineChart.js
9973 $jit.ST.Plot.NodeTypes.implement({
9974 'linechart-basic' : {
9975 'render' : function(node, canvas) {
9976 var pos = node.pos.getc(true),
9977 width = node.getData('width'),
9978 height = node.getData('height'),
9979 algnPos = this.getAlignedPos(pos, width, height),
9980 x = algnPos.x + width/2 , y = algnPos.y,
9981 stringArray = node.getData('stringArray'),
9982 lastNode = node.getData('lastNode'),
9983 dimArray = node.getData('dimArray'),
9984 valArray = node.getData('valueArray'),
9985 colorArray = node.getData('colorArray'),
9986 colorLength = colorArray.length,
9987 config = node.getData('config'),
9988 gradient = node.getData('gradient'),
9989 showLabels = config.showLabels,
9990 aggregates = config.showAggregates,
9991 label = config.Label,
9992 prev = node.getData('prev'),
9993 dataPointSize = config.dataPointSize;
9995 var ctx = canvas.getCtx(), border = node.getData('border');
9996 if (colorArray && dimArray && stringArray) {
9998 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
9999 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10001 ctx.lineCap = "round";
10005 //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
10007 ctx.moveTo(x, y - dimArray[i][0]);
10008 ctx.lineTo(x + width, y - dimArray[i][1]);
10012 //render data point
10013 ctx.fillRect(x - (dataPointSize/2), y - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10017 if(label.type == 'Native' && showLabels) {
10019 ctx.fillStyle = ctx.strokeStyle = label.color;
10020 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10021 ctx.textAlign = 'center';
10022 ctx.textBaseline = 'middle';
10023 ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10029 'contains': function(node, mpos) {
10030 var pos = node.pos.getc(true),
10031 width = node.getData('width'),
10032 height = node.getData('height'),
10033 config = node.getData('config'),
10034 dataPointSize = config.dataPointSize,
10035 dataPointMidPoint = dataPointSize/2,
10036 algnPos = this.getAlignedPos(pos, width, height),
10037 x = algnPos.x + width/2, y = algnPos.y,
10038 dimArray = node.getData('dimArray');
10039 //bounding box check
10040 if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10044 for(var i=0, l=dimArray.length; i<l; i++) {
10045 var dimi = dimArray[i];
10046 var url = Url.decode(node.getData('linkArray')[i]);
10047 if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10048 var valArrayCur = node.getData('valArrayCur');
10049 var results = array_match(valArrayCur[i],valArrayCur);
10050 var matches = results[0];
10051 var indexValues = results[1];
10053 var names = new Array(),
10054 values = new Array(),
10055 percentages = new Array(),
10056 linksArr = new Array();
10057 for(var j=0, il=indexValues.length; j<il; j++) {
10058 names[j] = node.getData('stringArray')[indexValues[j]];
10059 values[j] = valArrayCur[indexValues[j]];
10060 percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10061 linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10066 'color': node.getData('colorArray')[i],
10068 'percentage': percentages,
10075 'name': node.getData('stringArray')[i],
10076 'color': node.getData('colorArray')[i],
10077 'value': node.getData('valueArray')[i][0],
10078 // 'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10079 'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10094 A visualization that displays line charts.
10096 Constructor Options:
10098 See <Options.Line>.
10101 $jit.LineChart = new Class({
10103 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10107 initialize: function(opt) {
10108 this.controller = this.config =
10109 $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10110 Label: { type: 'Native' }
10112 //set functions for showLabels and showAggregates
10113 var showLabels = this.config.showLabels,
10114 typeLabels = $.type(showLabels),
10115 showAggregates = this.config.showAggregates,
10116 typeAggregates = $.type(showAggregates);
10117 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10118 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10119 Options.Fx.clearCanvas = false;
10120 this.initializeViz();
10123 initializeViz: function() {
10124 var config = this.config,
10126 nodeType = config.type.split(":")[0],
10129 var st = new $jit.ST({
10130 injectInto: config.injectInto,
10131 orientation: "bottom",
10132 backgroundColor: config.backgroundColor,
10133 renderBackground: config.renderBackground,
10137 withLabels: config.Label.type != 'Native',
10138 useCanvas: config.useCanvas,
10140 type: config.Label.type
10144 type: 'linechart-' + nodeType,
10153 enable: config.Tips.enable,
10156 onShow: function(tip, node, contains) {
10157 var elem = contains;
10158 config.Tips.onShow(tip, elem, node);
10164 onClick: function(node, eventInfo, evt) {
10165 if(!config.filterOnClick && !config.Events.enable) return;
10166 var elem = eventInfo.getContains();
10167 if(elem) config.filterOnClick && that.filter(elem.name);
10168 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10170 onRightClick: function(node, eventInfo, evt) {
10171 if(!config.restoreOnRightClick) return;
10174 onMouseMove: function(node, eventInfo, evt) {
10175 if(!config.selectOnHover) return;
10177 var elem = eventInfo.getContains();
10178 that.select(node.id, elem.name, elem.index);
10180 that.select(false, false, false);
10184 onCreateLabel: function(domElement, node) {
10185 var labelConf = config.Label,
10186 valueArray = node.getData('valueArray'),
10187 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10188 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10189 if(node.getData('prev')) {
10191 wrapper: document.createElement('div'),
10192 aggregate: document.createElement('div'),
10193 label: document.createElement('div')
10195 var wrapper = nlbs.wrapper,
10196 label = nlbs.label,
10197 aggregate = nlbs.aggregate,
10198 wrapperStyle = wrapper.style,
10199 labelStyle = label.style,
10200 aggregateStyle = aggregate.style;
10201 //store node labels
10202 nodeLabels[node.id] = nlbs;
10204 wrapper.appendChild(label);
10205 wrapper.appendChild(aggregate);
10206 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10207 label.style.display = 'none';
10209 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10210 aggregate.style.display = 'none';
10212 wrapperStyle.position = 'relative';
10213 wrapperStyle.overflow = 'visible';
10214 wrapperStyle.fontSize = labelConf.size + 'px';
10215 wrapperStyle.fontFamily = labelConf.family;
10216 wrapperStyle.color = labelConf.color;
10217 wrapperStyle.textAlign = 'center';
10218 aggregateStyle.position = labelStyle.position = 'absolute';
10220 domElement.style.width = node.getData('width') + 'px';
10221 domElement.style.height = node.getData('height') + 'px';
10222 label.innerHTML = node.name;
10224 domElement.appendChild(wrapper);
10227 onPlaceLabel: function(domElement, node) {
10228 if(!node.getData('prev')) return;
10229 var labels = nodeLabels[node.id],
10230 wrapperStyle = labels.wrapper.style,
10231 labelStyle = labels.label.style,
10232 aggregateStyle = labels.aggregate.style,
10233 width = node.getData('width'),
10234 height = node.getData('height'),
10235 dimArray = node.getData('dimArray'),
10236 valArray = node.getData('valueArray'),
10237 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10238 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10239 font = parseInt(wrapperStyle.fontSize, 10),
10240 domStyle = domElement.style;
10242 if(dimArray && valArray) {
10243 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10244 labelStyle.display = '';
10246 labelStyle.display = 'none';
10248 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10249 aggregateStyle.display = '';
10251 aggregateStyle.display = 'none';
10253 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10254 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10255 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10256 if(dimArray[i][0] > 0) {
10257 acum+= valArray[i][0];
10258 leftAcum+= dimArray[i][0];
10261 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10262 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10263 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10264 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10265 labels.aggregate.innerHTML = acum;
10270 var size = st.canvas.getSize(),
10271 margin = config.Margin;
10272 st.config.offsetY = -size.height/2 + margin.bottom
10273 + (config.showLabels && (config.labelOffset + config.Label.size));
10274 st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10276 this.canvas = this.st.canvas;
10279 renderTitle: function() {
10280 var canvas = this.canvas,
10281 size = canvas.getSize(),
10282 config = this.config,
10283 margin = config.Margin,
10284 label = config.Label,
10285 title = config.Title;
10286 ctx = canvas.getCtx();
10287 ctx.fillStyle = title.color;
10288 ctx.textAlign = 'left';
10289 ctx.textBaseline = 'top';
10290 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10291 if(label.type == 'Native') {
10292 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10296 renderTicks: function() {
10298 var canvas = this.canvas,
10299 size = canvas.getSize(),
10300 config = this.config,
10301 margin = config.Margin,
10302 ticks = config.Ticks,
10303 title = config.Title,
10304 subtitle = config.Subtitle,
10305 label = config.Label,
10306 maxValue = this.maxValue,
10307 maxTickValue = Math.ceil(maxValue*.1)*10;
10308 if(maxTickValue == maxValue) {
10309 var length = maxTickValue.toString().length;
10310 maxTickValue = maxTickValue + parseInt(pad(1,length));
10315 labelIncrement = maxTickValue/ticks.segments,
10316 ctx = canvas.getCtx();
10317 ctx.strokeStyle = ticks.color;
10318 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10319 ctx.textAlign = 'center';
10320 ctx.textBaseline = 'middle';
10322 idLabel = canvas.id + "-label";
10324 container = document.getElementById(idLabel);
10327 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10328 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10329 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)),
10330 segmentLength = grid/ticks.segments;
10331 ctx.fillStyle = ticks.color;
10332 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));
10334 while(axis>=grid) {
10336 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10337 ctx.rotate(Math.PI / 2);
10338 ctx.fillStyle = label.color;
10339 if(config.showLabels) {
10340 if(label.type == 'Native') {
10341 ctx.fillText(labelValue, 0, 0);
10343 //html labels on y axis
10344 labelDiv = document.createElement('div');
10345 labelDiv.innerHTML = labelValue;
10346 labelDiv.className = "rotatedLabel";
10347 // labelDiv.class = "rotatedLabel";
10348 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10349 labelDiv.style.left = margin.left + "px";
10350 labelDiv.style.width = labelDim + "px";
10351 labelDiv.style.height = labelDim + "px";
10352 labelDiv.style.textAlign = "center";
10353 labelDiv.style.verticalAlign = "middle";
10354 labelDiv.style.position = "absolute";
10355 container.appendChild(labelDiv);
10359 ctx.fillStyle = ticks.color;
10360 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 );
10361 htmlOrigin += segmentLength;
10362 axis += segmentLength;
10363 labelValue += labelIncrement;
10373 renderBackground: function() {
10374 var canvas = this.canvas,
10375 config = this.config,
10376 backgroundColor = config.backgroundColor,
10377 size = canvas.getSize(),
10378 ctx = canvas.getCtx();
10379 //ctx.globalCompositeOperation = "destination-over";
10380 ctx.fillStyle = backgroundColor;
10381 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10388 Loads JSON data into the visualization.
10392 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>.
10396 var areaChart = new $jit.AreaChart(options);
10397 areaChart.loadJSON(json);
10400 loadJSON: function(json) {
10401 var prefix = $.time(),
10404 name = $.splat(json.label),
10405 color = $.splat(json.color || this.colors),
10406 config = this.config,
10407 ticks = config.Ticks,
10408 renderBackground = config.renderBackground,
10409 gradient = !!config.type.split(":")[1],
10410 animate = config.animate,
10411 title = config.Title,
10412 groupTotalValue = 0;
10414 var valArrayAll = new Array();
10416 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10417 var val = values[i];
10418 var valArray = $.splat(val.values);
10419 for (var j=0, len=valArray.length; j<len; j++) {
10420 valArrayAll.push(parseInt(valArray[j]));
10422 groupTotalValue += parseInt(valArray.sum());
10425 this.maxValue = Math.max.apply(null, valArrayAll);
10427 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10428 var val = values[i], prev = values[i-1];
10430 var next = (i+1 < l) ? values[i+1] : 0;
10431 var valLeft = $.splat(values[i].values);
10432 var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10433 var valArray = $.zip(valLeft, valRight);
10434 var valArrayCur = $.splat(values[i].values);
10435 var linkArray = $.splat(values[i].links);
10436 var acumLeft = 0, acumRight = 0;
10437 var lastNode = (l-1 == i) ? true : false;
10439 'id': prefix + val.label,
10443 '$valueArray': valArray,
10444 '$valArrayCur': valArrayCur,
10445 '$colorArray': color,
10446 '$linkArray': linkArray,
10447 '$stringArray': name,
10448 '$next': next? next.label:false,
10449 '$prev': prev? prev.label:false,
10451 '$lastNode': lastNode,
10452 '$groupTotalValue': groupTotalValue,
10453 '$gradient': gradient
10459 'id': prefix + '$root',
10470 this.normalizeDims();
10472 if(renderBackground) {
10473 this.renderBackground();
10476 if(!animate && ticks.enable) {
10477 this.renderTicks();
10482 this.renderTitle();
10486 st.select(st.root);
10489 modes: ['node-property:height:dimArray'],
10498 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.
10502 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10503 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10508 areaChart.updateJSON(json, {
10509 onComplete: function() {
10510 alert('update complete!');
10515 updateJSON: function(json, onComplete) {
10516 if(this.busy) return;
10521 labels = json.label && $.splat(json.label),
10522 values = json.values,
10523 animate = this.config.animate,
10525 $.each(values, function(v) {
10526 var n = graph.getByName(v.label);
10528 v.values = $.splat(v.values);
10529 var stringArray = n.getData('stringArray'),
10530 valArray = n.getData('valueArray');
10531 $.each(valArray, function(a, i) {
10532 a[0] = v.values[i];
10533 if(labels) stringArray[i] = labels[i];
10535 n.setData('valueArray', valArray);
10536 var prev = n.getData('prev'),
10537 next = n.getData('next'),
10538 nextNode = graph.getByName(next);
10540 var p = graph.getByName(prev);
10542 var valArray = p.getData('valueArray');
10543 $.each(valArray, function(a, i) {
10544 a[1] = v.values[i];
10549 var valArray = n.getData('valueArray');
10550 $.each(valArray, function(a, i) {
10551 a[1] = v.values[i];
10556 this.normalizeDims();
10559 st.select(st.root);
10562 modes: ['node-property:height:dimArray'],
10564 onComplete: function() {
10566 onComplete && onComplete.onComplete();
10575 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10579 Variable strings arguments with the name of the stacks.
10584 areaChart.filter('label A', 'label C');
10589 <AreaChart.restore>.
10591 filter: function() {
10592 if(this.busy) return;
10594 if(this.config.Tips.enable) this.st.tips.hide();
10595 this.select(false, false, false);
10596 var args = Array.prototype.slice.call(arguments);
10597 var rt = this.st.graph.getNode(this.st.root);
10599 rt.eachAdjacency(function(adj) {
10600 var n = adj.nodeTo,
10601 dimArray = n.getData('dimArray'),
10602 stringArray = n.getData('stringArray');
10603 n.setData('dimArray', $.map(dimArray, function(d, i) {
10604 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10607 this.st.fx.animate({
10608 modes: ['node-property:dimArray'],
10610 onComplete: function() {
10619 Sets all stacks that could have been filtered visible.
10624 areaChart.restore();
10629 <AreaChart.filter>.
10631 restore: function() {
10632 if(this.busy) return;
10634 if(this.config.Tips.enable) this.st.tips.hide();
10635 this.select(false, false, false);
10636 this.normalizeDims();
10638 this.st.fx.animate({
10639 modes: ['node-property:height:dimArray'],
10641 onComplete: function() {
10646 //adds the little brown bar when hovering the node
10647 select: function(id, name, index) {
10648 if(!this.config.selectOnHover) return;
10649 var s = this.selected;
10650 if(s.id != id || s.name != name
10651 || s.index != index) {
10655 this.st.graph.eachNode(function(n) {
10656 n.setData('border', false);
10659 var n = this.st.graph.getNode(id);
10660 n.setData('border', s);
10661 var link = index === 0? 'prev':'next';
10662 link = n.getData(link);
10664 n = this.st.graph.getByName(link);
10666 n.setData('border', {
10680 Returns an object containing as keys the legend names and as values hex strings with color values.
10685 var legend = areaChart.getLegend();
10688 getLegend: function() {
10689 var legend = new Array();
10690 var name = new Array();
10691 var color = new Array();
10693 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10696 var colors = n.getData('colorArray'),
10697 len = colors.length;
10698 $.each(n.getData('stringArray'), function(s, i) {
10699 color[i] = colors[i % len];
10702 legend['name'] = name;
10703 legend['color'] = color;
10708 Method: getMaxValue
10710 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10715 var ans = areaChart.getMaxValue();
10718 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10723 //will return 100 for all AreaChart instances,
10724 //displaying all of them with the same scale
10725 $jit.AreaChart.implement({
10726 'getMaxValue': function() {
10734 normalizeDims: function() {
10735 //number of elements
10736 var root = this.st.graph.getNode(this.st.root), l=0;
10737 root.eachAdjacency(function() {
10742 var maxValue = this.maxValue || 1,
10743 size = this.st.canvas.getSize(),
10744 config = this.config,
10745 margin = config.Margin,
10746 labelOffset = config.labelOffset + config.Label.size,
10747 fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10748 animate = config.animate,
10749 ticks = config.Ticks,
10750 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10751 - (config.showLabels && labelOffset);
10754 var maxTickValue = Math.ceil(maxValue*.1)*10;
10755 if(maxTickValue == maxValue) {
10756 var length = maxTickValue.toString().length;
10757 maxTickValue = maxTickValue + parseInt(pad(1,length));
10762 this.st.graph.eachNode(function(n) {
10763 var acumLeft = 0, acumRight = 0, animateValue = [];
10764 $.each(n.getData('valueArray'), function(v) {
10766 acumRight += +v[1];
10767 animateValue.push([0, 0]);
10769 var acum = acumRight>acumLeft? acumRight:acumLeft;
10771 n.setData('width', fixedDim);
10773 n.setData('height', acum * height / maxValue, 'end');
10774 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10775 return [n[0] * height / maxValue, n[1] * height / maxValue];
10777 var dimArray = n.getData('dimArray');
10779 n.setData('dimArray', animateValue);
10784 n.setData('height', acum * height / maxValue);
10785 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10786 return [n[0] * height / maxTickValue, n[1] * height / maxTickValue];
10789 n.setData('height', acum * height / maxValue);
10790 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10791 return [n[0] * height / maxValue, n[1] * height / maxValue];
10806 * File: AreaChart.js
10810 $jit.ST.Plot.NodeTypes.implement({
10811 'areachart-stacked' : {
10812 'render' : function(node, canvas) {
10813 var pos = node.pos.getc(true),
10814 width = node.getData('width'),
10815 height = node.getData('height'),
10816 algnPos = this.getAlignedPos(pos, width, height),
10817 x = algnPos.x, y = algnPos.y,
10818 stringArray = node.getData('stringArray'),
10819 dimArray = node.getData('dimArray'),
10820 valArray = node.getData('valueArray'),
10821 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10822 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10823 colorArray = node.getData('colorArray'),
10824 colorLength = colorArray.length,
10825 config = node.getData('config'),
10826 gradient = node.getData('gradient'),
10827 showLabels = config.showLabels,
10828 aggregates = config.showAggregates,
10829 label = config.Label,
10830 prev = node.getData('prev');
10832 var ctx = canvas.getCtx(), border = node.getData('border');
10833 if (colorArray && dimArray && stringArray) {
10834 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10835 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10837 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10838 var h1 = acumLeft + dimArray[i][0],
10839 h2 = acumRight + dimArray[i][1],
10840 alpha = Math.atan((h2 - h1) / width),
10842 var linear = ctx.createLinearGradient(x + width/2,
10844 x + width/2 + delta * Math.sin(alpha),
10845 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10846 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10847 function(v) { return (v * 0.85) >> 0; }));
10848 linear.addColorStop(0, colorArray[i % colorLength]);
10849 linear.addColorStop(1, color);
10850 ctx.fillStyle = linear;
10853 ctx.moveTo(x, y - acumLeft);
10854 ctx.lineTo(x + width, y - acumRight);
10855 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10856 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10857 ctx.lineTo(x, y - acumLeft);
10861 var strong = border.name == stringArray[i];
10862 var perc = strong? 0.7 : 0.8;
10863 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10864 function(v) { return (v * perc) >> 0; }));
10865 ctx.strokeStyle = color;
10866 ctx.lineWidth = strong? 4 : 1;
10869 if(border.index === 0) {
10870 ctx.moveTo(x, y - acumLeft);
10871 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10873 ctx.moveTo(x + width, y - acumRight);
10874 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10879 acumLeft += (dimArray[i][0] || 0);
10880 acumRight += (dimArray[i][1] || 0);
10882 if(dimArray[i][0] > 0)
10883 valAcum += (valArray[i][0] || 0);
10885 if(prev && label.type == 'Native') {
10888 ctx.fillStyle = ctx.strokeStyle = label.color;
10889 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10890 ctx.textAlign = 'center';
10891 ctx.textBaseline = 'middle';
10892 if(aggregates(node.name, valLeft, valRight, node)) {
10893 ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10895 if(showLabels(node.name, valLeft, valRight, node)) {
10896 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10902 'contains': function(node, mpos) {
10903 var pos = node.pos.getc(true),
10904 width = node.getData('width'),
10905 height = node.getData('height'),
10906 algnPos = this.getAlignedPos(pos, width, height),
10907 x = algnPos.x, y = algnPos.y,
10908 dimArray = node.getData('dimArray'),
10910 //bounding box check
10911 if(mpos.x < x || mpos.x > x + width
10912 || mpos.y > y || mpos.y < y - height) {
10916 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10917 var dimi = dimArray[i];
10920 var intersec = lAcum + (rAcum - lAcum) * rx / width;
10921 if(mpos.y >= intersec) {
10922 var index = +(rx > width/2);
10924 'name': node.getData('stringArray')[i],
10925 'color': node.getData('colorArray')[i],
10926 'value': node.getData('valueArray')[i][index],
10939 A visualization that displays stacked area charts.
10941 Constructor Options:
10943 See <Options.AreaChart>.
10946 $jit.AreaChart = new Class({
10948 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10952 initialize: function(opt) {
10953 this.controller = this.config =
10954 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10955 Label: { type: 'Native' }
10957 //set functions for showLabels and showAggregates
10958 var showLabels = this.config.showLabels,
10959 typeLabels = $.type(showLabels),
10960 showAggregates = this.config.showAggregates,
10961 typeAggregates = $.type(showAggregates);
10962 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10963 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10965 this.initializeViz();
10968 initializeViz: function() {
10969 var config = this.config,
10971 nodeType = config.type.split(":")[0],
10974 var st = new $jit.ST({
10975 injectInto: config.injectInto,
10976 orientation: "bottom",
10980 withLabels: config.Label.type != 'Native',
10981 useCanvas: config.useCanvas,
10983 type: config.Label.type
10987 type: 'areachart-' + nodeType,
10996 enable: config.Tips.enable,
10999 onShow: function(tip, node, contains) {
11000 var elem = contains;
11001 config.Tips.onShow(tip, elem, node);
11007 onClick: function(node, eventInfo, evt) {
11008 if(!config.filterOnClick && !config.Events.enable) return;
11009 var elem = eventInfo.getContains();
11010 if(elem) config.filterOnClick && that.filter(elem.name);
11011 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11013 onRightClick: function(node, eventInfo, evt) {
11014 if(!config.restoreOnRightClick) return;
11017 onMouseMove: function(node, eventInfo, evt) {
11018 if(!config.selectOnHover) return;
11020 var elem = eventInfo.getContains();
11021 that.select(node.id, elem.name, elem.index);
11023 that.select(false, false, false);
11027 onCreateLabel: function(domElement, node) {
11028 var labelConf = config.Label,
11029 valueArray = node.getData('valueArray'),
11030 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11031 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11032 if(node.getData('prev')) {
11034 wrapper: document.createElement('div'),
11035 aggregate: document.createElement('div'),
11036 label: document.createElement('div')
11038 var wrapper = nlbs.wrapper,
11039 label = nlbs.label,
11040 aggregate = nlbs.aggregate,
11041 wrapperStyle = wrapper.style,
11042 labelStyle = label.style,
11043 aggregateStyle = aggregate.style;
11044 //store node labels
11045 nodeLabels[node.id] = nlbs;
11047 wrapper.appendChild(label);
11048 wrapper.appendChild(aggregate);
11049 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11050 label.style.display = 'none';
11052 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11053 aggregate.style.display = 'none';
11055 wrapperStyle.position = 'relative';
11056 wrapperStyle.overflow = 'visible';
11057 wrapperStyle.fontSize = labelConf.size + 'px';
11058 wrapperStyle.fontFamily = labelConf.family;
11059 wrapperStyle.color = labelConf.color;
11060 wrapperStyle.textAlign = 'center';
11061 aggregateStyle.position = labelStyle.position = 'absolute';
11063 domElement.style.width = node.getData('width') + 'px';
11064 domElement.style.height = node.getData('height') + 'px';
11065 label.innerHTML = node.name;
11067 domElement.appendChild(wrapper);
11070 onPlaceLabel: function(domElement, node) {
11071 if(!node.getData('prev')) return;
11072 var labels = nodeLabels[node.id],
11073 wrapperStyle = labels.wrapper.style,
11074 labelStyle = labels.label.style,
11075 aggregateStyle = labels.aggregate.style,
11076 width = node.getData('width'),
11077 height = node.getData('height'),
11078 dimArray = node.getData('dimArray'),
11079 valArray = node.getData('valueArray'),
11080 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11081 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11082 font = parseInt(wrapperStyle.fontSize, 10),
11083 domStyle = domElement.style;
11085 if(dimArray && valArray) {
11086 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11087 labelStyle.display = '';
11089 labelStyle.display = 'none';
11091 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11092 aggregateStyle.display = '';
11094 aggregateStyle.display = 'none';
11096 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11097 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11098 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11099 if(dimArray[i][0] > 0) {
11100 acum+= valArray[i][0];
11101 leftAcum+= dimArray[i][0];
11104 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11105 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11106 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11107 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11108 labels.aggregate.innerHTML = acum;
11113 var size = st.canvas.getSize(),
11114 margin = config.Margin;
11115 st.config.offsetY = -size.height/2 + margin.bottom
11116 + (config.showLabels && (config.labelOffset + config.Label.size));
11117 st.config.offsetX = (margin.right - margin.left)/2;
11119 this.canvas = this.st.canvas;
11125 Loads JSON data into the visualization.
11129 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>.
11133 var areaChart = new $jit.AreaChart(options);
11134 areaChart.loadJSON(json);
11137 loadJSON: function(json) {
11138 var prefix = $.time(),
11141 name = $.splat(json.label),
11142 color = $.splat(json.color || this.colors),
11143 config = this.config,
11144 gradient = !!config.type.split(":")[1],
11145 animate = config.animate;
11147 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11148 var val = values[i], prev = values[i-1], next = values[i+1];
11149 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11150 var valArray = $.zip(valLeft, valRight);
11151 var acumLeft = 0, acumRight = 0;
11153 'id': prefix + val.label,
11157 '$valueArray': valArray,
11158 '$colorArray': color,
11159 '$stringArray': name,
11160 '$next': next.label,
11161 '$prev': prev? prev.label:false,
11163 '$gradient': gradient
11169 'id': prefix + '$root',
11180 this.normalizeDims();
11182 st.select(st.root);
11185 modes: ['node-property:height:dimArray'],
11194 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.
11198 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11199 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11204 areaChart.updateJSON(json, {
11205 onComplete: function() {
11206 alert('update complete!');
11211 updateJSON: function(json, onComplete) {
11212 if(this.busy) return;
11217 labels = json.label && $.splat(json.label),
11218 values = json.values,
11219 animate = this.config.animate,
11221 $.each(values, function(v) {
11222 var n = graph.getByName(v.label);
11224 v.values = $.splat(v.values);
11225 var stringArray = n.getData('stringArray'),
11226 valArray = n.getData('valueArray');
11227 $.each(valArray, function(a, i) {
11228 a[0] = v.values[i];
11229 if(labels) stringArray[i] = labels[i];
11231 n.setData('valueArray', valArray);
11232 var prev = n.getData('prev'),
11233 next = n.getData('next'),
11234 nextNode = graph.getByName(next);
11236 var p = graph.getByName(prev);
11238 var valArray = p.getData('valueArray');
11239 $.each(valArray, function(a, i) {
11240 a[1] = v.values[i];
11245 var valArray = n.getData('valueArray');
11246 $.each(valArray, function(a, i) {
11247 a[1] = v.values[i];
11252 this.normalizeDims();
11254 st.select(st.root);
11257 modes: ['node-property:height:dimArray'],
11259 onComplete: function() {
11261 onComplete && onComplete.onComplete();
11270 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11274 Variable strings arguments with the name of the stacks.
11279 areaChart.filter('label A', 'label C');
11284 <AreaChart.restore>.
11286 filter: function() {
11287 if(this.busy) return;
11289 if(this.config.Tips.enable) this.st.tips.hide();
11290 this.select(false, false, false);
11291 var args = Array.prototype.slice.call(arguments);
11292 var rt = this.st.graph.getNode(this.st.root);
11294 rt.eachAdjacency(function(adj) {
11295 var n = adj.nodeTo,
11296 dimArray = n.getData('dimArray'),
11297 stringArray = n.getData('stringArray');
11298 n.setData('dimArray', $.map(dimArray, function(d, i) {
11299 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11302 this.st.fx.animate({
11303 modes: ['node-property:dimArray'],
11305 onComplete: function() {
11314 Sets all stacks that could have been filtered visible.
11319 areaChart.restore();
11324 <AreaChart.filter>.
11326 restore: function() {
11327 if(this.busy) return;
11329 if(this.config.Tips.enable) this.st.tips.hide();
11330 this.select(false, false, false);
11331 this.normalizeDims();
11333 this.st.fx.animate({
11334 modes: ['node-property:height:dimArray'],
11336 onComplete: function() {
11341 //adds the little brown bar when hovering the node
11342 select: function(id, name, index) {
11343 if(!this.config.selectOnHover) return;
11344 var s = this.selected;
11345 if(s.id != id || s.name != name
11346 || s.index != index) {
11350 this.st.graph.eachNode(function(n) {
11351 n.setData('border', false);
11354 var n = this.st.graph.getNode(id);
11355 n.setData('border', s);
11356 var link = index === 0? 'prev':'next';
11357 link = n.getData(link);
11359 n = this.st.graph.getByName(link);
11361 n.setData('border', {
11375 Returns an object containing as keys the legend names and as values hex strings with color values.
11380 var legend = areaChart.getLegend();
11383 getLegend: function() {
11386 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11389 var colors = n.getData('colorArray'),
11390 len = colors.length;
11391 $.each(n.getData('stringArray'), function(s, i) {
11392 legend[s] = colors[i % len];
11398 Method: getMaxValue
11400 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11405 var ans = areaChart.getMaxValue();
11408 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11413 //will return 100 for all AreaChart instances,
11414 //displaying all of them with the same scale
11415 $jit.AreaChart.implement({
11416 'getMaxValue': function() {
11423 getMaxValue: function() {
11425 this.st.graph.eachNode(function(n) {
11426 var valArray = n.getData('valueArray'),
11427 acumLeft = 0, acumRight = 0;
11428 $.each(valArray, function(v) {
11430 acumRight += +v[1];
11432 var acum = acumRight>acumLeft? acumRight:acumLeft;
11433 maxValue = maxValue>acum? maxValue:acum;
11438 normalizeDims: function() {
11439 //number of elements
11440 var root = this.st.graph.getNode(this.st.root), l=0;
11441 root.eachAdjacency(function() {
11444 var maxValue = this.getMaxValue() || 1,
11445 size = this.st.canvas.getSize(),
11446 config = this.config,
11447 margin = config.Margin,
11448 labelOffset = config.labelOffset + config.Label.size,
11449 fixedDim = (size.width - (margin.left + margin.right)) / l,
11450 animate = config.animate,
11451 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
11452 - (config.showLabels && labelOffset);
11453 this.st.graph.eachNode(function(n) {
11454 var acumLeft = 0, acumRight = 0, animateValue = [];
11455 $.each(n.getData('valueArray'), function(v) {
11457 acumRight += +v[1];
11458 animateValue.push([0, 0]);
11460 var acum = acumRight>acumLeft? acumRight:acumLeft;
11461 n.setData('width', fixedDim);
11463 n.setData('height', acum * height / maxValue, 'end');
11464 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11465 return [n[0] * height / maxValue, n[1] * height / maxValue];
11467 var dimArray = n.getData('dimArray');
11469 n.setData('dimArray', animateValue);
11472 n.setData('height', acum * height / maxValue);
11473 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11474 return [n[0] * height / maxValue, n[1] * height / maxValue];
11482 * File: Options.BarChart.js
11487 Object: Options.BarChart
11489 <BarChart> options.
11490 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11496 Options.BarChart = {
11501 hoveredColor: '#9fd4ff',
11502 orientation: 'horizontal',
11503 showAggregates: true,
11513 var barChart = new $jit.BarChart({
11516 type: 'stacked:gradient'
11523 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11524 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11525 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11526 barsOffset - (number) Default's *0*. Separation between bars.
11527 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11528 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11529 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11530 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11531 showLabels - (boolean) Default's *true*. Display the name of the slots.
11535 Options.BarChart = {
11539 type: 'stacked', //stacked, grouped, : gradient
11540 labelOffset: 3, //label offset
11541 barsOffset: 0, //distance between bars
11542 nodeCount: 0, //number of bars
11543 hoveredColor: '#9fd4ff',
11545 renderBackground: false,
11546 orientation: 'horizontal',
11547 showAggregates: true,
11566 * File: BarChart.js
11570 $jit.ST.Plot.NodeTypes.implement({
11571 'barchart-stacked' : {
11572 'render' : function(node, canvas) {
11573 var pos = node.pos.getc(true),
11574 width = node.getData('width'),
11575 height = node.getData('height'),
11576 algnPos = this.getAlignedPos(pos, width, height),
11577 x = algnPos.x, y = algnPos.y,
11578 dimArray = node.getData('dimArray'),
11579 valueArray = node.getData('valueArray'),
11580 stringArray = node.getData('stringArray'),
11581 linkArray = node.getData('linkArray'),
11582 gvl = node.getData('gvl'),
11583 colorArray = node.getData('colorArray'),
11584 colorLength = colorArray.length,
11585 nodeCount = node.getData('nodeCount');
11586 var ctx = canvas.getCtx(),
11587 canvasSize = canvas.getSize(),
11589 border = node.getData('border'),
11590 gradient = node.getData('gradient'),
11591 config = node.getData('config'),
11592 horz = config.orientation == 'horizontal',
11593 aggregates = config.showAggregates,
11594 showLabels = config.showLabels,
11595 label = config.Label,
11596 margin = config.Margin;
11599 if (colorArray && dimArray && stringArray) {
11600 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11601 acum += (dimArray[i] || 0);
11606 if(config.shadow.enable) {
11607 shadowThickness = config.shadow.size;
11608 ctx.fillStyle = "rgba(0,0,0,.2)";
11610 ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11612 ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11616 if (colorArray && dimArray && stringArray) {
11617 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11618 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11625 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
11626 x + acum + dimArray[i]/2, y + height);
11628 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
11629 x + width, y - acum- dimArray[i]/2);
11631 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11632 function(v) { return (v * 0.8) >> 0; }));
11633 linear.addColorStop(0, color);
11634 linear.addColorStop(0.3, colorArray[i % colorLength]);
11635 linear.addColorStop(0.7, colorArray[i % colorLength]);
11636 linear.addColorStop(1, color);
11637 ctx.fillStyle = linear;
11640 ctx.fillRect(x + acum, y, dimArray[i], height);
11642 ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
11644 if(border && border.name == stringArray[i]) {
11646 opt.dimValue = dimArray[i];
11648 acum += (dimArray[i] || 0);
11649 valAcum += (valueArray[i] || 0);
11654 ctx.strokeStyle = border.color;
11656 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11658 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11662 if(label.type == 'Native') {
11664 ctx.fillStyle = ctx.strokeStyle = label.color;
11665 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11666 ctx.textBaseline = 'middle';
11668 acumValueLabel = gvl;
11670 acumValueLabel = valAcum;
11672 if(aggregates(node.name, valAcum)) {
11674 ctx.textAlign = 'center';
11675 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11678 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11679 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11680 (label ? label.size + config.labelOffset : 0));
11681 mtxt = ctx.measureText(acumValueLabel);
11682 boxWidth = mtxt.width+10;
11684 boxHeight = label.size+6;
11686 if(boxHeight + acum + config.labelOffset > gridHeight) {
11687 bottomPadding = acum - config.labelOffset - boxHeight;
11689 bottomPadding = acum + config.labelOffset + inset;
11693 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11696 boxY = -boxHeight/2;
11698 ctx.rotate(0 * Math.PI / 180);
11699 ctx.fillStyle = "rgba(255,255,255,.8)";
11700 if(boxHeight + acum + config.labelOffset > gridHeight) {
11701 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11703 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11704 ctx.fillStyle = ctx.strokeStyle = label.color;
11705 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11710 if(showLabels(node.name, valAcum, node)) {
11715 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11718 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11719 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11720 boxWidth = mtxt.width+10;
11723 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11724 leftPadding = acum - config.labelOffset - boxWidth - inset;
11726 leftPadding = acum + config.labelOffset;
11730 ctx.textAlign = 'left';
11731 ctx.translate(x + inset + leftPadding, y + height/2);
11732 boxHeight = label.size+6;
11734 boxY = -boxHeight/2;
11735 ctx.fillStyle = "rgba(255,255,255,.8)";
11737 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11738 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11740 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11742 ctx.fillStyle = label.color;
11743 ctx.rotate(0 * Math.PI / 180);
11744 ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11748 ctx.textAlign = 'center';
11749 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11756 'contains': function(node, mpos) {
11757 var pos = node.pos.getc(true),
11758 width = node.getData('width'),
11759 height = node.getData('height'),
11760 algnPos = this.getAlignedPos(pos, width, height),
11761 x = algnPos.x, y = algnPos.y,
11762 dimArray = node.getData('dimArray'),
11763 config = node.getData('config'),
11765 horz = config.orientation == 'horizontal';
11766 //bounding box check
11768 if(mpos.x < x || mpos.x > x + width
11769 || mpos.y > y + height || mpos.y < y) {
11773 if(mpos.x < x || mpos.x > x + width
11774 || mpos.y > y || mpos.y < y - height) {
11779 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11780 var dimi = dimArray[i];
11781 var url = Url.decode(node.getData('linkArray')[i]);
11784 var intersec = acum;
11785 if(mpos.x <= intersec) {
11787 'name': node.getData('stringArray')[i],
11788 'color': node.getData('colorArray')[i],
11789 'value': node.getData('valueArray')[i],
11790 'valuelabel': node.getData('valuelabelArray')[i],
11791 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11798 var intersec = acum;
11799 if(mpos.y >= intersec) {
11801 'name': node.getData('stringArray')[i],
11802 'color': node.getData('colorArray')[i],
11803 'value': node.getData('valueArray')[i],
11804 'valuelabel': node.getData('valuelabelArray')[i],
11805 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11815 'barchart-grouped' : {
11816 'render' : function(node, canvas) {
11817 var pos = node.pos.getc(true),
11818 width = node.getData('width'),
11819 height = node.getData('height'),
11820 algnPos = this.getAlignedPos(pos, width, height),
11821 x = algnPos.x, y = algnPos.y,
11822 dimArray = node.getData('dimArray'),
11823 valueArray = node.getData('valueArray'),
11824 valuelabelArray = node.getData('valuelabelArray'),
11825 linkArray = node.getData('linkArray'),
11826 valueLength = valueArray.length,
11827 colorArray = node.getData('colorArray'),
11828 colorLength = colorArray.length,
11829 stringArray = node.getData('stringArray');
11831 var ctx = canvas.getCtx(),
11832 canvasSize = canvas.getSize(),
11834 border = node.getData('border'),
11835 gradient = node.getData('gradient'),
11836 config = node.getData('config'),
11837 horz = config.orientation == 'horizontal',
11838 aggregates = config.showAggregates,
11839 showLabels = config.showLabels,
11840 label = config.Label,
11841 shadow = config.shadow,
11842 margin = config.Margin,
11843 fixedDim = (horz? height : width) / valueLength;
11847 maxValue = Math.max.apply(null, dimArray);
11851 ctx.fillStyle = "rgba(0,0,0,.2)";
11852 if (colorArray && dimArray && stringArray && shadow.enable) {
11853 shadowThickness = shadow.size;
11855 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11856 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11857 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11860 ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11865 if(nextBar && nextBar > dimArray[i]) {
11866 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11867 } else if (nextBar && nextBar < dimArray[i]){
11868 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11870 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11872 } else if (i> 0 && i<l-1) {
11873 if(nextBar && nextBar > dimArray[i]) {
11874 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11875 } else if (nextBar && nextBar < dimArray[i]){
11876 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11878 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11880 } else if (i == l-1) {
11881 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11891 if (colorArray && dimArray && stringArray) {
11892 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11893 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11897 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
11898 x + dimArray[i]/2, y + fixedDim * (i + 1));
11900 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
11901 x + fixedDim * (i + 1), y - dimArray[i]/2);
11903 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11904 function(v) { return (v * 0.8) >> 0; }));
11905 linear.addColorStop(0, color);
11906 linear.addColorStop(0.3, colorArray[i % colorLength]);
11907 linear.addColorStop(0.7, colorArray[i % colorLength]);
11908 linear.addColorStop(1, color);
11909 ctx.fillStyle = linear;
11912 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11914 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11916 if(border && border.name == stringArray[i]) {
11917 opt.acum = fixedDim * i;
11918 opt.dimValue = dimArray[i];
11920 acum += (dimArray[i] || 0);
11921 valAcum += (valueArray[i] || 0);
11922 ctx.fillStyle = ctx.strokeStyle = label.color;
11923 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11926 if(aggregates(node.name, valAcum) && label.type == 'Native') {
11927 if(valuelabelArray[i]) {
11928 acumValueLabel = valuelabelArray[i];
11930 acumValueLabel = valueArray[i];
11933 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11934 ctx.textAlign = 'left';
11935 ctx.textBaseline = 'top';
11936 ctx.fillStyle = "rgba(255,255,255,.8)";
11938 gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
11939 mtxt = ctx.measureText(acumValueLabel);
11940 boxWidth = mtxt.width+10;
11942 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11943 leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
11945 leftPadding = dimArray[i] + config.labelOffset + inset;
11947 boxHeight = label.size+6;
11948 boxX = x + leftPadding;
11949 boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
11953 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11954 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11956 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11958 ctx.fillStyle = ctx.strokeStyle = label.color;
11959 ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
11966 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11968 ctx.textAlign = 'center';
11971 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11972 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11973 (label ? label.size + config.labelOffset : 0));
11975 mtxt = ctx.measureText(acumValueLabel);
11976 boxWidth = mtxt.width+10;
11977 boxHeight = label.size+6;
11978 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
11979 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
11981 bottomPadding = dimArray[i] + config.labelOffset + inset;
11985 ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
11987 boxX = -boxWidth/2;
11988 boxY = -boxHeight/2;
11989 ctx.fillStyle = "rgba(255,255,255,.8)";
11993 //ctx.rotate(270* Math.PI / 180);
11994 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
11995 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11997 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11999 ctx.fillStyle = ctx.strokeStyle = label.color;
12000 ctx.fillText(acumValueLabel, 0,0);
12009 ctx.strokeStyle = border.color;
12011 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12013 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12017 if(label.type == 'Native') {
12019 ctx.fillStyle = ctx.strokeStyle = label.color;
12020 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12021 ctx.textBaseline = 'middle';
12023 if(showLabels(node.name, valAcum, node)) {
12025 ctx.textAlign = 'center';
12026 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12027 ctx.rotate(Math.PI / 2);
12028 ctx.fillText(node.name, 0, 0);
12030 ctx.textAlign = 'center';
12031 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12038 'contains': function(node, mpos) {
12039 var pos = node.pos.getc(true),
12040 width = node.getData('width'),
12041 height = node.getData('height'),
12042 algnPos = this.getAlignedPos(pos, width, height),
12043 x = algnPos.x, y = algnPos.y,
12044 dimArray = node.getData('dimArray'),
12045 len = dimArray.length,
12046 config = node.getData('config'),
12048 horz = config.orientation == 'horizontal',
12049 fixedDim = (horz? height : width) / len;
12050 //bounding box check
12052 if(mpos.x < x || mpos.x > x + width
12053 || mpos.y > y + height || mpos.y < y) {
12057 if(mpos.x < x || mpos.x > x + width
12058 || mpos.y > y || mpos.y < y - height) {
12063 for(var i=0, l=dimArray.length; i<l; i++) {
12064 var dimi = dimArray[i];
12065 var url = Url.decode(node.getData('linkArray')[i]);
12067 var limit = y + fixedDim * i;
12068 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12070 'name': node.getData('stringArray')[i],
12071 'color': node.getData('colorArray')[i],
12072 'value': node.getData('valueArray')[i],
12073 'valuelabel': node.getData('valuelabelArray')[i],
12074 'title': node.getData('titleArray')[i],
12075 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12081 var limit = x + fixedDim * i;
12082 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12084 'name': node.getData('stringArray')[i],
12085 'color': node.getData('colorArray')[i],
12086 'value': node.getData('valueArray')[i],
12087 'valuelabel': node.getData('valuelabelArray')[i],
12088 'title': node.getData('titleArray')[i],
12089 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12099 'barchart-basic' : {
12100 'render' : function(node, canvas) {
12101 var pos = node.pos.getc(true),
12102 width = node.getData('width'),
12103 height = node.getData('height'),
12104 algnPos = this.getAlignedPos(pos, width, height),
12105 x = algnPos.x, y = algnPos.y,
12106 dimArray = node.getData('dimArray'),
12107 valueArray = node.getData('valueArray'),
12108 valuelabelArray = node.getData('valuelabelArray'),
12109 linkArray = node.getData('linkArray'),
12110 valueLength = valueArray.length,
12111 colorArray = node.getData('colorMono'),
12112 colorLength = colorArray.length,
12113 stringArray = node.getData('stringArray');
12115 var ctx = canvas.getCtx(),
12116 canvasSize = canvas.getSize(),
12118 border = node.getData('border'),
12119 gradient = node.getData('gradient'),
12120 config = node.getData('config'),
12121 horz = config.orientation == 'horizontal',
12122 aggregates = config.showAggregates,
12123 showLabels = config.showLabels,
12124 label = config.Label,
12125 fixedDim = (horz? height : width) / valueLength,
12126 margin = config.Margin;
12128 if (colorArray && dimArray && stringArray) {
12129 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12130 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12135 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
12136 x + dimArray[i]/2, y + fixedDim * (i + 1));
12138 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
12139 x + fixedDim * (i + 1), y - dimArray[i]/2);
12142 if(config.shadow.size) {
12143 shadowThickness = config.shadow.size;
12144 ctx.fillStyle = "rgba(0,0,0,.2)";
12146 ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12148 ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12152 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
12153 function(v) { return (v * 0.8) >> 0; }));
12154 linear.addColorStop(0, color);
12155 linear.addColorStop(0.3, colorArray[i % colorLength]);
12156 linear.addColorStop(0.7, colorArray[i % colorLength]);
12157 linear.addColorStop(1, color);
12158 ctx.fillStyle = linear;
12161 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12163 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12165 if(border && border.name == stringArray[i]) {
12166 opt.acum = fixedDim * i;
12167 opt.dimValue = dimArray[i];
12169 acum += (dimArray[i] || 0);
12170 valAcum += (valueArray[i] || 0);
12172 if(label.type == 'Native') {
12173 ctx.fillStyle = ctx.strokeStyle = label.color;
12174 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12175 if(aggregates(node.name, valAcum)) {
12176 if(valuelabelArray[i]) {
12177 acumValueLabel = valuelabelArray[i];
12179 acumValueLabel = valueArray[i];
12182 ctx.textAlign = 'center';
12183 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12186 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12187 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12188 (label ? label.size + config.labelOffset : 0));
12189 mtxt = ctx.measureText(acumValueLabel);
12190 boxWidth = mtxt.width+10;
12192 boxHeight = label.size+6;
12194 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12195 bottomPadding = dimArray[i] - config.labelOffset - inset;
12197 bottomPadding = dimArray[i] + config.labelOffset + inset;
12201 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12204 boxY = -boxHeight/2;
12206 //ctx.rotate(270* Math.PI / 180);
12207 ctx.fillStyle = "rgba(255,255,255,.6)";
12208 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12209 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12211 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12212 ctx.fillStyle = ctx.strokeStyle = label.color;
12213 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12222 ctx.strokeStyle = border.color;
12224 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12226 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12230 if(label.type == 'Native') {
12232 ctx.fillStyle = ctx.strokeStyle = label.color;
12233 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12234 ctx.textBaseline = 'middle';
12235 if(showLabels(node.name, valAcum, node)) {
12239 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12240 mtxt = ctx.measureText(node.name + ": " + valAcum);
12241 boxWidth = mtxt.width+10;
12244 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12245 leftPadding = acum - config.labelOffset - boxWidth - inset;
12247 leftPadding = acum + config.labelOffset;
12251 ctx.textAlign = 'left';
12252 ctx.translate(x + inset + leftPadding, y + height/2);
12253 boxHeight = label.size+6;
12255 boxY = -boxHeight/2;
12256 ctx.fillStyle = "rgba(255,255,255,.8)";
12259 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12260 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12262 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12265 ctx.fillStyle = label.color;
12266 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12270 if(stringArray.length > 8) {
12271 ctx.textAlign = 'left';
12272 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12273 ctx.rotate(45* Math.PI / 180);
12274 ctx.fillText(node.name, 0, 0);
12276 ctx.textAlign = 'center';
12277 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12286 'contains': function(node, mpos) {
12287 var pos = node.pos.getc(true),
12288 width = node.getData('width'),
12289 height = node.getData('height'),
12290 config = node.getData('config'),
12291 algnPos = this.getAlignedPos(pos, width, height),
12292 x = algnPos.x, y = algnPos.y ,
12293 dimArray = node.getData('dimArray'),
12294 len = dimArray.length,
12296 horz = config.orientation == 'horizontal',
12297 fixedDim = (horz? height : width) / len;
12299 //bounding box check
12301 if(mpos.x < x || mpos.x > x + width
12302 || mpos.y > y + height || mpos.y < y) {
12306 if(mpos.x < x || mpos.x > x + width
12307 || mpos.y > y || mpos.y < y - height) {
12312 for(var i=0, l=dimArray.length; i<l; i++) {
12313 var dimi = dimArray[i];
12314 var url = Url.decode(node.getData('linkArray')[i]);
12316 var limit = y + fixedDim * i;
12317 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12319 'name': node.getData('stringArray')[i],
12320 'color': node.getData('colorArray')[i],
12321 'value': node.getData('valueArray')[i],
12322 'valuelabel': node.getData('valuelabelArray')[i],
12323 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12329 var limit = x + fixedDim * i;
12330 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12332 'name': node.getData('stringArray')[i],
12333 'color': node.getData('colorArray')[i],
12334 'value': node.getData('valueArray')[i],
12335 'valuelabel': node.getData('valuelabelArray')[i],
12336 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12351 A visualization that displays stacked bar charts.
12353 Constructor Options:
12355 See <Options.BarChart>.
12358 $jit.BarChart = new Class({
12360 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12364 initialize: function(opt) {
12365 this.controller = this.config =
12366 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12367 Label: { type: 'Native' }
12369 //set functions for showLabels and showAggregates
12370 var showLabels = this.config.showLabels,
12371 typeLabels = $.type(showLabels),
12372 showAggregates = this.config.showAggregates,
12373 typeAggregates = $.type(showAggregates);
12374 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12375 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12376 Options.Fx.clearCanvas = false;
12377 this.initializeViz();
12380 initializeViz: function() {
12381 var config = this.config, that = this;
12382 var nodeType = config.type.split(":")[0],
12383 horz = config.orientation == 'horizontal',
12385 var st = new $jit.ST({
12386 injectInto: config.injectInto,
12387 orientation: horz? 'left' : 'bottom',
12388 background: config.background,
12389 renderBackground: config.renderBackground,
12390 backgroundColor: config.backgroundColor,
12391 colorStop1: config.colorStop1,
12392 colorStop2: config.colorStop2,
12394 nodeCount: config.nodeCount,
12395 siblingOffset: config.barsOffset,
12397 withLabels: config.Label.type != 'Native',
12398 useCanvas: config.useCanvas,
12400 type: config.Label.type
12404 type: 'barchart-' + nodeType,
12413 enable: config.Tips.enable,
12416 onShow: function(tip, node, contains) {
12417 var elem = contains;
12418 config.Tips.onShow(tip, elem, node);
12419 if(elem.link != 'undefined' && elem.link != '') {
12420 document.body.style.cursor = 'pointer';
12423 onHide: function(call) {
12424 document.body.style.cursor = 'default';
12431 onClick: function(node, eventInfo, evt) {
12432 if(!config.Events.enable) return;
12433 var elem = eventInfo.getContains();
12434 config.Events.onClick(elem, eventInfo, evt);
12436 onMouseMove: function(node, eventInfo, evt) {
12437 if(!config.hoveredColor) return;
12439 var elem = eventInfo.getContains();
12440 that.select(node.id, elem.name, elem.index);
12442 that.select(false, false, false);
12446 onCreateLabel: function(domElement, node) {
12447 var labelConf = config.Label,
12448 valueArray = node.getData('valueArray'),
12449 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12450 grouped = config.type.split(':')[0] == 'grouped',
12451 horz = config.orientation == 'horizontal';
12453 wrapper: document.createElement('div'),
12454 aggregate: document.createElement('div'),
12455 label: document.createElement('div')
12458 var wrapper = nlbs.wrapper,
12459 label = nlbs.label,
12460 aggregate = nlbs.aggregate,
12461 wrapperStyle = wrapper.style,
12462 labelStyle = label.style,
12463 aggregateStyle = aggregate.style;
12464 //store node labels
12465 nodeLabels[node.id] = nlbs;
12467 wrapper.appendChild(label);
12468 wrapper.appendChild(aggregate);
12469 if(!config.showLabels(node.name, acum, node)) {
12470 labelStyle.display = 'none';
12472 if(!config.showAggregates(node.name, acum, node)) {
12473 aggregateStyle.display = 'none';
12475 wrapperStyle.position = 'relative';
12476 wrapperStyle.overflow = 'visible';
12477 wrapperStyle.fontSize = labelConf.size + 'px';
12478 wrapperStyle.fontFamily = labelConf.family;
12479 wrapperStyle.color = labelConf.color;
12480 wrapperStyle.textAlign = 'center';
12481 aggregateStyle.position = labelStyle.position = 'absolute';
12483 domElement.style.width = node.getData('width') + 'px';
12484 domElement.style.height = node.getData('height') + 'px';
12485 aggregateStyle.left = "0px";
12486 labelStyle.left = config.labelOffset + 'px';
12487 labelStyle.whiteSpace = "nowrap";
12488 label.innerHTML = node.name;
12490 domElement.appendChild(wrapper);
12492 onPlaceLabel: function(domElement, node) {
12493 if(!nodeLabels[node.id]) return;
12494 var labels = nodeLabels[node.id],
12495 wrapperStyle = labels.wrapper.style,
12496 labelStyle = labels.label.style,
12497 aggregateStyle = labels.aggregate.style,
12498 grouped = config.type.split(':')[0] == 'grouped',
12499 horz = config.orientation == 'horizontal',
12500 dimArray = node.getData('dimArray'),
12501 valArray = node.getData('valueArray'),
12502 nodeCount = node.getData('nodeCount'),
12503 valueLength = valArray.length;
12504 valuelabelArray = node.getData('valuelabelArray'),
12505 stringArray = node.getData('stringArray'),
12506 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12507 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12508 font = parseInt(wrapperStyle.fontSize, 10),
12509 domStyle = domElement.style,
12510 fixedDim = (horz? height : width) / valueLength;
12513 if(dimArray && valArray) {
12514 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12516 aggregateStyle.width = width - config.labelOffset + "px";
12517 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12518 if(dimArray[i] > 0) {
12519 acum+= valArray[i];
12522 if(config.showLabels(node.name, acum, node)) {
12523 labelStyle.display = '';
12525 labelStyle.display = 'none';
12527 if(config.showAggregates(node.name, acum, node)) {
12528 aggregateStyle.display = '';
12530 aggregateStyle.display = 'none';
12532 if(config.orientation == 'horizontal') {
12533 aggregateStyle.textAlign = 'right';
12534 labelStyle.textAlign = 'left';
12535 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12536 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12537 domElement.style.height = wrapperStyle.height = height + 'px';
12539 aggregateStyle.top = (-font - config.labelOffset) + 'px';
12540 labelStyle.top = (config.labelOffset + height) + 'px';
12541 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12542 domElement.style.height = wrapperStyle.height = height + 'px';
12543 if(stringArray.length > 8) {
12544 labels.label.className = "rotatedLabelReverse";
12545 labelStyle.textAlign = "left";
12546 labelStyle.top = config.labelOffset + height + width/2 + "px";
12552 labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12553 labels.aggregate.innerHTML = "";
12558 maxValue = Math.max.apply(null,dimArray);
12559 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12560 valueLabelDim = 50;
12561 valueLabel = document.createElement('div');
12562 valueLabel.innerHTML = valuelabelArray[i];
12563 // valueLabel.class = "rotatedLabel";
12564 valueLabel.className = "rotatedLabel";
12565 valueLabel.style.position = "absolute";
12566 valueLabel.style.textAlign = "left";
12567 valueLabel.style.verticalAlign = "middle";
12568 valueLabel.style.height = valueLabelDim + "px";
12569 valueLabel.style.width = valueLabelDim + "px";
12570 valueLabel.style.top = (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12571 valueLabel.style.left = (fixedDim * i) + "px";
12572 labels.wrapper.appendChild(valueLabel);
12575 labels.aggregate.innerHTML = acum;
12582 var size = st.canvas.getSize(),
12583 l = config.nodeCount,
12584 margin = config.Margin;
12585 title = config.Title;
12586 subtitle = config.Subtitle,
12587 grouped = config.type.split(':')[0] == 'grouped',
12588 margin = config.Margin,
12589 ticks = config.Ticks,
12590 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12591 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12592 horz = config.orientation == 'horizontal',
12593 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12594 fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12595 whiteSpace = size.width - (marginWidth + (fixedDim * l));
12597 //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12598 if(!grouped && !horz) {
12599 st.config.siblingOffset = whiteSpace/(l+1);
12606 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12607 if(config.Ticks.enable) {
12608 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;
12610 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12613 st.config.offsetY = -size.height/2 + margin.bottom
12614 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12615 if(config.Ticks.enable) {
12616 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12618 st.config.offsetX = (margin.right - margin.left)/2;
12622 this.canvas = this.st.canvas;
12625 renderTitle: function() {
12626 var canvas = this.canvas,
12627 size = canvas.getSize(),
12628 config = this.config,
12629 margin = config.Margin,
12630 label = config.Label,
12631 title = config.Title;
12632 ctx = canvas.getCtx();
12633 ctx.fillStyle = title.color;
12634 ctx.textAlign = 'left';
12635 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12636 if(label.type == 'Native') {
12637 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12641 renderSubtitle: function() {
12642 var canvas = this.canvas,
12643 size = canvas.getSize(),
12644 config = this.config,
12645 margin = config.Margin,
12646 label = config.Label,
12647 subtitle = config.Subtitle;
12648 ctx = canvas.getCtx();
12649 ctx.fillStyle = title.color;
12650 ctx.textAlign = 'left';
12651 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12652 if(label.type == 'Native') {
12653 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
12657 renderScrollNote: function() {
12658 var canvas = this.canvas,
12659 size = canvas.getSize(),
12660 config = this.config,
12661 margin = config.Margin,
12662 label = config.Label,
12663 note = config.ScrollNote;
12664 ctx = canvas.getCtx();
12665 ctx.fillStyle = title.color;
12666 title = config.Title;
12667 ctx.textAlign = 'center';
12668 ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12669 if(label.type == 'Native') {
12670 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12674 renderTicks: function() {
12676 var canvas = this.canvas,
12677 size = canvas.getSize(),
12678 config = this.config,
12679 margin = config.Margin,
12680 ticks = config.Ticks,
12681 title = config.Title,
12682 subtitle = config.Subtitle,
12683 label = config.Label,
12684 shadow = config.shadow;
12685 horz = config.orientation == 'horizontal',
12686 maxValue = this.getMaxValue(),
12687 maxTickValue = Math.ceil(maxValue*.1)*10;
12688 if(maxTickValue == maxValue) {
12689 var length = maxTickValue.toString().length;
12690 maxTickValue = maxTickValue + parseInt(pad(1,length));
12692 grouped = config.type.split(':')[0] == 'grouped',
12694 labelIncrement = maxTickValue/ticks.segments,
12695 ctx = canvas.getCtx();
12696 ctx.strokeStyle = ticks.color;
12697 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12699 ctx.textAlign = 'center';
12700 ctx.textBaseline = 'middle';
12702 idLabel = canvas.id + "-label";
12704 container = document.getElementById(idLabel);
12708 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12709 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12710 segmentLength = grid/ticks.segments;
12711 ctx.fillStyle = ticks.color;
12713 (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12714 size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12716 while(axis<=grid) {
12717 ctx.fillStyle = ticks.color;
12718 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);
12719 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));
12720 ctx.fillStyle = label.color;
12722 if(label.type == 'Native' && config.showLabels) {
12723 ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12725 axis += segmentLength;
12726 labelValue += labelIncrement;
12731 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12732 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12733 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)),
12734 segmentLength = grid/ticks.segments;
12735 ctx.fillStyle = ticks.color;
12736 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));
12738 while(axis>=grid) {
12740 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12741 ctx.rotate(0 * Math.PI / 180 );
12742 ctx.fillStyle = label.color;
12743 if(config.showLabels) {
12744 if(label.type == 'Native') {
12745 ctx.fillText(labelValue, 0, 0);
12747 //html labels on y axis
12748 labelDiv = document.createElement('div');
12749 labelDiv.innerHTML = labelValue;
12750 labelDiv.className = "rotatedLabel";
12751 // labelDiv.class = "rotatedLabel";
12752 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12753 labelDiv.style.left = margin.left + "px";
12754 labelDiv.style.width = labelDim + "px";
12755 labelDiv.style.height = labelDim + "px";
12756 labelDiv.style.textAlign = "center";
12757 labelDiv.style.verticalAlign = "middle";
12758 labelDiv.style.position = "absolute";
12759 container.appendChild(labelDiv);
12763 ctx.fillStyle = ticks.color;
12764 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 );
12765 htmlOrigin += segmentLength;
12766 axis += segmentLength;
12767 labelValue += labelIncrement;
12776 renderBackground: function() {
12777 var canvas = this.canvas,
12778 config = this.config,
12779 backgroundColor = config.backgroundColor,
12780 size = canvas.getSize(),
12781 ctx = canvas.getCtx();
12782 ctx.fillStyle = backgroundColor;
12783 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12788 Loads JSON data into the visualization.
12792 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>.
12796 var barChart = new $jit.BarChart(options);
12797 barChart.loadJSON(json);
12800 loadJSON: function(json) {
12801 if(this.busy) return;
12804 var prefix = $.time(),
12807 name = $.splat(json.label),
12808 color = $.splat(json.color || this.colors),
12809 config = this.config,
12810 gradient = !!config.type.split(":")[1],
12811 renderBackground = config.renderBackground,
12812 animate = config.animate,
12813 ticks = config.Ticks,
12814 title = config.Title,
12815 note = config.ScrollNote,
12816 subtitle = config.Subtitle,
12817 horz = config.orientation == 'horizontal',
12819 colorLength = color.length,
12820 nameLength = name.length;
12821 groupTotalValue = 0;
12822 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12823 var val = values[i];
12824 var valArray = $.splat(val.values);
12825 groupTotalValue += parseInt(valArray.sum());
12828 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12829 var val = values[i];
12830 var valArray = $.splat(values[i].values);
12831 var valuelabelArray = $.splat(values[i].valuelabels);
12832 var linkArray = $.splat(values[i].links);
12833 var titleArray = $.splat(values[i].titles);
12834 var barTotalValue = valArray.sum();
12837 'id': prefix + val.label,
12842 '$linkArray': linkArray,
12843 '$gvl': val.gvaluelabel,
12844 '$titleArray': titleArray,
12845 '$valueArray': valArray,
12846 '$valuelabelArray': valuelabelArray,
12847 '$colorArray': color,
12848 '$colorMono': $.splat(color[i % colorLength]),
12849 '$stringArray': name,
12850 '$barTotalValue': barTotalValue,
12851 '$groupTotalValue': groupTotalValue,
12852 '$nodeCount': values.length,
12853 '$gradient': gradient,
12860 'id': prefix + '$root',
12871 this.normalizeDims();
12873 if(renderBackground) {
12874 this.renderBackground();
12877 if(!animate && ticks.enable) {
12878 this.renderTicks();
12880 if(!animate && note.text) {
12881 this.renderScrollNote();
12883 if(!animate && title.text) {
12884 this.renderTitle();
12886 if(!animate && subtitle.text) {
12887 this.renderSubtitle();
12890 st.select(st.root);
12894 modes: ['node-property:width:dimArray'],
12896 onComplete: function() {
12902 modes: ['node-property:height:dimArray'],
12904 onComplete: function() {
12917 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.
12921 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
12922 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
12927 barChart.updateJSON(json, {
12928 onComplete: function() {
12929 alert('update complete!');
12934 updateJSON: function(json, onComplete) {
12935 if(this.busy) return;
12939 var graph = st.graph;
12940 var values = json.values;
12941 var animate = this.config.animate;
12943 var horz = this.config.orientation == 'horizontal';
12944 $.each(values, function(v) {
12945 var n = graph.getByName(v.label);
12947 n.setData('valueArray', $.splat(v.values));
12949 n.setData('stringArray', $.splat(json.label));
12953 this.normalizeDims();
12955 st.select(st.root);
12959 modes: ['node-property:width:dimArray'],
12961 onComplete: function() {
12963 onComplete && onComplete.onComplete();
12968 modes: ['node-property:height:dimArray'],
12970 onComplete: function() {
12972 onComplete && onComplete.onComplete();
12979 //adds the little brown bar when hovering the node
12980 select: function(id, name) {
12982 if(!this.config.hoveredColor) return;
12983 var s = this.selected;
12984 if(s.id != id || s.name != name) {
12987 s.color = this.config.hoveredColor;
12988 this.st.graph.eachNode(function(n) {
12990 n.setData('border', s);
12992 n.setData('border', false);
13002 Returns an object containing as keys the legend names and as values hex strings with color values.
13007 var legend = barChart.getLegend();
13010 getLegend: function() {
13011 var legend = new Array();
13012 var name = new Array();
13013 var color = new Array();
13015 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13018 var colors = n.getData('colorArray'),
13019 len = colors.length;
13020 $.each(n.getData('stringArray'), function(s, i) {
13021 color[i] = colors[i % len];
13024 legend['name'] = name;
13025 legend['color'] = color;
13030 Method: getMaxValue
13032 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13037 var ans = barChart.getMaxValue();
13040 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13045 //will return 100 for all BarChart instances,
13046 //displaying all of them with the same scale
13047 $jit.BarChart.implement({
13048 'getMaxValue': function() {
13055 getMaxValue: function() {
13056 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13057 this.st.graph.eachNode(function(n) {
13058 var valArray = n.getData('valueArray'),
13060 if(!valArray) return;
13062 $.each(valArray, function(v) {
13066 acum = Math.max.apply(null, valArray);
13068 maxValue = maxValue>acum? maxValue:acum;
13073 setBarType: function(type) {
13074 this.config.type = type;
13075 this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13078 normalizeDims: function() {
13079 //number of elements
13080 var root = this.st.graph.getNode(this.st.root), l=0;
13081 root.eachAdjacency(function() {
13084 var maxValue = this.getMaxValue() || 1,
13085 size = this.st.canvas.getSize(),
13086 config = this.config,
13087 margin = config.Margin,
13088 ticks = config.Ticks,
13089 title = config.Title,
13090 subtitle = config.Subtitle,
13091 grouped = config.type.split(':')[0] == 'grouped',
13092 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13093 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13094 horz = config.orientation == 'horizontal',
13095 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13096 animate = config.animate,
13097 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13099 - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13100 dim1 = horz? 'height':'width',
13101 dim2 = horz? 'width':'height',
13102 basic = config.type.split(':')[0] == 'basic';
13105 var maxTickValue = Math.ceil(maxValue*.1)*10;
13106 if(maxTickValue == maxValue) {
13107 var length = maxTickValue.toString().length;
13108 maxTickValue = maxTickValue + parseInt(pad(1,length));
13111 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13114 this.st.graph.eachNode(function(n) {
13115 var acum = 0, animateValue = [];
13116 $.each(n.getData('valueArray'), function(v) {
13118 animateValue.push(0);
13122 fixedDim = animateValue.length * 40;
13124 n.setData(dim1, fixedDim);
13128 n.setData(dim2, acum * height / maxValue, 'end');
13129 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13130 return n * height / maxValue;
13132 var dimArray = n.getData('dimArray');
13134 n.setData('dimArray', animateValue);
13140 n.setData(dim2, acum * height / maxTickValue);
13141 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13142 return n * height / maxTickValue;
13145 n.setData(dim2, acum * height / maxValue);
13146 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13147 return n * height / maxValue;
13155 //funnel chart options
13158 Options.FunnelChart = {
13162 type: 'stacked', //stacked, grouped, : gradient
13163 labelOffset: 3, //label offset
13164 barsOffset: 0, //distance between bars
13165 hoveredColor: '#9fd4ff',
13166 orientation: 'vertical',
13167 showAggregates: true,
13180 $jit.ST.Plot.NodeTypes.implement({
13181 'funnelchart-basic' : {
13182 'render' : function(node, canvas) {
13183 var pos = node.pos.getc(true),
13184 width = node.getData('width'),
13185 height = node.getData('height'),
13186 algnPos = this.getAlignedPos(pos, width, height),
13187 x = algnPos.x, y = algnPos.y,
13188 dimArray = node.getData('dimArray'),
13189 valueArray = node.getData('valueArray'),
13190 valuelabelArray = node.getData('valuelabelArray'),
13191 linkArray = node.getData('linkArray'),
13192 colorArray = node.getData('colorArray'),
13193 colorLength = colorArray.length,
13194 stringArray = node.getData('stringArray');
13195 var ctx = canvas.getCtx(),
13197 border = node.getData('border'),
13198 gradient = node.getData('gradient'),
13199 config = node.getData('config'),
13200 horz = config.orientation == 'horizontal',
13201 aggregates = config.showAggregates,
13202 showLabels = config.showLabels,
13203 label = config.Label,
13204 size = canvas.getSize(),
13205 labelOffset = config.labelOffset + 10;
13206 minWidth = width * .25;
13209 if (colorArray && dimArray && stringArray) {
13212 // horizontal lines
13213 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13214 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13216 if(label.type == 'Native') {
13217 if(showLabels(node.name, valAcum, node)) {
13218 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13219 var stringValue = stringArray[i];
13220 var valueLabel = String(valuelabelArray[i]);
13221 var mV = ctx.measureText(stringValue);
13222 var mVL = ctx.measureText(valueLabel);
13223 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13224 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13225 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13226 var bottomWidth = minWidth + ((acum) * ratio);
13227 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13228 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13229 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13230 // ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13234 ctx.moveTo(bottomWidth/2,y - acum); //
13235 ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight); // top right
13236 ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight); // bottom right
13240 ctx.moveTo(-bottomWidth/2,y - acum); //
13241 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight); // top right
13242 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight); // bottom right
13247 acum += (dimArray[i] || 0);
13248 valAcum += (valueArray[i] || 0);
13255 //funnel segments and labels
13256 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13257 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13258 var colori = colorArray[i % colorLength];
13259 if(label.type == 'Native') {
13260 var stringValue = stringArray[i];
13261 var valueLabel = String(valuelabelArray[i]);
13262 var mV = ctx.measureText(stringValue);
13263 var mVL = ctx.measureText(valueLabel);
13268 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13269 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13270 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13271 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13273 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13274 var bottomWidth = minWidth + ((acum) * ratio);
13275 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13280 linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13281 var colorRgb = $.hexToRgb(colori);
13282 var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
13283 function(v) { return (v * .5) >> 0; });
13284 linear.addColorStop(0, 'rgba('+color+',1)');
13285 linear.addColorStop(0.5, 'rgba('+colorRgb+',1)');
13286 linear.addColorStop(1, 'rgba('+color+',1)');
13287 ctx.fillStyle = linear;
13291 ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13292 ctx.lineTo(topWidth/2,y - acum - dimArray[i]); // top right
13293 ctx.lineTo(bottomWidth/2,y - acum); // bottom right
13294 ctx.lineTo(-bottomWidth/2,y - acum); // bottom left
13299 if(border && border.name == stringArray[i]) {
13301 opt.dimValue = dimArray[i];
13308 ctx.strokeStyle = border.color;
13310 //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13314 if(label.type == 'Native') {
13316 ctx.fillStyle = ctx.strokeStyle = label.color;
13317 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13318 ctx.textBaseline = 'middle';
13320 acumValueLabel = valAcum;
13322 if(showLabels(node.name, valAcum, node)) {
13325 ctx.textAlign = 'left';
13326 ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13327 ctx.textAlign = 'right';
13328 ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13333 acum += (dimArray[i] || 0);
13334 valAcum += (valueArray[i] || 0);
13340 'contains': function(node, mpos) {
13341 var pos = node.pos.getc(true),
13342 width = node.getData('width'),
13343 height = node.getData('height'),
13344 algnPos = this.getAlignedPos(pos, width, height),
13345 x = algnPos.x, y = algnPos.y,
13346 dimArray = node.getData('dimArray'),
13347 config = node.getData('config'),
13348 st = node.getData('st'),
13350 horz = config.orientation == 'horizontal',
13351 minWidth = width * .25;
13353 canvas = node.getData('canvas'),
13354 size = canvas.getSize(),
13355 offsetY = st.config.offsetY;
13356 //bounding box check
13358 if(mpos.y > y || mpos.y < y - height) {
13362 var newY = Math.abs(mpos.y + offsetY);
13363 var bound = minWidth + (newY * ratio);
13364 var boundLeft = -bound/2;
13365 var boundRight = bound/2;
13366 if(mpos.x < boundLeft || mpos.x > boundRight ) {
13372 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13373 var dimi = dimArray[i];
13377 var url = Url.decode(node.getData('linkArray')[i]);
13379 var intersec = acum;
13380 if(mpos.y >= intersec) {
13382 'name': node.getData('stringArray')[i],
13383 'color': node.getData('colorArray')[i],
13384 'value': node.getData('valueArray')[i],
13385 'percentage': node.getData('percentageArray')[i],
13386 'valuelabel': node.getData('valuelabelArray')[i],
13401 A visualization that displays funnel charts.
13403 Constructor Options:
13405 See <Options.FunnelChart>.
13408 $jit.FunnelChart = new Class({
13410 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13414 initialize: function(opt) {
13415 this.controller = this.config =
13416 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13417 Label: { type: 'Native' }
13419 //set functions for showLabels and showAggregates
13420 var showLabels = this.config.showLabels,
13421 typeLabels = $.type(showLabels),
13422 showAggregates = this.config.showAggregates,
13423 typeAggregates = $.type(showAggregates);
13424 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13425 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13426 Options.Fx.clearCanvas = false;
13427 this.initializeViz();
13430 initializeViz: function() {
13431 var config = this.config, that = this;
13432 var nodeType = config.type.split(":")[0],
13433 horz = config.orientation == 'horizontal',
13435 var st = new $jit.ST({
13436 injectInto: config.injectInto,
13437 orientation: horz? 'left' : 'bottom',
13439 background: config.background,
13440 renderBackground: config.renderBackground,
13441 backgroundColor: config.backgroundColor,
13442 colorStop1: config.colorStop1,
13443 colorStop2: config.colorStop2,
13444 siblingOffset: config.segmentOffset,
13446 withLabels: config.Label.type != 'Native',
13447 useCanvas: config.useCanvas,
13449 type: config.Label.type
13453 type: 'funnelchart-' + nodeType,
13462 enable: config.Tips.enable,
13465 onShow: function(tip, node, contains) {
13466 var elem = contains;
13467 config.Tips.onShow(tip, elem, node);
13468 if(elem.link != 'undefined' && elem.link != '') {
13469 document.body.style.cursor = 'pointer';
13472 onHide: function(call) {
13473 document.body.style.cursor = 'default';
13480 onClick: function(node, eventInfo, evt) {
13481 if(!config.Events.enable) return;
13482 var elem = eventInfo.getContains();
13483 config.Events.onClick(elem, eventInfo, evt);
13485 onMouseMove: function(node, eventInfo, evt) {
13486 if(!config.hoveredColor) return;
13488 var elem = eventInfo.getContains();
13489 that.select(node.id, elem.name, elem.index);
13491 that.select(false, false, false);
13495 onCreateLabel: function(domElement, node) {
13496 var labelConf = config.Label,
13497 valueArray = node.getData('valueArray'),
13498 idArray = node.getData('idArray'),
13499 valuelabelArray = node.getData('valuelabelArray'),
13500 stringArray = node.getData('stringArray');
13501 size = st.canvas.getSize()
13504 for(var i=0, l=valueArray.length; i<l; i++) {
13506 wrapper: document.createElement('div'),
13507 valueLabel: document.createElement('div'),
13508 label: document.createElement('div')
13510 var wrapper = nlbs.wrapper,
13511 label = nlbs.label,
13512 valueLabel = nlbs.valueLabel,
13513 wrapperStyle = wrapper.style,
13514 labelStyle = label.style,
13515 valueLabelStyle = valueLabel.style;
13516 //store node labels
13517 nodeLabels[idArray[i]] = nlbs;
13519 wrapper.appendChild(label);
13520 wrapper.appendChild(valueLabel);
13522 wrapperStyle.position = 'relative';
13523 wrapperStyle.overflow = 'visible';
13524 wrapperStyle.fontSize = labelConf.size + 'px';
13525 wrapperStyle.fontFamily = labelConf.family;
13526 wrapperStyle.color = labelConf.color;
13527 wrapperStyle.textAlign = 'center';
13528 wrapperStyle.width = size.width + 'px';
13529 valueLabelStyle.position = labelStyle.position = 'absolute';
13530 valueLabelStyle.left = labelStyle.left = '0px';
13531 valueLabelStyle.width = (size.width/3) + 'px';
13532 valueLabelStyle.textAlign = 'right';
13533 label.innerHTML = stringArray[i];
13534 valueLabel.innerHTML = valuelabelArray[i];
13535 domElement.id = prefix+'funnel';
13536 domElement.style.width = size.width + 'px';
13538 domElement.appendChild(wrapper);
13542 onPlaceLabel: function(domElement, node) {
13544 var dimArray = node.getData('dimArray'),
13545 idArray = node.getData('idArray'),
13546 valueArray = node.getData('valueArray'),
13547 valuelabelArray = node.getData('valuelabelArray'),
13548 stringArray = node.getData('stringArray');
13549 size = st.canvas.getSize(),
13550 pos = node.pos.getc(true),
13551 domElement.style.left = "0px",
13552 domElement.style.top = "0px",
13553 minWidth = node.getData('width') * .25,
13555 pos = node.pos.getc(true),
13556 labelConf = config.Label;
13559 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13561 var labels = nodeLabels[idArray[i]],
13562 wrapperStyle = labels.wrapper.style,
13563 labelStyle = labels.label.style,
13564 valueLabelStyle = labels.valueLabel.style;
13565 var bottomWidth = minWidth + (acum * ratio);
13567 font = parseInt(wrapperStyle.fontSize, 10),
13568 domStyle = domElement.style;
13572 wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13573 valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13574 labelStyle.left = (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13576 acum += (dimArray[i] || 0);
13584 var size = st.canvas.getSize(),
13585 margin = config.Margin;
13586 title = config.Title;
13587 subtitle = config.Subtitle;
13590 st.config.offsetY = -size.height/2 + margin.bottom
13591 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13593 st.config.offsetX = (margin.right - margin.left)/2;
13597 this.canvas = this.st.canvas;
13600 renderTitle: function() {
13601 var canvas = this.canvas,
13602 size = canvas.getSize(),
13603 config = this.config,
13604 margin = config.Margin,
13605 label = config.Label,
13606 title = config.Title;
13607 ctx = canvas.getCtx();
13608 ctx.fillStyle = title.color;
13609 ctx.textAlign = 'left';
13610 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13611 if(label.type == 'Native') {
13612 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13616 renderSubtitle: function() {
13617 var canvas = this.canvas,
13618 size = canvas.getSize(),
13619 config = this.config,
13620 margin = config.Margin,
13621 label = config.Label,
13622 subtitle = config.Subtitle;
13623 ctx = canvas.getCtx();
13624 ctx.fillStyle = title.color;
13625 ctx.textAlign = 'left';
13626 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13627 if(label.type == 'Native') {
13628 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13633 renderDropShadow: function() {
13634 var canvas = this.canvas,
13635 size = canvas.getSize(),
13636 config = this.config,
13637 margin = config.Margin,
13638 horz = config.orientation == 'horizontal',
13639 label = config.Label,
13640 title = config.Title,
13641 shadowThickness = 4,
13642 subtitle = config.Subtitle,
13643 ctx = canvas.getCtx(),
13644 minwidth = (size.width/8) * .25,
13645 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13646 topMargin = (title.text? title.size + title.offset : 0) + margin.top,
13647 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13648 - (config.showLabels && (config.Label.size + config.labelOffset)),
13650 topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13651 topY = (-size.height/2) + topMargin - shadowThickness;
13652 bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13653 bottomWidth = minwidth + shadowThickness;
13655 ctx.fillStyle = "rgba(0,0,0,.2)";
13656 ctx.moveTo(0,topY);
13657 ctx.lineTo(-topWidth/2,topY); //top left
13658 ctx.lineTo(-bottomWidth/2,bottomY); // bottom left
13659 ctx.lineTo(bottomWidth/2,bottomY); // bottom right
13660 ctx.lineTo(topWidth/2,topY); // top right
13667 renderBackground: function() {
13668 var canvas = this.canvas,
13669 config = this.config,
13670 backgroundColor = config.backgroundColor,
13671 size = canvas.getSize(),
13672 ctx = canvas.getCtx();
13673 //ctx.globalCompositeOperation = "destination-over";
13674 ctx.fillStyle = backgroundColor;
13675 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13679 loadJSON: function(json) {
13680 if(this.busy) return;
13682 var prefix = $.time(),
13685 name = $.splat(json.label),
13686 color = $.splat(json.color || this.colors),
13687 config = this.config,
13688 canvas = this.canvas,
13689 gradient = !!config.type.split(":")[1],
13690 animate = config.animate,
13691 title = config.Title,
13692 subtitle = config.Subtitle,
13693 renderBackground = config.renderBackground,
13694 horz = config.orientation == 'horizontal',
13696 colorLength = color.length,
13697 nameLength = name.length,
13700 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13701 var val = values[i];
13702 var valArray = $.splat(val.values);
13703 totalValue += parseInt(valArray.sum());
13707 var idArray = new Array();
13708 var valArray = new Array();
13709 var valuelabelArray = new Array();
13710 var linkArray = new Array();
13711 var titleArray = new Array();
13712 var percentageArray = new Array();
13714 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13715 var val = values[i];
13716 idArray[i] = $.splat(prefix + val.label);
13717 valArray[i] = $.splat(val.values);
13718 valuelabelArray[i] = $.splat(val.valuelabels);
13719 linkArray[i] = $.splat(val.links);
13720 titleArray[i] = $.splat(val.titles);
13721 percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13727 valArray.reverse();
13728 valuelabelArray.reverse();
13729 linkArray.reverse();
13730 titleArray.reverse();
13731 percentageArray.reverse();
13736 'id': prefix + val.label,
13741 '$idArray': idArray,
13742 '$linkArray': linkArray,
13743 '$titleArray': titleArray,
13744 '$valueArray': valArray,
13745 '$valuelabelArray': valuelabelArray,
13746 '$colorArray': color,
13747 '$colorMono': $.splat(color[i % colorLength]),
13748 '$stringArray': name.reverse(),
13749 '$gradient': gradient,
13751 '$percentageArray' : percentageArray,
13759 'id': prefix + '$root',
13770 this.normalizeDims();
13772 if(renderBackground) {
13773 this.renderBackground();
13775 if(!animate && title.text) {
13776 this.renderTitle();
13778 if(!animate && subtitle.text) {
13779 this.renderSubtitle();
13781 if(typeof FlashCanvas == "undefined") {
13782 this.renderDropShadow();
13785 st.select(st.root);
13789 modes: ['node-property:width:dimArray'],
13791 onComplete: function() {
13797 modes: ['node-property:height:dimArray'],
13799 onComplete: function() {
13812 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.
13816 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13817 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13822 barChart.updateJSON(json, {
13823 onComplete: function() {
13824 alert('update complete!');
13829 updateJSON: function(json, onComplete) {
13830 if(this.busy) return;
13834 var graph = st.graph;
13835 var values = json.values;
13836 var animate = this.config.animate;
13838 var horz = this.config.orientation == 'horizontal';
13839 $.each(values, function(v) {
13840 var n = graph.getByName(v.label);
13842 n.setData('valueArray', $.splat(v.values));
13844 n.setData('stringArray', $.splat(json.label));
13848 this.normalizeDims();
13850 st.select(st.root);
13854 modes: ['node-property:width:dimArray'],
13856 onComplete: function() {
13858 onComplete && onComplete.onComplete();
13863 modes: ['node-property:height:dimArray'],
13865 onComplete: function() {
13867 onComplete && onComplete.onComplete();
13874 //adds the little brown bar when hovering the node
13875 select: function(id, name) {
13877 if(!this.config.hoveredColor) return;
13878 var s = this.selected;
13879 if(s.id != id || s.name != name) {
13882 s.color = this.config.hoveredColor;
13883 this.st.graph.eachNode(function(n) {
13885 n.setData('border', s);
13887 n.setData('border', false);
13897 Returns an object containing as keys the legend names and as values hex strings with color values.
13902 var legend = barChart.getLegend();
13905 getLegend: function() {
13906 var legend = new Array();
13907 var name = new Array();
13908 var color = new Array();
13910 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13913 var colors = n.getData('colorArray'),
13914 len = colors.length;
13915 $.each(n.getData('stringArray'), function(s, i) {
13916 color[i] = colors[i % len];
13919 legend['name'] = name;
13920 legend['color'] = color;
13925 Method: getMaxValue
13927 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13932 var ans = barChart.getMaxValue();
13935 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13940 //will return 100 for all BarChart instances,
13941 //displaying all of them with the same scale
13942 $jit.BarChart.implement({
13943 'getMaxValue': function() {
13950 getMaxValue: function() {
13951 var maxValue = 0, stacked = true;
13952 this.st.graph.eachNode(function(n) {
13953 var valArray = n.getData('valueArray'),
13955 if(!valArray) return;
13957 $.each(valArray, function(v) {
13961 acum = Math.max.apply(null, valArray);
13963 maxValue = maxValue>acum? maxValue:acum;
13968 setBarType: function(type) {
13969 this.config.type = type;
13970 this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
13973 normalizeDims: function() {
13974 //number of elements
13975 var root = this.st.graph.getNode(this.st.root), l=0;
13976 root.eachAdjacency(function() {
13979 var maxValue = this.getMaxValue() || 1,
13980 size = this.st.canvas.getSize(),
13981 config = this.config,
13982 margin = config.Margin,
13983 title = config.Title,
13984 subtitle = config.Subtitle,
13985 marginWidth = margin.left + margin.right,
13986 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13987 horz = config.orientation == 'horizontal',
13988 animate = config.animate,
13989 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13991 - (config.showLabels && (config.Label.size + config.labelOffset)),
13992 dim1 = horz? 'height':'width',
13993 dim2 = horz? 'width':'height';
13996 minWidth = size.width/8;
14000 this.st.graph.eachNode(function(n) {
14001 var acum = 0, animateValue = [];
14002 $.each(n.getData('valueArray'), function(v) {
14004 animateValue.push(0);
14006 n.setData(dim1, minWidth);
14009 n.setData(dim2, acum * height / maxValue, 'end');
14010 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14011 return n * height / maxValue;
14013 var dimArray = n.getData('dimArray');
14015 n.setData('dimArray', animateValue);
14018 n.setData(dim2, acum * height / maxValue);
14019 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14020 return n * height / maxValue;
14031 * File: Options.PieChart.js
14035 Object: Options.PieChart
14037 <PieChart> options.
14038 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14044 Options.PieChart = {
14050 hoveredColor: '#9fd4ff',
14052 resizeLabels: false,
14053 updateHeights: false
14062 var pie = new $jit.PieChart({
14065 type: 'stacked:gradient'
14072 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14073 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14074 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14075 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14076 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14077 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14078 showLabels - (boolean) Default's *true*. Display the name of the slots.
14079 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.
14080 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.
14083 Options.PieChart = {
14087 offset: 25, // page offset
14089 labelOffset: 3, // label offset
14090 type: 'stacked', // gradient
14092 hoveredColor: '#9fd4ff',
14103 resizeLabels: false,
14105 //only valid for mono-valued datasets
14106 updateHeights: false
14110 * Class: Layouts.Radial
14112 * Implements a Radial Layout.
14116 * <RGraph>, <Hypertree>
14119 Layouts.Radial = new Class({
14124 * Computes nodes' positions.
14128 * property - _optional_ A <Graph.Node> position property to store the new
14129 * positions. Possible values are 'pos', 'end' or 'start'.
14132 compute : function(property) {
14133 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14134 NodeDim.compute(this.graph, prop, this.config);
14135 this.graph.computeLevels(this.root, 0, "ignore");
14136 var lengthFunc = this.createLevelDistanceFunc();
14137 this.computeAngularWidths(prop);
14138 this.computePositions(prop, lengthFunc);
14144 * Performs the main algorithm for computing node positions.
14146 computePositions : function(property, getLength) {
14147 var propArray = property;
14148 var graph = this.graph;
14149 var root = graph.getNode(this.root);
14150 var parent = this.parent;
14151 var config = this.config;
14153 for ( var i=0, l=propArray.length; i < l; i++) {
14154 var pi = propArray[i];
14155 root.setPos($P(0, 0), pi);
14156 root.setData('span', Math.PI * 2, pi);
14164 graph.eachBFS(this.root, function(elem) {
14165 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14166 var angleInit = elem.angleSpan.begin;
14167 var len = getLength(elem);
14168 //Calculate the sum of all angular widths
14169 var totalAngularWidths = 0, subnodes = [], maxDim = {};
14170 elem.eachSubnode(function(sib) {
14171 totalAngularWidths += sib._treeAngularWidth;
14173 for ( var i=0, l=propArray.length; i < l; i++) {
14174 var pi = propArray[i], dim = sib.getData('dim', pi);
14175 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14177 subnodes.push(sib);
14179 //Maintain children order
14180 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14181 if (parent && parent.id == elem.id && subnodes.length > 0
14182 && subnodes[0].dist) {
14183 subnodes.sort(function(a, b) {
14184 return (a.dist >= b.dist) - (a.dist <= b.dist);
14187 //Calculate nodes positions.
14188 for (var k = 0, ls=subnodes.length; k < ls; k++) {
14189 var child = subnodes[k];
14190 if (!child._flag) {
14191 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14192 var theta = angleInit + angleProportion / 2;
14194 for ( var i=0, l=propArray.length; i < l; i++) {
14195 var pi = propArray[i];
14196 child.setPos($P(theta, len), pi);
14197 child.setData('span', angleProportion, pi);
14198 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14201 child.angleSpan = {
14203 end : angleInit + angleProportion
14205 angleInit += angleProportion;
14212 * Method: setAngularWidthForNodes
14214 * Sets nodes angular widths.
14216 setAngularWidthForNodes : function(prop) {
14217 this.graph.eachBFS(this.root, function(elem, i) {
14218 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14219 elem._angularWidth = diamValue / i;
14224 * Method: setSubtreesAngularWidth
14226 * Sets subtrees angular widths.
14228 setSubtreesAngularWidth : function() {
14230 this.graph.eachNode(function(elem) {
14231 that.setSubtreeAngularWidth(elem);
14236 * Method: setSubtreeAngularWidth
14238 * Sets the angular width for a subtree.
14240 setSubtreeAngularWidth : function(elem) {
14241 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14242 elem.eachSubnode(function(child) {
14243 that.setSubtreeAngularWidth(child);
14244 sumAW += child._treeAngularWidth;
14246 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14250 * Method: computeAngularWidths
14252 * Computes nodes and subtrees angular widths.
14254 computeAngularWidths : function(prop) {
14255 this.setAngularWidthForNodes(prop);
14256 this.setSubtreesAngularWidth();
14263 * File: Sunburst.js
14269 A radial space filling tree visualization.
14273 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14277 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.
14281 All <Loader> methods
14283 Constructor Options:
14285 Inherits options from
14288 - <Options.Controller>
14294 - <Options.NodeStyles>
14295 - <Options.Navigation>
14297 Additionally, there are other parameters and some default values changed
14299 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14300 levelDistance - (number) Default's *100*. The distance between levels of the tree.
14301 Node.type - Described in <Options.Node>. Default's to *multipie*.
14302 Node.height - Described in <Options.Node>. Default's *0*.
14303 Edge.type - Described in <Options.Edge>. Default's *none*.
14304 Label.textAlign - Described in <Options.Label>. Default's *start*.
14305 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14307 Instance Properties:
14309 canvas - Access a <Canvas> instance.
14310 graph - Access a <Graph> instance.
14311 op - Access a <Sunburst.Op> instance.
14312 fx - Access a <Sunburst.Plot> instance.
14313 labels - Access a <Sunburst.Label> interface implementation.
14317 $jit.Sunburst = new Class({
14319 Implements: [ Loader, Extras, Layouts.Radial ],
14321 initialize: function(controller) {
14322 var $Sunburst = $jit.Sunburst;
14325 interpolation: 'linear',
14326 levelDistance: 100,
14328 'type': 'multipie',
14335 textAlign: 'start',
14336 textBaseline: 'middle'
14340 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14341 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14343 var canvasConfig = this.config;
14344 if(canvasConfig.useCanvas) {
14345 this.canvas = canvasConfig.useCanvas;
14346 this.config.labelContainer = this.canvas.id + '-label';
14348 if(canvasConfig.background) {
14349 canvasConfig.background = $.merge({
14351 colorStop1: this.config.colorStop1,
14352 colorStop2: this.config.colorStop2
14353 }, canvasConfig.background);
14355 this.canvas = new Canvas(this, canvasConfig);
14356 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14359 this.graphOptions = {
14367 this.graph = new Graph(this.graphOptions, this.config.Node,
14369 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14370 this.fx = new $Sunburst.Plot(this, $Sunburst);
14371 this.op = new $Sunburst.Op(this);
14374 this.rotated = null;
14376 // initialize extras
14377 this.initializeExtras();
14382 createLevelDistanceFunc
14384 Returns the levelDistance function used for calculating a node distance
14385 to its origin. This function returns a function that is computed
14386 per level and not per node, such that all nodes with the same depth will have the
14387 same distance to the origin. The resulting function gets the
14388 parent node as parameter and returns a float.
14391 createLevelDistanceFunc: function() {
14392 var ld = this.config.levelDistance;
14393 return function(elem) {
14394 return (elem._depth + 1) * ld;
14401 Computes positions and plots the tree.
14404 refresh: function() {
14412 An alias for computing new positions to _endPos_
14419 reposition: function() {
14420 this.compute('end');
14426 Rotates the graph so that the selected node is horizontal on the right.
14430 node - (object) A <Graph.Node>.
14431 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14432 opt - (object) Configuration options merged with this visualization configuration options.
14436 <Sunburst.rotateAngle>
14439 rotate: function(node, method, opt) {
14440 var theta = node.getPos(opt.property || 'current').getp(true).theta;
14441 this.rotated = node;
14442 this.rotateAngle(-theta, method, opt);
14446 Method: rotateAngle
14448 Rotates the graph of an angle theta.
14452 node - (object) A <Graph.Node>.
14453 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14454 opt - (object) Configuration options merged with this visualization configuration options.
14461 rotateAngle: function(theta, method, opt) {
14463 var options = $.merge(this.config, opt || {}, {
14466 var prop = opt.property || (method === "animate" ? 'end' : 'current');
14467 if(method === 'animate') {
14468 this.fx.animation.pause();
14470 this.graph.eachNode(function(n) {
14471 var p = n.getPos(prop);
14474 p.theta += Math.PI * 2;
14477 if (method == 'animate') {
14478 this.fx.animate(options);
14479 } else if (method == 'replot') {
14488 Plots the Sunburst. This is a shortcut to *fx.plot*.
14495 $jit.Sunburst.$extend = true;
14497 (function(Sunburst) {
14502 Custom extension of <Graph.Op>.
14506 All <Graph.Op> methods
14513 Sunburst.Op = new Class( {
14515 Implements: Graph.Op
14520 Class: Sunburst.Plot
14522 Custom extension of <Graph.Plot>.
14526 All <Graph.Plot> methods
14533 Sunburst.Plot = new Class( {
14535 Implements: Graph.Plot
14540 Class: Sunburst.Label
14542 Custom extension of <Graph.Label>.
14543 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14547 All <Graph.Label> methods and subclasses.
14551 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14554 Sunburst.Label = {};
14557 Sunburst.Label.Native
14559 Custom extension of <Graph.Label.Native>.
14563 All <Graph.Label.Native> methods
14567 <Graph.Label.Native>
14569 Sunburst.Label.Native = new Class( {
14570 Implements: Graph.Label.Native,
14572 initialize: function(viz) {
14574 this.label = viz.config.Label;
14575 this.config = viz.config;
14578 renderLabel: function(canvas, node, controller) {
14579 var span = node.getData('span');
14580 if(span < Math.PI /2 && Math.tan(span) *
14581 this.config.levelDistance * node._depth < 10) {
14584 var ctx = canvas.getCtx();
14585 var measure = ctx.measureText(node.name);
14586 if (node.id == this.viz.root) {
14587 var x = -measure.width / 2, y = 0, thetap = 0;
14591 var ld = controller.levelDistance - indent;
14592 var clone = node.pos.clone();
14593 clone.rho += indent;
14594 var p = clone.getp(true);
14595 var ct = clone.getc(true);
14596 var x = ct.x, y = ct.y;
14597 // get angle in degrees
14599 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14600 var thetap = cond ? p.theta + pi : p.theta;
14602 x -= Math.abs(Math.cos(p.theta) * measure.width);
14603 y += Math.sin(p.theta) * measure.width;
14604 } else if (node.id == this.viz.root) {
14605 x -= measure.width / 2;
14609 ctx.translate(x, y);
14610 ctx.rotate(thetap);
14611 ctx.fillText(node.name, 0, 0);
14619 Custom extension of <Graph.Label.SVG>.
14623 All <Graph.Label.SVG> methods
14630 Sunburst.Label.SVG = new Class( {
14631 Implements: Graph.Label.SVG,
14633 initialize: function(viz) {
14640 Overrides abstract method placeLabel in <Graph.Plot>.
14644 tag - A DOM label element.
14645 node - A <Graph.Node>.
14646 controller - A configuration/controller object passed to the visualization.
14649 placeLabel: function(tag, node, controller) {
14650 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14651 var radius = canvas.getSize();
14653 x: Math.round(pos.x + radius.width / 2),
14654 y: Math.round(pos.y + radius.height / 2)
14656 tag.setAttribute('x', labelPos.x);
14657 tag.setAttribute('y', labelPos.y);
14659 var bb = tag.getBBox();
14661 // center the label
14662 var x = tag.getAttribute('x');
14663 var y = tag.getAttribute('y');
14664 // get polar coordinates
14665 var p = node.pos.getp(true);
14666 // get angle in degrees
14668 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14670 tag.setAttribute('x', x - bb.width);
14671 tag.setAttribute('y', y - bb.height);
14672 } else if (node.id == viz.root) {
14673 tag.setAttribute('x', x - bb.width / 2);
14676 var thetap = cond ? p.theta + pi : p.theta;
14678 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14682 controller.onPlaceLabel(tag, node);
14687 Sunburst.Label.HTML
14689 Custom extension of <Graph.Label.HTML>.
14693 All <Graph.Label.HTML> methods.
14700 Sunburst.Label.HTML = new Class( {
14701 Implements: Graph.Label.HTML,
14703 initialize: function(viz) {
14709 Overrides abstract method placeLabel in <Graph.Plot>.
14713 tag - A DOM label element.
14714 node - A <Graph.Node>.
14715 controller - A configuration/controller object passed to the visualization.
14718 placeLabel: function(tag, node, controller) {
14719 var pos = node.pos.clone(),
14720 canvas = this.viz.canvas,
14721 height = node.getData('height'),
14722 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14723 radius = canvas.getSize();
14725 pos = pos.getc(true);
14728 x: Math.round(pos.x + radius.width / 2),
14729 y: Math.round(pos.y + radius.height / 2)
14732 var style = tag.style;
14733 style.left = labelPos.x + 'px';
14734 style.top = labelPos.y + 'px';
14735 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14737 controller.onPlaceLabel(tag, node);
14742 Class: Sunburst.Plot.NodeTypes
14744 This class contains a list of <Graph.Node> built-in types.
14745 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14747 You can add your custom node types, customizing your visualization to the extreme.
14752 Sunburst.Plot.NodeTypes.implement({
14754 'render': function(node, canvas) {
14755 //print your custom node to canvas
14758 'contains': function(node, pos) {
14759 //return true if pos is inside the node or false otherwise
14766 Sunburst.Plot.NodeTypes = new Class( {
14769 'contains': $.lambda(false),
14770 'anglecontains': function(node, pos) {
14771 var span = node.getData('span') / 2, theta = node.pos.theta;
14772 var begin = theta - span, end = theta + span;
14774 begin += Math.PI * 2;
14775 var atan = Math.atan2(pos.y, pos.x);
14777 atan += Math.PI * 2;
14779 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14781 return atan > begin && atan < end;
14784 'anglecontainsgauge': function(node, pos) {
14785 var span = node.getData('span') / 2, theta = node.pos.theta;
14786 var config = node.getData('config');
14787 var ld = this.config.levelDistance;
14788 var yOffset = pos.y-(ld/2);
14789 var begin = ((theta - span)/2)+Math.PI,
14790 end = ((theta + span)/2)+Math.PI;
14793 begin += Math.PI * 2;
14794 var atan = Math.atan2(yOffset, pos.x);
14798 atan += Math.PI * 2;
14802 return (atan > begin && atan <= Math.PI * 2) || atan < end;
14804 return atan > begin && atan < end;
14810 'render': function(node, canvas) {
14811 var span = node.getData('span') / 2, theta = node.pos.theta;
14812 var begin = theta - span, end = theta + span;
14813 var polarNode = node.pos.getp(true);
14814 var polar = new Polar(polarNode.rho, begin);
14815 var p1coord = polar.getc(true);
14817 var p2coord = polar.getc(true);
14819 var ctx = canvas.getCtx();
14822 ctx.lineTo(p1coord.x, p1coord.y);
14824 ctx.lineTo(p2coord.x, p2coord.y);
14826 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14830 'contains': function(node, pos) {
14831 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14832 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14833 var ld = this.config.levelDistance, d = node._depth;
14834 return (rho <= ld * d);
14840 'render': function(node, canvas) {
14841 var height = node.getData('height');
14842 var ldist = height? height : this.config.levelDistance;
14843 var span = node.getData('span') / 2, theta = node.pos.theta;
14844 var begin = theta - span, end = theta + span;
14845 var polarNode = node.pos.getp(true);
14847 var polar = new Polar(polarNode.rho, begin);
14848 var p1coord = polar.getc(true);
14851 var p2coord = polar.getc(true);
14853 polar.rho += ldist;
14854 var p3coord = polar.getc(true);
14856 polar.theta = begin;
14857 var p4coord = polar.getc(true);
14859 var ctx = canvas.getCtx();
14862 ctx.arc(0, 0, polarNode.rho, begin, end, false);
14863 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
14864 ctx.moveTo(p1coord.x, p1coord.y);
14865 ctx.lineTo(p4coord.x, p4coord.y);
14866 ctx.moveTo(p2coord.x, p2coord.y);
14867 ctx.lineTo(p3coord.x, p3coord.y);
14870 if (node.collapsed) {
14875 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
14881 'contains': function(node, pos) {
14882 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14883 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14884 var height = node.getData('height');
14885 var ldist = height? height : this.config.levelDistance;
14886 var ld = this.config.levelDistance, d = node._depth;
14887 return (rho >= ld * d) && (rho <= (ld * d + ldist));
14893 'gradient-multipie': {
14894 'render': function(node, canvas) {
14895 var ctx = canvas.getCtx();
14896 var height = node.getData('height');
14897 var ldist = height? height : this.config.levelDistance;
14898 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
14899 0, 0, node.getPos().rho + ldist);
14901 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
14902 $.each(colorArray, function(i) {
14903 ans.push(parseInt(i * 0.5, 10));
14905 var endColor = $.rgbToHex(ans);
14906 radialGradient.addColorStop(0, endColor);
14907 radialGradient.addColorStop(1, node.getData('color'));
14908 ctx.fillStyle = radialGradient;
14909 this.nodeTypes['multipie'].render.call(this, node, canvas);
14911 'contains': function(node, pos) {
14912 return this.nodeTypes['multipie'].contains.call(this, node, pos);
14917 'render': function(node, canvas) {
14918 var ctx = canvas.getCtx();
14919 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
14922 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
14923 $.each(colorArray, function(i) {
14924 ans.push(parseInt(i * 0.5, 10));
14926 var endColor = $.rgbToHex(ans);
14927 radialGradient.addColorStop(1, endColor);
14928 radialGradient.addColorStop(0, node.getData('color'));
14929 ctx.fillStyle = radialGradient;
14930 this.nodeTypes['pie'].render.call(this, node, canvas);
14932 'contains': function(node, pos) {
14933 return this.nodeTypes['pie'].contains.call(this, node, pos);
14939 Class: Sunburst.Plot.EdgeTypes
14941 This class contains a list of <Graph.Adjacence> built-in types.
14942 Edge types implemented are 'none', 'line' and 'arrow'.
14944 You can add your custom edge types, customizing your visualization to the extreme.
14949 Sunburst.Plot.EdgeTypes.implement({
14951 'render': function(adj, canvas) {
14952 //print your custom edge to canvas
14955 'contains': function(adj, pos) {
14956 //return true if pos is inside the arc or false otherwise
14963 Sunburst.Plot.EdgeTypes = new Class({
14966 'render': function(adj, canvas) {
14967 var from = adj.nodeFrom.pos.getc(true),
14968 to = adj.nodeTo.pos.getc(true);
14969 this.edgeHelper.line.render(from, to, canvas);
14971 'contains': function(adj, pos) {
14972 var from = adj.nodeFrom.pos.getc(true),
14973 to = adj.nodeTo.pos.getc(true);
14974 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
14978 'render': function(adj, canvas) {
14979 var from = adj.nodeFrom.pos.getc(true),
14980 to = adj.nodeTo.pos.getc(true),
14981 dim = adj.getData('dim'),
14982 direction = adj.data.$direction,
14983 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
14984 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
14986 'contains': function(adj, pos) {
14987 var from = adj.nodeFrom.pos.getc(true),
14988 to = adj.nodeTo.pos.getc(true);
14989 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
14993 'render': function(adj, canvas) {
14994 var from = adj.nodeFrom.pos.getc(),
14995 to = adj.nodeTo.pos.getc(),
14996 dim = Math.max(from.norm(), to.norm());
14997 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
14999 'contains': $.lambda(false) //TODO(nico): Implement this!
15007 * File: PieChart.js
15011 $jit.Sunburst.Plot.NodeTypes.implement({
15012 'piechart-stacked' : {
15013 'render' : function(node, canvas) {
15014 var pos = node.pos.getp(true),
15015 dimArray = node.getData('dimArray'),
15016 valueArray = node.getData('valueArray'),
15017 colorArray = node.getData('colorArray'),
15018 colorLength = colorArray.length,
15019 stringArray = node.getData('stringArray'),
15020 span = node.getData('span') / 2,
15021 theta = node.pos.theta,
15022 begin = theta - span,
15023 end = theta + span,
15026 var ctx = canvas.getCtx(),
15028 gradient = node.getData('gradient'),
15029 border = node.getData('border'),
15030 config = node.getData('config'),
15031 showLabels = config.showLabels,
15032 resizeLabels = config.resizeLabels,
15033 label = config.Label;
15035 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15036 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15038 if (colorArray && dimArray && stringArray) {
15039 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15040 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15041 if(dimi <= 0) continue;
15042 ctx.fillStyle = ctx.strokeStyle = colori;
15043 if(gradient && dimi) {
15044 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15045 xpos, ypos, acum + dimi + config.sliceOffset);
15046 var colorRgb = $.hexToRgb(colori),
15047 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15048 endColor = $.rgbToHex(ans);
15050 radialGradient.addColorStop(0, colori);
15051 radialGradient.addColorStop(0.5, colori);
15052 radialGradient.addColorStop(1, endColor);
15053 ctx.fillStyle = radialGradient;
15056 polar.rho = acum + config.sliceOffset;
15057 polar.theta = begin;
15058 var p1coord = polar.getc(true);
15060 var p2coord = polar.getc(true);
15062 var p3coord = polar.getc(true);
15063 polar.theta = begin;
15064 var p4coord = polar.getc(true);
15067 //fixing FF arc method + fill
15068 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15069 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15071 if(border && border.name == stringArray[i]) {
15073 opt.dimValue = dimArray[i];
15077 acum += (dimi || 0);
15078 valAcum += (valueArray[i] || 0);
15082 ctx.globalCompositeOperation = "source-over";
15084 ctx.strokeStyle = border.color;
15085 var s = begin < end? 1 : -1;
15087 //fixing FF arc method + fill
15088 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15089 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15094 if(showLabels && label.type == 'Native') {
15096 ctx.fillStyle = ctx.strokeStyle = label.color;
15097 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15098 fontSize = (label.size * scale) >> 0;
15099 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15101 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15102 ctx.textBaseline = 'middle';
15103 ctx.textAlign = 'center';
15105 polar.rho = acum + config.labelOffset + config.sliceOffset;
15106 polar.theta = node.pos.theta;
15107 var cart = polar.getc(true);
15109 ctx.fillText(node.name, cart.x, cart.y);
15114 'contains': function(node, pos) {
15115 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15116 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15117 var ld = this.config.levelDistance, d = node._depth;
15118 var config = node.getData('config');
15119 if(rho <=ld * d + config.sliceOffset) {
15120 var dimArray = node.getData('dimArray');
15121 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15122 var dimi = dimArray[i];
15123 if(rho >= acum && rho <= acum + dimi) {
15125 name: node.getData('stringArray')[i],
15126 color: node.getData('colorArray')[i],
15127 value: node.getData('valueArray')[i],
15140 'piechart-basic' : {
15141 'render' : function(node, canvas) {
15142 var pos = node.pos.getp(true),
15143 dimArray = node.getData('dimArray'),
15144 valueArray = node.getData('valueArray'),
15145 colorArray = node.getData('colorMono'),
15146 colorLength = colorArray.length,
15147 stringArray = node.getData('stringArray'),
15148 percentage = node.getData('percentage'),
15149 span = node.getData('span') / 2,
15150 theta = node.pos.theta,
15151 begin = theta - span,
15152 end = theta + span,
15155 var ctx = canvas.getCtx(),
15157 gradient = node.getData('gradient'),
15158 border = node.getData('border'),
15159 config = node.getData('config'),
15160 showLabels = config.showLabels,
15161 resizeLabels = config.resizeLabels,
15162 label = config.Label;
15164 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15165 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15167 if (colorArray && dimArray && stringArray) {
15168 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15169 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15170 if(dimi <= 0) continue;
15171 ctx.fillStyle = ctx.strokeStyle = colori;
15173 polar.rho = acum + config.sliceOffset;
15174 polar.theta = begin;
15175 var p1coord = polar.getc(true);
15177 var p2coord = polar.getc(true);
15179 var p3coord = polar.getc(true);
15180 polar.theta = begin;
15181 var p4coord = polar.getc(true);
15183 if(typeof FlashCanvas == "undefined") {
15186 ctx.fillStyle = "rgba(0,0,0,.2)";
15187 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15188 ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);
15191 if(gradient && dimi) {
15192 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15193 xpos, ypos, acum + dimi + config.sliceOffset);
15194 var colorRgb = $.hexToRgb(colori),
15195 endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15196 endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15198 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15199 radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15200 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15201 radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15202 ctx.fillStyle = radialGradient;
15207 //fixing FF arc method + fill
15209 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15210 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15212 if(border && border.name == stringArray[i]) {
15214 opt.dimValue = dimArray[i];
15217 opt.sliceValue = valueArray[i];
15219 acum += (dimi || 0);
15220 valAcum += (valueArray[i] || 0);
15224 ctx.globalCompositeOperation = "source-over";
15226 ctx.strokeStyle = border.color;
15227 var s = begin < end? 1 : -1;
15229 //fixing FF arc method + fill
15230 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15231 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15236 if(showLabels && label.type == 'Native') {
15238 ctx.fillStyle = ctx.strokeStyle = label.color;
15239 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15240 fontSize = (label.size * scale) >> 0;
15241 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15243 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15244 ctx.textBaseline = 'middle';
15245 ctx.textAlign = 'center';
15247 angle = theta * 360 / (2 * pi);
15248 polar.rho = acum + config.labelOffset + config.sliceOffset;
15249 polar.theta = node.pos.theta;
15250 var cart = polar.getc(true);
15251 if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15254 if(config.labelType == 'name') {
15255 ctx.fillText(node.name, cart.x, cart.y);
15257 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15264 'contains': function(node, pos) {
15265 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15266 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15267 var ld = this.config.levelDistance, d = node._depth;
15268 var config = node.getData('config');
15270 if(rho <=ld * d + config.sliceOffset) {
15271 var dimArray = node.getData('dimArray');
15272 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15273 var dimi = dimArray[i];
15274 if(rho >= acum && rho <= acum + dimi) {
15275 var url = Url.decode(node.getData('linkArray')[i]);
15277 name: node.getData('stringArray')[i],
15279 color: node.getData('colorArray')[i],
15280 value: node.getData('valueArray')[i],
15281 percentage: node.getData('percentage'),
15282 valuelabel: node.getData('valuelabelsArray')[i],
15300 A visualization that displays stacked bar charts.
15302 Constructor Options:
15304 See <Options.PieChart>.
15307 $jit.PieChart = new Class({
15309 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15313 initialize: function(opt) {
15314 this.controller = this.config =
15315 $.merge(Options("Canvas", "PieChart", "Label"), {
15316 Label: { type: 'Native' }
15318 this.initializeViz();
15321 initializeViz: function() {
15322 var config = this.config, that = this;
15323 var nodeType = config.type.split(":")[0];
15324 var sb = new $jit.Sunburst({
15325 injectInto: config.injectInto,
15326 useCanvas: config.useCanvas,
15327 withLabels: config.Label.type != 'Native',
15328 background: config.background,
15329 renderBackground: config.renderBackground,
15330 backgroundColor: config.backgroundColor,
15331 colorStop1: config.colorStop1,
15332 colorStop2: config.colorStop2,
15334 type: config.Label.type
15338 type: 'piechart-' + nodeType,
15346 enable: config.Tips.enable,
15349 onShow: function(tip, node, contains) {
15350 var elem = contains;
15351 config.Tips.onShow(tip, elem, node);
15352 if(elem.link != 'undefined' && elem.link != '') {
15353 document.body.style.cursor = 'pointer';
15356 onHide: function() {
15357 document.body.style.cursor = 'default';
15363 onClick: function(node, eventInfo, evt) {
15364 if(!config.Events.enable) return;
15365 var elem = eventInfo.getContains();
15366 config.Events.onClick(elem, eventInfo, evt);
15368 onMouseMove: function(node, eventInfo, evt) {
15369 if(!config.hoveredColor) return;
15371 var elem = eventInfo.getContains();
15372 that.select(node.id, elem.name, elem.index);
15374 that.select(false, false, false);
15378 onCreateLabel: function(domElement, node) {
15379 var labelConf = config.Label;
15380 if(config.showLabels) {
15381 var style = domElement.style;
15382 style.fontSize = labelConf.size + 'px';
15383 style.fontFamily = labelConf.family;
15384 style.color = labelConf.color;
15385 style.textAlign = 'center';
15386 if(config.labelType == 'name') {
15387 domElement.innerHTML = node.name;
15389 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15391 domElement.style.width = '400px';
15394 onPlaceLabel: function(domElement, node) {
15395 if(!config.showLabels) return;
15396 var pos = node.pos.getp(true),
15397 dimArray = node.getData('dimArray'),
15398 span = node.getData('span') / 2,
15399 theta = node.pos.theta,
15400 begin = theta - span,
15401 end = theta + span,
15404 var showLabels = config.showLabels,
15405 resizeLabels = config.resizeLabels,
15406 label = config.Label;
15409 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15410 acum += dimArray[i];
15412 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15413 fontSize = (label.size * scale) >> 0;
15414 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15415 domElement.style.fontSize = fontSize + 'px';
15416 polar.rho = acum + config.labelOffset + config.sliceOffset;
15417 polar.theta = (begin + end) / 2;
15418 var pos = polar.getc(true);
15419 var radius = that.canvas.getSize();
15421 x: Math.round(pos.x + radius.width / 2),
15422 y: Math.round(pos.y + radius.height / 2)
15424 domElement.style.left = (labelPos.x - 200) + 'px';
15425 domElement.style.top = labelPos.y + 'px';
15430 var size = sb.canvas.getSize(),
15432 sb.config.levelDistance = min(size.width, size.height)/2
15433 - config.offset - config.sliceOffset;
15435 this.canvas = this.sb.canvas;
15436 this.canvas.getCtx().globalCompositeOperation = 'lighter';
15438 renderBackground: function() {
15439 var canvas = this.canvas,
15440 config = this.config,
15441 backgroundColor = config.backgroundColor,
15442 size = canvas.getSize(),
15443 ctx = canvas.getCtx();
15444 ctx.globalCompositeOperation = "destination-over";
15445 ctx.fillStyle = backgroundColor;
15446 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15451 Loads JSON data into the visualization.
15455 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>.
15459 var pieChart = new $jit.PieChart(options);
15460 pieChart.loadJSON(json);
15463 loadJSON: function(json) {
15464 var prefix = $.time(),
15467 name = $.splat(json.label),
15468 nameLength = name.length,
15469 color = $.splat(json.color || this.colors),
15470 colorLength = color.length,
15471 config = this.config,
15472 renderBackground = config.renderBackground,
15473 gradient = !!config.type.split(":")[1],
15474 animate = config.animate,
15475 mono = nameLength == 1;
15477 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15478 var val = values[i];
15479 var valArray = $.splat(val.values);
15480 totalValue += parseInt(valArray.sum());
15483 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15484 var val = values[i];
15485 var valArray = $.splat(val.values);
15486 var percentage = (valArray.sum()/totalValue) * 100;
15488 var linkArray = $.splat(val.links);
15489 var valuelabelsArray = $.splat(val.valuelabels);
15493 'id': prefix + val.label,
15497 'valuelabel': valuelabelsArray,
15498 '$linkArray': linkArray,
15499 '$valuelabelsArray': valuelabelsArray,
15500 '$valueArray': valArray,
15501 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15502 '$colorMono': $.splat(color[i % colorLength]),
15503 '$stringArray': name,
15504 '$gradient': gradient,
15506 '$percentage': percentage.toFixed(1),
15507 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15513 'id': prefix + '$root',
15527 this.normalizeDims();
15532 if(renderBackground) {
15533 this.renderBackground();
15539 modes: ['node-property:dimArray'],
15548 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.
15552 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15553 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15558 pieChart.updateJSON(json, {
15559 onComplete: function() {
15560 alert('update complete!');
15565 updateJSON: function(json, onComplete) {
15566 if(this.busy) return;
15570 var graph = sb.graph;
15571 var values = json.values;
15572 var animate = this.config.animate;
15574 $.each(values, function(v) {
15575 var n = graph.getByName(v.label),
15576 vals = $.splat(v.values);
15578 n.setData('valueArray', vals);
15579 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15581 n.setData('stringArray', $.splat(json.label));
15585 this.normalizeDims();
15589 modes: ['node-property:dimArray:span', 'linear'],
15591 onComplete: function() {
15593 onComplete && onComplete.onComplete();
15601 //adds the little brown bar when hovering the node
15602 select: function(id, name) {
15603 if(!this.config.hoveredColor) return;
15604 var s = this.selected;
15605 if(s.id != id || s.name != name) {
15608 s.color = this.config.hoveredColor;
15609 this.sb.graph.eachNode(function(n) {
15611 n.setData('border', s);
15613 n.setData('border', false);
15623 Returns an object containing as keys the legend names and as values hex strings with color values.
15628 var legend = pieChart.getLegend();
15631 getLegend: function() {
15632 var legend = new Array();
15633 var name = new Array();
15634 var color = new Array();
15636 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15639 var colors = n.getData('colorArray'),
15640 len = colors.length;
15641 $.each(n.getData('stringArray'), function(s, i) {
15642 color[i] = colors[i % len];
15645 legend['name'] = name;
15646 legend['color'] = color;
15651 Method: getMaxValue
15653 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15658 var ans = pieChart.getMaxValue();
15661 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15666 //will return 100 for all PieChart instances,
15667 //displaying all of them with the same scale
15668 $jit.PieChart.implement({
15669 'getMaxValue': function() {
15676 getMaxValue: function() {
15678 this.sb.graph.eachNode(function(n) {
15679 var valArray = n.getData('valueArray'),
15681 $.each(valArray, function(v) {
15684 maxValue = maxValue>acum? maxValue:acum;
15689 normalizeDims: function() {
15690 //number of elements
15691 var root = this.sb.graph.getNode(this.sb.root), l=0;
15692 root.eachAdjacency(function() {
15695 var maxValue = this.getMaxValue() || 1,
15696 config = this.config,
15697 animate = config.animate,
15698 rho = this.sb.config.levelDistance;
15699 this.sb.graph.eachNode(function(n) {
15700 var acum = 0, animateValue = [];
15701 $.each(n.getData('valueArray'), function(v) {
15703 animateValue.push(1);
15705 var stat = (animateValue.length == 1) && !config.updateHeights;
15707 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15708 return stat? rho: (n * rho / maxValue);
15710 var dimArray = n.getData('dimArray');
15712 n.setData('dimArray', animateValue);
15715 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
15716 return stat? rho : (n * rho / maxValue);
15719 n.setData('normalizedDim', acum / maxValue);
15727 Options.GaugeChart = {
15731 offset: 25, // page offset
15733 labelOffset: 3, // label offset
15734 type: 'stacked', // gradient
15736 hoveredColor: '#9fd4ff',
15747 resizeLabels: false,
15749 //only valid for mono-valued datasets
15750 updateHeights: false
15755 $jit.Sunburst.Plot.NodeTypes.implement({
15756 'gaugechart-basic' : {
15757 'render' : function(node, canvas) {
15758 var pos = node.pos.getp(true),
15759 dimArray = node.getData('dimArray'),
15760 valueArray = node.getData('valueArray'),
15761 valuelabelsArray = node.getData('valuelabelsArray'),
15762 gaugeTarget = node.getData('gaugeTarget'),
15763 nodeIteration = node.getData('nodeIteration'),
15764 nodeLength = node.getData('nodeLength'),
15765 colorArray = node.getData('colorMono'),
15766 colorLength = colorArray.length,
15767 stringArray = node.getData('stringArray'),
15768 span = node.getData('span') / 2,
15769 theta = node.pos.theta,
15770 begin = ((theta - span)/2)+Math.PI,
15771 end = ((theta + span)/2)+Math.PI,
15775 var ctx = canvas.getCtx(),
15777 gradient = node.getData('gradient'),
15778 border = node.getData('border'),
15779 config = node.getData('config'),
15780 showLabels = config.showLabels,
15781 resizeLabels = config.resizeLabels,
15782 label = config.Label;
15784 var xpos = Math.cos((begin + end) /2);
15785 var ypos = Math.sin((begin + end) /2);
15787 if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
15788 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15789 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15790 if(dimi <= 0) continue;
15791 ctx.fillStyle = ctx.strokeStyle = colori;
15792 if(gradient && dimi) {
15793 var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
15794 xpos, (ypos + dimi/2), acum + dimi);
15795 var colorRgb = $.hexToRgb(colori),
15796 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
15797 endColor = $.rgbToHex(ans);
15799 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15800 radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
15801 radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
15802 radialGradient.addColorStop(1, 'rgba('+ans+',1)');
15803 ctx.fillStyle = radialGradient;
15807 polar.theta = begin;
15808 var p1coord = polar.getc(true);
15810 var p2coord = polar.getc(true);
15812 var p3coord = polar.getc(true);
15813 polar.theta = begin;
15814 var p4coord = polar.getc(true);
15818 //fixing FF arc method + fill
15819 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
15820 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
15824 acum += (dimi || 0);
15825 valAcum += (valueArray[i] || 0);
15828 if(showLabels && label.type == 'Native') {
15830 ctx.fillStyle = ctx.strokeStyle = label.color;
15833 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
15834 ctx.textBaseline = 'bottom';
15835 ctx.textAlign = 'center';
15837 polar.rho = acum * .65;
15838 polar.theta = begin;
15839 var cart = polar.getc(true);
15841 //changes y pos of first label
15842 if(nodeIteration == 1) {
15843 textY = cart.y - (label.size/2) + acum /2;
15845 textY = cart.y + acum/2;
15848 if(config.labelType == 'name') {
15849 ctx.fillText(node.name, cart.x, textY);
15851 ctx.fillText(valuelabelsArray[0], cart.x, textY);
15855 if(nodeIteration == nodeLength) {
15857 var cart = polar.getc(true);
15858 if(config.labelType == 'name') {
15859 ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
15861 ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
15870 'contains': function(node, pos) {
15874 if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
15875 var config = node.getData('config');
15876 var ld = this.config.levelDistance , d = node._depth;
15877 var yOffset = pos.y - (ld/2);
15878 var xOffset = pos.x;
15879 var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
15880 if(rho <=parseInt(ld * d)) {
15881 var dimArray = node.getData('dimArray');
15882 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15883 var dimi = dimArray[i];
15884 if(rho >= ld * .8 && rho <= acum + dimi) {
15886 var url = Url.decode(node.getData('linkArray')[i]);
15888 name: node.getData('stringArray')[i],
15890 color: node.getData('colorArray')[i],
15891 value: node.getData('valueArray')[i],
15892 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
15912 A visualization that displays gauge charts
15914 Constructor Options:
15916 See <Options.Gauge>.
15919 $jit.GaugeChart = new Class({
15921 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15925 initialize: function(opt) {
15926 this.controller = this.config =
15927 $.merge(Options("Canvas", "GaugeChart", "Label"), {
15928 Label: { type: 'Native' }
15930 this.initializeViz();
15933 initializeViz: function() {
15934 var config = this.config, that = this;
15935 var nodeType = config.type.split(":")[0];
15936 var sb = new $jit.Sunburst({
15937 injectInto: config.injectInto,
15938 useCanvas: config.useCanvas,
15939 withLabels: config.Label.type != 'Native',
15940 background: config.background,
15941 renderBackground: config.renderBackground,
15942 backgroundColor: config.backgroundColor,
15943 colorStop1: config.colorStop1,
15944 colorStop2: config.colorStop2,
15946 type: config.Label.type
15950 type: 'gaugechart-' + nodeType,
15958 enable: config.Tips.enable,
15961 onShow: function(tip, node, contains) {
15962 var elem = contains;
15963 config.Tips.onShow(tip, elem, node);
15964 if(elem.link != 'undefined' && elem.link != '') {
15965 document.body.style.cursor = 'pointer';
15968 onHide: function() {
15969 document.body.style.cursor = 'default';
15975 onClick: function(node, eventInfo, evt) {
15976 if(!config.Events.enable) return;
15977 var elem = eventInfo.getContains();
15978 config.Events.onClick(elem, eventInfo, evt);
15981 onCreateLabel: function(domElement, node) {
15982 var labelConf = config.Label;
15983 if(config.showLabels) {
15984 var style = domElement.style;
15985 style.fontSize = labelConf.size + 'px';
15986 style.fontFamily = labelConf.family;
15987 style.color = labelConf.color;
15988 style.textAlign = 'center';
15989 valuelabelsArray = node.getData('valuelabelsArray'),
15990 nodeIteration = node.getData('nodeIteration'),
15991 nodeLength = node.getData('nodeLength'),
15992 canvas = sb.canvas,
15995 if(config.labelType == 'name') {
15996 domElement.innerHTML = node.name;
15998 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16001 domElement.style.width = '400px';
16004 if(nodeIteration == nodeLength && nodeLength != 0) {
16005 idLabel = canvas.id + "-label";
16006 container = document.getElementById(idLabel);
16007 finalLabel = document.createElement('div');
16008 finalLabelStyle = finalLabel.style;
16009 finalLabel.id = prefix + "finalLabel";
16010 finalLabelStyle.position = "absolute";
16011 finalLabelStyle.width = "400px";
16012 finalLabelStyle.left = "0px";
16013 container.appendChild(finalLabel);
16014 if(config.labelType == 'name') {
16015 finalLabel.innerHTML = node.name;
16017 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16023 onPlaceLabel: function(domElement, node) {
16024 if(!config.showLabels) return;
16025 var pos = node.pos.getp(true),
16026 dimArray = node.getData('dimArray'),
16027 nodeIteration = node.getData('nodeIteration'),
16028 nodeLength = node.getData('nodeLength'),
16029 span = node.getData('span') / 2,
16030 theta = node.pos.theta,
16031 begin = ((theta - span)/2)+Math.PI,
16032 end = ((theta + span)/2)+Math.PI,
16035 var showLabels = config.showLabels,
16036 resizeLabels = config.resizeLabels,
16037 label = config.Label,
16038 radiusOffset = sb.config.levelDistance;
16041 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16042 acum += dimArray[i];
16044 var scale = resizeLabels? node.getData('normalizedDim') : 1,
16045 fontSize = (label.size * scale) >> 0;
16046 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16047 domElement.style.fontSize = fontSize + 'px';
16048 polar.rho = acum * .65;
16049 polar.theta = begin;
16050 var pos = polar.getc(true);
16051 var radius = that.canvas.getSize();
16053 x: Math.round(pos.x + radius.width / 2),
16054 y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16059 domElement.style.left = (labelPos.x - 200) + 'px';
16060 domElement.style.top = labelPos.y + 'px';
16062 //reposition first label
16063 if(nodeIteration == 1) {
16064 domElement.style.top = labelPos.y - label.size + 'px';
16068 //position final label
16069 if(nodeIteration == nodeLength && nodeLength != 0) {
16071 var final = polar.getc(true);
16073 x: Math.round(final.x + radius.width / 2),
16074 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16076 finalLabel.style.left = (finalPos.x - 200) + "px";
16077 finalLabel.style.top = finalPos.y - label.size + "px";
16085 this.canvas = this.sb.canvas;
16086 var size = sb.canvas.getSize(),
16088 sb.config.levelDistance = min(size.width, size.height)/2
16089 - config.offset - config.sliceOffset;
16094 renderBackground: function() {
16095 var canvas = this.sb.canvas,
16096 config = this.config,
16097 style = config.gaugeStyle,
16098 ctx = canvas.getCtx(),
16099 size = canvas.getSize(),
16100 radius = this.sb.config.levelDistance,
16101 startAngle = (Math.PI/180)*1,
16102 endAngle = (Math.PI/180)*179;
16105 //background border
16106 ctx.fillStyle = style.borderColor;
16108 ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true);
16112 var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16113 radialGradient.addColorStop(0, '#ffffff');
16114 radialGradient.addColorStop(0.3, style.backgroundColor);
16115 radialGradient.addColorStop(0.6, style.backgroundColor);
16116 radialGradient.addColorStop(1, '#FFFFFF');
16117 ctx.fillStyle = radialGradient;
16120 startAngle = (Math.PI/180)*0;
16121 endAngle = (Math.PI/180)*180;
16123 ctx.arc(0,radius/2,radius,startAngle,endAngle, true);
16131 renderNeedle: function(gaugePosition,target) {
16132 var canvas = this.sb.canvas,
16133 config = this.config,
16134 style = config.gaugeStyle,
16135 ctx = canvas.getCtx(),
16136 size = canvas.getSize(),
16137 radius = this.sb.config.levelDistance;
16138 gaugeCenter = (radius/2);
16140 endAngle = (Math.PI/180)*180;
16144 ctx.fillStyle = style.needleColor;
16145 var segments = 180/target;
16146 needleAngle = gaugePosition * segments;
16147 ctx.translate(0, gaugeCenter);
16149 ctx.rotate(needleAngle * Math.PI / 180);
16153 ctx.lineTo(-radius*.9,-1);
16154 ctx.lineTo(-radius*.9,1);
16164 ctx.strokeStyle = '#aa0000';
16166 ctx.rotate(needleAngle * Math.PI / 180);
16170 ctx.lineTo(-radius*.8,-1);
16171 ctx.lineTo(-radius*.8,1);
16179 ctx.fillStyle = "#000000";
16180 ctx.lineWidth = style.borderSize;
16181 ctx.strokeStyle = style.borderColor;
16182 var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16183 radialGradient.addColorStop(0, '#666666');
16184 radialGradient.addColorStop(0.8, '#444444');
16185 radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
16186 ctx.fillStyle = radialGradient;
16187 ctx.translate(0,5);
16190 ctx.arc(0,0,radius*.2,startAngle,endAngle, true);
16197 renderTicks: function(values) {
16198 var canvas = this.sb.canvas,
16199 config = this.config,
16200 style = config.gaugeStyle,
16201 ctx = canvas.getCtx(),
16202 size = canvas.getSize(),
16203 radius = this.sb.config.levelDistance,
16204 gaugeCenter = (radius/2);
16207 ctx.strokeStyle = style.borderColor;
16209 ctx.lineCap = "round";
16210 for(var i=0, total = 0, l=values.length; i<l; i++) {
16211 var val = values[i];
16212 if(val.label != 'GaugePosition') {
16213 total += (parseInt(val.values) || 0);
16217 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16218 var val = values[i];
16219 if(val.label != 'GaugePosition') {
16220 acum += (parseInt(val.values) || 0);
16222 var segments = 180/total;
16223 angle = acum * segments;
16227 ctx.translate(0, gaugeCenter);
16229 ctx.rotate(angle * (Math.PI/180));
16230 ctx.moveTo(-radius,0);
16231 ctx.lineTo(-radius*.75,0);
16239 renderPositionLabel: function(position) {
16240 var canvas = this.sb.canvas,
16241 config = this.config,
16242 label = config.Label,
16243 style = config.gaugeStyle,
16244 ctx = canvas.getCtx(),
16245 size = canvas.getSize(),
16246 radius = this.sb.config.levelDistance,
16247 gaugeCenter = (radius/2);
16248 ctx.textBaseline = 'middle';
16249 ctx.textAlign = 'center';
16250 ctx.font = style.positionFontSize + 'px ' + label.family;
16251 ctx.fillStyle = "#ffffff";
16253 height = style.positionFontSize + 10,
16255 idLabel = canvas.id + "-label";
16256 container = document.getElementById(idLabel);
16257 if(label.type == 'Native') {
16258 var m = ctx.measureText(position),
16259 width = m.width + 40;
16263 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16264 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16265 if(label.type == 'Native') {
16266 ctx.fillStyle = label.color;
16267 ctx.fillText(position, 0, (height/2) + style.positionOffset);
16269 var labelDiv = document.createElement('div');
16270 labelDivStyle = labelDiv.style;
16271 labelDivStyle.color = label.color;
16272 labelDivStyle.fontSize = style.positionFontSize + "px";
16273 labelDivStyle.position = "absolute";
16274 labelDivStyle.width = width + "px";
16275 labelDivStyle.left = (size.width/2) - (width/2) + "px";
16276 labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16277 labelDiv.innerHTML = position;
16278 container.appendChild(labelDiv);
16283 renderSubtitle: function() {
16284 var canvas = this.canvas,
16285 size = canvas.getSize(),
16286 config = this.config,
16287 margin = config.Margin,
16288 radius = this.sb.config.levelDistance,
16289 title = config.Title,
16290 label = config.Label,
16291 subtitle = config.Subtitle;
16292 ctx = canvas.getCtx();
16293 ctx.fillStyle = title.color;
16294 ctx.textAlign = 'left';
16295 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16297 if(label.type == 'Native') {
16298 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset);
16302 renderChartBackground: function() {
16303 var canvas = this.canvas,
16304 config = this.config,
16305 backgroundColor = config.backgroundColor,
16306 size = canvas.getSize(),
16307 ctx = canvas.getCtx();
16308 //ctx.globalCompositeOperation = "destination-over";
16309 ctx.fillStyle = backgroundColor;
16310 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16313 loadJSON: function(json) {
16315 var prefix = $.time(),
16318 name = $.splat(json.label),
16319 nameLength = name.length,
16320 color = $.splat(json.color || this.colors),
16321 colorLength = color.length,
16322 config = this.config,
16323 renderBackground = config.renderBackground,
16324 gradient = !!config.type.split(":")[1],
16325 animate = config.animate,
16326 mono = nameLength == 1;
16327 var props = $.splat(json.properties)[0];
16329 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16331 var val = values[i];
16332 if(val.label != 'GaugePosition') {
16333 var valArray = $.splat(val.values);
16334 var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16335 var valuelabelsArray = $.splat(val.valuelabels);
16338 'id': prefix + val.label,
16342 'valuelabel': valuelabelsArray,
16343 '$linkArray': linkArray,
16344 '$valuelabelsArray': valuelabelsArray,
16345 '$valueArray': valArray,
16346 '$nodeIteration': i,
16347 '$nodeLength': l-1,
16348 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16349 '$colorMono': $.splat(color[i % colorLength]),
16350 '$stringArray': name,
16351 '$gradient': gradient,
16353 '$gaugeTarget': props['gaugeTarget'],
16354 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16359 var gaugePosition = val.gvalue;
16360 var gaugePositionLabel = val.gvaluelabel;
16364 'id': prefix + '$root',
16377 if(renderBackground) {
16378 this.renderChartBackground();
16381 this.renderBackground();
16384 this.normalizeDims();
16389 modes: ['node-property:dimArray'],
16395 this.renderPositionLabel(gaugePositionLabel);
16396 if (props['gaugeTarget'] != 0) {
16397 this.renderTicks(json.values);
16398 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16401 this.renderSubtitle();
16405 updateJSON: function(json, onComplete) {
16406 if(this.busy) return;
16410 var graph = sb.graph;
16411 var values = json.values;
16412 var animate = this.config.animate;
16414 $.each(values, function(v) {
16415 var n = graph.getByName(v.label),
16416 vals = $.splat(v.values);
16418 n.setData('valueArray', vals);
16419 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16421 n.setData('stringArray', $.splat(json.label));
16425 this.normalizeDims();
16429 modes: ['node-property:dimArray:span', 'linear'],
16431 onComplete: function() {
16433 onComplete && onComplete.onComplete();
16441 //adds the little brown bar when hovering the node
16442 select: function(id, name) {
16443 if(!this.config.hoveredColor) return;
16444 var s = this.selected;
16445 if(s.id != id || s.name != name) {
16448 s.color = this.config.hoveredColor;
16449 this.sb.graph.eachNode(function(n) {
16451 n.setData('border', s);
16453 n.setData('border', false);
16463 Returns an object containing as keys the legend names and as values hex strings with color values.
16468 var legend = pieChart.getLegend();
16471 getLegend: function() {
16472 var legend = new Array();
16473 var name = new Array();
16474 var color = new Array();
16476 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16479 var colors = n.getData('colorArray'),
16480 len = colors.length;
16481 $.each(n.getData('stringArray'), function(s, i) {
16482 color[i] = colors[i % len];
16485 legend['name'] = name;
16486 legend['color'] = color;
16491 Method: getMaxValue
16493 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16498 var ans = pieChart.getMaxValue();
16501 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16506 //will return 100 for all PieChart instances,
16507 //displaying all of them with the same scale
16508 $jit.PieChart.implement({
16509 'getMaxValue': function() {
16516 getMaxValue: function() {
16518 this.sb.graph.eachNode(function(n) {
16519 var valArray = n.getData('valueArray'),
16521 $.each(valArray, function(v) {
16524 maxValue = maxValue>acum? maxValue:acum;
16529 normalizeDims: function() {
16530 //number of elements
16531 var root = this.sb.graph.getNode(this.sb.root), l=0;
16532 root.eachAdjacency(function() {
16535 var maxValue = this.getMaxValue() || 1,
16536 config = this.config,
16537 animate = config.animate,
16538 rho = this.sb.config.levelDistance;
16539 this.sb.graph.eachNode(function(n) {
16540 var acum = 0, animateValue = [];
16541 $.each(n.getData('valueArray'), function(v) {
16543 animateValue.push(1);
16545 var stat = (animateValue.length == 1) && !config.updateHeights;
16547 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16548 return stat? rho: (n * rho / maxValue);
16550 var dimArray = n.getData('dimArray');
16552 n.setData('dimArray', animateValue);
16555 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16556 return stat? rho : (n * rho / maxValue);
16559 n.setData('normalizedDim', acum / maxValue);
16566 * Class: Layouts.TM
16568 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16577 Layouts.TM.SliceAndDice = new Class({
16578 compute: function(prop) {
16579 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16580 this.controller.onBeforeCompute(root);
16581 var size = this.canvas.getSize(),
16582 config = this.config,
16583 width = size.width,
16584 height = size.height;
16585 this.graph.computeLevels(this.root, 0, "ignore");
16586 //set root position and dimensions
16587 root.getPos(prop).setc(-width/2, -height/2);
16588 root.setData('width', width, prop);
16589 root.setData('height', height + config.titleHeight, prop);
16590 this.computePositions(root, root, this.layout.orientation, prop);
16591 this.controller.onAfterCompute(root);
16594 computePositions: function(par, ch, orn, prop) {
16595 //compute children areas
16597 par.eachSubnode(function(n) {
16598 totalArea += n.getData('area', prop);
16601 var config = this.config,
16602 offst = config.offset,
16603 width = par.getData('width', prop),
16604 height = par.getData('height', prop) - config.titleHeight,
16605 fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16607 var otherSize, size, dim, pos, pos2, posth, pos2th;
16608 var horizontal = (orn == "h");
16611 otherSize = height;
16612 size = width * fact;
16616 posth = config.titleHeight;
16620 otherSize = height * fact;
16626 pos2th = config.titleHeight;
16628 var cpos = ch.getPos(prop);
16629 ch.setData('width', size, prop);
16630 ch.setData('height', otherSize, prop);
16631 var offsetSize = 0, tm = this;
16632 ch.eachSubnode(function(n) {
16633 var p = n.getPos(prop);
16634 p[pos] = offsetSize + cpos[pos] + posth;
16635 p[pos2] = cpos[pos2] + pos2th;
16636 tm.computePositions(ch, n, orn, prop);
16637 offsetSize += n.getData(dim, prop);
16643 Layouts.TM.Area = {
16647 Called by loadJSON to calculate recursively all node positions and lay out the tree.
16651 json - A JSON tree. See also <Loader.loadJSON>.
16652 coord - A coordinates object specifying width, height, left and top style properties.
16654 compute: function(prop) {
16655 prop = prop || "current";
16656 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16657 this.controller.onBeforeCompute(root);
16658 var config = this.config,
16659 size = this.canvas.getSize(),
16660 width = size.width,
16661 height = size.height,
16662 offst = config.offset,
16663 offwdth = width - offst,
16664 offhght = height - offst;
16665 this.graph.computeLevels(this.root, 0, "ignore");
16666 //set root position and dimensions
16667 root.getPos(prop).setc(-width/2, -height/2);
16668 root.setData('width', width, prop);
16669 root.setData('height', height, prop);
16670 //create a coordinates object
16672 'top': -height/2 + config.titleHeight,
16675 'height': offhght - config.titleHeight
16677 this.computePositions(root, coord, prop);
16678 this.controller.onAfterCompute(root);
16684 Computes dimensions and positions of a group of nodes
16685 according to a custom layout row condition.
16689 tail - An array of nodes.
16690 initElem - An array of nodes (containing the initial node to be laid).
16691 w - A fixed dimension where nodes will be layed out.
16692 coord - A coordinates object specifying width, height, left and top style properties.
16693 comp - A custom comparison function
16695 computeDim: function(tail, initElem, w, coord, comp, prop) {
16696 if(tail.length + initElem.length == 1) {
16697 var l = (tail.length == 1)? tail : initElem;
16698 this.layoutLast(l, w, coord, prop);
16701 if(tail.length >= 2 && initElem.length == 0) {
16702 initElem = [tail.shift()];
16704 if(tail.length == 0) {
16705 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
16709 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
16710 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
16712 var newCoords = this.layoutRow(initElem, w, coord, prop);
16713 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
16719 Method: worstAspectRatio
16721 Calculates the worst aspect ratio of a group of rectangles.
16725 <http://en.wikipedia.org/wiki/Aspect_ratio>
16729 ch - An array of nodes.
16730 w - The fixed dimension where rectangles are being laid out.
16734 The worst aspect ratio.
16738 worstAspectRatio: function(ch, w) {
16739 if(!ch || ch.length == 0) return Number.MAX_VALUE;
16740 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
16741 for(var i=0, l=ch.length; i<l; i++) {
16742 var area = ch[i]._area;
16744 minArea = minArea < area? minArea : area;
16745 maxArea = maxArea > area? maxArea : area;
16747 var sqw = w * w, sqAreaSum = areaSum * areaSum;
16748 return Math.max(sqw * maxArea / sqAreaSum,
16749 sqAreaSum / (sqw * minArea));
16753 Method: avgAspectRatio
16755 Calculates the average aspect ratio of a group of rectangles.
16759 <http://en.wikipedia.org/wiki/Aspect_ratio>
16763 ch - An array of nodes.
16764 w - The fixed dimension where rectangles are being laid out.
16768 The average aspect ratio.
16772 avgAspectRatio: function(ch, w) {
16773 if(!ch || ch.length == 0) return Number.MAX_VALUE;
16775 for(var i=0, l=ch.length; i<l; i++) {
16776 var area = ch[i]._area;
16778 arSum += w > h? w / h : h / w;
16786 Performs the layout of the last computed sibling.
16790 ch - An array of nodes.
16791 w - A fixed dimension where nodes will be layed out.
16792 coord - A coordinates object specifying width, height, left and top style properties.
16794 layoutLast: function(ch, w, coord, prop) {
16796 child.getPos(prop).setc(coord.left, coord.top);
16797 child.setData('width', coord.width, prop);
16798 child.setData('height', coord.height, prop);
16803 Layouts.TM.Squarified = new Class({
16804 Implements: Layouts.TM.Area,
16806 computePositions: function(node, coord, prop) {
16807 var config = this.config;
16809 if (coord.width >= coord.height)
16810 this.layout.orientation = 'h';
16812 this.layout.orientation = 'v';
16814 var ch = node.getSubnodes([1, 1], "ignore");
16815 if(ch.length > 0) {
16816 this.processChildrenLayout(node, ch, coord, prop);
16817 for(var i=0, l=ch.length; i<l; i++) {
16819 var offst = config.offset,
16820 height = chi.getData('height', prop) - offst - config.titleHeight,
16821 width = chi.getData('width', prop) - offst;
16822 var chipos = chi.getPos(prop);
16826 'top': chipos.y + config.titleHeight,
16829 this.computePositions(chi, coord, prop);
16835 Method: processChildrenLayout
16837 Computes children real areas and other useful parameters for performing the Squarified algorithm.
16841 par - The parent node of the json subtree.
16842 ch - An Array of nodes
16843 coord - A coordinates object specifying width, height, left and top style properties.
16845 processChildrenLayout: function(par, ch, coord, prop) {
16846 //compute children real areas
16847 var parentArea = coord.width * coord.height;
16848 var i, l=ch.length, totalChArea=0, chArea = [];
16849 for(i=0; i<l; i++) {
16850 chArea[i] = parseFloat(ch[i].getData('area', prop));
16851 totalChArea += chArea[i];
16853 for(i=0; i<l; i++) {
16854 ch[i]._area = parentArea * chArea[i] / totalChArea;
16856 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
16857 ch.sort(function(a, b) {
16858 var diff = b._area - a._area;
16859 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
16861 var initElem = [ch[0]];
16862 var tail = ch.slice(1);
16863 this.squarify(tail, initElem, minimumSideValue, coord, prop);
16869 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
16873 tail - An array of nodes.
16874 initElem - An array of nodes, containing the initial node to be laid out.
16875 w - A fixed dimension where nodes will be laid out.
16876 coord - A coordinates object specifying width, height, left and top style properties.
16878 squarify: function(tail, initElem, w, coord, prop) {
16879 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
16885 Performs the layout of an array of nodes.
16889 ch - An array of nodes.
16890 w - A fixed dimension where nodes will be laid out.
16891 coord - A coordinates object specifying width, height, left and top style properties.
16893 layoutRow: function(ch, w, coord, prop) {
16894 if(this.layout.horizontal()) {
16895 return this.layoutV(ch, w, coord, prop);
16897 return this.layoutH(ch, w, coord, prop);
16901 layoutV: function(ch, w, coord, prop) {
16902 var totalArea = 0, rnd = function(x) { return x; };
16903 $.each(ch, function(elem) { totalArea += elem._area; });
16904 var width = rnd(totalArea / w), top = 0;
16905 for(var i=0, l=ch.length; i<l; i++) {
16906 var h = rnd(ch[i]._area / width);
16908 chi.getPos(prop).setc(coord.left, coord.top + top);
16909 chi.setData('width', width, prop);
16910 chi.setData('height', h, prop);
16914 'height': coord.height,
16915 'width': coord.width - width,
16917 'left': coord.left + width
16919 //take minimum side value.
16920 ans.dim = Math.min(ans.width, ans.height);
16921 if(ans.dim != ans.height) this.layout.change();
16925 layoutH: function(ch, w, coord, prop) {
16927 $.each(ch, function(elem) { totalArea += elem._area; });
16928 var height = totalArea / w,
16932 for(var i=0, l=ch.length; i<l; i++) {
16934 var w = chi._area / height;
16935 chi.getPos(prop).setc(coord.left + left, top);
16936 chi.setData('width', w, prop);
16937 chi.setData('height', height, prop);
16941 'height': coord.height - height,
16942 'width': coord.width,
16943 'top': coord.top + height,
16946 ans.dim = Math.min(ans.width, ans.height);
16947 if(ans.dim != ans.width) this.layout.change();
16952 Layouts.TM.Strip = new Class({
16953 Implements: Layouts.TM.Area,
16958 Called by loadJSON to calculate recursively all node positions and lay out the tree.
16962 json - A JSON subtree. See also <Loader.loadJSON>.
16963 coord - A coordinates object specifying width, height, left and top style properties.
16965 computePositions: function(node, coord, prop) {
16966 var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
16967 if(ch.length > 0) {
16968 this.processChildrenLayout(node, ch, coord, prop);
16969 for(var i=0, l=ch.length; i<l; i++) {
16971 var offst = config.offset,
16972 height = chi.getData('height', prop) - offst - config.titleHeight,
16973 width = chi.getData('width', prop) - offst;
16974 var chipos = chi.getPos(prop);
16978 'top': chipos.y + config.titleHeight,
16981 this.computePositions(chi, coord, prop);
16987 Method: processChildrenLayout
16989 Computes children real areas and other useful parameters for performing the Strip algorithm.
16993 par - The parent node of the json subtree.
16994 ch - An Array of nodes
16995 coord - A coordinates object specifying width, height, left and top style properties.
16997 processChildrenLayout: function(par, ch, coord, prop) {
16998 //compute children real areas
16999 var parentArea = coord.width * coord.height;
17000 var i, l=ch.length, totalChArea=0, chArea = [];
17001 for(i=0; i<l; i++) {
17002 chArea[i] = +ch[i].getData('area', prop);
17003 totalChArea += chArea[i];
17005 for(i=0; i<l; i++) {
17006 ch[i]._area = parentArea * chArea[i] / totalChArea;
17008 var side = this.layout.horizontal()? coord.width : coord.height;
17009 var initElem = [ch[0]];
17010 var tail = ch.slice(1);
17011 this.stripify(tail, initElem, side, coord, prop);
17017 Performs an heuristic method to calculate div elements sizes in order to have
17018 a good compromise between aspect ratio and order.
17022 tail - An array of nodes.
17023 initElem - An array of nodes.
17024 w - A fixed dimension where nodes will be layed out.
17025 coord - A coordinates object specifying width, height, left and top style properties.
17027 stripify: function(tail, initElem, w, coord, prop) {
17028 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17034 Performs the layout of an array of nodes.
17038 ch - An array of nodes.
17039 w - A fixed dimension where nodes will be laid out.
17040 coord - A coordinates object specifying width, height, left and top style properties.
17042 layoutRow: function(ch, w, coord, prop) {
17043 if(this.layout.horizontal()) {
17044 return this.layoutH(ch, w, coord, prop);
17046 return this.layoutV(ch, w, coord, prop);
17050 layoutV: function(ch, w, coord, prop) {
17052 $.each(ch, function(elem) { totalArea += elem._area; });
17053 var width = totalArea / w, top = 0;
17054 for(var i=0, l=ch.length; i<l; i++) {
17056 var h = chi._area / width;
17057 chi.getPos(prop).setc(coord.left,
17058 coord.top + (w - h - top));
17059 chi.setData('width', width, prop);
17060 chi.setData('height', h, prop);
17065 'height': coord.height,
17066 'width': coord.width - width,
17068 'left': coord.left + width,
17073 layoutH: function(ch, w, coord, prop) {
17075 $.each(ch, function(elem) { totalArea += elem._area; });
17076 var height = totalArea / w,
17077 top = coord.height - height,
17080 for(var i=0, l=ch.length; i<l; i++) {
17082 var s = chi._area / height;
17083 chi.getPos(prop).setc(coord.left + left, coord.top + top);
17084 chi.setData('width', s, prop);
17085 chi.setData('height', height, prop);
17089 'height': coord.height - height,
17090 'width': coord.width,
17092 'left': coord.left,
17099 * Class: Layouts.Icicle
17101 * Implements the icicle tree layout.
17109 Layouts.Icicle = new Class({
17113 * Called by loadJSON to calculate all node positions.
17117 * posType - The nodes' position to compute. Either "start", "end" or
17118 * "current". Defaults to "current".
17120 compute: function(posType) {
17121 posType = posType || "current";
17122 var root = this.graph.getNode(this.root),
17123 config = this.config,
17124 size = this.canvas.getSize(),
17125 width = size.width,
17126 height = size.height,
17127 offset = config.offset,
17128 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17130 this.controller.onBeforeCompute(root);
17132 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17136 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17138 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17139 var maxDepth = Math.min(treeDepth, levelsToShow-1);
17140 var initialDepth = startNode._depth;
17141 if(this.layout.horizontal()) {
17142 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17144 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17148 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17149 root.getPos(posType).setc(x, y);
17150 root.setData('width', width, posType);
17151 root.setData('height', height, posType);
17153 var nodeLength, prevNodeLength = 0, totalDim = 0;
17154 var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17156 if(!children.length)
17159 $.each(children, function(e) { totalDim += e.getData('dim'); });
17161 for(var i=0, l=children.length; i < l; i++) {
17162 if(this.layout.horizontal()) {
17163 nodeLength = height * children[i].getData('dim') / totalDim;
17164 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17167 nodeLength = width * children[i].getData('dim') / totalDim;
17168 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17185 Icicle space filling visualization.
17189 All <Loader> methods
17191 Constructor Options:
17193 Inherits options from
17196 - <Options.Controller>
17202 - <Options.NodeStyles>
17203 - <Options.Navigation>
17205 Additionally, there are other parameters and some default values changed
17207 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17208 offset - (number) Default's *2*. Boxes offset.
17209 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17210 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17211 animate - (boolean) Default's *false*. Whether to animate transitions.
17212 Node.type - Described in <Options.Node>. Default's *rectangle*.
17213 Label.type - Described in <Options.Label>. Default's *Native*.
17214 duration - Described in <Options.Fx>. Default's *700*.
17215 fps - Described in <Options.Fx>. Default's *45*.
17217 Instance Properties:
17219 canvas - Access a <Canvas> instance.
17220 graph - Access a <Graph> instance.
17221 op - Access a <Icicle.Op> instance.
17222 fx - Access a <Icicle.Plot> instance.
17223 labels - Access a <Icicle.Label> interface implementation.
17227 $jit.Icicle = new Class({
17228 Implements: [ Loader, Extras, Layouts.Icicle ],
17232 vertical: function(){
17233 return this.orientation == "v";
17235 horizontal: function(){
17236 return this.orientation == "h";
17238 change: function(){
17239 this.orientation = this.vertical()? "h" : "v";
17243 initialize: function(controller) {
17248 levelsToShow: Number.MAX_VALUE,
17249 constrained: false,
17264 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17265 "Events", "Navigation", "Controller", "Label");
17266 this.controller = this.config = $.merge(opts, config, controller);
17267 this.layout.orientation = this.config.orientation;
17269 var canvasConfig = this.config;
17270 if (canvasConfig.useCanvas) {
17271 this.canvas = canvasConfig.useCanvas;
17272 this.config.labelContainer = this.canvas.id + '-label';
17274 this.canvas = new Canvas(this, canvasConfig);
17275 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17278 this.graphOptions = {
17287 this.graph = new Graph(
17288 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17290 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17291 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17292 this.op = new $jit.Icicle.Op(this);
17293 this.group = new $jit.Icicle.Group(this);
17294 this.clickedNode = null;
17296 this.initializeExtras();
17302 Computes positions and plots the tree.
17304 refresh: function(){
17305 var labelType = this.config.Label.type;
17306 if(labelType != 'Native') {
17308 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17317 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
17321 this.fx.plot(this.config);
17327 Sets the node as root.
17331 node - (object) A <Graph.Node>.
17334 enter: function (node) {
17340 config = this.config;
17343 onComplete: function() {
17344 //compute positions of newly inserted nodes
17348 if(config.animate) {
17349 that.graph.nodeList.setDataset(['current', 'end'], {
17350 'alpha': [1, 0] //fade nodes
17353 Graph.Util.eachSubgraph(node, function(n) {
17354 n.setData('alpha', 1, 'end');
17359 modes:['node-property:alpha'],
17360 onComplete: function() {
17361 that.clickedNode = node;
17362 that.compute('end');
17365 modes:['linear', 'node-property:width:height'],
17367 onComplete: function() {
17369 that.clickedNode = node;
17375 that.clickedNode = node;
17382 if(config.request) {
17383 this.requestNodes(clickedNode, callback);
17385 callback.onComplete();
17392 Sets the parent node of the current selected node as root.
17400 GUtil = Graph.Util,
17401 config = this.config,
17402 graph = this.graph,
17403 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17404 parent = parents[0],
17405 clickedNode = parent,
17406 previousClickedNode = this.clickedNode;
17409 this.events.hoveredNode = false;
17416 //final plot callback
17418 onComplete: function() {
17419 that.clickedNode = parent;
17420 if(config.request) {
17421 that.requestNodes(parent, {
17422 onComplete: function() {
17436 //animate node positions
17437 if(config.animate) {
17438 this.clickedNode = clickedNode;
17439 this.compute('end');
17440 //animate the visible subtree only
17441 this.clickedNode = previousClickedNode;
17443 modes:['linear', 'node-property:width:height'],
17445 onComplete: function() {
17446 //animate the parent subtree
17447 that.clickedNode = clickedNode;
17448 //change nodes alpha
17449 graph.nodeList.setDataset(['current', 'end'], {
17452 GUtil.eachSubgraph(previousClickedNode, function(node) {
17453 node.setData('alpha', 1);
17457 modes:['node-property:alpha'],
17458 onComplete: function() {
17459 callback.onComplete();
17465 callback.onComplete();
17468 requestNodes: function(node, onComplete){
17469 var handler = $.merge(this.controller, onComplete),
17470 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17472 if (handler.request) {
17473 var leaves = [], d = node._depth;
17474 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17475 if (n.drawn && !Graph.Util.anySubnode(n)) {
17477 n._level = n._depth - d;
17478 if (this.config.constrained)
17479 n._level = levelsToShow - n._level;
17483 this.group.requestNodes(leaves, handler);
17485 handler.onComplete();
17493 Custom extension of <Graph.Op>.
17497 All <Graph.Op> methods
17504 $jit.Icicle.Op = new Class({
17506 Implements: Graph.Op
17511 * Performs operations on group of nodes.
17513 $jit.Icicle.Group = new Class({
17515 initialize: function(viz){
17517 this.canvas = viz.canvas;
17518 this.config = viz.config;
17522 * Calls the request method on the controller to request a subtree for each node.
17524 requestNodes: function(nodes, controller){
17525 var counter = 0, len = nodes.length, nodeSelected = {};
17526 var complete = function(){
17527 controller.onComplete();
17529 var viz = this.viz;
17532 for(var i = 0; i < len; i++) {
17533 nodeSelected[nodes[i].id] = nodes[i];
17534 controller.request(nodes[i].id, nodes[i]._level, {
17535 onComplete: function(nodeId, data){
17536 if (data && data.children) {
17542 if (++counter == len) {
17543 Graph.Util.computeLevels(viz.graph, viz.root, 0);
17555 Custom extension of <Graph.Plot>.
17559 All <Graph.Plot> methods
17566 $jit.Icicle.Plot = new Class({
17567 Implements: Graph.Plot,
17569 plot: function(opt, animating){
17570 opt = opt || this.viz.controller;
17571 var viz = this.viz,
17573 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17574 initialDepth = root._depth;
17576 viz.canvas.clear();
17577 this.plotTree(root, $.merge(opt, {
17578 'withLabels': true,
17579 'hideLabels': false,
17580 'plotSubtree': function(root, node) {
17581 return !viz.config.constrained ||
17582 (node._depth - initialDepth < viz.config.levelsToShow);
17589 Class: Icicle.Label
17591 Custom extension of <Graph.Label>.
17592 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17596 All <Graph.Label> methods and subclasses.
17600 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17603 $jit.Icicle.Label = {};
17606 Icicle.Label.Native
17608 Custom extension of <Graph.Label.Native>.
17612 All <Graph.Label.Native> methods
17616 <Graph.Label.Native>
17619 $jit.Icicle.Label.Native = new Class({
17620 Implements: Graph.Label.Native,
17622 renderLabel: function(canvas, node, controller) {
17623 var ctx = canvas.getCtx(),
17624 width = node.getData('width'),
17625 height = node.getData('height'),
17626 size = node.getLabelData('size'),
17627 m = ctx.measureText(node.name);
17629 // Guess as much as possible if the label will fit in the node
17630 if(height < (size * 1.5) || width < m.width)
17633 var pos = node.pos.getc(true);
17634 ctx.fillText(node.name,
17636 pos.y + height / 2);
17643 Custom extension of <Graph.Label.SVG>.
17647 All <Graph.Label.SVG> methods
17653 $jit.Icicle.Label.SVG = new Class( {
17654 Implements: Graph.Label.SVG,
17656 initialize: function(viz){
17663 Overrides abstract method placeLabel in <Graph.Plot>.
17667 tag - A DOM label element.
17668 node - A <Graph.Node>.
17669 controller - A configuration/controller object passed to the visualization.
17671 placeLabel: function(tag, node, controller){
17672 var pos = node.pos.getc(true), canvas = this.viz.canvas;
17673 var radius = canvas.getSize();
17675 x: Math.round(pos.x + radius.width / 2),
17676 y: Math.round(pos.y + radius.height / 2)
17678 tag.setAttribute('x', labelPos.x);
17679 tag.setAttribute('y', labelPos.y);
17681 controller.onPlaceLabel(tag, node);
17688 Custom extension of <Graph.Label.HTML>.
17692 All <Graph.Label.HTML> methods.
17699 $jit.Icicle.Label.HTML = new Class( {
17700 Implements: Graph.Label.HTML,
17702 initialize: function(viz){
17709 Overrides abstract method placeLabel in <Graph.Plot>.
17713 tag - A DOM label element.
17714 node - A <Graph.Node>.
17715 controller - A configuration/controller object passed to the visualization.
17717 placeLabel: function(tag, node, controller){
17718 var pos = node.pos.getc(true), canvas = this.viz.canvas;
17719 var radius = canvas.getSize();
17721 x: Math.round(pos.x + radius.width / 2),
17722 y: Math.round(pos.y + radius.height / 2)
17725 var style = tag.style;
17726 style.left = labelPos.x + 'px';
17727 style.top = labelPos.y + 'px';
17728 style.display = '';
17730 controller.onPlaceLabel(tag, node);
17735 Class: Icicle.Plot.NodeTypes
17737 This class contains a list of <Graph.Node> built-in types.
17738 Node types implemented are 'none', 'rectangle'.
17740 You can add your custom node types, customizing your visualization to the extreme.
17745 Icicle.Plot.NodeTypes.implement({
17747 'render': function(node, canvas) {
17748 //print your custom node to canvas
17751 'contains': function(node, pos) {
17752 //return true if pos is inside the node or false otherwise
17759 $jit.Icicle.Plot.NodeTypes = new Class( {
17765 'render': function(node, canvas, animating) {
17766 var config = this.viz.config;
17767 var offset = config.offset;
17768 var width = node.getData('width');
17769 var height = node.getData('height');
17770 var border = node.getData('border');
17771 var pos = node.pos.getc(true);
17772 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
17773 var ctx = canvas.getCtx();
17775 if(width - offset < 2 || height - offset < 2) return;
17777 if(config.cushion) {
17778 var color = node.getData('color');
17779 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
17780 posy + (height - offset)/2, 1,
17781 posx + (width-offset)/2, posy + (height-offset)/2,
17782 width < height? height : width);
17783 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
17784 function(r) { return r * 0.3 >> 0; }));
17785 lg.addColorStop(0, color);
17786 lg.addColorStop(1, colorGrad);
17787 ctx.fillStyle = lg;
17791 ctx.strokeStyle = border;
17795 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
17796 border && ctx.strokeRect(pos.x, pos.y, width, height);
17799 'contains': function(node, pos) {
17800 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
17801 var npos = node.pos.getc(true),
17802 width = node.getData('width'),
17803 height = node.getData('height');
17804 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
17809 $jit.Icicle.Plot.EdgeTypes = new Class( {
17816 * File: Layouts.ForceDirected.js
17821 * Class: Layouts.ForceDirected
17823 * Implements a Force Directed Layout.
17831 * Marcus Cobden <http://marcuscobden.co.uk>
17834 Layouts.ForceDirected = new Class({
17836 getOptions: function(random) {
17837 var s = this.canvas.getSize();
17838 var w = s.width, h = s.height;
17841 this.graph.eachNode(function(n) {
17844 var k2 = w * h / count, k = Math.sqrt(k2);
17845 var l = this.config.levelDistance;
17851 nodef: function(x) { return k2 / (x || 1); },
17852 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
17856 compute: function(property, incremental) {
17857 var prop = $.splat(property || ['current', 'start', 'end']);
17858 var opt = this.getOptions();
17859 NodeDim.compute(this.graph, prop, this.config);
17860 this.graph.computeLevels(this.root, 0, "ignore");
17861 this.graph.eachNode(function(n) {
17862 $.each(prop, function(p) {
17863 var pos = n.getPos(p);
17864 if(pos.equals(Complex.KER)) {
17865 pos.x = opt.width/5 * (Math.random() - 0.5);
17866 pos.y = opt.height/5 * (Math.random() - 0.5);
17868 //initialize disp vector
17870 $.each(prop, function(p) {
17871 n.disp[p] = $C(0, 0);
17875 this.computePositions(prop, opt, incremental);
17878 computePositions: function(property, opt, incremental) {
17879 var times = this.config.iterations, i = 0, that = this;
17882 for(var total=incremental.iter, j=0; j<total; j++) {
17883 opt.t = opt.tstart * (1 - i++/(times -1));
17884 that.computePositionStep(property, opt);
17886 incremental.onComplete();
17890 incremental.onStep(Math.round(i / (times -1) * 100));
17891 setTimeout(iter, 1);
17894 for(; i < times; i++) {
17895 opt.t = opt.tstart * (1 - i/(times -1));
17896 this.computePositionStep(property, opt);
17901 computePositionStep: function(property, opt) {
17902 var graph = this.graph;
17903 var min = Math.min, max = Math.max;
17904 var dpos = $C(0, 0);
17905 //calculate repulsive forces
17906 graph.eachNode(function(v) {
17908 $.each(property, function(p) {
17909 v.disp[p].x = 0; v.disp[p].y = 0;
17911 graph.eachNode(function(u) {
17913 $.each(property, function(p) {
17914 var vp = v.getPos(p), up = u.getPos(p);
17915 dpos.x = vp.x - up.x;
17916 dpos.y = vp.y - up.y;
17917 var norm = dpos.norm() || 1;
17918 v.disp[p].$add(dpos
17919 .$scale(opt.nodef(norm) / norm));
17924 //calculate attractive forces
17925 var T = !!graph.getNode(this.root).visited;
17926 graph.eachNode(function(node) {
17927 node.eachAdjacency(function(adj) {
17928 var nodeTo = adj.nodeTo;
17929 if(!!nodeTo.visited === T) {
17930 $.each(property, function(p) {
17931 var vp = node.getPos(p), up = nodeTo.getPos(p);
17932 dpos.x = vp.x - up.x;
17933 dpos.y = vp.y - up.y;
17934 var norm = dpos.norm() || 1;
17935 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
17936 nodeTo.disp[p].$add(dpos.$scale(-1));
17942 //arrange positions to fit the canvas
17943 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
17944 graph.eachNode(function(u) {
17945 $.each(property, function(p) {
17946 var disp = u.disp[p];
17947 var norm = disp.norm() || 1;
17948 var p = u.getPos(p);
17949 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
17950 disp.y * min(Math.abs(disp.y), t) / norm));
17951 p.x = min(w2, max(-w2, p.x));
17952 p.y = min(h2, max(-h2, p.y));
17959 * File: ForceDirected.js
17963 Class: ForceDirected
17965 A visualization that lays graphs using a Force-Directed layout algorithm.
17969 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
17973 All <Loader> methods
17975 Constructor Options:
17977 Inherits options from
17980 - <Options.Controller>
17986 - <Options.NodeStyles>
17987 - <Options.Navigation>
17989 Additionally, there are two parameters
17991 levelDistance - (number) Default's *50*. The natural length desired for the edges.
17992 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*.
17994 Instance Properties:
17996 canvas - Access a <Canvas> instance.
17997 graph - Access a <Graph> instance.
17998 op - Access a <ForceDirected.Op> instance.
17999 fx - Access a <ForceDirected.Plot> instance.
18000 labels - Access a <ForceDirected.Label> interface implementation.
18004 $jit.ForceDirected = new Class( {
18006 Implements: [ Loader, Extras, Layouts.ForceDirected ],
18008 initialize: function(controller) {
18009 var $ForceDirected = $jit.ForceDirected;
18016 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18017 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18019 var canvasConfig = this.config;
18020 if(canvasConfig.useCanvas) {
18021 this.canvas = canvasConfig.useCanvas;
18022 this.config.labelContainer = this.canvas.id + '-label';
18024 if(canvasConfig.background) {
18025 canvasConfig.background = $.merge({
18027 }, canvasConfig.background);
18029 this.canvas = new Canvas(this, canvasConfig);
18030 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18033 this.graphOptions = {
18041 this.graph = new Graph(this.graphOptions, this.config.Node,
18043 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18044 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18045 this.op = new $ForceDirected.Op(this);
18048 // initialize extras
18049 this.initializeExtras();
18055 Computes positions and plots the tree.
18057 refresh: function() {
18062 reposition: function() {
18063 this.compute('end');
18067 Method: computeIncremental
18069 Performs the Force Directed algorithm incrementally.
18073 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
18074 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
18075 avoiding browser messages such as "This script is taking too long to complete".
18079 opt - (object) The object properties are described below
18081 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
18082 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18084 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
18085 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
18086 computations for final animation positions then you can just choose 'end'.
18088 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
18089 parameter a percentage value.
18091 onComplete - A callback function called when the algorithm completed.
18095 In this example I calculate the end positions and then animate the graph to those positions
18098 var fd = new $jit.ForceDirected(...);
18099 fd.computeIncremental({
18102 onStep: function(perc) {
18103 Log.write("loading " + perc + "%");
18105 onComplete: function() {
18112 In this example I calculate all positions and (re)plot the graph
18115 var fd = new ForceDirected(...);
18116 fd.computeIncremental({
18118 property: ['end', 'start', 'current'],
18119 onStep: function(perc) {
18120 Log.write("loading " + perc + "%");
18122 onComplete: function() {
18130 computeIncremental: function(opt) {
18135 onComplete: $.empty
18138 this.config.onBeforeCompute(this.graph.getNode(this.root));
18139 this.compute(opt.property, opt);
18145 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18154 Animates the graph from the current positions to the 'end' node positions.
18156 animate: function(opt) {
18157 this.fx.animate($.merge( {
18158 modes: [ 'linear' ]
18163 $jit.ForceDirected.$extend = true;
18165 (function(ForceDirected) {
18168 Class: ForceDirected.Op
18170 Custom extension of <Graph.Op>.
18174 All <Graph.Op> methods
18181 ForceDirected.Op = new Class( {
18183 Implements: Graph.Op
18188 Class: ForceDirected.Plot
18190 Custom extension of <Graph.Plot>.
18194 All <Graph.Plot> methods
18201 ForceDirected.Plot = new Class( {
18203 Implements: Graph.Plot
18208 Class: ForceDirected.Label
18210 Custom extension of <Graph.Label>.
18211 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18215 All <Graph.Label> methods and subclasses.
18219 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18222 ForceDirected.Label = {};
18225 ForceDirected.Label.Native
18227 Custom extension of <Graph.Label.Native>.
18231 All <Graph.Label.Native> methods
18235 <Graph.Label.Native>
18238 ForceDirected.Label.Native = new Class( {
18239 Implements: Graph.Label.Native
18243 ForceDirected.Label.SVG
18245 Custom extension of <Graph.Label.SVG>.
18249 All <Graph.Label.SVG> methods
18256 ForceDirected.Label.SVG = new Class( {
18257 Implements: Graph.Label.SVG,
18259 initialize: function(viz) {
18266 Overrides abstract method placeLabel in <Graph.Label>.
18270 tag - A DOM label element.
18271 node - A <Graph.Node>.
18272 controller - A configuration/controller object passed to the visualization.
18275 placeLabel: function(tag, node, controller) {
18276 var pos = node.pos.getc(true),
18277 canvas = this.viz.canvas,
18278 ox = canvas.translateOffsetX,
18279 oy = canvas.translateOffsetY,
18280 sx = canvas.scaleOffsetX,
18281 sy = canvas.scaleOffsetY,
18282 radius = canvas.getSize();
18284 x: Math.round(pos.x * sx + ox + radius.width / 2),
18285 y: Math.round(pos.y * sy + oy + radius.height / 2)
18287 tag.setAttribute('x', labelPos.x);
18288 tag.setAttribute('y', labelPos.y);
18290 controller.onPlaceLabel(tag, node);
18295 ForceDirected.Label.HTML
18297 Custom extension of <Graph.Label.HTML>.
18301 All <Graph.Label.HTML> methods.
18308 ForceDirected.Label.HTML = new Class( {
18309 Implements: Graph.Label.HTML,
18311 initialize: function(viz) {
18317 Overrides abstract method placeLabel in <Graph.Plot>.
18321 tag - A DOM label element.
18322 node - A <Graph.Node>.
18323 controller - A configuration/controller object passed to the visualization.
18326 placeLabel: function(tag, node, controller) {
18327 var pos = node.pos.getc(true),
18328 canvas = this.viz.canvas,
18329 ox = canvas.translateOffsetX,
18330 oy = canvas.translateOffsetY,
18331 sx = canvas.scaleOffsetX,
18332 sy = canvas.scaleOffsetY,
18333 radius = canvas.getSize();
18335 x: Math.round(pos.x * sx + ox + radius.width / 2),
18336 y: Math.round(pos.y * sy + oy + radius.height / 2)
18338 var style = tag.style;
18339 style.left = labelPos.x + 'px';
18340 style.top = labelPos.y + 'px';
18341 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18343 controller.onPlaceLabel(tag, node);
18348 Class: ForceDirected.Plot.NodeTypes
18350 This class contains a list of <Graph.Node> built-in types.
18351 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18353 You can add your custom node types, customizing your visualization to the extreme.
18358 ForceDirected.Plot.NodeTypes.implement({
18360 'render': function(node, canvas) {
18361 //print your custom node to canvas
18364 'contains': function(node, pos) {
18365 //return true if pos is inside the node or false otherwise
18372 ForceDirected.Plot.NodeTypes = new Class({
18375 'contains': $.lambda(false)
18378 'render': function(node, canvas){
18379 var pos = node.pos.getc(true),
18380 dim = node.getData('dim');
18381 this.nodeHelper.circle.render('fill', pos, dim, canvas);
18383 'contains': function(node, pos){
18384 var npos = node.pos.getc(true),
18385 dim = node.getData('dim');
18386 return this.nodeHelper.circle.contains(npos, pos, dim);
18390 'render': function(node, canvas){
18391 var pos = node.pos.getc(true),
18392 width = node.getData('width'),
18393 height = node.getData('height');
18394 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18396 // TODO(nico): be more precise...
18397 'contains': function(node, pos){
18398 var npos = node.pos.getc(true),
18399 width = node.getData('width'),
18400 height = node.getData('height');
18401 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18405 'render': function(node, canvas){
18406 var pos = node.pos.getc(true),
18407 dim = node.getData('dim');
18408 this.nodeHelper.square.render('fill', pos, dim, canvas);
18410 'contains': function(node, pos){
18411 var npos = node.pos.getc(true),
18412 dim = node.getData('dim');
18413 return this.nodeHelper.square.contains(npos, pos, dim);
18417 'render': function(node, canvas){
18418 var pos = node.pos.getc(true),
18419 width = node.getData('width'),
18420 height = node.getData('height');
18421 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18423 'contains': function(node, pos){
18424 var npos = node.pos.getc(true),
18425 width = node.getData('width'),
18426 height = node.getData('height');
18427 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18431 'render': function(node, canvas){
18432 var pos = node.pos.getc(true),
18433 dim = node.getData('dim');
18434 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18436 'contains': function(node, pos) {
18437 var npos = node.pos.getc(true),
18438 dim = node.getData('dim');
18439 return this.nodeHelper.triangle.contains(npos, pos, dim);
18443 'render': function(node, canvas){
18444 var pos = node.pos.getc(true),
18445 dim = node.getData('dim');
18446 this.nodeHelper.star.render('fill', pos, dim, canvas);
18448 'contains': function(node, pos) {
18449 var npos = node.pos.getc(true),
18450 dim = node.getData('dim');
18451 return this.nodeHelper.star.contains(npos, pos, dim);
18457 Class: ForceDirected.Plot.EdgeTypes
18459 This class contains a list of <Graph.Adjacence> built-in types.
18460 Edge types implemented are 'none', 'line' and 'arrow'.
18462 You can add your custom edge types, customizing your visualization to the extreme.
18467 ForceDirected.Plot.EdgeTypes.implement({
18469 'render': function(adj, canvas) {
18470 //print your custom edge to canvas
18473 'contains': function(adj, pos) {
18474 //return true if pos is inside the arc or false otherwise
18481 ForceDirected.Plot.EdgeTypes = new Class({
18484 'render': function(adj, canvas) {
18485 var from = adj.nodeFrom.pos.getc(true),
18486 to = adj.nodeTo.pos.getc(true);
18487 this.edgeHelper.line.render(from, to, canvas);
18489 'contains': function(adj, pos) {
18490 var from = adj.nodeFrom.pos.getc(true),
18491 to = adj.nodeTo.pos.getc(true);
18492 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18496 'render': function(adj, canvas) {
18497 var from = adj.nodeFrom.pos.getc(true),
18498 to = adj.nodeTo.pos.getc(true),
18499 dim = adj.getData('dim'),
18500 direction = adj.data.$direction,
18501 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18502 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18504 'contains': function(adj, pos) {
18505 var from = adj.nodeFrom.pos.getc(true),
18506 to = adj.nodeTo.pos.getc(true);
18507 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18512 })($jit.ForceDirected);
18524 $jit.TM.$extend = true;
18529 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18533 All <Loader> methods
18535 Constructor Options:
18537 Inherits options from
18540 - <Options.Controller>
18546 - <Options.NodeStyles>
18547 - <Options.Navigation>
18549 Additionally, there are other parameters and some default values changed
18551 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18552 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18553 offset - (number) Default's *2*. Boxes offset.
18554 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18555 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18556 animate - (boolean) Default's *false*. Whether to animate transitions.
18557 Node.type - Described in <Options.Node>. Default's *rectangle*.
18558 duration - Described in <Options.Fx>. Default's *700*.
18559 fps - Described in <Options.Fx>. Default's *45*.
18561 Instance Properties:
18563 canvas - Access a <Canvas> instance.
18564 graph - Access a <Graph> instance.
18565 op - Access a <TM.Op> instance.
18566 fx - Access a <TM.Plot> instance.
18567 labels - Access a <TM.Label> interface implementation.
18571 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18573 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18577 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.
18583 vertical: function(){
18584 return this.orientation == "v";
18586 horizontal: function(){
18587 return this.orientation == "h";
18589 change: function(){
18590 this.orientation = this.vertical()? "h" : "v";
18594 initialize: function(controller){
18600 constrained: false,
18605 //we all know why this is not zero,
18612 textAlign: 'center',
18613 textBaseline: 'top'
18622 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18623 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18624 this.layout.orientation = this.config.orientation;
18626 var canvasConfig = this.config;
18627 if (canvasConfig.useCanvas) {
18628 this.canvas = canvasConfig.useCanvas;
18629 this.config.labelContainer = this.canvas.id + '-label';
18631 if(canvasConfig.background) {
18632 canvasConfig.background = $.merge({
18634 }, canvasConfig.background);
18636 this.canvas = new Canvas(this, canvasConfig);
18637 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18640 this.graphOptions = {
18648 this.graph = new Graph(this.graphOptions, this.config.Node,
18650 this.labels = new TM.Label[canvasConfig.Label.type](this);
18651 this.fx = new TM.Plot(this);
18652 this.op = new TM.Op(this);
18653 this.group = new TM.Group(this);
18654 this.geom = new TM.Geom(this);
18655 this.clickedNode = null;
18657 // initialize extras
18658 this.initializeExtras();
18664 Computes positions and plots the tree.
18666 refresh: function(){
18667 if(this.busy) return;
18670 if(this.config.animate) {
18671 this.compute('end');
18672 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18673 && this.clickedNode.id || this.root));
18674 this.fx.animate($.merge(this.config, {
18675 modes: ['linear', 'node-property:width:height'],
18676 onComplete: function() {
18681 var labelType = this.config.Label.type;
18682 if(labelType != 'Native') {
18684 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18688 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
18689 && this.clickedNode.id || this.root));
18697 Plots the TreeMap. This is a shortcut to *fx.plot*.
18707 Returns whether the node is a leaf.
18711 n - (object) A <Graph.Node>.
18715 return n.getSubnodes([
18717 ], "ignore").length == 0;
18723 Sets the node as root.
18727 n - (object) A <Graph.Node>.
18730 enter: function(n){
18731 if(this.busy) return;
18735 config = this.config,
18736 graph = this.graph,
18738 previousClickedNode = this.clickedNode;
18741 onComplete: function() {
18742 //ensure that nodes are shown for that level
18743 if(config.levelsToShow > 0) {
18744 that.geom.setRightLevelToShow(n);
18746 //compute positions of newly inserted nodes
18747 if(config.levelsToShow > 0 || config.request) that.compute();
18748 if(config.animate) {
18750 graph.nodeList.setData('alpha', 0, 'end');
18751 n.eachSubgraph(function(n) {
18752 n.setData('alpha', 1, 'end');
18756 modes:['node-property:alpha'],
18757 onComplete: function() {
18758 //compute end positions
18759 that.clickedNode = clickedNode;
18760 that.compute('end');
18761 //animate positions
18762 //TODO(nico) commenting this line didn't seem to throw errors...
18763 that.clickedNode = previousClickedNode;
18765 modes:['linear', 'node-property:width:height'],
18767 onComplete: function() {
18769 //TODO(nico) check comment above
18770 that.clickedNode = clickedNode;
18777 that.clickedNode = n;
18782 if(config.request) {
18783 this.requestNodes(clickedNode, callback);
18785 callback.onComplete();
18792 Sets the parent node of the current selected node as root.
18796 if(this.busy) return;
18798 this.events.hoveredNode = false;
18800 config = this.config,
18801 graph = this.graph,
18802 parents = graph.getNode(this.clickedNode
18803 && this.clickedNode.id || this.root).getParents(),
18804 parent = parents[0],
18805 clickedNode = parent,
18806 previousClickedNode = this.clickedNode;
18808 //if no parents return
18813 //final plot callback
18815 onComplete: function() {
18816 that.clickedNode = parent;
18817 if(config.request) {
18818 that.requestNodes(parent, {
18819 onComplete: function() {
18833 if (config.levelsToShow > 0)
18834 this.geom.setRightLevelToShow(parent);
18835 //animate node positions
18836 if(config.animate) {
18837 this.clickedNode = clickedNode;
18838 this.compute('end');
18839 //animate the visible subtree only
18840 this.clickedNode = previousClickedNode;
18842 modes:['linear', 'node-property:width:height'],
18844 onComplete: function() {
18845 //animate the parent subtree
18846 that.clickedNode = clickedNode;
18847 //change nodes alpha
18848 graph.eachNode(function(n) {
18849 n.setDataset(['current', 'end'], {
18853 previousClickedNode.eachSubgraph(function(node) {
18854 node.setData('alpha', 1);
18858 modes:['node-property:alpha'],
18859 onComplete: function() {
18860 callback.onComplete();
18866 callback.onComplete();
18870 requestNodes: function(node, onComplete){
18871 var handler = $.merge(this.controller, onComplete),
18872 lev = this.config.levelsToShow;
18873 if (handler.request) {
18874 var leaves = [], d = node._depth;
18875 node.eachLevel(0, lev, function(n){
18876 var nodeLevel = lev - (n._depth - d);
18877 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
18879 n._level = nodeLevel;
18882 this.group.requestNodes(leaves, handler);
18884 handler.onComplete();
18892 Custom extension of <Graph.Op>.
18896 All <Graph.Op> methods
18903 TM.Op = new Class({
18904 Implements: Graph.Op,
18906 initialize: function(viz){
18911 //extend level methods of Graph.Geom
18912 TM.Geom = new Class({
18913 Implements: Graph.Geom,
18915 getRightLevelToShow: function() {
18916 return this.viz.config.levelsToShow;
18919 setRightLevelToShow: function(node) {
18920 var level = this.getRightLevelToShow(),
18921 fx = this.viz.labels;
18922 node.eachLevel(0, level+1, function(n) {
18923 var d = n._depth - node._depth;
18928 fx.hideLabel(n, false);
18936 delete node.ignore;
18942 Performs operations on group of nodes.
18945 TM.Group = new Class( {
18947 initialize: function(viz){
18949 this.canvas = viz.canvas;
18950 this.config = viz.config;
18955 Calls the request method on the controller to request a subtree for each node.
18957 requestNodes: function(nodes, controller){
18958 var counter = 0, len = nodes.length, nodeSelected = {};
18959 var complete = function(){
18960 controller.onComplete();
18962 var viz = this.viz;
18965 for ( var i = 0; i < len; i++) {
18966 nodeSelected[nodes[i].id] = nodes[i];
18967 controller.request(nodes[i].id, nodes[i]._level, {
18968 onComplete: function(nodeId, data){
18969 if (data && data.children) {
18975 if (++counter == len) {
18976 viz.graph.computeLevels(viz.root, 0);
18988 Custom extension of <Graph.Plot>.
18992 All <Graph.Plot> methods
18999 TM.Plot = new Class({
19001 Implements: Graph.Plot,
19003 initialize: function(viz){
19005 this.config = viz.config;
19006 this.node = this.config.Node;
19007 this.edge = this.config.Edge;
19008 this.animation = new Animation;
19009 this.nodeTypes = new TM.Plot.NodeTypes;
19010 this.edgeTypes = new TM.Plot.EdgeTypes;
19011 this.labels = viz.labels;
19014 plot: function(opt, animating){
19015 var viz = this.viz,
19017 viz.canvas.clear();
19018 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19019 'withLabels': true,
19020 'hideLabels': false,
19021 'plotSubtree': function(n, ch){
19022 return n.anySubnode("exist");
19031 Custom extension of <Graph.Label>.
19032 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19036 All <Graph.Label> methods and subclasses.
19040 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19048 Custom extension of <Graph.Label.Native>.
19052 All <Graph.Label.Native> methods
19056 <Graph.Label.Native>
19058 TM.Label.Native = new Class({
19059 Implements: Graph.Label.Native,
19061 initialize: function(viz) {
19062 this.config = viz.config;
19063 this.leaf = viz.leaf;
19066 renderLabel: function(canvas, node, controller){
19067 if(!this.leaf(node) && !this.config.titleHeight) return;
19068 var pos = node.pos.getc(true),
19069 ctx = canvas.getCtx(),
19070 width = node.getData('width'),
19071 height = node.getData('height'),
19072 x = pos.x + width/2,
19075 ctx.fillText(node.name, x, y, width);
19082 Custom extension of <Graph.Label.SVG>.
19086 All <Graph.Label.SVG> methods
19092 TM.Label.SVG = new Class( {
19093 Implements: Graph.Label.SVG,
19095 initialize: function(viz){
19097 this.leaf = viz.leaf;
19098 this.config = viz.config;
19104 Overrides abstract method placeLabel in <Graph.Plot>.
19108 tag - A DOM label element.
19109 node - A <Graph.Node>.
19110 controller - A configuration/controller object passed to the visualization.
19113 placeLabel: function(tag, node, controller){
19114 var pos = node.pos.getc(true),
19115 canvas = this.viz.canvas,
19116 ox = canvas.translateOffsetX,
19117 oy = canvas.translateOffsetY,
19118 sx = canvas.scaleOffsetX,
19119 sy = canvas.scaleOffsetY,
19120 radius = canvas.getSize();
19122 x: Math.round(pos.x * sx + ox + radius.width / 2),
19123 y: Math.round(pos.y * sy + oy + radius.height / 2)
19125 tag.setAttribute('x', labelPos.x);
19126 tag.setAttribute('y', labelPos.y);
19128 if(!this.leaf(node) && !this.config.titleHeight) {
19129 tag.style.display = 'none';
19131 controller.onPlaceLabel(tag, node);
19138 Custom extension of <Graph.Label.HTML>.
19142 All <Graph.Label.HTML> methods.
19149 TM.Label.HTML = new Class( {
19150 Implements: Graph.Label.HTML,
19152 initialize: function(viz){
19154 this.leaf = viz.leaf;
19155 this.config = viz.config;
19161 Overrides abstract method placeLabel in <Graph.Plot>.
19165 tag - A DOM label element.
19166 node - A <Graph.Node>.
19167 controller - A configuration/controller object passed to the visualization.
19170 placeLabel: function(tag, node, controller){
19171 var pos = node.pos.getc(true),
19172 canvas = this.viz.canvas,
19173 ox = canvas.translateOffsetX,
19174 oy = canvas.translateOffsetY,
19175 sx = canvas.scaleOffsetX,
19176 sy = canvas.scaleOffsetY,
19177 radius = canvas.getSize();
19179 x: Math.round(pos.x * sx + ox + radius.width / 2),
19180 y: Math.round(pos.y * sy + oy + radius.height / 2)
19183 var style = tag.style;
19184 style.left = labelPos.x + 'px';
19185 style.top = labelPos.y + 'px';
19186 style.width = node.getData('width') * sx + 'px';
19187 style.height = node.getData('height') * sy + 'px';
19188 style.zIndex = node._depth * 100;
19189 style.display = '';
19191 if(!this.leaf(node) && !this.config.titleHeight) {
19192 tag.style.display = 'none';
19194 controller.onPlaceLabel(tag, node);
19199 Class: TM.Plot.NodeTypes
19201 This class contains a list of <Graph.Node> built-in types.
19202 Node types implemented are 'none', 'rectangle'.
19204 You can add your custom node types, customizing your visualization to the extreme.
19209 TM.Plot.NodeTypes.implement({
19211 'render': function(node, canvas) {
19212 //print your custom node to canvas
19215 'contains': function(node, pos) {
19216 //return true if pos is inside the node or false otherwise
19223 TM.Plot.NodeTypes = new Class( {
19229 'render': function(node, canvas, animating){
19230 var leaf = this.viz.leaf(node),
19231 config = this.config,
19232 offst = config.offset,
19233 titleHeight = config.titleHeight,
19234 pos = node.pos.getc(true),
19235 width = node.getData('width'),
19236 height = node.getData('height'),
19237 border = node.getData('border'),
19238 ctx = canvas.getCtx(),
19239 posx = pos.x + offst / 2,
19240 posy = pos.y + offst / 2;
19241 if(width <= offst || height <= offst) return;
19243 if(config.cushion) {
19244 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
19245 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19246 var color = node.getData('color');
19247 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
19248 function(r) { return r * 0.2 >> 0; }));
19249 lg.addColorStop(0, color);
19250 lg.addColorStop(1, colorGrad);
19251 ctx.fillStyle = lg;
19253 ctx.fillRect(posx, posy, width - offst, height - offst);
19256 ctx.strokeStyle = border;
19257 ctx.strokeRect(posx, posy, width - offst, height - offst);
19260 } else if(titleHeight > 0){
19261 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19262 titleHeight - offst);
19265 ctx.strokeStyle = border;
19266 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19272 'contains': function(node, pos) {
19273 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19274 var npos = node.pos.getc(true),
19275 width = node.getData('width'),
19276 leaf = this.viz.leaf(node),
19277 height = leaf? node.getData('height') : this.config.titleHeight;
19278 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19283 TM.Plot.EdgeTypes = new Class( {
19288 Class: TM.SliceAndDice
19290 A slice and dice TreeMap visualization.
19294 All <TM.Base> methods and properties.
19296 TM.SliceAndDice = new Class( {
19298 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19303 Class: TM.Squarified
19305 A squarified TreeMap visualization.
19309 All <TM.Base> methods and properties.
19311 TM.Squarified = new Class( {
19313 Loader, Extras, TM.Base, Layouts.TM.Squarified
19320 A strip TreeMap visualization.
19324 All <TM.Base> methods and properties.
19326 TM.Strip = new Class( {
19328 Loader, Extras, TM.Base, Layouts.TM.Strip
19341 A radial graph visualization with advanced animations.
19345 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>
19349 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.
19353 All <Loader> methods
19355 Constructor Options:
19357 Inherits options from
19360 - <Options.Controller>
19366 - <Options.NodeStyles>
19367 - <Options.Navigation>
19369 Additionally, there are other parameters and some default values changed
19371 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19372 levelDistance - (number) Default's *100*. The distance between levels of the tree.
19374 Instance Properties:
19376 canvas - Access a <Canvas> instance.
19377 graph - Access a <Graph> instance.
19378 op - Access a <RGraph.Op> instance.
19379 fx - Access a <RGraph.Plot> instance.
19380 labels - Access a <RGraph.Label> interface implementation.
19383 $jit.RGraph = new Class( {
19386 Loader, Extras, Layouts.Radial
19389 initialize: function(controller){
19390 var $RGraph = $jit.RGraph;
19393 interpolation: 'linear',
19397 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19398 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19400 var canvasConfig = this.config;
19401 if(canvasConfig.useCanvas) {
19402 this.canvas = canvasConfig.useCanvas;
19403 this.config.labelContainer = this.canvas.id + '-label';
19405 if(canvasConfig.background) {
19406 canvasConfig.background = $.merge({
19408 }, canvasConfig.background);
19410 this.canvas = new Canvas(this, canvasConfig);
19411 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19414 this.graphOptions = {
19422 this.graph = new Graph(this.graphOptions, this.config.Node,
19424 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19425 this.fx = new $RGraph.Plot(this, $RGraph);
19426 this.op = new $RGraph.Op(this);
19430 this.parent = false;
19431 // initialize extras
19432 this.initializeExtras();
19437 createLevelDistanceFunc
19439 Returns the levelDistance function used for calculating a node distance
19440 to its origin. This function returns a function that is computed
19441 per level and not per node, such that all nodes with the same depth will have the
19442 same distance to the origin. The resulting function gets the
19443 parent node as parameter and returns a float.
19446 createLevelDistanceFunc: function(){
19447 var ld = this.config.levelDistance;
19448 return function(elem){
19449 return (elem._depth + 1) * ld;
19456 Computes positions and plots the tree.
19459 refresh: function(){
19464 reposition: function(){
19465 this.compute('end');
19471 Plots the RGraph. This is a shortcut to *fx.plot*.
19477 getNodeAndParentAngle
19479 Returns the _parent_ of the given node, also calculating its angle span.
19481 getNodeAndParentAngle: function(id){
19483 var n = this.graph.getNode(id);
19484 var ps = n.getParents();
19485 var p = (ps.length > 0)? ps[0] : false;
19487 var posParent = p.pos.getc(), posChild = n.pos.getc();
19488 var newPos = posParent.add(posChild.scale(-1));
19489 theta = Math.atan2(newPos.y, newPos.x);
19491 theta += 2 * Math.PI;
19501 Enumerates the children in order to maintain child ordering (second constraint of the paper).
19503 tagChildren: function(par, id){
19504 if (par.angleSpan) {
19506 par.eachAdjacency(function(elem){
19507 adjs.push(elem.nodeTo);
19509 var len = adjs.length;
19510 for ( var i = 0; i < len && id != adjs[i].id; i++)
19512 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19513 adjs[j].dist = k++;
19520 Animates the <RGraph> to center the node specified by *id*.
19524 id - A <Graph.Node> id.
19525 opt - (optional|object) An object containing some extra properties described below
19526 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19531 rgraph.onClick('someid');
19533 rgraph.onClick('someid', {
19539 onClick: function(id, opt){
19540 if (this.root != id && !this.busy) {
19544 this.controller.onBeforeCompute(this.graph.getNode(id));
19545 var obj = this.getNodeAndParentAngle(id);
19547 // second constraint
19548 this.tagChildren(obj.parent, id);
19549 this.parent = obj.parent;
19550 this.compute('end');
19552 // first constraint
19553 var thetaDiff = obj.theta - obj.parent.endPos.theta;
19554 this.graph.eachNode(function(elem){
19555 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19558 var mode = this.config.interpolation;
19560 onComplete: $.empty
19563 this.fx.animate($.merge( {
19569 onComplete: function(){
19578 $jit.RGraph.$extend = true;
19585 Custom extension of <Graph.Op>.
19589 All <Graph.Op> methods
19596 RGraph.Op = new Class( {
19598 Implements: Graph.Op
19605 Custom extension of <Graph.Plot>.
19609 All <Graph.Plot> methods
19616 RGraph.Plot = new Class( {
19618 Implements: Graph.Plot
19623 Object: RGraph.Label
19625 Custom extension of <Graph.Label>.
19626 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19630 All <Graph.Label> methods and subclasses.
19634 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19640 RGraph.Label.Native
19642 Custom extension of <Graph.Label.Native>.
19646 All <Graph.Label.Native> methods
19650 <Graph.Label.Native>
19653 RGraph.Label.Native = new Class( {
19654 Implements: Graph.Label.Native
19660 Custom extension of <Graph.Label.SVG>.
19664 All <Graph.Label.SVG> methods
19671 RGraph.Label.SVG = new Class( {
19672 Implements: Graph.Label.SVG,
19674 initialize: function(viz){
19681 Overrides abstract method placeLabel in <Graph.Plot>.
19685 tag - A DOM label element.
19686 node - A <Graph.Node>.
19687 controller - A configuration/controller object passed to the visualization.
19690 placeLabel: function(tag, node, controller){
19691 var pos = node.pos.getc(true),
19692 canvas = this.viz.canvas,
19693 ox = canvas.translateOffsetX,
19694 oy = canvas.translateOffsetY,
19695 sx = canvas.scaleOffsetX,
19696 sy = canvas.scaleOffsetY,
19697 radius = canvas.getSize();
19699 x: Math.round(pos.x * sx + ox + radius.width / 2),
19700 y: Math.round(pos.y * sy + oy + radius.height / 2)
19702 tag.setAttribute('x', labelPos.x);
19703 tag.setAttribute('y', labelPos.y);
19705 controller.onPlaceLabel(tag, node);
19712 Custom extension of <Graph.Label.HTML>.
19716 All <Graph.Label.HTML> methods.
19723 RGraph.Label.HTML = new Class( {
19724 Implements: Graph.Label.HTML,
19726 initialize: function(viz){
19732 Overrides abstract method placeLabel in <Graph.Plot>.
19736 tag - A DOM label element.
19737 node - A <Graph.Node>.
19738 controller - A configuration/controller object passed to the visualization.
19741 placeLabel: function(tag, node, controller){
19742 var pos = node.pos.getc(true),
19743 canvas = this.viz.canvas,
19744 ox = canvas.translateOffsetX,
19745 oy = canvas.translateOffsetY,
19746 sx = canvas.scaleOffsetX,
19747 sy = canvas.scaleOffsetY,
19748 radius = canvas.getSize();
19750 x: Math.round(pos.x * sx + ox + radius.width / 2),
19751 y: Math.round(pos.y * sy + oy + radius.height / 2)
19754 var style = tag.style;
19755 style.left = labelPos.x + 'px';
19756 style.top = labelPos.y + 'px';
19757 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
19759 controller.onPlaceLabel(tag, node);
19764 Class: RGraph.Plot.NodeTypes
19766 This class contains a list of <Graph.Node> built-in types.
19767 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
19769 You can add your custom node types, customizing your visualization to the extreme.
19774 RGraph.Plot.NodeTypes.implement({
19776 'render': function(node, canvas) {
19777 //print your custom node to canvas
19780 'contains': function(node, pos) {
19781 //return true if pos is inside the node or false otherwise
19788 RGraph.Plot.NodeTypes = new Class({
19791 'contains': $.lambda(false)
19794 'render': function(node, canvas){
19795 var pos = node.pos.getc(true),
19796 dim = node.getData('dim');
19797 this.nodeHelper.circle.render('fill', pos, dim, canvas);
19799 'contains': function(node, pos){
19800 var npos = node.pos.getc(true),
19801 dim = node.getData('dim');
19802 return this.nodeHelper.circle.contains(npos, pos, dim);
19806 'render': function(node, canvas){
19807 var pos = node.pos.getc(true),
19808 width = node.getData('width'),
19809 height = node.getData('height');
19810 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
19812 // TODO(nico): be more precise...
19813 'contains': function(node, pos){
19814 var npos = node.pos.getc(true),
19815 width = node.getData('width'),
19816 height = node.getData('height');
19817 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
19821 'render': function(node, canvas){
19822 var pos = node.pos.getc(true),
19823 dim = node.getData('dim');
19824 this.nodeHelper.square.render('fill', pos, dim, canvas);
19826 'contains': function(node, pos){
19827 var npos = node.pos.getc(true),
19828 dim = node.getData('dim');
19829 return this.nodeHelper.square.contains(npos, pos, dim);
19833 'render': function(node, canvas){
19834 var pos = node.pos.getc(true),
19835 width = node.getData('width'),
19836 height = node.getData('height');
19837 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
19839 'contains': function(node, pos){
19840 var npos = node.pos.getc(true),
19841 width = node.getData('width'),
19842 height = node.getData('height');
19843 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
19847 'render': function(node, canvas){
19848 var pos = node.pos.getc(true),
19849 dim = node.getData('dim');
19850 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
19852 'contains': function(node, pos) {
19853 var npos = node.pos.getc(true),
19854 dim = node.getData('dim');
19855 return this.nodeHelper.triangle.contains(npos, pos, dim);
19859 'render': function(node, canvas){
19860 var pos = node.pos.getc(true),
19861 dim = node.getData('dim');
19862 this.nodeHelper.star.render('fill', pos, dim, canvas);
19864 'contains': function(node, pos) {
19865 var npos = node.pos.getc(true),
19866 dim = node.getData('dim');
19867 return this.nodeHelper.star.contains(npos, pos, dim);
19873 Class: RGraph.Plot.EdgeTypes
19875 This class contains a list of <Graph.Adjacence> built-in types.
19876 Edge types implemented are 'none', 'line' and 'arrow'.
19878 You can add your custom edge types, customizing your visualization to the extreme.
19883 RGraph.Plot.EdgeTypes.implement({
19885 'render': function(adj, canvas) {
19886 //print your custom edge to canvas
19889 'contains': function(adj, pos) {
19890 //return true if pos is inside the arc or false otherwise
19897 RGraph.Plot.EdgeTypes = new Class({
19900 'render': function(adj, canvas) {
19901 var from = adj.nodeFrom.pos.getc(true),
19902 to = adj.nodeTo.pos.getc(true);
19903 this.edgeHelper.line.render(from, to, canvas);
19905 'contains': function(adj, pos) {
19906 var from = adj.nodeFrom.pos.getc(true),
19907 to = adj.nodeTo.pos.getc(true);
19908 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
19912 'render': function(adj, canvas) {
19913 var from = adj.nodeFrom.pos.getc(true),
19914 to = adj.nodeTo.pos.getc(true),
19915 dim = adj.getData('dim'),
19916 direction = adj.data.$direction,
19917 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
19918 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
19920 'contains': function(adj, pos) {
19921 var from = adj.nodeFrom.pos.getc(true),
19922 to = adj.nodeTo.pos.getc(true);
19923 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
19932 * File: Hypertree.js
19939 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
19943 moebiusTransformation
19945 Calculates a moebius transformation for this point / complex.
19946 For more information go to:
19947 http://en.wikipedia.org/wiki/Moebius_transformation.
19951 c - An initialized Complex instance representing a translation Vector.
19954 Complex.prototype.moebiusTransformation = function(c) {
19955 var num = this.add(c);
19956 var den = c.$conjugate().$prod(this);
19958 return num.$div(den);
19962 moebiusTransformation
19964 Calculates a moebius transformation for the hyperbolic tree.
19966 <http://en.wikipedia.org/wiki/Moebius_transformation>
19970 graph - A <Graph> instance.
19972 prop - A property array.
19973 theta - Rotation angle.
19974 startPos - _optional_ start position.
19976 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
19977 this.eachNode(graph, function(elem) {
19978 for ( var i = 0; i < prop.length; i++) {
19979 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
19980 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
19988 A Hyperbolic Tree/Graph visualization.
19992 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
19993 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
19997 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.
20001 All <Loader> methods
20003 Constructor Options:
20005 Inherits options from
20008 - <Options.Controller>
20014 - <Options.NodeStyles>
20015 - <Options.Navigation>
20017 Additionally, there are other parameters and some default values changed
20019 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*.
20020 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.
20021 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20022 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20023 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
20025 Instance Properties:
20027 canvas - Access a <Canvas> instance.
20028 graph - Access a <Graph> instance.
20029 op - Access a <Hypertree.Op> instance.
20030 fx - Access a <Hypertree.Plot> instance.
20031 labels - Access a <Hypertree.Label> interface implementation.
20035 $jit.Hypertree = new Class( {
20037 Implements: [ Loader, Extras, Layouts.Radial ],
20039 initialize: function(controller) {
20040 var $Hypertree = $jit.Hypertree;
20051 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20052 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20054 var canvasConfig = this.config;
20055 if(canvasConfig.useCanvas) {
20056 this.canvas = canvasConfig.useCanvas;
20057 this.config.labelContainer = this.canvas.id + '-label';
20059 if(canvasConfig.background) {
20060 canvasConfig.background = $.merge({
20062 }, canvasConfig.background);
20064 this.canvas = new Canvas(this, canvasConfig);
20065 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20068 this.graphOptions = {
20076 this.graph = new Graph(this.graphOptions, this.config.Node,
20078 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20079 this.fx = new $Hypertree.Plot(this, $Hypertree);
20080 this.op = new $Hypertree.Op(this);
20084 // initialize extras
20085 this.initializeExtras();
20090 createLevelDistanceFunc
20092 Returns the levelDistance function used for calculating a node distance
20093 to its origin. This function returns a function that is computed
20094 per level and not per node, such that all nodes with the same depth will have the
20095 same distance to the origin. The resulting function gets the
20096 parent node as parameter and returns a float.
20099 createLevelDistanceFunc: function() {
20100 // get max viz. length.
20101 var r = this.getRadius();
20103 var depth = 0, max = Math.max, config = this.config;
20104 this.graph.eachNode(function(node) {
20105 depth = max(node._depth, depth);
20108 // node distance generator
20109 var genDistFunc = function(a) {
20110 return function(node) {
20112 var d = node._depth + 1;
20113 var acum = 0, pow = Math.pow;
20115 acum += pow(a, d--);
20117 return acum - config.offset;
20120 // estimate better edge length.
20121 for ( var i = 0.51; i <= 1; i += 0.01) {
20122 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20123 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20125 return genDistFunc(0.75);
20131 Returns the current radius of the visualization. If *config.radius* is *auto* then it
20132 calculates the radius by taking the smaller size of the <Canvas> widget.
20139 getRadius: function() {
20140 var rad = this.config.radius;
20141 if (rad !== "auto") { return rad; }
20142 var s = this.canvas.getSize();
20143 return Math.min(s.width, s.height) / 2;
20149 Computes positions and plots the tree.
20153 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20156 refresh: function(reposition) {
20159 this.graph.eachNode(function(node) {
20160 node.startPos.rho = node.pos.rho = node.endPos.rho;
20161 node.startPos.theta = node.pos.theta = node.endPos.theta;
20172 Computes nodes' positions and restores the tree to its previous position.
20174 For calculating nodes' positions the root must be placed on its origin. This method does this
20175 and then attemps to restore the hypertree to its previous position.
20178 reposition: function() {
20179 this.compute('end');
20180 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20181 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20183 this.graph.eachNode(function(node) {
20185 node.endPos.rho = node.pos.rho;
20186 node.endPos.theta = node.pos.theta;
20194 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
20204 Animates the <Hypertree> to center the node specified by *id*.
20208 id - A <Graph.Node> id.
20209 opt - (optional|object) An object containing some extra properties described below
20210 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20215 ht.onClick('someid');
20217 ht.onClick('someid', {
20223 onClick: function(id, opt) {
20224 var pos = this.graph.getNode(id).pos.getc(true);
20225 this.move(pos, opt);
20231 Translates the tree to the given position.
20235 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20236 opt - This object has been defined in <Hypertree.onClick>
20241 ht.move({ x: 0, y: 0.7 }, {
20247 move: function(pos, opt) {
20248 var versor = $C(pos.x, pos.y);
20249 if (this.busy === false && versor.norm() < 1) {
20251 var root = this.graph.getClosestNodeToPos(versor), that = this;
20252 this.graph.computeLevels(root.id, 0);
20253 this.controller.onBeforeCompute(root);
20255 onComplete: $.empty
20257 this.fx.animate($.merge( {
20258 modes: [ 'moebius' ],
20261 onComplete: function() {
20270 $jit.Hypertree.$extend = true;
20272 (function(Hypertree) {
20275 Class: Hypertree.Op
20277 Custom extension of <Graph.Op>.
20281 All <Graph.Op> methods
20288 Hypertree.Op = new Class( {
20290 Implements: Graph.Op
20295 Class: Hypertree.Plot
20297 Custom extension of <Graph.Plot>.
20301 All <Graph.Plot> methods
20308 Hypertree.Plot = new Class( {
20310 Implements: Graph.Plot
20315 Object: Hypertree.Label
20317 Custom extension of <Graph.Label>.
20318 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20322 All <Graph.Label> methods and subclasses.
20326 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20329 Hypertree.Label = {};
20332 Hypertree.Label.Native
20334 Custom extension of <Graph.Label.Native>.
20338 All <Graph.Label.Native> methods
20342 <Graph.Label.Native>
20345 Hypertree.Label.Native = new Class( {
20346 Implements: Graph.Label.Native,
20348 initialize: function(viz) {
20352 renderLabel: function(canvas, node, controller) {
20353 var ctx = canvas.getCtx();
20354 var coord = node.pos.getc(true);
20355 var s = this.viz.getRadius();
20356 ctx.fillText(node.name, coord.x * s, coord.y * s);
20361 Hypertree.Label.SVG
20363 Custom extension of <Graph.Label.SVG>.
20367 All <Graph.Label.SVG> methods
20374 Hypertree.Label.SVG = new Class( {
20375 Implements: Graph.Label.SVG,
20377 initialize: function(viz) {
20384 Overrides abstract method placeLabel in <Graph.Plot>.
20388 tag - A DOM label element.
20389 node - A <Graph.Node>.
20390 controller - A configuration/controller object passed to the visualization.
20393 placeLabel: function(tag, node, controller) {
20394 var pos = node.pos.getc(true),
20395 canvas = this.viz.canvas,
20396 ox = canvas.translateOffsetX,
20397 oy = canvas.translateOffsetY,
20398 sx = canvas.scaleOffsetX,
20399 sy = canvas.scaleOffsetY,
20400 radius = canvas.getSize(),
20401 r = this.viz.getRadius();
20403 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20404 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20406 tag.setAttribute('x', labelPos.x);
20407 tag.setAttribute('y', labelPos.y);
20408 controller.onPlaceLabel(tag, node);
20413 Hypertree.Label.HTML
20415 Custom extension of <Graph.Label.HTML>.
20419 All <Graph.Label.HTML> methods.
20426 Hypertree.Label.HTML = new Class( {
20427 Implements: Graph.Label.HTML,
20429 initialize: function(viz) {
20435 Overrides abstract method placeLabel in <Graph.Plot>.
20439 tag - A DOM label element.
20440 node - A <Graph.Node>.
20441 controller - A configuration/controller object passed to the visualization.
20444 placeLabel: function(tag, node, controller) {
20445 var pos = node.pos.getc(true),
20446 canvas = this.viz.canvas,
20447 ox = canvas.translateOffsetX,
20448 oy = canvas.translateOffsetY,
20449 sx = canvas.scaleOffsetX,
20450 sy = canvas.scaleOffsetY,
20451 radius = canvas.getSize(),
20452 r = this.viz.getRadius();
20454 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20455 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20457 var style = tag.style;
20458 style.left = labelPos.x + 'px';
20459 style.top = labelPos.y + 'px';
20460 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20462 controller.onPlaceLabel(tag, node);
20467 Class: Hypertree.Plot.NodeTypes
20469 This class contains a list of <Graph.Node> built-in types.
20470 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20472 You can add your custom node types, customizing your visualization to the extreme.
20477 Hypertree.Plot.NodeTypes.implement({
20479 'render': function(node, canvas) {
20480 //print your custom node to canvas
20483 'contains': function(node, pos) {
20484 //return true if pos is inside the node or false otherwise
20491 Hypertree.Plot.NodeTypes = new Class({
20494 'contains': $.lambda(false)
20497 'render': function(node, canvas) {
20498 var nconfig = this.node,
20499 dim = node.getData('dim'),
20500 p = node.pos.getc();
20501 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20502 p.$scale(node.scale);
20504 this.nodeHelper.circle.render('fill', p, dim, canvas);
20507 'contains': function(node, pos) {
20508 var dim = node.getData('dim'),
20509 npos = node.pos.getc().$scale(node.scale);
20510 return this.nodeHelper.circle.contains(npos, pos, dim);
20514 'render': function(node, canvas) {
20515 var pos = node.pos.getc().$scale(node.scale),
20516 width = node.getData('width'),
20517 height = node.getData('height');
20518 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20520 'contains': function(node, pos) {
20521 var width = node.getData('width'),
20522 height = node.getData('height'),
20523 npos = node.pos.getc().$scale(node.scale);
20524 return this.nodeHelper.circle.contains(npos, pos, width, height);
20528 'render': function(node, canvas) {
20529 var nconfig = this.node,
20530 dim = node.getData('dim'),
20531 p = node.pos.getc();
20532 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20533 p.$scale(node.scale);
20535 this.nodeHelper.square.render('fill', p, dim, canvas);
20538 'contains': function(node, pos) {
20539 var dim = node.getData('dim'),
20540 npos = node.pos.getc().$scale(node.scale);
20541 return this.nodeHelper.square.contains(npos, pos, dim);
20545 'render': function(node, canvas) {
20546 var nconfig = this.node,
20547 width = node.getData('width'),
20548 height = node.getData('height'),
20549 pos = node.pos.getc();
20550 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20551 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20552 pos.$scale(node.scale);
20553 if (width > 0.2 && height > 0.2) {
20554 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20557 'contains': function(node, pos) {
20558 var width = node.getData('width'),
20559 height = node.getData('height'),
20560 npos = node.pos.getc().$scale(node.scale);
20561 return this.nodeHelper.square.contains(npos, pos, width, height);
20565 'render': function(node, canvas) {
20566 var nconfig = this.node,
20567 dim = node.getData('dim'),
20568 p = node.pos.getc();
20569 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20570 p.$scale(node.scale);
20572 this.nodeHelper.triangle.render('fill', p, dim, canvas);
20575 'contains': function(node, pos) {
20576 var dim = node.getData('dim'),
20577 npos = node.pos.getc().$scale(node.scale);
20578 return this.nodeHelper.triangle.contains(npos, pos, dim);
20582 'render': function(node, canvas) {
20583 var nconfig = this.node,
20584 dim = node.getData('dim'),
20585 p = node.pos.getc();
20586 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20587 p.$scale(node.scale);
20589 this.nodeHelper.star.render('fill', p, dim, canvas);
20592 'contains': function(node, pos) {
20593 var dim = node.getData('dim'),
20594 npos = node.pos.getc().$scale(node.scale);
20595 return this.nodeHelper.star.contains(npos, pos, dim);
20601 Class: Hypertree.Plot.EdgeTypes
20603 This class contains a list of <Graph.Adjacence> built-in types.
20604 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20606 You can add your custom edge types, customizing your visualization to the extreme.
20611 Hypertree.Plot.EdgeTypes.implement({
20613 'render': function(adj, canvas) {
20614 //print your custom edge to canvas
20617 'contains': function(adj, pos) {
20618 //return true if pos is inside the arc or false otherwise
20625 Hypertree.Plot.EdgeTypes = new Class({
20628 'render': function(adj, canvas) {
20629 var from = adj.nodeFrom.pos.getc(true),
20630 to = adj.nodeTo.pos.getc(true),
20631 r = adj.nodeFrom.scale;
20632 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20634 'contains': function(adj, pos) {
20635 var from = adj.nodeFrom.pos.getc(true),
20636 to = adj.nodeTo.pos.getc(true),
20637 r = adj.nodeFrom.scale;
20638 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20642 'render': function(adj, canvas) {
20643 var from = adj.nodeFrom.pos.getc(true),
20644 to = adj.nodeTo.pos.getc(true),
20645 r = adj.nodeFrom.scale,
20646 dim = adj.getData('dim'),
20647 direction = adj.data.$direction,
20648 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20649 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20651 'contains': function(adj, pos) {
20652 var from = adj.nodeFrom.pos.getc(true),
20653 to = adj.nodeTo.pos.getc(true),
20654 r = adj.nodeFrom.scale;
20655 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20659 'render': function(adj, canvas) {
20660 var from = adj.nodeFrom.pos.getc(),
20661 to = adj.nodeTo.pos.getc(),
20662 dim = this.viz.getRadius();
20663 this.edgeHelper.hyperline.render(from, to, dim, canvas);
20665 'contains': $.lambda(false)
20669 })($jit.Hypertree);