2 Copyright (c) 2010, Nicolas Garcia Belmonte
5 > Redistribution and use in source and binary forms, with or without
6 > modification, are permitted provided that the following conditions are met:
7 > * Redistributions of source code must retain the above copyright
8 > notice, this list of conditions and the following disclaimer.
9 > * Redistributions in binary form must reproduce the above copyright
10 > notice, this list of conditions and the following disclaimer in the
11 > documentation and/or other materials provided with the distribution.
12 > * Neither the name of the organization nor the
13 > names of its contributors may be used to endorse or promote products
14 > derived from this software without specific prior written permission.
16 > THIS SOFTWARE IS PROVIDED BY NICOLAS GARCIA BELMONTE ``AS IS'' AND ANY
17 > EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 > WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 > DISCLAIMED. IN NO EVENT SHALL NICOLAS GARCIA BELMONTE BE LIABLE FOR ANY
20 > DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 > (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 > LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 > ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 > (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 > SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 /** Lam Huynh on 10/10/2010 added funnel,guage charts **/
29 /** Lam Huynh on 02/27/2011 added image exporting **/
30 /** Lam Huynh on 02/23/2011 added line charts **/
42 Defines the namespace for all library Classes and Objects.
43 This variable is the *only* global variable defined in the Toolkit.
44 There are also other interesting properties attached to this variable described below.
46 window.$jit = function(w) {
55 $jit.version = '2.0.0b';
59 Works just like *document.getElementById*
63 var element = $jit.id('elementId');
71 Contains utility functions.
73 Some of the utility functions and the Class system were based in the MooTools Framework
74 <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>.
75 MIT license <http://mootools.net/license.txt>.
77 These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
78 I'd suggest you to use the functions from those libraries instead of using these, since their functions
79 are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
83 return document.getElementById(d);
86 $.empty = function() {
89 function pad(number, length) {
91 var str = '' + number;
92 while (str.length < length) {
102 // public method for url encoding
103 encode : function (string) {
104 return escape(this._utf8_encode(string));
107 // public method for url decoding
108 decode : function (string) {
109 return this._utf8_decode(unescape(string));
112 // private method for UTF-8 encoding
113 _utf8_encode : function (string) {
114 string = string.replace(/\r\n/g,"\n");
117 for (var n = 0; n < string.length; n++) {
119 var c = string.charCodeAt(n);
122 utftext += String.fromCharCode(c);
124 else if((c > 127) && (c < 2048)) {
125 utftext += String.fromCharCode((c >> 6) | 192);
126 utftext += String.fromCharCode((c & 63) | 128);
129 utftext += String.fromCharCode((c >> 12) | 224);
130 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
131 utftext += String.fromCharCode((c & 63) | 128);
139 // private method for UTF-8 decoding
140 _utf8_decode : function (utftext) {
145 while ( i < utftext.length ) {
147 c = utftext.charCodeAt(i);
150 string += String.fromCharCode(c);
153 else if((c > 191) && (c < 224)) {
154 c2 = utftext.charCodeAt(i+1);
155 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
159 c2 = utftext.charCodeAt(i+1);
160 c3 = utftext.charCodeAt(i+2);
161 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
172 Array.prototype.sum = function() {
173 return (! this.length) ? 0 : this.slice(1).sum() +
174 ((typeof this[0] == 'number') ? this[0] : 0);
177 function array_match(needle, haystack) {
178 var length = haystack.length;
179 var indexValue = new Array();
180 for(var i = 0, count = 0; i < length; i++) {
181 if(haystack[i] == needle) {
182 indexValue[count] = i;
186 return new Array(count,indexValue);
189 $.roundedRect = function (ctx,x,y,width,height,radius,fillType){
191 ctx.moveTo(x,y+radius);
192 ctx.lineTo(x,y+height-radius);
193 ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
194 ctx.lineTo(x+width-radius,y+height);
195 ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
196 ctx.lineTo(x+width,y+radius);
197 ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
198 ctx.lineTo(x+radius,y);
199 ctx.quadraticCurveTo(x,y,x,y+radius);
200 if(fillType=="fill") {
207 $.saveImageFile = function (id,jsonfilename,imageExt) {
208 var parts = jsonfilename.split("/");
209 var filename = parts[2].replace(".js","."+imageExt);
210 var oCanvas = document.getElementById(id+"-canvas");
213 if(imageExt == "jpg") {
214 var strDataURI = oCanvas.toDataURL("image/jpeg");
216 var strDataURI = oCanvas.toDataURL("image/png");
218 var handleFailure = function(o){
219 //alert('failed to write image' + filename);
220 //remove alert since chrome triggers this function when user navigates away from page before image gets written.
222 var handleSuccess = function(o){
226 success:handleSuccess,
227 failure:handleFailure,
228 argument: { foo:'foo', bar:''}
230 var path = "index.php?action=DynamicAction&DynamicAction=saveImage&module=Charts&to_pdf=1";
231 var postData = "imageStr=" + strDataURI + "&filename=" + filename;
232 var request = YAHOO.util.Connect.asyncRequest('POST', path, callback, postData);
236 $.saveImageTest = function (id,jsonfilename,imageExt) {
237 if(typeof FlashCanvas != "undefined") {
238 setTimeout(function(){$.saveImageFile(id,jsonfilename,imageExt)},10000);
240 $.saveImageFile(id,jsonfilename,imageExt);
246 Augment an object by appending another object's properties.
250 original - (object) The object to be extended.
251 extended - (object) An object which properties are going to be appended to the original object.
255 $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
258 $.extend = function(original, extended) {
259 for ( var key in (extended || {}))
260 original[key] = extended[key];
264 $.lambda = function(value) {
265 return (typeof value == 'function') ? value : function() {
270 $.time = Date.now || function() {
277 Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
281 obj - (mixed) The object to be wrapped in an array.
285 $jit.util.splat(3); //[3]
286 $jit.util.splat([3]); //[3]
289 $.splat = function(obj) {
290 var type = $.type(obj);
291 return type ? ((type != 'array') ? [ obj ] : obj) : [];
294 $.type = function(elem) {
295 var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
296 if(type != 'object') return type;
297 if(elem && elem.$$family) return elem.$$family;
298 return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
300 $.type.s = Object.prototype.toString;
305 Iterates through an iterable applying *f*.
309 iterable - (array) The original array.
310 fn - (function) The function to apply to the array elements.
314 $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
317 $.each = function(iterable, fn) {
318 var type = $.type(iterable);
319 if (type == 'object') {
320 for ( var key in iterable)
321 fn(iterable[key], key);
323 for ( var i = 0, l = iterable.length; i < l; i++)
328 $.indexOf = function(array, item) {
329 if(Array.indexOf) return array.indexOf(item);
330 for(var i=0,l=array.length; i<l; i++) {
331 if(array[i] === item) return i;
339 Maps or collects an array by applying *f*.
343 array - (array) The original array.
344 f - (function) The function to apply to the array elements.
348 $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
351 $.map = function(array, f) {
353 $.each(array, function(elem, i) {
354 ans.push(f(elem, i));
362 Iteratively applies the binary function *f* storing the result in an accumulator.
366 array - (array) The original array.
367 f - (function) The function to apply to the array elements.
368 opt - (optional|mixed) The starting value for the acumulator.
372 $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
375 $.reduce = function(array, f, opt) {
376 var l = array.length;
378 var acum = arguments.length == 3? opt : array[--l];
380 acum = f(acum, array[l]);
388 Merges n-objects and their sub-objects creating a new, fresh object.
392 An arbitrary number of objects.
396 $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
399 $.merge = function() {
401 for ( var i = 0, l = arguments.length; i < l; i++) {
402 var object = arguments[i];
403 if ($.type(object) != 'object')
405 for ( var key in object) {
406 var op = object[key], mp = mix[key];
407 mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
408 .merge(mp, op) : $.unlink(op);
414 $.unlink = function(object) {
416 switch ($.type(object)) {
419 for ( var p in object)
420 unlinked[p] = $.unlink(object[p]);
424 for ( var i = 0, l = object.length; i < l; i++)
425 unlinked[i] = $.unlink(object[i]);
434 if(arguments.length === 0) return [];
435 for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
436 for(var i=0, row=[]; i<l; i++) {
437 row.push(arguments[i][j]);
447 Converts an RGB array into a Hex string.
451 srcArray - (array) An array with R, G and B values
455 $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
458 $.rgbToHex = function(srcArray, array) {
459 if (srcArray.length < 3)
461 if (srcArray.length == 4 && srcArray[3] == 0 && !array)
462 return 'transparent';
464 for ( var i = 0; i < 3; i++) {
465 var bit = (srcArray[i] - 0).toString(16);
466 hex.push(bit.length == 1 ? '0' + bit : bit);
468 return array ? hex : '#' + hex.join('');
474 Converts an Hex color string into an RGB array.
478 hex - (string) A color hex string.
482 $jit.util.hexToRgb('#fff'); //[255, 255, 255]
485 $.hexToRgb = function(hex) {
486 if (hex.length != 7) {
487 hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
492 for ( var i = 0; i < 3; i++) {
494 if (value.length == 1)
496 rgb.push(parseInt(value, 16));
500 hex = parseInt(hex.slice(1), 16);
501 return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
505 $.destroy = function(elem) {
508 elem.parentNode.removeChild(elem);
509 if (elem.clearAttributes)
510 elem.clearAttributes();
513 $.clean = function(elem) {
514 for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
522 Cross-browser add event listener.
526 obj - (obj) The Element to attach the listener to.
527 type - (string) The listener type. For example 'click', or 'mousemove'.
528 fn - (function) The callback function to be used when the event is fired.
532 $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
535 $.addEvent = function(obj, type, fn) {
536 if (obj.addEventListener)
537 obj.addEventListener(type, fn, false);
539 obj.attachEvent('on' + type, fn);
542 $.addEvents = function(obj, typeObj) {
543 for(var type in typeObj) {
544 $.addEvent(obj, type, typeObj[type]);
548 $.hasClass = function(obj, klass) {
549 return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
552 $.addClass = function(obj, klass) {
553 if (!$.hasClass(obj, klass))
554 obj.className = (obj.className + " " + klass);
557 $.removeClass = function(obj, klass) {
558 obj.className = obj.className.replace(new RegExp(
559 '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
562 $.getPos = function(elem) {
563 var offset = getOffsets(elem);
564 var scroll = getScrolls(elem);
566 x: offset.x - scroll.x,
567 y: offset.y - scroll.y
570 function getOffsets(elem) {
575 while (elem && !isBody(elem)) {
576 position.x += elem.offsetLeft;
577 position.y += elem.offsetTop;
578 try{elem=elem.offsetParent;}catch(e){elem=document.body;}
583 function getScrolls(elem) {
588 while (elem && !isBody(elem)) {
589 position.x += elem.scrollLeft;
590 position.y += elem.scrollTop;
591 elem = elem.parentNode;
596 function isBody(element) {
597 return (/^(?:body|html)$/i).test(element.tagName);
602 get: function(e, win) {
604 return e || win.event;
606 getWheel: function(e) {
607 return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
609 isRightClick: function(e) {
610 return (e.which == 3 || e.button == 2);
612 getPos: function(e, win) {
613 // get mouse position
616 var doc = win.document;
617 doc = doc.documentElement || doc.body;
618 //TODO(nico): make touch event handling better
619 if(e.touches && e.touches.length) {
623 x: e.pageX || (e.clientX + doc.scrollLeft),
624 y: e.pageY || (e.clientY + doc.scrollTop)
629 if (e.stopPropagation) e.stopPropagation();
630 e.cancelBubble = true;
631 if (e.preventDefault) e.preventDefault();
632 else e.returnValue = false;
636 $jit.util = $jit.id = $;
638 var Class = function(properties) {
639 properties = properties || {};
640 var klass = function() {
641 for ( var key in this) {
642 if (typeof this[key] != 'function')
643 this[key] = $.unlink(this[key]);
645 this.constructor = klass;
646 if (Class.prototyping)
648 var instance = this.initialize ? this.initialize.apply(this, arguments)
651 this.$$family = 'class';
655 for ( var mutator in Class.Mutators) {
656 if (!properties[mutator])
658 properties = Class.Mutators[mutator](properties, properties[mutator]);
659 delete properties[mutator];
662 $.extend(klass, this);
663 klass.constructor = Class;
664 klass.prototype = properties;
670 Implements: function(self, klasses) {
671 $.each($.splat(klasses), function(klass) {
672 Class.prototyping = klass;
673 var instance = (typeof klass == 'function') ? new klass : klass;
674 for ( var prop in instance) {
675 if (!(prop in self)) {
676 self[prop] = instance[prop];
679 delete Class.prototyping;
688 inherit: function(object, properties) {
689 for ( var key in properties) {
690 var override = properties[key];
691 var previous = object[key];
692 var type = $.type(override);
693 if (previous && type == 'function') {
694 if (override != previous) {
695 Class.override(object, key, override);
697 } else if (type == 'object') {
698 object[key] = $.merge(previous, override);
700 object[key] = override;
706 override: function(object, name, method) {
707 var parent = Class.prototyping;
708 if (parent && object[name] != parent[name])
710 var override = function() {
711 var previous = this.parent;
712 this.parent = parent ? parent[name] : object[name];
713 var value = method.apply(this, arguments);
714 this.parent = previous;
717 object[name] = override;
722 Class.prototype.implement = function() {
723 var proto = this.prototype;
724 $.each(Array.prototype.slice.call(arguments || []), function(properties) {
725 Class.inherit(proto, properties);
735 Provides JSON utility functions.
737 Most of these functions are JSON-tree traversal and manipulation functions.
743 Clears all tree nodes having depth greater than maxLevel.
747 tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
748 maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
751 prune: function(tree, maxLevel) {
752 this.each(tree, function(elem, i) {
753 if (i == maxLevel && elem.children) {
754 delete elem.children;
762 Returns the parent node of the node having _id_ as id.
766 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
767 id - (string) The _id_ of the child node whose parent will be returned.
771 A tree JSON node if any, or false otherwise.
774 getParent: function(tree, id) {
777 var ch = tree.children;
778 if (ch && ch.length > 0) {
779 for ( var i = 0; i < ch.length; i++) {
783 var ans = this.getParent(ch[i], id);
794 Returns the subtree that matches the given id.
798 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
799 id - (string) A node *unique* identifier.
803 A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
806 getSubtree: function(tree, id) {
809 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
810 var t = this.getSubtree(ch[i], id);
819 Iterates on tree nodes with relative depth less or equal than a specified level.
823 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
824 initLevel - (number) An integer specifying the initial relative level. Usually zero.
825 toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
826 action - (function) A function that receives a node and an integer specifying the actual level of the node.
830 $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
831 alert(node.name + ' ' + depth);
835 eachLevel: function(tree, initLevel, toLevel, action) {
836 if (initLevel <= toLevel) {
837 action(tree, initLevel);
838 if(!tree.children) return;
839 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
840 this.eachLevel(ch[i], initLevel + 1, toLevel, action);
847 A JSON tree iterator.
851 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
852 action - (function) A function that receives a node.
856 $jit.json.each(tree, function(node) {
862 each: function(tree, action) {
863 this.eachLevel(tree, 0, Number.MAX_VALUE, action);
869 An object containing multiple type of transformations.
880 var Trans = $jit.Trans;
884 var makeTrans = function(transition, params){
885 params = $.splat(params);
886 return $.extend(transition, {
887 easeIn: function(pos){
888 return transition(pos, params);
890 easeOut: function(pos){
891 return 1 - transition(1 - pos, params);
893 easeInOut: function(pos){
894 return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
895 2 * (1 - pos), params)) / 2;
903 return Math.pow(p, x[0] || 6);
907 return Math.pow(2, 8 * (p - 1));
911 return 1 - Math.sin(Math.acos(p));
915 return 1 - Math.sin((1 - p) * Math.PI / 2);
918 Back: function(p, x){
920 return Math.pow(p, 2) * ((x + 1) * p - x);
925 for ( var a = 0, b = 1; 1; a += b, b /= 2) {
926 if (p >= (7 - 4 * a) / 11) {
927 value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
934 Elastic: function(p, x){
935 return Math.pow(2, 10 * --p)
936 * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
941 $.each(transitions, function(val, key){
942 Trans[key] = makeTrans(val);
946 'Quad', 'Cubic', 'Quart', 'Quint'
947 ], function(elem, i){
948 Trans[elem] = makeTrans(function(p){
958 A Class that can perform animations for generic objects.
960 If you are looking for animation transitions please take a look at the <Trans> object.
968 The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
972 var Animation = new Class( {
974 initialize: function(options){
975 this.setOptions(options);
978 setOptions: function(options){
982 transition: Trans.Quart.easeInOut,
987 this.opt = $.merge(opt, options || {});
992 var time = $.time(), opt = this.opt;
993 if (time < this.time + opt.duration) {
994 var delta = opt.transition((time - this.time) / opt.duration);
997 this.timer = clearInterval(this.timer);
1011 startTimer: function(){
1012 var that = this, fps = this.opt.fps;
1015 this.time = $.time() - this.time;
1016 this.timer = setInterval((function(){
1018 }), Math.round(1000 / fps));
1032 stopTimer: function(){
1035 this.time = $.time() - this.time;
1036 this.timer = clearInterval(this.timer);
1043 if (this.opt.link == 'cancel') {
1052 var Options = function() {
1053 var args = arguments;
1054 for(var i=0, l=args.length, ans={}; i<l; i++) {
1055 var opt = Options[args[i]];
1066 * File: Options.AreaChart.js
1071 Object: Options.AreaChart
1073 <AreaChart> options.
1074 Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1080 Options.AreaChart = {
1084 selectOnHover: true,
1085 showAggregates: true,
1087 filterOnClick: false,
1088 restoreOnRightClick: false
1097 var areaChart = new $jit.AreaChart({
1099 type: 'stacked:gradient',
1100 selectOnHover: true,
1101 filterOnClick: true,
1102 restoreOnRightClick: true
1109 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1110 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1111 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1112 selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1113 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1114 showLabels - (boolean) Default's *true*. Display the name of the slots.
1115 filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1116 restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1120 Options.AreaChart = {
1124 labelOffset: 3, // label offset
1125 type: 'stacked', // gradient
1135 selectOnHover: true,
1136 showAggregates: true,
1138 filterOnClick: false,
1139 restoreOnRightClick: false
1143 * File: Options.Margin.js
1148 Object: Options.Margin
1150 Canvas drawing margins.
1169 var viz = new $jit.Viz({
1180 top - (number) Default's *0*. Top margin.
1181 left - (number) Default's *0*. Left margin.
1182 right - (number) Default's *0*. Right margin.
1183 bottom - (number) Default's *0*. Bottom margin.
1197 * File: Options.Canvas.js
1202 Object: Options.Canvas
1204 These are Canvas general options, like where to append it in the DOM, its dimensions, background,
1205 and other more advanced options.
1224 var viz = new $jit.Viz({
1225 injectInto: 'someContainerId',
1233 injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
1234 width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1235 height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1236 useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1237 withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1238 background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1250 colorStop1: 'rgba(255,255,255,1)',
1251 colorStop2: 'rgba(255,255,255,0)'
1255 * File: Options.Tree.js
1260 Object: Options.Tree
1262 Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1268 orientation: "left",
1280 var st = new $jit.ST({
1281 orientation: 'left',
1290 subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1291 siblingOffset - (number) Default's 5. Separation offset between siblings.
1292 orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1293 align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1294 indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1295 multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1301 orientation: "left",
1311 * File: Options.Node.js
1316 Object: Options.Node
1318 Provides Node rendering options for Tree and Graph based visualizations.
1345 var viz = new $jit.Viz({
1357 overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1358 type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
1359 color - (string) Default's *#ccb*. Node color.
1360 alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1361 dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1362 height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1363 width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1364 autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1365 autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1366 lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1367 transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1368 align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
1369 angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1370 span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1371 CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
1391 //Raw canvas styles to be
1392 //applied to the context instance
1393 //before plotting a node
1399 * File: Options.Edge.js
1404 Object: Options.Edge
1406 Provides Edge rendering options for Tree and Graph based visualizations.
1425 var viz = new $jit.Viz({
1431 shadowColor: '#ccc',
1440 overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1441 type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
1442 color - (string) Default's '#ccb'. Edge color.
1443 lineWidth - (number) Default's *1*. Line/Edge width.
1444 alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1445 dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
1446 epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
1447 CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
1451 If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
1464 //Raw canvas styles to be
1465 //applied to the context instance
1466 //before plotting an edge
1472 * File: Options.Fx.js
1479 Provides animation options like duration of the animations, frames per second and animation transitions.
1487 transition: $jit.Trans.Quart.easeInOut,
1495 var viz = new $jit.Viz({
1498 transition: $jit.Trans.linear
1504 clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1505 duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1506 fps - (number) Default's *40*. Frames per second.
1507 transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1511 This object is used for specifying different animation transitions in all visualizations.
1513 There are many different type of animation transitions.
1517 Displays a linear transition
1525 Displays a Quadratic transition.
1529 >Trans.Quad.easeInOut
1535 Displays a Cubic transition.
1538 >Trans.Cubic.easeOut
1539 >Trans.Cubic.easeInOut
1545 Displays a Quartetic transition.
1548 >Trans.Quart.easeOut
1549 >Trans.Quart.easeInOut
1555 Displays a Quintic transition.
1558 >Trans.Quint.easeOut
1559 >Trans.Quint.easeInOut
1565 Displays an Exponential transition.
1569 >Trans.Expo.easeInOut
1575 Displays a Circular transition.
1579 >Trans.Circ.easeInOut
1585 Displays a Sineousidal transition.
1589 >Trans.Sine.easeInOut
1597 >Trans.Back.easeInOut
1605 >Trans.Bounce.easeIn
1606 >Trans.Bounce.easeOut
1607 >Trans.Bounce.easeInOut
1615 >Trans.Elastic.easeIn
1616 >Trans.Elastic.easeOut
1617 >Trans.Elastic.easeInOut
1623 Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
1632 transition: $jit.Trans.Quart.easeInOut,
1637 * File: Options.Label.js
1641 Object: Options.Label
1643 Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.
1650 type: 'HTML', //'SVG', 'Native'
1653 family: 'sans-serif',
1654 textAlign: 'center',
1655 textBaseline: 'alphabetic',
1663 var viz = new $jit.Viz({
1674 overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1675 type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1676 style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1677 size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1678 family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1679 color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1685 type: 'HTML', //'SVG', 'Native'
1688 family: 'sans-serif',
1689 textAlign: 'center',
1690 textBaseline: 'alphabetic',
1696 * File: Options.Tips.js
1701 Object: Options.Tips
1721 var viz = new $jit.Viz({
1727 onShow: function(tip, node) {
1728 tip.innerHTML = node.name;
1736 enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class.
1737 type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
1738 offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
1739 offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
1740 onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
1741 onHide() - This callack is used when hiding a tooltip.
1758 * File: Options.NodeStyles.js
1763 Object: Options.NodeStyles
1765 Apply different styles when a node is hovered or selected.
1770 Options.NodeStyles = {
1781 var viz = new $jit.Viz({
1796 enable - (boolean) Default's *false*. Whether to enable this option.
1797 type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
1798 stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799 stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1802 Options.NodeStyles = {
1813 * File: Options.Events.js
1818 Object: Options.Events
1820 Configuration for adding mouse/touch event handlers to Nodes.
1827 enableForEdges: false,
1830 onRightClick: $.empty,
1831 onMouseMove: $.empty,
1832 onMouseEnter: $.empty,
1833 onMouseLeave: $.empty,
1834 onDragStart: $.empty,
1835 onDragMove: $.empty,
1836 onDragCancel: $.empty,
1838 onTouchStart: $.empty,
1839 onTouchMove: $.empty,
1840 onTouchEnd: $.empty,
1841 onTouchCancel: $.empty,
1842 onMouseWheel: $.empty
1849 var viz = new $jit.Viz({
1852 onClick: function(node, eventInfo, e) {
1855 onMouseEnter: function(node, eventInfo, e) {
1856 viz.canvas.getElement().style.cursor = 'pointer';
1858 onMouseLeave: function(node, eventInfo, e) {
1859 viz.canvas.getElement().style.cursor = '';
1867 enable - (boolean) Default's *false*. Whether to enable the Event system.
1868 enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
1869 type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
1870 onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1871 onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1872 onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1873 onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1874 onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1875 onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1876 onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1877 onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1878 onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1879 onTouchStart(node, eventInfo, e) - Behaves just like onDragStart.
1880 onTouchMove(node, eventInfo, e) - Behaves just like onDragMove.
1881 onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd.
1882 onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1883 onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
1890 enableForEdges: false,
1893 onRightClick: $.empty,
1894 onMouseMove: $.empty,
1895 onMouseEnter: $.empty,
1896 onMouseLeave: $.empty,
1897 onDragStart: $.empty,
1898 onDragMove: $.empty,
1899 onDragCancel: $.empty,
1901 onTouchStart: $.empty,
1902 onTouchMove: $.empty,
1903 onTouchEnd: $.empty,
1904 onMouseWheel: $.empty
1908 * File: Options.Navigation.js
1913 Object: Options.Navigation
1915 Panning and zooming options for Graph/Tree based visualizations. These options are implemented
1916 by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1922 Options.Navigation = {
1925 panning: false, //true, 'avoid nodes'
1934 var viz = new $jit.Viz({
1937 panning: 'avoid nodes',
1945 enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1946 panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
1947 zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
1951 Options.Navigation = {
1956 panning: false, //true | 'avoid nodes'
1961 * File: Options.Controller.js
1966 Object: Options.Controller
1968 Provides controller methods. Controller methods are callback functions that get called at different stages
1969 of the animation, computing or plotting of the visualization.
1973 All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1979 Options.Controller = {
1980 onBeforeCompute: $.empty,
1981 onAfterCompute: $.empty,
1982 onCreateLabel: $.empty,
1983 onPlaceLabel: $.empty,
1984 onComplete: $.empty,
1985 onBeforePlotLine:$.empty,
1986 onAfterPlotLine: $.empty,
1987 onBeforePlotNode:$.empty,
1988 onAfterPlotNode: $.empty,
1997 var viz = new $jit.Viz({
1998 onBeforePlotNode: function(node) {
2000 node.setData('color', '#ffc');
2002 node.removeData('color');
2005 onBeforePlotLine: function(adj) {
2006 if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2007 adj.setData('color', '#ffc');
2009 adj.removeData('color');
2012 onAfterCompute: function() {
2020 onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2021 onAfterCompute() - This method is triggered after all animations or computations ended.
2022 onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
2023 onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
2024 onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
2025 onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2026 onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
2027 onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2029 *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2031 request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.
2034 Options.Controller = {
2037 onBeforeCompute: $.empty,
2038 onAfterCompute: $.empty,
2039 onCreateLabel: $.empty,
2040 onPlaceLabel: $.empty,
2041 onComplete: $.empty,
2042 onBeforePlotLine:$.empty,
2043 onAfterPlotLine: $.empty,
2044 onBeforePlotNode:$.empty,
2045 onAfterPlotNode: $.empty,
2053 * Provides Extras such as Tips and Style Effects.
2057 * Provides the <Tips> and <NodeStyles> classes and functions.
2062 * Manager for mouse events (clicking and mouse moving).
2064 * This class is used for registering objects implementing onClick
2065 * and onMousemove methods. These methods are called when clicking or
2066 * moving the mouse around the Canvas.
2067 * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2070 var ExtrasInitializer = {
2071 initialize: function(className, viz) {
2073 this.canvas = viz.canvas;
2074 this.config = viz.config[className];
2075 this.nodeTypes = viz.fx.nodeTypes;
2076 var type = this.config.type;
2077 this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2078 this.labelContainer = this.dom && viz.labels.getLabelContainer();
2079 this.isEnabled() && this.initializePost();
2081 initializePost: $.empty,
2082 setAsProperty: $.lambda(false),
2083 isEnabled: function() {
2084 return this.config.enable;
2086 isLabel: function(e, win) {
2087 e = $.event.get(e, win);
2088 var labelContainer = this.labelContainer,
2089 target = e.target || e.srcElement;
2090 if(target && target.parentNode == labelContainer)
2096 var EventsInterface = {
2098 onMouseDown: $.empty,
2099 onMouseMove: $.empty,
2100 onMouseOver: $.empty,
2101 onMouseOut: $.empty,
2102 onMouseWheel: $.empty,
2103 onTouchStart: $.empty,
2104 onTouchMove: $.empty,
2105 onTouchEnd: $.empty,
2106 onTouchCancel: $.empty
2109 var MouseEventsManager = new Class({
2110 initialize: function(viz) {
2112 this.canvas = viz.canvas;
2115 this.registeredObjects = [];
2116 this.attachEvents();
2119 attachEvents: function() {
2120 var htmlCanvas = this.canvas.getElement(),
2122 htmlCanvas.oncontextmenu = $.lambda(false);
2123 $.addEvents(htmlCanvas, {
2124 'mouseup': function(e, win) {
2125 var event = $.event.get(e, win);
2126 that.handleEvent('MouseUp', e, win,
2127 that.makeEventObject(e, win),
2128 $.event.isRightClick(event));
2130 'mousedown': function(e, win) {
2131 var event = $.event.get(e, win);
2132 that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win),
2133 $.event.isRightClick(event));
2135 'mousemove': function(e, win) {
2136 that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2138 'mouseover': function(e, win) {
2139 that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2141 'mouseout': function(e, win) {
2142 that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2144 'touchstart': function(e, win) {
2145 that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2147 'touchmove': function(e, win) {
2148 that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2150 'touchend': function(e, win) {
2151 that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2154 //attach mousewheel event
2155 var handleMouseWheel = function(e, win) {
2156 var event = $.event.get(e, win);
2157 var wheel = $.event.getWheel(event);
2158 that.handleEvent('MouseWheel', e, win, wheel);
2160 //TODO(nico): this is a horrible check for non-gecko browsers!
2161 if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2162 $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2164 htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2168 register: function(obj) {
2169 this.registeredObjects.push(obj);
2172 handleEvent: function() {
2173 var args = Array.prototype.slice.call(arguments),
2174 type = args.shift();
2175 for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2176 regs[i]['on' + type].apply(regs[i], args);
2180 makeEventObject: function(e, win) {
2182 graph = this.viz.graph,
2184 ntypes = fx.nodeTypes,
2185 etypes = fx.edgeTypes;
2191 getNodeCalled: false,
2192 getEdgeCalled: false,
2193 getPos: function() {
2194 //TODO(nico): check why this can't be cache anymore when using edge detection
2195 //if(this.pos) return this.pos;
2196 var canvas = that.viz.canvas,
2197 s = canvas.getSize(),
2198 p = canvas.getPos(),
2199 ox = canvas.translateOffsetX,
2200 oy = canvas.translateOffsetY,
2201 sx = canvas.scaleOffsetX,
2202 sy = canvas.scaleOffsetY,
2203 pos = $.event.getPos(e, win);
2205 x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2206 y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2210 getNode: function() {
2211 if(this.getNodeCalled) return this.node;
2212 this.getNodeCalled = true;
2213 for(var id in graph.nodes) {
2214 var n = graph.nodes[id],
2215 geom = n && ntypes[n.getData('type')],
2216 contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2218 this.contains = contains;
2219 return that.node = this.node = n;
2222 return that.node = this.node = false;
2224 getEdge: function() {
2225 if(this.getEdgeCalled) return this.edge;
2226 this.getEdgeCalled = true;
2228 for(var id in graph.edges) {
2229 var edgeFrom = graph.edges[id];
2231 for(var edgeId in edgeFrom) {
2232 if(edgeId in hashset) continue;
2233 var e = edgeFrom[edgeId],
2234 geom = e && etypes[e.getData('type')],
2235 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2237 this.contains = contains;
2238 return that.edge = this.edge = e;
2242 return that.edge = this.edge = false;
2244 getContains: function() {
2245 if(this.getNodeCalled) return this.contains;
2247 return this.contains;
2254 * Provides the initialization function for <NodeStyles> and <Tips> implemented
2255 * by all main visualizations.
2259 initializeExtras: function() {
2260 var mem = new MouseEventsManager(this), that = this;
2261 $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2262 var obj = new Extras.Classes[k](k, that);
2263 if(obj.isEnabled()) {
2266 if(obj.setAsProperty()) {
2267 that[k.toLowerCase()] = obj;
2273 Extras.Classes = {};
2277 This class defines an Event API to be accessed by the user.
2278 The methods implemented are the ones defined in the <Options.Events> object.
2281 Extras.Classes.Events = new Class({
2282 Implements: [ExtrasInitializer, EventsInterface],
2284 initializePost: function() {
2285 this.fx = this.viz.fx;
2286 this.ntypes = this.viz.fx.nodeTypes;
2287 this.etypes = this.viz.fx.edgeTypes;
2289 this.hovered = false;
2290 this.pressed = false;
2291 this.touched = false;
2293 this.touchMoved = false;
2298 setAsProperty: $.lambda(true),
2300 onMouseUp: function(e, win, event, isRightClick) {
2301 var evt = $.event.get(e, win);
2304 this.config.onRightClick(this.hovered, event, evt);
2306 this.config.onClick(this.pressed, event, evt);
2311 this.config.onDragEnd(this.pressed, event, evt);
2313 this.config.onDragCancel(this.pressed, event, evt);
2315 this.pressed = this.moved = false;
2319 onMouseOut: function(e, win, event) {
2321 var evt = $.event.get(e, win), label;
2322 if(this.dom && (label = this.isLabel(e, win))) {
2323 this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2325 this.hovered = false;
2329 var rt = evt.relatedTarget,
2330 canvasWidget = this.canvas.getElement();
2331 while(rt && rt.parentNode) {
2332 if(canvasWidget == rt.parentNode) return;
2336 this.config.onMouseLeave(this.hovered,
2338 this.hovered = false;
2342 onMouseOver: function(e, win, event) {
2344 var evt = $.event.get(e, win), label;
2345 if(this.dom && (label = this.isLabel(e, win))) {
2346 this.hovered = this.viz.graph.getNode(label.id);
2347 this.config.onMouseEnter(this.hovered,
2352 onMouseMove: function(e, win, event) {
2353 var label, evt = $.event.get(e, win);
2356 this.config.onDragMove(this.pressed, event, evt);
2360 this.config.onMouseMove(this.hovered,
2364 var hn = this.hovered;
2365 var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2366 var contains = geom && geom.contains
2367 && geom.contains.call(this.fx, hn, event.getPos());
2369 this.config.onMouseMove(hn, event, evt);
2372 this.config.onMouseLeave(hn, event, evt);
2373 this.hovered = false;
2376 if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2377 this.config.onMouseEnter(this.hovered, event, evt);
2379 this.config.onMouseMove(false, event, evt);
2384 onMouseWheel: function(e, win, delta) {
2385 this.config.onMouseWheel(delta, $.event.get(e, win));
2388 onMouseDown: function(e, win, event) {
2389 var evt = $.event.get(e, win);
2390 this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2391 this.config.onDragStart(this.pressed, event, evt);
2394 onTouchStart: function(e, win, event) {
2395 var evt = $.event.get(e, win);
2396 this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2397 this.config.onTouchStart(this.touched, event, evt);
2400 onTouchMove: function(e, win, event) {
2401 var evt = $.event.get(e, win);
2403 this.touchMoved = true;
2404 this.config.onTouchMove(this.touched, event, evt);
2408 onTouchEnd: function(e, win, event) {
2409 var evt = $.event.get(e, win);
2411 if(this.touchMoved) {
2412 this.config.onTouchEnd(this.touched, event, evt);
2414 this.config.onTouchCancel(this.touched, event, evt);
2416 this.touched = this.touchMoved = false;
2424 A class containing tip related functions. This class is used internally.
2428 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2435 Extras.Classes.Tips = new Class({
2436 Implements: [ExtrasInitializer, EventsInterface],
2438 initializePost: function() {
2441 var tip = $('_tooltip') || document.createElement('div');
2442 tip.id = '_tooltip';
2443 tip.className = 'tip';
2444 $.extend(tip.style, {
2445 position: 'absolute',
2449 document.body.appendChild(tip);
2455 setAsProperty: $.lambda(true),
2457 onMouseOut: function(e, win) {
2459 if(this.dom && this.isLabel(e, win)) {
2464 var rt = e.relatedTarget,
2465 canvasWidget = this.canvas.getElement();
2466 while(rt && rt.parentNode) {
2467 if(canvasWidget == rt.parentNode) return;
2473 onMouseOver: function(e, win) {
2476 if(this.dom && (label = this.isLabel(e, win))) {
2477 this.node = this.viz.graph.getNode(label.id);
2478 this.config.onShow(this.tip, this.node, label);
2482 onMouseMove: function(e, win, opt) {
2483 if(this.dom && this.isLabel(e, win)) {
2484 this.setTooltipPosition($.event.getPos(e, win));
2487 var node = opt.getNode();
2492 if(this.config.force || !this.node || this.node.id != node.id) {
2494 this.config.onShow(this.tip, node, opt.getContains());
2496 this.setTooltipPosition($.event.getPos(e, win));
2500 setTooltipPosition: function(pos) {
2505 //get window dimensions
2507 'height': document.body.clientHeight,
2508 'width': document.body.clientWidth
2510 //get tooltip dimensions
2512 'width': tip.offsetWidth,
2513 'height': tip.offsetHeight
2515 //set tooltip position
2516 var x = cont.offsetX, y = cont.offsetY;
2517 style.top = ((pos.y + y + obj.height > win.height)?
2518 (pos.y - obj.height - y) : pos.y + y) + 'px';
2519 style.left = ((pos.x + obj.width + x > win.width)?
2520 (pos.x - obj.width - x) : pos.x + x) + 'px';
2523 hide: function(triggerCallback) {
2524 if(!SUGAR.util.isTouchScreen()) {
2525 this.tip.style.display = 'none';
2527 triggerCallback && this.config.onHide();
2534 Change node styles when clicking or hovering a node. This class is used internally.
2538 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2542 <Options.NodeStyles>
2544 Extras.Classes.NodeStyles = new Class({
2545 Implements: [ExtrasInitializer, EventsInterface],
2547 initializePost: function() {
2548 this.fx = this.viz.fx;
2549 this.types = this.viz.fx.nodeTypes;
2550 this.nStyles = this.config;
2551 this.nodeStylesOnHover = this.nStyles.stylesHover;
2552 this.nodeStylesOnClick = this.nStyles.stylesClick;
2553 this.hoveredNode = false;
2554 this.fx.nodeFxAnimation = new Animation();
2560 onMouseOut: function(e, win) {
2561 this.down = this.move = false;
2562 if(!this.hoveredNode) return;
2564 if(this.dom && this.isLabel(e, win)) {
2565 this.toggleStylesOnHover(this.hoveredNode, false);
2568 var rt = e.relatedTarget,
2569 canvasWidget = this.canvas.getElement();
2570 while(rt && rt.parentNode) {
2571 if(canvasWidget == rt.parentNode) return;
2574 this.toggleStylesOnHover(this.hoveredNode, false);
2575 this.hoveredNode = false;
2578 onMouseOver: function(e, win) {
2581 if(this.dom && (label = this.isLabel(e, win))) {
2582 var node = this.viz.graph.getNode(label.id);
2583 if(node.selected) return;
2584 this.hoveredNode = node;
2585 this.toggleStylesOnHover(this.hoveredNode, true);
2589 onMouseDown: function(e, win, event, isRightClick) {
2590 if(isRightClick) return;
2592 if(this.dom && (label = this.isLabel(e, win))) {
2593 this.down = this.viz.graph.getNode(label.id);
2594 } else if(!this.dom) {
2595 this.down = event.getNode();
2600 onMouseUp: function(e, win, event, isRightClick) {
2601 if(isRightClick) return;
2603 this.onClick(event.getNode());
2605 this.down = this.move = false;
2608 getRestoredStyles: function(node, type) {
2609 var restoredStyles = {},
2610 nStyles = this['nodeStylesOn' + type];
2611 for(var prop in nStyles) {
2612 restoredStyles[prop] = node.styles['$' + prop];
2614 return restoredStyles;
2617 toggleStylesOnHover: function(node, set) {
2618 if(this.nodeStylesOnHover) {
2619 this.toggleStylesOn('Hover', node, set);
2623 toggleStylesOnClick: function(node, set) {
2624 if(this.nodeStylesOnClick) {
2625 this.toggleStylesOn('Click', node, set);
2629 toggleStylesOn: function(type, node, set) {
2631 var nStyles = this.nStyles;
2635 node.styles = $.merge(node.data, {});
2637 for(var s in this['nodeStylesOn' + type]) {
2639 if(!($s in node.styles)) {
2640 node.styles[$s] = node.getData(s);
2643 viz.fx.nodeFx($.extend({
2646 'properties': that['nodeStylesOn' + type]
2648 transition: Trans.Quart.easeOut,
2653 var restoredStyles = this.getRestoredStyles(node, type);
2654 viz.fx.nodeFx($.extend({
2657 'properties': restoredStyles
2659 transition: Trans.Quart.easeOut,
2666 onClick: function(node) {
2668 var nStyles = this.nodeStylesOnClick;
2669 if(!nStyles) return;
2670 //if the node is selected then unselect it
2672 this.toggleStylesOnClick(node, false);
2673 delete node.selected;
2675 //unselect all selected nodes...
2676 this.viz.graph.eachNode(function(n) {
2678 for(var s in nStyles) {
2679 n.setData(s, n.styles['$' + s], 'end');
2684 //select clicked node
2685 this.toggleStylesOnClick(node, true);
2686 node.selected = true;
2687 delete node.hovered;
2688 this.hoveredNode = false;
2692 onMouseMove: function(e, win, event) {
2693 //if mouse button is down and moving set move=true
2694 if(this.down) this.move = true;
2695 //already handled by mouseover/out
2696 if(this.dom && this.isLabel(e, win)) return;
2697 var nStyles = this.nodeStylesOnHover;
2698 if(!nStyles) return;
2701 if(this.hoveredNode) {
2702 var geom = this.types[this.hoveredNode.getData('type')];
2703 var contains = geom && geom.contains && geom.contains.call(this.fx,
2704 this.hoveredNode, event.getPos());
2705 if(contains) return;
2707 var node = event.getNode();
2708 //if no node is being hovered then just exit
2709 if(!this.hoveredNode && !node) return;
2710 //if the node is hovered then exit
2711 if(node.hovered) return;
2712 //select hovered node
2713 if(node && !node.selected) {
2714 //check if an animation is running and exit it
2715 this.fx.nodeFxAnimation.stopTimer();
2716 //unselect all hovered nodes...
2717 this.viz.graph.eachNode(function(n) {
2718 if(n.hovered && !n.selected) {
2719 for(var s in nStyles) {
2720 n.setData(s, n.styles['$' + s], 'end');
2725 //select hovered node
2726 node.hovered = true;
2727 this.hoveredNode = node;
2728 this.toggleStylesOnHover(node, true);
2729 } else if(this.hoveredNode && !this.hoveredNode.selected) {
2730 //check if an animation is running and exit it
2731 this.fx.nodeFxAnimation.stopTimer();
2732 //unselect hovered node
2733 this.toggleStylesOnHover(this.hoveredNode, false);
2734 delete this.hoveredNode.hovered;
2735 this.hoveredNode = false;
2741 Extras.Classes.Navigation = new Class({
2742 Implements: [ExtrasInitializer, EventsInterface],
2744 initializePost: function() {
2746 this.pressed = false;
2749 onMouseWheel: function(e, win, scroll) {
2750 if(!this.config.zooming) return;
2751 $.event.stop($.event.get(e, win));
2752 var val = this.config.zooming / 1000,
2753 ans = 1 + scroll * val;
2754 this.canvas.scale(ans, ans);
2757 onMouseDown: function(e, win, eventInfo) {
2758 if(!this.config.panning) return;
2759 if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2760 this.pressed = true;
2761 this.pos = eventInfo.getPos();
2762 var canvas = this.canvas,
2763 ox = canvas.translateOffsetX,
2764 oy = canvas.translateOffsetY,
2765 sx = canvas.scaleOffsetX,
2766 sy = canvas.scaleOffsetY;
2773 onMouseMove: function(e, win, eventInfo) {
2774 if(!this.config.panning) return;
2775 if(!this.pressed) return;
2776 if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2777 var thispos = this.pos,
2778 currentPos = eventInfo.getPos(),
2779 canvas = this.canvas,
2780 ox = canvas.translateOffsetX,
2781 oy = canvas.translateOffsetY,
2782 sx = canvas.scaleOffsetX,
2783 sy = canvas.scaleOffsetY;
2788 var x = currentPos.x - thispos.x,
2789 y = currentPos.y - thispos.y;
2790 this.pos = currentPos;
2791 this.canvas.translate(x * 1/sx, y * 1/sy);
2794 onMouseUp: function(e, win, eventInfo, isRightClick) {
2795 if(!this.config.panning) return;
2796 this.pressed = false;
2809 A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to
2810 know more about <Canvas> options take a look at <Options.Canvas>.
2812 A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior
2813 across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2817 Suppose we have this HTML
2820 <div id="infovis"></div>
2823 Now we create a new Visualization
2826 var viz = new $jit.Viz({
2827 //Where to inject the canvas. Any div container will do.
2828 'injectInto':'infovis',
2829 //width and height for canvas.
2830 //Default's to the container offsetWidth and Height.
2836 The generated HTML will look like this
2840 <div id="infovis-canvaswidget" style="position:relative;">
2841 <canvas id="infovis-canvas" width=900 height=500
2842 style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2843 <div id="infovis-label"
2844 style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2850 As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2851 of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2856 //check for native canvas support
2857 var canvasType = typeof HTMLCanvasElement,
2858 supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2859 //create element function
2860 function $E(tag, props) {
2861 var elem = document.createElement(tag);
2862 for(var p in props) {
2863 if(typeof props[p] == "object") {
2864 $.extend(elem[p], props[p]);
2869 if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2870 elem = G_vmlCanvasManager.initElement(elem);
2874 //canvas widget which we will call just Canvas
2875 $jit.Canvas = Canvas = new Class({
2879 labelContainer: false,
2880 translateOffsetX: 0,
2881 translateOffsetY: 0,
2885 initialize: function(viz, opt) {
2888 var id = $.type(opt.injectInto) == 'string'?
2889 opt.injectInto:opt.injectInto.id,
2890 idLabel = id + "-label",
2892 width = opt.width || wrapper.offsetWidth,
2893 height = opt.height || wrapper.offsetHeight;
2896 var canvasOptions = {
2901 //create main wrapper
2902 this.element = $E('div', {
2903 'id': id + '-canvaswidget',
2905 'position': 'relative',
2906 'width': width + 'px',
2907 'height': height + 'px'
2910 //create label container
2911 this.labelContainer = this.createLabelContainer(opt.Label.type,
2912 idLabel, canvasOptions);
2913 //create primary canvas
2914 this.canvases.push(new Canvas.Base({
2915 config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2916 plot: function(base) {
2919 resize: function() {
2923 //create secondary canvas
2924 var back = opt.background;
2926 var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2927 this.canvases.push(new Canvas.Base(backCanvas));
2930 var len = this.canvases.length;
2932 this.element.appendChild(this.canvases[len].canvas);
2934 this.canvases[len].plot();
2937 this.element.appendChild(this.labelContainer);
2938 wrapper.appendChild(this.element);
2939 //Update canvas position when the page is scrolled.
2940 var timer = null, that = this;
2941 $.addEvent(window, 'scroll', function() {
2942 clearTimeout(timer);
2943 timer = setTimeout(function() {
2944 that.getPos(true); //update canvas position
2947 $.addEvent(window, 'click', function() {
2948 clearTimeout(timer);
2949 timer = setTimeout(function() {
2950 that.getPos(true); //update canvas position
2953 sb = document.getElementById('sb'+id);
2954 $.addEvent(sb, 'scroll', function() {
2955 clearTimeout(timer);
2956 timer = setTimeout(function() {
2957 that.getPos(true); //update canvas position
2964 Returns the main canvas context object
2969 var ctx = canvas.getCtx();
2970 //Now I can use the native canvas context
2971 //and for example change some canvas styles
2972 ctx.globalAlpha = 1;
2975 getCtx: function(i) {
2976 return this.canvases[i || 0].getCtx();
2981 Returns the current Configuration for this Canvas Widget.
2986 var config = canvas.getConfig();
2989 getConfig: function() {
2995 Returns the main Canvas DOM wrapper
3000 var wrapper = canvas.getElement();
3001 //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3004 getElement: function() {
3005 return this.element;
3010 Returns canvas dimensions.
3014 An object with *width* and *height* properties.
3018 canvas.getSize(); //returns { width: 900, height: 500 }
3021 getSize: function(i) {
3022 return this.canvases[i || 0].getSize();
3031 width - New canvas width.
3032 height - New canvas height.
3037 canvas.resize(width, height);
3041 resize: function(width, height) {
3043 this.translateOffsetX = this.translateOffsetY = 0;
3044 this.scaleOffsetX = this.scaleOffsetY = 1;
3045 for(var i=0, l=this.canvases.length; i<l; i++) {
3046 this.canvases[i].resize(width, height);
3048 var style = this.element.style;
3049 style.width = width + 'px';
3050 style.height = height + 'px';
3051 if(this.labelContainer)
3052 this.labelContainer.style.width = width + 'px';
3057 Applies a translation to the canvas.
3061 x - (number) x offset.
3062 y - (number) y offset.
3063 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3068 canvas.translate(30, 30);
3072 translate: function(x, y, disablePlot) {
3073 this.translateOffsetX += x*this.scaleOffsetX;
3074 this.translateOffsetY += y*this.scaleOffsetY;
3075 for(var i=0, l=this.canvases.length; i<l; i++) {
3076 this.canvases[i].translate(x, y, disablePlot);
3086 x - (number) scale value.
3087 y - (number) scale value.
3088 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3093 canvas.scale(0.5, 0.5);
3097 scale: function(x, y, disablePlot) {
3099 disablePlot = false;
3101 var px = this.scaleOffsetX * x,
3102 py = this.scaleOffsetY * y;
3103 var dx = this.translateOffsetX * (x -1) / px,
3104 dy = this.translateOffsetY * (y -1) / py;
3105 this.scaleOffsetX = px;
3106 this.scaleOffsetY = py;
3107 for(var i=0, l=this.canvases.length; i<l; i++) {
3108 this.canvases[i].scale(x, y, true);
3110 this.translate(dx, dy, disablePlot);
3115 Returns the canvas position as an *x, y* object.
3119 force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3123 An object with *x* and *y* properties.
3127 canvas.getPos(true); //returns { x: 900, y: 500 }
3130 getPos: function(force){
3131 if(force || !this.pos) {
3132 return this.pos = $.getPos(this.getElement());
3142 this.canvases[i||0].clear();
3145 path: function(type, action){
3146 var ctx = this.canvases[0].getCtx();
3153 createLabelContainer: function(type, idLabel, dim) {
3154 var NS = 'http://www.w3.org/2000/svg';
3155 if(type == 'HTML' || type == 'Native') {
3159 'overflow': 'visible',
3160 'position': 'absolute',
3163 'width': dim.width + 'px',
3167 } else if(type == 'SVG') {
3168 var svgContainer = document.createElementNS(NS, 'svg:svg');
3169 svgContainer.setAttribute("width", dim.width);
3170 svgContainer.setAttribute('height', dim.height);
3171 var style = svgContainer.style;
3172 style.position = 'absolute';
3173 style.left = style.top = '0px';
3174 var labelContainer = document.createElementNS(NS, 'svg:g');
3175 labelContainer.setAttribute('width', dim.width);
3176 labelContainer.setAttribute('height', dim.height);
3177 labelContainer.setAttribute('x', 0);
3178 labelContainer.setAttribute('y', 0);
3179 labelContainer.setAttribute('id', idLabel);
3180 svgContainer.appendChild(labelContainer);
3181 return svgContainer;
3185 //base canvas wrapper
3186 Canvas.Base = new Class({
3187 translateOffsetX: 0,
3188 translateOffsetY: 0,
3192 initialize: function(viz) {
3194 this.opt = viz.config;
3196 this.createCanvas();
3197 this.translateToCenter();
3199 createCanvas: function() {
3202 height = opt.height;
3203 this.canvas = $E('canvas', {
3204 'id': opt.injectInto + opt.idSuffix,
3208 'position': 'absolute',
3211 'width': width + 'px',
3212 'height': height + 'px'
3216 getCtx: function() {
3218 return this.ctx = this.canvas.getContext('2d');
3221 getSize: function() {
3222 if(this.size) return this.size;
3223 var canvas = this.canvas;
3224 return this.size = {
3225 width: canvas.width,
3226 height: canvas.height
3229 translateToCenter: function(ps) {
3230 var size = this.getSize(),
3231 width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3232 height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3233 var ctx = this.getCtx();
3234 ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3235 ctx.translate(width/2, height/2);
3237 resize: function(width, height) {
3238 var size = this.getSize(),
3239 canvas = this.canvas,
3240 styles = canvas.style;
3242 canvas.width = width;
3243 canvas.height = height;
3244 styles.width = width + "px";
3245 styles.height = height + "px";
3246 //small ExCanvas fix
3247 if (!supportsCanvas) {
3248 // for some unknown reason _resize() method is not being called in some
3249 // IE browser instances, so call it manually
3250 // in obfuscated version of flashcanvas.js the method is renamed to I()
3251 this.getCtx().I(width, height);
3253 this.translateToCenter();
3254 this.translateOffsetX =
3255 this.translateOffsetY = 0;
3257 this.scaleOffsetY = 1;
3259 this.viz.resize(width, height, this);
3261 translate: function(x, y, disablePlot) {
3262 var sx = this.scaleOffsetX,
3263 sy = this.scaleOffsetY;
3264 this.translateOffsetX += x*sx;
3265 this.translateOffsetY += y*sy;
3266 this.getCtx().translate(x, y);
3267 !disablePlot && this.plot();
3269 scale: function(x, y, disablePlot) {
3270 this.scaleOffsetX *= x;
3271 this.scaleOffsetY *= y;
3272 this.getCtx().scale(x, y);
3273 !disablePlot && this.plot();
3276 var size = this.getSize(),
3277 ox = this.translateOffsetX,
3278 oy = this.translateOffsetY,
3279 sx = this.scaleOffsetX,
3280 sy = this.scaleOffsetY;
3281 this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx,
3282 (-size.height / 2 - oy) * 1/sy,
3283 size.width * 1/sx, size.height * 1/sy);
3287 this.viz.plot(this);
3290 //background canvases
3291 //TODO(nico): document this!
3292 Canvas.Background = {};
3293 Canvas.Background.Circles = new Class({
3294 initialize: function(viz, options) {
3296 this.config = $.merge({
3297 idSuffix: '-bkcanvas',
3304 resize: function(width, height, base) {
3307 plot: function(base) {
3308 var canvas = base.canvas,
3309 ctx = base.getCtx(),
3311 styles = conf.CanvasStyles;
3313 for(var s in styles) ctx[s] = styles[s];
3314 var n = conf.numberOfCircles,
3315 rho = conf.levelDistance;
3316 for(var i=1; i<=n; i++) {
3318 ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3322 //TODO(nico): print labels too!
3325 Canvas.Background.Fade = new Class({
3326 initialize: function(viz, options) {
3328 this.config = $.merge({
3329 idSuffix: '-bkcanvas',
3334 resize: function(width, height, base) {
3337 plot: function(base) {
3338 var canvas = base.canvas,
3339 ctx = base.getCtx(),
3341 styles = conf.CanvasStyles,
3342 size = base.getSize();
3343 ctx.fillStyle = 'rgb(255,255,255)';
3344 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3345 //TODO(nico): print labels too!
3354 * Defines the <Polar> class.
3358 * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3362 * <http://en.wikipedia.org/wiki/Polar_coordinates>
3369 A multi purpose polar representation.
3373 The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3377 <http://en.wikipedia.org/wiki/Polar_coordinates>
3385 var Polar = function(theta, rho) {
3396 Returns a complex number.
3400 simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3406 getc: function(simple) {
3407 return this.toComplex(simple);
3413 Returns a <Polar> representation.
3417 A variable in polar coordinates.
3431 v - A <Complex> or <Polar> instance.
3436 this.theta = v.theta; this.rho = v.rho;
3442 Sets a <Complex> number.
3446 x - A <Complex> number real part.
3447 y - A <Complex> number imaginary part.
3450 setc: function(x, y) {
3451 this.rho = Math.sqrt(x * x + y * y);
3452 this.theta = Math.atan2(y, x);
3453 if(this.theta < 0) this.theta += Math.PI * 2;
3459 Sets a polar number.
3463 theta - A <Polar> number angle property.
3464 rho - A <Polar> number rho property.
3467 setp: function(theta, rho) {
3475 Returns a copy of the current object.
3479 A copy of the real object.
3482 return new Polar(this.theta, this.rho);
3488 Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3492 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*.
3496 A new <Complex> instance.
3498 toComplex: function(simple) {
3499 var x = Math.cos(this.theta) * this.rho;
3500 var y = Math.sin(this.theta) * this.rho;
3501 if(simple) return { 'x': x, 'y': y};
3502 return new Complex(x, y);
3508 Adds two <Polar> instances.
3512 polar - A <Polar> number.
3516 A new Polar instance.
3518 add: function(polar) {
3519 return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3525 Scales a polar norm.
3529 number - A scale factor.
3533 A new Polar instance.
3535 scale: function(number) {
3536 return new Polar(this.theta, this.rho * number);
3544 Returns *true* if the theta and rho properties are equal.
3548 c - A <Polar> number.
3552 *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3554 equals: function(c) {
3555 return this.theta == c.theta && this.rho == c.rho;
3561 Adds two <Polar> instances affecting the current object.
3565 polar - A <Polar> instance.
3571 $add: function(polar) {
3572 this.theta = this.theta + polar.theta; this.rho += polar.rho;
3579 Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3583 polar - A <Polar> instance.
3589 $madd: function(polar) {
3590 this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3598 Scales a polar instance affecting the object.
3602 number - A scaling factor.
3608 $scale: function(number) {
3616 Calculates a polar interpolation between two points at a given delta moment.
3620 elem - A <Polar> instance.
3621 delta - A delta factor ranging [0, 1].
3625 A new <Polar> instance representing an interpolation between _this_ and _elem_
3627 interpolate: function(elem, delta) {
3628 var pi = Math.PI, pi2 = pi * 2;
3629 var ch = function(t) {
3630 var a = (t < 0)? (t % pi2) + pi2 : t % pi2;
3633 var tt = this.theta, et = elem.theta;
3634 var sum, diff = Math.abs(tt - et);
3637 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3639 sum = ch((et - pi2 + (tt - (et)) * delta));
3641 } else if(diff >= pi) {
3643 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3645 sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3648 sum = ch((et + (tt - et) * delta)) ;
3650 var r = (this.rho - elem.rho) * delta + elem.rho;
3659 var $P = function(a, b) { return new Polar(a, b); };
3661 Polar.KER = $P(0, 0);
3668 * Defines the <Complex> class.
3672 * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3676 * <http://en.wikipedia.org/wiki/Complex_number>
3683 A multi-purpose Complex Class with common methods.
3687 The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3691 <http://en.wikipedia.org/wiki/Complex_number>
3695 x - _optional_ A Complex number real part.
3696 y - _optional_ A Complex number imaginary part.
3700 var Complex = function(x, y) {
3705 $jit.Complex = Complex;
3707 Complex.prototype = {
3711 Returns a complex number.
3724 Returns a <Polar> representation of this number.
3728 simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3732 A variable in <Polar> coordinates.
3734 getp: function(simple) {
3735 return this.toPolar(simple);
3746 c - A <Complex> or <Polar> instance.
3758 Sets a complex number.
3762 x - A <Complex> number Real part.
3763 y - A <Complex> number Imaginary part.
3766 setc: function(x, y) {
3774 Sets a polar number.
3778 theta - A <Polar> number theta property.
3779 rho - A <Polar> number rho property.
3782 setp: function(theta, rho) {
3783 this.x = Math.cos(theta) * rho;
3784 this.y = Math.sin(theta) * rho;
3790 Returns a copy of the current object.
3794 A copy of the real object.
3797 return new Complex(this.x, this.y);
3803 Transforms cartesian to polar coordinates.
3807 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*.
3811 A new <Polar> instance.
3814 toPolar: function(simple) {
3815 var rho = this.norm();
3816 var atan = Math.atan2(this.y, this.x);
3817 if(atan < 0) atan += Math.PI * 2;
3818 if(simple) return { 'theta': atan, 'rho': rho };
3819 return new Polar(atan, rho);
3824 Calculates a <Complex> number norm.
3828 A real number representing the complex norm.
3831 return Math.sqrt(this.squaredNorm());
3837 Calculates a <Complex> number squared norm.
3841 A real number representing the complex squared norm.
3843 squaredNorm: function () {
3844 return this.x*this.x + this.y*this.y;
3850 Returns the result of adding two complex numbers.
3852 Does not alter the original object.
3856 pos - A <Complex> instance.
3860 The result of adding two complex numbers.
3862 add: function(pos) {
3863 return new Complex(this.x + pos.x, this.y + pos.y);
3869 Returns the result of multiplying two <Complex> numbers.
3871 Does not alter the original object.
3875 pos - A <Complex> instance.
3879 The result of multiplying two complex numbers.
3881 prod: function(pos) {
3882 return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3888 Returns the conjugate of this <Complex> number.
3890 Does not alter the original object.
3894 The conjugate of this <Complex> number.
3896 conjugate: function() {
3897 return new Complex(this.x, -this.y);
3904 Returns the result of scaling a <Complex> instance.
3906 Does not alter the original object.
3910 factor - A scale factor.
3914 The result of scaling this complex to a factor.
3916 scale: function(factor) {
3917 return new Complex(this.x * factor, this.y * factor);
3925 Returns *true* if both real and imaginary parts are equal.
3929 c - A <Complex> instance.
3933 A boolean instance indicating if both <Complex> numbers are equal.
3935 equals: function(c) {
3936 return this.x == c.x && this.y == c.y;
3942 Returns the result of adding two <Complex> numbers.
3944 Alters the original object.
3948 pos - A <Complex> instance.
3952 The result of adding two complex numbers.
3954 $add: function(pos) {
3955 this.x += pos.x; this.y += pos.y;
3962 Returns the result of multiplying two <Complex> numbers.
3964 Alters the original object.
3968 pos - A <Complex> instance.
3972 The result of multiplying two complex numbers.
3974 $prod:function(pos) {
3975 var x = this.x, y = this.y;
3976 this.x = x*pos.x - y*pos.y;
3977 this.y = y*pos.x + x*pos.y;
3984 Returns the conjugate for this <Complex>.
3986 Alters the original object.
3990 The conjugate for this complex.
3992 $conjugate: function() {
4000 Returns the result of scaling a <Complex> instance.
4002 Alters the original object.
4006 factor - A scale factor.
4010 The result of scaling this complex to a factor.
4012 $scale: function(factor) {
4013 this.x *= factor; this.y *= factor;
4020 Returns the division of two <Complex> numbers.
4022 Alters the original object.
4026 pos - A <Complex> number.
4030 The result of scaling this complex to a factor.
4032 $div: function(pos) {
4033 var x = this.x, y = this.y;
4034 var sq = pos.squaredNorm();
4035 this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4036 return this.$scale(1 / sq);
4040 var $C = function(a, b) { return new Complex(a, b); };
4042 Complex.KER = $C(0, 0);
4054 A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4056 An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4061 //create new visualization
4062 var viz = new $jit.Viz(options);
4066 viz.graph; //<Graph> instance
4071 The following <Graph.Util> methods are implemented in <Graph>
4073 - <Graph.Util.getNode>
4074 - <Graph.Util.eachNode>
4075 - <Graph.Util.computeLevels>
4076 - <Graph.Util.eachBFS>
4077 - <Graph.Util.clean>
4078 - <Graph.Util.getClosestNodeToPos>
4079 - <Graph.Util.getClosestNodeToOrigin>
4083 $jit.Graph = new Class({
4085 initialize: function(opt, Node, Edge, Label) {
4086 var innerOptions = {
4093 this.opt = $.merge(innerOptions, opt || {});
4097 //add nodeList methods
4100 for(var p in Accessors) {
4101 that.nodeList[p] = (function(p) {
4103 var args = Array.prototype.slice.call(arguments);
4104 that.eachNode(function(n) {
4105 n[p].apply(n, args);
4116 Returns a <Graph.Node> by *id*.
4120 id - (string) A <Graph.Node> id.
4125 var node = graph.getNode('nodeId');
4128 getNode: function(id) {
4129 if(this.hasNode(id)) return this.nodes[id];
4136 Returns a <Graph.Node> by *name*.
4140 name - (string) A <Graph.Node> name.
4145 var node = graph.getByName('someName');
4148 getByName: function(name) {
4149 for(var id in this.nodes) {
4150 var n = this.nodes[id];
4151 if(n.name == name) return n;
4157 Method: getAdjacence
4159 Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4163 id - (string) A <Graph.Node> id.
4164 id2 - (string) A <Graph.Node> id.
4166 getAdjacence: function (id, id2) {
4167 if(id in this.edges) {
4168 return this.edges[id][id2];
4180 obj - An object with the properties described below
4182 id - (string) A node id
4183 name - (string) A node's name
4184 data - (object) A node's data hash
4190 addNode: function(obj) {
4191 if(!this.nodes[obj.id]) {
4192 var edges = this.edges[obj.id] = {};
4193 this.nodes[obj.id] = new Graph.Node($.extend({
4196 'data': $.merge(obj.data || {}, {}),
4197 'adjacencies': edges
4204 return this.nodes[obj.id];
4208 Method: addAdjacence
4210 Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4214 obj - (object) A <Graph.Node> object.
4215 obj2 - (object) Another <Graph.Node> object.
4216 data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4220 <Graph.Node>, <Graph.Adjacence>
4222 addAdjacence: function (obj, obj2, data) {
4223 if(!this.hasNode(obj.id)) { this.addNode(obj); }
4224 if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4225 obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4226 if(!obj.adjacentTo(obj2)) {
4227 var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4228 var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4229 adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4230 return adjsObj[obj2.id];
4232 return this.edges[obj.id][obj2.id];
4238 Removes a <Graph.Node> matching the specified *id*.
4242 id - (string) A node's id.
4245 removeNode: function(id) {
4246 if(this.hasNode(id)) {
4247 delete this.nodes[id];
4248 var adjs = this.edges[id];
4249 for(var to in adjs) {
4250 delete this.edges[to][id];
4252 delete this.edges[id];
4257 Method: removeAdjacence
4259 Removes a <Graph.Adjacence> matching *id1* and *id2*.
4263 id1 - (string) A <Graph.Node> id.
4264 id2 - (string) A <Graph.Node> id.
4266 removeAdjacence: function(id1, id2) {
4267 delete this.edges[id1][id2];
4268 delete this.edges[id2][id1];
4274 Returns a boolean indicating if the node belongs to the <Graph> or not.
4278 id - (string) Node id.
4280 hasNode: function(id) {
4281 return id in this.nodes;
4290 empty: function() { this.nodes = {}; this.edges = {};}
4294 var Graph = $jit.Graph;
4299 Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4305 var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4307 type = type || 'current';
4308 prefix = "$" + (prefix ? prefix + "-" : "");
4310 if(type == 'current') {
4312 } else if(type == 'start') {
4313 data = this.startData;
4314 } else if(type == 'end') {
4315 data = this.endData;
4318 var dollar = prefix + prop;
4321 return data[dollar];
4324 if(!this.Config.overridable)
4325 return prefixConfig[prop] || 0;
4327 return (dollar in data) ?
4328 data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4331 var setDataInternal = function(prefix, prop, value, type) {
4332 type = type || 'current';
4333 prefix = '$' + (prefix ? prefix + '-' : '');
4337 if(type == 'current') {
4339 } else if(type == 'start') {
4340 data = this.startData;
4341 } else if(type == 'end') {
4342 data = this.endData;
4345 data[prefix + prop] = value;
4348 var removeDataInternal = function(prefix, properties) {
4349 prefix = '$' + (prefix ? prefix + '-' : '');
4351 $.each(properties, function(prop) {
4352 var pref = prefix + prop;
4353 delete that.data[pref];
4354 delete that.endData[pref];
4355 delete that.startData[pref];
4363 Returns the specified data value property.
4364 This is useful for querying special/reserved <Graph.Node> data properties
4365 (i.e dollar prefixed properties).
4369 prop - (string) The name of the property. The dollar sign is not needed. For
4370 example *getData(width)* will return *data.$width*.
4371 type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4372 data properties also. These properties are used when making animations.
4373 force - (boolean) Whether to obtain the true value of the property (equivalent to
4374 *data.$prop*) or to check for *node.overridable = true* first.
4378 The value of the dollar prefixed property or the global Node/Edge property
4379 value if *overridable=false*
4383 node.getData('width'); //will return node.data.$width if Node.overridable=true;
4386 getData: function(prop, type, force) {
4387 return getDataInternal.call(this, "", prop, type, force, this.Config);
4394 Sets the current data property with some specific value.
4395 This method is only useful for reserved (dollar prefixed) properties.
4399 prop - (string) The name of the property. The dollar sign is not necessary. For
4400 example *setData(width)* will set *data.$width*.
4401 value - (mixed) The value to store.
4402 type - (string) The type of the data property to store. Default's "current" but
4403 can also be "start" or "end".
4408 node.setData('width', 30);
4411 If we were to make an animation of a node/edge width then we could do
4414 var node = viz.getNode('nodeId');
4415 //set start and end values
4416 node.setData('width', 10, 'start');
4417 node.setData('width', 30, 'end');
4418 //will animate nodes width property
4420 modes: ['node-property:width'],
4425 setData: function(prop, value, type) {
4426 setDataInternal.call(this, "", prop, value, type);
4432 Convenience method to set multiple data values at once.
4436 types - (array|string) A set of 'current', 'end' or 'start' values.
4437 obj - (object) A hash containing the names and values of the properties to be altered.
4441 node.setDataset(['current', 'end'], {
4443 'color': ['#fff', '#ccc']
4446 node.setDataset('end', {
4457 setDataset: function(types, obj) {
4458 types = $.splat(types);
4459 for(var attr in obj) {
4460 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4461 this.setData(attr, val[i], types[i]);
4469 Remove data properties.
4473 One or more property names as arguments. The dollar sign is not needed.
4477 node.removeData('width'); //now the default width value is returned
4480 removeData: function() {
4481 removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4485 Method: getCanvasStyle
4487 Returns the specified canvas style data value property. This is useful for
4488 querying special/reserved <Graph.Node> canvas style data properties (i.e.
4489 dollar prefixed properties that match with $canvas-<name of canvas style>).
4493 prop - (string) The name of the property. The dollar sign is not needed. For
4494 example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4495 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4496 data properties also.
4500 node.getCanvasStyle('shadowBlur');
4507 getCanvasStyle: function(prop, type, force) {
4508 return getDataInternal.call(
4509 this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4513 Method: setCanvasStyle
4515 Sets the canvas style data property with some specific value.
4516 This method is only useful for reserved (dollar prefixed) properties.
4520 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4521 value - (mixed) The value to set to the property.
4522 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4527 node.setCanvasStyle('shadowBlur', 30);
4530 If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4533 var node = viz.getNode('nodeId');
4534 //set start and end values
4535 node.setCanvasStyle('shadowBlur', 10, 'start');
4536 node.setCanvasStyle('shadowBlur', 30, 'end');
4537 //will animate nodes canvas style property for nodes
4539 modes: ['node-style:shadowBlur'],
4546 <Accessors.setData>.
4548 setCanvasStyle: function(prop, value, type) {
4549 setDataInternal.call(this, 'canvas', prop, value, type);
4553 Method: setCanvasStyles
4555 Convenience method to set multiple styles at once.
4559 types - (array|string) A set of 'current', 'end' or 'start' values.
4560 obj - (object) A hash containing the names and values of the properties to be altered.
4564 <Accessors.setDataset>.
4566 setCanvasStyles: function(types, obj) {
4567 types = $.splat(types);
4568 for(var attr in obj) {
4569 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4570 this.setCanvasStyle(attr, val[i], types[i]);
4576 Method: removeCanvasStyle
4578 Remove canvas style properties from data.
4582 A variable number of canvas style strings.
4586 <Accessors.removeData>.
4588 removeCanvasStyle: function() {
4589 removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4593 Method: getLabelData
4595 Returns the specified label data value property. This is useful for
4596 querying special/reserved <Graph.Node> label options (i.e.
4597 dollar prefixed properties that match with $label-<name of label style>).
4601 prop - (string) The name of the property. The dollar sign prefix is not needed. For
4602 example *getLabelData(size)* will return *data[$label-size]*.
4603 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4604 data properties also.
4608 <Accessors.getData>.
4610 getLabelData: function(prop, type, force) {
4611 return getDataInternal.call(
4612 this, 'label', prop, type, force, this.Label);
4616 Method: setLabelData
4618 Sets the current label data with some specific value.
4619 This method is only useful for reserved (dollar prefixed) properties.
4623 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4624 value - (mixed) The value to set to the property.
4625 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4630 node.setLabelData('size', 30);
4633 If we were to make an animation of a node label size then we could do
4636 var node = viz.getNode('nodeId');
4637 //set start and end values
4638 node.setLabelData('size', 10, 'start');
4639 node.setLabelData('size', 30, 'end');
4640 //will animate nodes label size
4642 modes: ['label-property:size'],
4649 <Accessors.setData>.
4651 setLabelData: function(prop, value, type) {
4652 setDataInternal.call(this, 'label', prop, value, type);
4656 Method: setLabelDataset
4658 Convenience function to set multiple label data at once.
4662 types - (array|string) A set of 'current', 'end' or 'start' values.
4663 obj - (object) A hash containing the names and values of the properties to be altered.
4667 <Accessors.setDataset>.
4669 setLabelDataset: function(types, obj) {
4670 types = $.splat(types);
4671 for(var attr in obj) {
4672 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4673 this.setLabelData(attr, val[i], types[i]);
4679 Method: removeLabelData
4681 Remove label properties from data.
4685 A variable number of label property strings.
4689 <Accessors.removeData>.
4691 removeLabelData: function() {
4692 removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4704 <Accessors> methods.
4706 The following <Graph.Util> methods are implemented by <Graph.Node>
4708 - <Graph.Util.eachAdjacency>
4709 - <Graph.Util.eachLevel>
4710 - <Graph.Util.eachSubgraph>
4711 - <Graph.Util.eachSubnode>
4712 - <Graph.Util.anySubnode>
4713 - <Graph.Util.getSubnodes>
4714 - <Graph.Util.getParents>
4715 - <Graph.Util.isDescendantOf>
4717 Graph.Node = new Class({
4719 initialize: function(opt, complex, Node, Edge, Label) {
4720 var innerOptions = {
4737 'pos': (complex && $C(0, 0)) || $P(0, 0),
4738 'startPos': (complex && $C(0, 0)) || $P(0, 0),
4739 'endPos': (complex && $C(0, 0)) || $P(0, 0)
4742 $.extend(this, $.extend(innerOptions, opt));
4743 this.Config = this.Node = Node;
4751 Indicates if the node is adjacent to the node specified by id
4755 id - (string) A node id.
4759 node.adjacentTo('nodeId') == true;
4762 adjacentTo: function(node) {
4763 return node.id in this.adjacencies;
4767 Method: getAdjacency
4769 Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4773 id - (string) A node id.
4775 getAdjacency: function(id) {
4776 return this.adjacencies[id];
4782 Returns the position of the node.
4786 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4790 A <Complex> or <Polar> instance.
4794 var pos = node.getPos('end');
4797 getPos: function(type) {
4798 type = type || "current";
4799 if(type == "current") {
4801 } else if(type == "end") {
4803 } else if(type == "start") {
4804 return this.startPos;
4810 Sets the node's position.
4814 value - (object) A <Complex> or <Polar> instance.
4815 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4819 node.setPos(new $jit.Complex(0, 0), 'end');
4822 setPos: function(value, type) {
4823 type = type || "current";
4825 if(type == "current") {
4827 } else if(type == "end") {
4829 } else if(type == "start") {
4830 pos = this.startPos;
4836 Graph.Node.implement(Accessors);
4839 Class: Graph.Adjacence
4841 A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4845 <Accessors> methods.
4849 <Graph>, <Graph.Node>
4853 nodeFrom - A <Graph.Node> connected by this edge.
4854 nodeTo - Another <Graph.Node> connected by this edge.
4855 data - Node data property containing a hash (i.e {}) with custom options.
4857 Graph.Adjacence = new Class({
4859 initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4860 this.nodeFrom = nodeFrom;
4861 this.nodeTo = nodeTo;
4862 this.data = data || {};
4863 this.startData = {};
4865 this.Config = this.Edge = Edge;
4870 Graph.Adjacence.implement(Accessors);
4875 <Graph> traversal and processing utility object.
4879 For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4885 For internal use only. Provides a filtering function based on flags.
4887 filter: function(param) {
4888 if(!param || !($.type(param) == 'string')) return function() { return true; };
4889 var props = param.split(" ");
4890 return function(elem) {
4891 for(var i=0; i<props.length; i++) {
4892 if(elem[props[i]]) {
4902 Returns a <Graph.Node> by *id*.
4904 Also implemented by:
4910 graph - (object) A <Graph> instance.
4911 id - (string) A <Graph.Node> id.
4916 $jit.Graph.Util.getNode(graph, 'nodeid');
4918 graph.getNode('nodeid');
4921 getNode: function(graph, id) {
4922 return graph.nodes[id];
4928 Iterates over <Graph> nodes performing an *action*.
4930 Also implemented by:
4936 graph - (object) A <Graph> instance.
4937 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4941 $jit.Graph.Util.eachNode(graph, function(node) {
4945 graph.eachNode(function(node) {
4950 eachNode: function(graph, action, flags) {
4951 var filter = this.filter(flags);
4952 for(var i in graph.nodes) {
4953 if(filter(graph.nodes[i])) action(graph.nodes[i]);
4958 Method: eachAdjacency
4960 Iterates over <Graph.Node> adjacencies applying the *action* function.
4962 Also implemented by:
4968 node - (object) A <Graph.Node>.
4969 action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4973 $jit.Graph.Util.eachAdjacency(node, function(adj) {
4974 alert(adj.nodeTo.name);
4977 node.eachAdjacency(function(adj) {
4978 alert(adj.nodeTo.name);
4982 eachAdjacency: function(node, action, flags) {
4983 var adj = node.adjacencies, filter = this.filter(flags);
4984 for(var id in adj) {
4987 if(a.nodeFrom != node) {
4988 var tmp = a.nodeFrom;
4989 a.nodeFrom = a.nodeTo;
4998 Method: computeLevels
5000 Performs a BFS traversal setting the correct depth for each node.
5002 Also implemented by:
5008 The depth of each node can then be accessed by
5013 graph - (object) A <Graph>.
5014 id - (string) A starting node id for the BFS traversal.
5015 startDepth - (optional|number) A minimum depth value. Default's 0.
5018 computeLevels: function(graph, id, startDepth, flags) {
5019 startDepth = startDepth || 0;
5020 var filter = this.filter(flags);
5021 this.eachNode(graph, function(elem) {
5025 var root = graph.getNode(id);
5026 root._depth = startDepth;
5028 while(queue.length != 0) {
5029 var node = queue.pop();
5031 this.eachAdjacency(node, function(adj) {
5033 if(n._flag == false && filter(n)) {
5034 if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5044 Performs a BFS traversal applying *action* to each <Graph.Node>.
5046 Also implemented by:
5052 graph - (object) A <Graph>.
5053 id - (string) A starting node id for the BFS traversal.
5054 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5058 $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5062 graph.eachBFS('mynodeid', function(node) {
5067 eachBFS: function(graph, id, action, flags) {
5068 var filter = this.filter(flags);
5070 var queue = [graph.getNode(id)];
5071 while(queue.length != 0) {
5072 var node = queue.pop();
5074 action(node, node._depth);
5075 this.eachAdjacency(node, function(adj) {
5077 if(n._flag == false && filter(n)) {
5088 Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5090 Also implemented by:
5096 node - (object) A <Graph.Node>.
5097 levelBegin - (number) A relative level value.
5098 levelEnd - (number) A relative level value.
5099 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5102 eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5103 var d = node._depth, filter = this.filter(flags), that = this;
5104 levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5105 (function loopLevel(node, levelBegin, levelEnd) {
5106 var d = node._depth;
5107 if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5109 that.eachAdjacency(node, function(adj) {
5111 if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5114 })(node, levelBegin + d, levelEnd + d);
5118 Method: eachSubgraph
5120 Iterates over a node's children recursively.
5122 Also implemented by:
5127 node - (object) A <Graph.Node>.
5128 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5132 $jit.Graph.Util.eachSubgraph(node, function(node) {
5136 node.eachSubgraph(function(node) {
5141 eachSubgraph: function(node, action, flags) {
5142 this.eachLevel(node, 0, false, action, flags);
5148 Iterates over a node's children (without deeper recursion).
5150 Also implemented by:
5155 node - (object) A <Graph.Node>.
5156 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5160 $jit.Graph.Util.eachSubnode(node, function(node) {
5164 node.eachSubnode(function(node) {
5169 eachSubnode: function(node, action, flags) {
5170 this.eachLevel(node, 1, 1, action, flags);
5176 Returns *true* if any subnode matches the given condition.
5178 Also implemented by:
5183 node - (object) A <Graph.Node>.
5184 cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5188 $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5190 node.anySubnode(function(node) { return node.name == 'mynodename'; });
5193 anySubnode: function(node, cond, flags) {
5195 cond = cond || $.lambda(true);
5196 var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5197 this.eachSubnode(node, function(elem) {
5198 if(c(elem)) flag = true;
5206 Collects all subnodes for a specified node.
5207 The *level* parameter filters nodes having relative depth of *level* from the root node.
5209 Also implemented by:
5214 node - (object) A <Graph.Node>.
5215 level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5221 getSubnodes: function(node, level, flags) {
5222 var ans = [], that = this;
5224 var levelStart, levelEnd;
5225 if($.type(level) == 'array') {
5226 levelStart = level[0];
5227 levelEnd = level[1];
5230 levelEnd = Number.MAX_VALUE - node._depth;
5232 this.eachLevel(node, levelStart, levelEnd, function(n) {
5242 Returns an Array of <Graph.Nodes> which are parents of the given node.
5244 Also implemented by:
5249 node - (object) A <Graph.Node>.
5252 An Array of <Graph.Nodes>.
5256 var pars = $jit.Graph.Util.getParents(node);
5258 var pars = node.getParents();
5260 if(pars.length > 0) {
5261 //do stuff with parents
5265 getParents: function(node) {
5267 this.eachAdjacency(node, function(adj) {
5269 if(n._depth < node._depth) ans.push(n);
5275 Method: isDescendantOf
5277 Returns a boolean indicating if some node is descendant of the node with the given id.
5279 Also implemented by:
5285 node - (object) A <Graph.Node>.
5286 id - (string) A <Graph.Node> id.
5290 $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5292 node.isDescendantOf('nodeid');//true|false
5295 isDescendantOf: function(node, id) {
5296 if(node.id == id) return true;
5297 var pars = this.getParents(node), ans = false;
5298 for ( var i = 0; !ans && i < pars.length; i++) {
5299 ans = ans || this.isDescendantOf(pars[i], id);
5307 Cleans flags from nodes.
5309 Also implemented by:
5314 graph - A <Graph> instance.
5316 clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5319 Method: getClosestNodeToOrigin
5321 Returns the closest node to the center of canvas.
5323 Also implemented by:
5329 graph - (object) A <Graph> instance.
5330 prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5333 getClosestNodeToOrigin: function(graph, prop, flags) {
5334 return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5338 Method: getClosestNodeToPos
5340 Returns the closest node to the given position.
5342 Also implemented by:
5348 graph - (object) A <Graph> instance.
5349 pos - (object) A <Complex> or <Polar> instance.
5350 prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5353 getClosestNodeToPos: function(graph, pos, prop, flags) {
5355 prop = prop || 'current';
5356 pos = pos && pos.getc(true) || Complex.KER;
5357 var distance = function(a, b) {
5358 var d1 = a.x - b.x, d2 = a.y - b.y;
5359 return d1 * d1 + d2 * d2;
5361 this.eachNode(graph, function(elem) {
5362 node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5363 node.getPos(prop).getc(true), pos)) ? elem : node;
5369 //Append graph methods to <Graph>
5370 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5371 Graph.prototype[m] = function() {
5372 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5376 //Append node methods to <Graph.Node>
5377 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5378 Graph.Node.prototype[m] = function() {
5379 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5391 Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5392 morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5404 initialize: function(viz) {
5411 Removes one or more <Graph.Nodes> from the visualization.
5412 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5416 node - (string|array) The node's id. Can also be an array having many ids.
5417 opt - (object) Animation options. It's an object with optional properties described below
5418 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5419 duration - Described in <Options.Fx>.
5420 fps - Described in <Options.Fx>.
5421 transition - Described in <Options.Fx>.
5422 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5426 var viz = new $jit.Viz(options);
5427 viz.op.removeNode('nodeId', {
5431 transition: $jit.Trans.Quart.easeOut
5434 viz.op.removeNode(['someId', 'otherId'], {
5441 removeNode: function(node, opt) {
5443 var options = $.merge(this.options, viz.controller, opt);
5444 var n = $.splat(node);
5445 var i, that, nodeObj;
5446 switch(options.type) {
5448 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5452 this.removeNode(n, { type: 'nothing' });
5453 viz.labels.clearLabels();
5457 case 'fade:seq': case 'fade':
5459 //set alpha to 0 for nodes to remove.
5460 for(i=0; i<n.length; i++) {
5461 nodeObj = viz.graph.getNode(n[i]);
5462 nodeObj.setData('alpha', 0, 'end');
5464 viz.fx.animate($.merge(options, {
5465 modes: ['node-property:alpha'],
5466 onComplete: function() {
5467 that.removeNode(n, { type: 'nothing' });
5468 viz.labels.clearLabels();
5470 viz.fx.animate($.merge(options, {
5479 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5480 for(i=0; i<n.length; i++) {
5481 nodeObj = viz.graph.getNode(n[i]);
5482 nodeObj.setData('alpha', 0, 'end');
5483 nodeObj.ignore = true;
5486 viz.fx.animate($.merge(options, {
5487 modes: ['node-property:alpha', 'linear'],
5488 onComplete: function() {
5489 that.removeNode(n, { type: 'nothing' });
5497 condition: function() { return n.length != 0; },
5498 step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5499 onComplete: function() { options.onComplete(); },
5500 duration: Math.ceil(options.duration / n.length)
5504 default: this.doError();
5511 Removes one or more <Graph.Adjacences> from the visualization.
5512 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5516 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'], ...]).
5517 opt - (object) Animation options. It's an object with optional properties described below
5518 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5519 duration - Described in <Options.Fx>.
5520 fps - Described in <Options.Fx>.
5521 transition - Described in <Options.Fx>.
5522 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5526 var viz = new $jit.Viz(options);
5527 viz.op.removeEdge(['nodeId', 'otherId'], {
5531 transition: $jit.Trans.Quart.easeOut
5534 viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5541 removeEdge: function(vertex, opt) {
5543 var options = $.merge(this.options, viz.controller, opt);
5544 var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5546 switch(options.type) {
5548 for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5552 this.removeEdge(v, { type: 'nothing' });
5556 case 'fade:seq': case 'fade':
5558 //set alpha to 0 for edges to remove.
5559 for(i=0; i<v.length; i++) {
5560 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5562 adj.setData('alpha', 0,'end');
5565 viz.fx.animate($.merge(options, {
5566 modes: ['edge-property:alpha'],
5567 onComplete: function() {
5568 that.removeEdge(v, { type: 'nothing' });
5570 viz.fx.animate($.merge(options, {
5579 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5580 for(i=0; i<v.length; i++) {
5581 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5583 adj.setData('alpha',0 ,'end');
5588 viz.fx.animate($.merge(options, {
5589 modes: ['edge-property:alpha', 'linear'],
5590 onComplete: function() {
5591 that.removeEdge(v, { type: 'nothing' });
5599 condition: function() { return v.length != 0; },
5600 step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5601 onComplete: function() { options.onComplete(); },
5602 duration: Math.ceil(options.duration / v.length)
5606 default: this.doError();
5613 Adds a new graph to the visualization.
5614 The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5615 The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5619 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5620 opt - (object) Animation options. It's an object with optional properties described below
5621 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5622 duration - Described in <Options.Fx>.
5623 fps - Described in <Options.Fx>.
5624 transition - Described in <Options.Fx>.
5625 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5629 //...json contains a tree or graph structure...
5631 var viz = new $jit.Viz(options);
5636 transition: $jit.Trans.Quart.easeOut
5646 sum: function(json, opt) {
5648 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5650 viz.root = opt.id || viz.root;
5651 switch(options.type) {
5653 graph = viz.construct(json);
5654 graph.eachNode(function(elem) {
5655 elem.eachAdjacency(function(adj) {
5656 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5663 this.sum(json, { type: 'nothing' });
5667 case 'fade:seq': case 'fade': case 'fade:con':
5669 graph = viz.construct(json);
5671 //set alpha to 0 for nodes to add.
5672 var fadeEdges = this.preprocessSum(graph);
5673 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5675 if(options.type != 'fade:con') {
5676 viz.fx.animate($.merge(options, {
5678 onComplete: function() {
5679 viz.fx.animate($.merge(options, {
5681 onComplete: function() {
5682 options.onComplete();
5688 viz.graph.eachNode(function(elem) {
5689 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5690 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5693 viz.fx.animate($.merge(options, {
5694 modes: ['linear'].concat(modes)
5699 default: this.doError();
5706 This method will transform the current visualized graph into the new JSON representation passed in the method.
5707 The JSON object must at least have the root node in common with the current visualized graph.
5711 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5712 opt - (object) Animation options. It's an object with optional properties described below
5713 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5714 duration - Described in <Options.Fx>.
5715 fps - Described in <Options.Fx>.
5716 transition - Described in <Options.Fx>.
5717 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5718 id - (string) The shared <Graph.Node> id between both graphs.
5720 extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5721 *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5722 For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5723 properties as values, just like specified in <Graph.Plot.animate>.
5727 //...json contains a tree or graph structure...
5729 var viz = new $jit.Viz(options);
5730 viz.op.morph(json, {
5734 transition: $jit.Trans.Quart.easeOut
5737 viz.op.morph(json, {
5741 //if the json data contains dollar prefixed params
5742 //like $width or $height these too can be animated
5743 viz.op.morph(json, {
5747 'node-property': ['width', 'height']
5752 morph: function(json, opt, extraModes) {
5754 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5756 //TODO(nico) this hack makes morphing work with the Hypertree.
5757 //Need to check if it has been solved and this can be removed.
5758 viz.root = opt.id || viz.root;
5759 switch(options.type) {
5761 graph = viz.construct(json);
5762 graph.eachNode(function(elem) {
5763 var nodeExists = viz.graph.hasNode(elem.id);
5764 elem.eachAdjacency(function(adj) {
5765 var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5766 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5767 //Update data properties if the node existed
5769 var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5770 for(var prop in (adj.data || {})) {
5771 addedAdj.data[prop] = adj.data[prop];
5775 //Update data properties if the node existed
5777 var addedNode = viz.graph.getNode(elem.id);
5778 for(var prop in (elem.data || {})) {
5779 addedNode.data[prop] = elem.data[prop];
5783 viz.graph.eachNode(function(elem) {
5784 elem.eachAdjacency(function(adj) {
5785 if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5786 viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5789 if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5795 viz.labels.clearLabels(true);
5796 this.morph(json, { type: 'nothing' });
5801 case 'fade:seq': case 'fade': case 'fade:con':
5803 graph = viz.construct(json);
5804 //preprocessing for nodes to delete.
5805 //get node property modes to interpolate
5806 var nodeModes = extraModes && ('node-property' in extraModes)
5807 && $.map($.splat(extraModes['node-property']),
5808 function(n) { return '$' + n; });
5809 viz.graph.eachNode(function(elem) {
5810 var graphNode = graph.getNode(elem.id);
5812 elem.setData('alpha', 1);
5813 elem.setData('alpha', 1, 'start');
5814 elem.setData('alpha', 0, 'end');
5817 //Update node data information
5818 var graphNodeData = graphNode.data;
5819 for(var prop in graphNodeData) {
5820 if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5821 elem.endData[prop] = graphNodeData[prop];
5823 elem.data[prop] = graphNodeData[prop];
5828 viz.graph.eachNode(function(elem) {
5829 if(elem.ignore) return;
5830 elem.eachAdjacency(function(adj) {
5831 if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5832 var nodeFrom = graph.getNode(adj.nodeFrom.id);
5833 var nodeTo = graph.getNode(adj.nodeTo.id);
5834 if(!nodeFrom.adjacentTo(nodeTo)) {
5835 var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5837 adj.setData('alpha', 1);
5838 adj.setData('alpha', 1, 'start');
5839 adj.setData('alpha', 0, 'end');
5843 //preprocessing for adding nodes.
5844 var fadeEdges = this.preprocessSum(graph);
5846 var modes = !fadeEdges? ['node-property:alpha'] :
5847 ['node-property:alpha',
5848 'edge-property:alpha'];
5849 //Append extra node-property animations (if any)
5850 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))?
5851 (':' + $.splat(extraModes['node-property']).join(':')) : '');
5852 //Append extra edge-property animations (if any)
5853 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))?
5854 (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5855 //Add label-property animations (if any)
5856 if(extraModes && ('label-property' in extraModes)) {
5857 modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5860 viz.graph.eachNode(function(elem) {
5861 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5862 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5865 viz.fx.animate($.merge(options, {
5866 modes: ['polar'].concat(modes),
5867 onComplete: function() {
5868 viz.graph.eachNode(function(elem) {
5869 if(elem.ignore) viz.graph.removeNode(elem.id);
5871 viz.graph.eachNode(function(elem) {
5872 elem.eachAdjacency(function(adj) {
5873 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5876 options.onComplete();
5889 Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5893 node - (object) A <Graph.Node>.
5894 opt - (object) An object containing options described below
5895 type - (string) Whether to 'replot' or 'animate' the contraction.
5897 There are also a number of Animation options. For more information see <Options.Fx>.
5901 var viz = new $jit.Viz(options);
5902 viz.op.contract(node, {
5906 transition: $jit.Trans.Quart.easeOut
5911 contract: function(node, opt) {
5913 if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5914 opt = $.merge(this.options, viz.config, opt || {}, {
5915 'modes': ['node-property:alpha:span', 'linear']
5917 node.collapsed = true;
5919 n.eachSubnode(function(ch) {
5921 ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5925 if(opt.type == 'animate') {
5928 viz.rotate(viz.rotated, 'none', {
5933 n.eachSubnode(function(ch) {
5934 ch.setPos(node.getPos('end'), 'end');
5938 viz.fx.animate(opt);
5939 } else if(opt.type == 'replot'){
5947 Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5951 node - (object) A <Graph.Node>.
5952 opt - (object) An object containing options described below
5953 type - (string) Whether to 'replot' or 'animate'.
5955 There are also a number of Animation options. For more information see <Options.Fx>.
5959 var viz = new $jit.Viz(options);
5960 viz.op.expand(node, {
5964 transition: $jit.Trans.Quart.easeOut
5969 expand: function(node, opt) {
5970 if(!('collapsed' in node)) return;
5972 opt = $.merge(this.options, viz.config, opt || {}, {
5973 'modes': ['node-property:alpha:span', 'linear']
5975 delete node.collapsed;
5977 n.eachSubnode(function(ch) {
5979 ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5983 if(opt.type == 'animate') {
5986 viz.rotate(viz.rotated, 'none', {
5990 viz.fx.animate(opt);
5991 } else if(opt.type == 'replot'){
5996 preprocessSum: function(graph) {
5998 graph.eachNode(function(elem) {
5999 if(!viz.graph.hasNode(elem.id)) {
6000 viz.graph.addNode(elem);
6001 var n = viz.graph.getNode(elem.id);
6002 n.setData('alpha', 0);
6003 n.setData('alpha', 0, 'start');
6004 n.setData('alpha', 1, 'end');
6007 var fadeEdges = false;
6008 graph.eachNode(function(elem) {
6009 elem.eachAdjacency(function(adj) {
6010 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6011 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6012 if(!nodeFrom.adjacentTo(nodeTo)) {
6013 var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6014 if(nodeFrom.startAlpha == nodeFrom.endAlpha
6015 && nodeTo.startAlpha == nodeTo.endAlpha) {
6017 adj.setData('alpha', 0);
6018 adj.setData('alpha', 0, 'start');
6019 adj.setData('alpha', 1, 'end');
6033 Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6034 Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6035 position is over the rendered shape.
6037 Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
6038 *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6042 //implement a new node type
6043 $jit.Viz.Plot.NodeTypes.implement({
6045 'render': function(node, canvas) {
6046 this.nodeHelper.circle.render ...
6048 'contains': function(node, pos) {
6049 this.nodeHelper.circle.contains ...
6053 //implement an edge type
6054 $jit.Viz.Plot.EdgeTypes.implement({
6056 'render': function(node, canvas) {
6057 this.edgeHelper.circle.render ...
6060 'contains': function(node, pos) {
6061 this.edgeHelper.circle.contains ...
6072 Contains rendering and other type of primitives for simple shapes.
6077 'contains': $.lambda(false)
6080 Object: NodeHelper.circle
6086 Renders a circle into the canvas.
6090 type - (string) Possible options are 'fill' or 'stroke'.
6091 pos - (object) An *x*, *y* object with the position of the center of the circle.
6092 radius - (number) The radius of the circle to be rendered.
6093 canvas - (object) A <Canvas> instance.
6097 NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6100 'render': function(type, pos, radius, canvas){
6101 var ctx = canvas.getCtx();
6103 ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6110 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6114 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6115 pos - (object) An *x*, *y* object with the position to check.
6116 radius - (number) The radius of the rendered circle.
6120 NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6123 'contains': function(npos, pos, radius){
6124 var diffx = npos.x - pos.x,
6125 diffy = npos.y - pos.y,
6126 diff = diffx * diffx + diffy * diffy;
6127 return diff <= radius * radius;
6131 Object: NodeHelper.ellipse
6137 Renders an ellipse into the canvas.
6141 type - (string) Possible options are 'fill' or 'stroke'.
6142 pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6143 width - (number) The width of the ellipse.
6144 height - (number) The height of the ellipse.
6145 canvas - (object) A <Canvas> instance.
6149 NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6152 'render': function(type, pos, width, height, canvas){
6153 var ctx = canvas.getCtx();
6157 ctx.scale(width / height, height / width);
6159 ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6168 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6172 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6173 pos - (object) An *x*, *y* object with the position to check.
6174 width - (number) The width of the rendered ellipse.
6175 height - (number) The height of the rendered ellipse.
6179 NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6182 'contains': function(npos, pos, width, height){
6183 // TODO(nico): be more precise...
6186 var dist = (width + height) / 2,
6187 diffx = npos.x - pos.x,
6188 diffy = npos.y - pos.y,
6189 diff = diffx * diffx + diffy * diffy;
6190 return diff <= dist * dist;
6194 Object: NodeHelper.square
6200 Renders a square into the canvas.
6204 type - (string) Possible options are 'fill' or 'stroke'.
6205 pos - (object) An *x*, *y* object with the position of the center of the square.
6206 dim - (number) The radius (or half-diameter) of the square.
6207 canvas - (object) A <Canvas> instance.
6211 NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6214 'render': function(type, pos, dim, canvas){
6215 canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6220 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6224 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6225 pos - (object) An *x*, *y* object with the position to check.
6226 dim - (number) The radius (or half-diameter) of the square.
6230 NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6233 'contains': function(npos, pos, dim){
6234 return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6238 Object: NodeHelper.rectangle
6244 Renders a rectangle into the canvas.
6248 type - (string) Possible options are 'fill' or 'stroke'.
6249 pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6250 width - (number) The width of the rectangle.
6251 height - (number) The height of the rectangle.
6252 canvas - (object) A <Canvas> instance.
6256 NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6259 'render': function(type, pos, width, height, canvas){
6260 canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6266 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6270 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6271 pos - (object) An *x*, *y* object with the position to check.
6272 width - (number) The width of the rendered rectangle.
6273 height - (number) The height of the rendered rectangle.
6277 NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6280 'contains': function(npos, pos, width, height){
6281 return Math.abs(pos.x - npos.x) <= width / 2
6282 && Math.abs(pos.y - npos.y) <= height / 2;
6286 Object: NodeHelper.triangle
6292 Renders a triangle into the canvas.
6296 type - (string) Possible options are 'fill' or 'stroke'.
6297 pos - (object) An *x*, *y* object with the position of the center of the triangle.
6298 dim - (number) The dimension of the triangle.
6299 canvas - (object) A <Canvas> instance.
6303 NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6306 'render': function(type, pos, dim, canvas){
6307 var ctx = canvas.getCtx(),
6315 ctx.moveTo(c1x, c1y);
6316 ctx.lineTo(c2x, c2y);
6317 ctx.lineTo(c3x, c3y);
6324 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6328 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6329 pos - (object) An *x*, *y* object with the position to check.
6330 dim - (number) The dimension of the shape.
6334 NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6337 'contains': function(npos, pos, dim) {
6338 return NodeHelper.circle.contains(npos, pos, dim);
6342 Object: NodeHelper.star
6348 Renders a star into the canvas.
6352 type - (string) Possible options are 'fill' or 'stroke'.
6353 pos - (object) An *x*, *y* object with the position of the center of the star.
6354 dim - (number) The dimension of the star.
6355 canvas - (object) A <Canvas> instance.
6359 NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6362 'render': function(type, pos, dim, canvas){
6363 var ctx = canvas.getCtx(),
6366 ctx.translate(pos.x, pos.y);
6369 for (var i = 0; i < 9; i++) {
6372 ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6384 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6388 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6389 pos - (object) An *x*, *y* object with the position to check.
6390 dim - (number) The dimension of the shape.
6394 NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6397 'contains': function(npos, pos, dim) {
6398 return NodeHelper.circle.contains(npos, pos, dim);
6406 Contains rendering primitives for simple edge shapes.
6410 Object: EdgeHelper.line
6416 Renders a line into the canvas.
6420 from - (object) An *x*, *y* object with the starting position of the line.
6421 to - (object) An *x*, *y* object with the ending position of the line.
6422 canvas - (object) A <Canvas> instance.
6426 EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6429 'render': function(from, to, canvas){
6430 var ctx = canvas.getCtx();
6432 ctx.moveTo(from.x, from.y);
6433 ctx.lineTo(to.x, to.y);
6439 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6443 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6444 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6445 pos - (object) An *x*, *y* object with the position to check.
6446 epsilon - (number) The dimension of the shape.
6450 EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6453 'contains': function(posFrom, posTo, pos, epsilon) {
6456 minPosX = min(posFrom.x, posTo.x),
6457 maxPosX = max(posFrom.x, posTo.x),
6458 minPosY = min(posFrom.y, posTo.y),
6459 maxPosY = max(posFrom.y, posTo.y);
6461 if(pos.x >= minPosX && pos.x <= maxPosX
6462 && pos.y >= minPosY && pos.y <= maxPosY) {
6463 if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6466 var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6467 return Math.abs(dist - pos.y) <= epsilon;
6473 Object: EdgeHelper.arrow
6479 Renders an arrow into the canvas.
6483 from - (object) An *x*, *y* object with the starting position of the arrow.
6484 to - (object) An *x*, *y* object with the ending position of the arrow.
6485 dim - (number) The dimension of the arrow.
6486 swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6487 canvas - (object) A <Canvas> instance.
6491 EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6494 'render': function(from, to, dim, swap, canvas){
6495 var ctx = canvas.getCtx();
6496 // invert edge direction
6502 var vect = new Complex(to.x - from.x, to.y - from.y);
6503 vect.$scale(dim / vect.norm());
6504 var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6505 normal = new Complex(-vect.y / 2, vect.x / 2),
6506 v1 = intermediatePoint.add(normal),
6507 v2 = intermediatePoint.$add(normal.$scale(-1));
6510 ctx.moveTo(from.x, from.y);
6511 ctx.lineTo(to.x, to.y);
6514 ctx.moveTo(v1.x, v1.y);
6515 ctx.lineTo(v2.x, v2.y);
6516 ctx.lineTo(to.x, to.y);
6523 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6527 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6528 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6529 pos - (object) An *x*, *y* object with the position to check.
6530 epsilon - (number) The dimension of the shape.
6534 EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6537 'contains': function(posFrom, posTo, pos, epsilon) {
6538 return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6542 Object: EdgeHelper.hyperline
6548 Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6552 from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6553 to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6554 r - (number) The scaling factor.
6555 canvas - (object) A <Canvas> instance.
6559 EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6562 'render': function(from, to, r, canvas){
6563 var ctx = canvas.getCtx();
6564 var centerOfCircle = computeArcThroughTwoPoints(from, to);
6565 if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6566 || centerOfCircle.ratio < 0) {
6568 ctx.moveTo(from.x * r, from.y * r);
6569 ctx.lineTo(to.x * r, to.y * r);
6572 var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6573 - centerOfCircle.x);
6574 var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6575 - centerOfCircle.x);
6576 var sense = sense(angleBegin, angleEnd);
6578 ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6579 * r, angleBegin, angleEnd, sense);
6583 Calculates the arc parameters through two points.
6585 More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6589 p1 - A <Complex> instance.
6590 p2 - A <Complex> instance.
6591 scale - The Disk's diameter.
6595 An object containing some arc properties.
6597 function computeArcThroughTwoPoints(p1, p2){
6598 var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6599 var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6600 // Fall back to a straight line
6608 var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6609 var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6612 var squaredRatio = (a * a + b * b) / 4 - 1;
6613 // Fall back to a straight line
6614 if (squaredRatio < 0)
6620 var ratio = Math.sqrt(squaredRatio);
6624 ratio: ratio > 1000? -1 : ratio,
6632 Sets angle direction to clockwise (true) or counterclockwise (false).
6636 angleBegin - Starting angle for drawing the arc.
6637 angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6641 A Boolean instance describing the sense for drawing the HyperLine.
6643 function sense(angleBegin, angleEnd){
6644 return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6645 : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6653 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6657 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6658 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6659 pos - (object) An *x*, *y* object with the position to check.
6660 epsilon - (number) The dimension of the shape.
6664 EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6667 'contains': $.lambda(false)
6673 * File: Graph.Plot.js
6679 <Graph> rendering and animation methods.
6683 nodeHelper - <NodeHelper> object.
6684 edgeHelper - <EdgeHelper> object.
6687 //Default intializer
6688 initialize: function(viz, klass){
6690 this.config = viz.config;
6691 this.node = viz.config.Node;
6692 this.edge = viz.config.Edge;
6693 this.animation = new Animation;
6694 this.nodeTypes = new klass.Plot.NodeTypes;
6695 this.edgeTypes = new klass.Plot.EdgeTypes;
6696 this.labels = viz.labels;
6700 nodeHelper: NodeHelper,
6701 edgeHelper: EdgeHelper,
6704 //node/edge property parsers
6712 'lineWidth': 'number',
6713 'angularWidth':'number',
6715 'valueArray':'array-number',
6716 'dimArray':'array-number'
6717 //'colorArray':'array-color'
6720 //canvas specific parsers
6722 'globalAlpha': 'number',
6723 'fillStyle': 'color',
6724 'strokeStyle': 'color',
6725 'lineWidth': 'number',
6726 'shadowBlur': 'number',
6727 'shadowColor': 'color',
6728 'shadowOffsetX': 'number',
6729 'shadowOffsetY': 'number',
6730 'miterLimit': 'number'
6739 //Number interpolator
6740 'compute': function(from, to, delta) {
6741 return from + (to - from) * delta;
6744 //Position interpolators
6745 'moebius': function(elem, props, delta, vector) {
6746 var v = vector.scale(-delta);
6748 var x = v.x, y = v.y;
6749 var ans = elem.startPos
6750 .getc().moebiusTransformation(v);
6751 elem.pos.setc(ans.x, ans.y);
6756 'linear': function(elem, props, delta) {
6757 var from = elem.startPos.getc(true);
6758 var to = elem.endPos.getc(true);
6759 elem.pos.setc(this.compute(from.x, to.x, delta),
6760 this.compute(from.y, to.y, delta));
6763 'polar': function(elem, props, delta) {
6764 var from = elem.startPos.getp(true);
6765 var to = elem.endPos.getp();
6766 var ans = to.interpolate(from, delta);
6767 elem.pos.setp(ans.theta, ans.rho);
6770 //Graph's Node/Edge interpolators
6771 'number': function(elem, prop, delta, getter, setter) {
6772 var from = elem[getter](prop, 'start');
6773 var to = elem[getter](prop, 'end');
6774 elem[setter](prop, this.compute(from, to, delta));
6777 'color': function(elem, prop, delta, getter, setter) {
6778 var from = $.hexToRgb(elem[getter](prop, 'start'));
6779 var to = $.hexToRgb(elem[getter](prop, 'end'));
6780 var comp = this.compute;
6781 var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6782 parseInt(comp(from[1], to[1], delta)),
6783 parseInt(comp(from[2], to[2], delta))]);
6785 elem[setter](prop, val);
6788 'array-number': function(elem, prop, delta, getter, setter) {
6789 var from = elem[getter](prop, 'start'),
6790 to = elem[getter](prop, 'end'),
6792 for(var i=0, l=from.length; i<l; i++) {
6793 var fromi = from[i], toi = to[i];
6795 for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6796 curi.push(this.compute(fromi[j], toi[j], delta));
6800 cur.push(this.compute(fromi, toi, delta));
6803 elem[setter](prop, cur);
6806 'node': function(elem, props, delta, map, getter, setter) {
6809 var len = props.length;
6810 for(var i=0; i<len; i++) {
6812 this[map[pi]](elem, pi, delta, getter, setter);
6815 for(var pi in map) {
6816 this[map[pi]](elem, pi, delta, getter, setter);
6821 'edge': function(elem, props, delta, mapKey, getter, setter) {
6822 var adjs = elem.adjacencies;
6823 for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6826 'node-property': function(elem, props, delta) {
6827 this['node'](elem, props, delta, 'map', 'getData', 'setData');
6830 'edge-property': function(elem, props, delta) {
6831 this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6834 'label-property': function(elem, props, delta) {
6835 this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6838 'node-style': function(elem, props, delta) {
6839 this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6842 'edge-style': function(elem, props, delta) {
6843 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6851 Iteratively performs an action while refreshing the state of the visualization.
6855 options - (object) An object containing some sequence options described below
6856 condition - (function) A function returning a boolean instance in order to stop iterations.
6857 step - (function) A function to execute on each step of the iteration.
6858 onComplete - (function) A function to execute when the sequence finishes.
6859 duration - (number) Duration (in milliseconds) of each step.
6863 var rg = new $jit.RGraph(options);
6866 condition: function() {
6872 onComplete: function() {
6879 sequence: function(options) {
6882 condition: $.lambda(false),
6884 onComplete: $.empty,
6888 var interval = setInterval(function() {
6889 if(options.condition()) {
6892 clearInterval(interval);
6893 options.onComplete();
6895 that.viz.refresh(true);
6896 }, options.duration);
6902 Prepare graph position and other attribute values before performing an Animation.
6903 This method is used internally by the Toolkit.
6907 <Animation>, <Graph.Plot.animate>
6910 prepare: function(modes) {
6911 var graph = this.viz.graph,
6914 'getter': 'getData',
6918 'getter': 'getData',
6922 'getter': 'getCanvasStyle',
6923 'setter': 'setCanvasStyle'
6926 'getter': 'getCanvasStyle',
6927 'setter': 'setCanvasStyle'
6933 if($.type(modes) == 'array') {
6934 for(var i=0, len=modes.length; i < len; i++) {
6935 var elems = modes[i].split(':');
6936 m[elems.shift()] = elems;
6939 for(var p in modes) {
6940 if(p == 'position') {
6941 m[modes.position] = [];
6943 m[p] = $.splat(modes[p]);
6948 graph.eachNode(function(node) {
6949 node.startPos.set(node.pos);
6950 $.each(['node-property', 'node-style'], function(p) {
6953 for(var i=0, l=prop.length; i < l; i++) {
6954 node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6958 $.each(['edge-property', 'edge-style'], function(p) {
6961 node.eachAdjacency(function(adj) {
6962 for(var i=0, l=prop.length; i < l; i++) {
6963 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6975 Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6979 opt - (object) Animation options. The object properties are described below
6980 duration - (optional) Described in <Options.Fx>.
6981 fps - (optional) Described in <Options.Fx>.
6982 hideLabels - (optional|boolean) Whether to hide labels during the animation.
6983 modes - (required|object) An object with animation modes (described below).
6987 Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6988 They are represented by an object that has as keys main categories of properties to animate and as values a list
6989 of these specific properties. The properties are described below
6991 position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6992 node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6993 edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6994 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.
6995 node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6996 edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
7000 var viz = new $jit.Viz(options);
7001 //...tweak some Data, CanvasStyles or LabelData properties...
7004 'position': 'linear',
7005 'node-property': ['width', 'height'],
7006 'node-style': 'shadowColor',
7007 'label-property': 'size'
7011 //...can also be written like this...
7014 'node-property:width:height',
7015 'node-style:shadowColor',
7016 'label-property:size'],
7021 animate: function(opt, versor) {
7022 opt = $.merge(this.viz.config, opt || {});
7026 interp = this.Interpolator,
7027 animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7028 //prepare graph values
7029 var m = this.prepare(opt.modes);
7032 if(opt.hideLabels) this.labels.hideLabels(true);
7033 animation.setOptions($.merge(opt, {
7035 compute: function(delta) {
7036 graph.eachNode(function(node) {
7038 interp[p](node, m[p], delta, versor);
7041 that.plot(opt, this.$animating, delta);
7042 this.$animating = true;
7044 complete: function() {
7045 if(opt.hideLabels) that.labels.hideLabels(false);
7048 opt.onAfterCompute();
7056 Apply animation to node properties like color, width, height, dim, etc.
7060 options - Animation options. This object properties is described below
7061 elements - The Elements to be transformed. This is an object that has a properties
7065 //can also be an array of ids
7066 'id': 'id-of-node-to-transform',
7067 //properties to be modified. All properties are optional.
7069 'color': '#ccc', //some color
7070 'width': 10, //some width
7071 'height': 10, //some height
7072 'dim': 20, //some dim
7073 'lineWidth': 10 //some line width
7078 - _reposition_ Whether to recalculate positions and add a motion animation.
7079 This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7081 - _onComplete_ A method that is called when the animation completes.
7083 ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7087 var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7094 'transition': Trans.Quart.easeOut
7099 nodeFx: function(opt) {
7102 animation = this.nodeFxAnimation,
7103 options = $.merge(this.viz.config, {
7110 opt = $.merge(options, opt || {}, {
7111 onBeforeCompute: $.empty,
7112 onAfterCompute: $.empty
7114 //check if an animation is running
7115 animation.stopTimer();
7116 var props = opt.elements.properties;
7117 //set end values for nodes
7118 if(!opt.elements.id) {
7119 graph.eachNode(function(n) {
7120 for(var prop in props) {
7121 n.setData(prop, props[prop], 'end');
7125 var ids = $.splat(opt.elements.id);
7126 $.each(ids, function(id) {
7127 var n = graph.getNode(id);
7129 for(var prop in props) {
7130 n.setData(prop, props[prop], 'end');
7137 for(var prop in props) propnames.push(prop);
7138 //add node properties modes
7139 var modes = ['node-property:' + propnames.join(':')];
7140 //set new node positions
7141 if(opt.reposition) {
7142 modes.push('linear');
7146 this.animate($.merge(opt, {
7160 opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7165 var viz = new $jit.Viz(options);
7170 plot: function(opt, animating) {
7173 canvas = viz.canvas,
7176 ctx = canvas.getCtx(),
7178 opt = opt || this.viz.controller;
7179 opt.clearCanvas && canvas.clear();
7181 var root = aGraph.getNode(id);
7184 var T = !!root.visited;
7185 aGraph.eachNode(function(node) {
7186 var nodeAlpha = node.getData('alpha');
7187 node.eachAdjacency(function(adj) {
7188 var nodeTo = adj.nodeTo;
7189 if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7190 !animating && opt.onBeforePlotLine(adj);
7192 ctx.globalAlpha = min(nodeAlpha,
7193 nodeTo.getData('alpha'),
7194 adj.getData('alpha'));
7195 that.plotLine(adj, canvas, animating);
7197 !animating && opt.onAfterPlotLine(adj);
7202 !animating && opt.onBeforePlotNode(node);
7203 that.plotNode(node, canvas, animating);
7204 !animating && opt.onAfterPlotNode(node);
7206 if(!that.labelsHidden && opt.withLabels) {
7207 if(node.drawn && nodeAlpha >= 0.95) {
7208 that.labels.plotLabel(canvas, node, opt);
7210 that.labels.hideLabel(node, false);
7221 plotTree: function(node, opt, animating) {
7224 canvas = viz.canvas,
7225 config = this.config,
7226 ctx = canvas.getCtx();
7227 var nodeAlpha = node.getData('alpha');
7228 node.eachSubnode(function(elem) {
7229 if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7230 var adj = node.getAdjacency(elem.id);
7231 !animating && opt.onBeforePlotLine(adj);
7232 ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7233 that.plotLine(adj, canvas, animating);
7234 !animating && opt.onAfterPlotLine(adj);
7235 that.plotTree(elem, opt, animating);
7239 !animating && opt.onBeforePlotNode(node);
7240 this.plotNode(node, canvas, animating);
7241 !animating && opt.onAfterPlotNode(node);
7242 if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7243 this.labels.plotLabel(canvas, node, opt);
7245 this.labels.hideLabel(node, false);
7247 this.labels.hideLabel(node, true);
7254 Plots a <Graph.Node>.
7258 node - (object) A <Graph.Node>.
7259 canvas - (object) A <Canvas> element.
7262 plotNode: function(node, canvas, animating) {
7263 var f = node.getData('type'),
7264 ctxObj = this.node.CanvasStyles;
7266 var width = node.getData('lineWidth'),
7267 color = node.getData('color'),
7268 alpha = node.getData('alpha'),
7269 ctx = canvas.getCtx();
7271 ctx.lineWidth = width;
7272 ctx.fillStyle = ctx.strokeStyle = color;
7273 ctx.globalAlpha = alpha;
7275 for(var s in ctxObj) {
7276 ctx[s] = node.getCanvasStyle(s);
7279 this.nodeTypes[f].render.call(this, node, canvas, animating);
7286 Plots a <Graph.Adjacence>.
7290 adj - (object) A <Graph.Adjacence>.
7291 canvas - (object) A <Canvas> instance.
7294 plotLine: function(adj, canvas, animating) {
7295 var f = adj.getData('type'),
7296 ctxObj = this.edge.CanvasStyles;
7298 var width = adj.getData('lineWidth'),
7299 color = adj.getData('color'),
7300 ctx = canvas.getCtx();
7302 ctx.lineWidth = width;
7303 ctx.fillStyle = ctx.strokeStyle = color;
7305 for(var s in ctxObj) {
7306 ctx[s] = adj.getCanvasStyle(s);
7309 this.edgeTypes[f].render.call(this, adj, canvas, animating);
7318 * File: Graph.Label.js
7325 An interface for plotting/hiding/showing labels.
7329 This is a generic interface for plotting/hiding/showing labels.
7330 The <Graph.Label> interface is implemented in multiple ways to provide
7331 different label types.
7333 For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7334 HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7335 The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7337 All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7343 Class: Graph.Label.Native
7345 Implements labels natively, using the Canvas text API.
7347 Graph.Label.Native = new Class({
7351 Plots a label for a given node.
7355 canvas - (object) A <Canvas> instance.
7356 node - (object) A <Graph.Node>.
7357 controller - (object) A configuration object.
7362 var viz = new $jit.Viz(options);
7363 var node = viz.graph.getNode('nodeId');
7364 viz.labels.plotLabel(viz.canvas, node, viz.config);
7367 plotLabel: function(canvas, node, controller) {
7368 var ctx = canvas.getCtx();
7369 var pos = node.pos.getc(true);
7371 ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7372 ctx.textAlign = node.getLabelData('textAlign');
7373 ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7374 ctx.textBaseline = node.getLabelData('textBaseline');
7376 this.renderLabel(canvas, node, controller);
7382 Does the actual rendering of the label in the canvas. The default
7383 implementation renders the label close to the position of the node, this
7384 method should be overriden to position the labels differently.
7388 canvas - A <Canvas> instance.
7389 node - A <Graph.Node>.
7390 controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7392 renderLabel: function(canvas, node, controller) {
7393 var ctx = canvas.getCtx();
7394 var pos = node.pos.getc(true);
7395 ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7403 Class: Graph.Label.DOM
7405 Abstract Class implementing some DOM label methods.
7409 <Graph.Label.HTML> and <Graph.Label.SVG>.
7412 Graph.Label.DOM = new Class({
7413 //A flag value indicating if node labels are being displayed or not.
7414 labelsHidden: false,
7416 labelContainer: false,
7417 //Label elements hash.
7421 Method: getLabelContainer
7423 Lazy fetcher for the label container.
7427 The label container DOM element.
7432 var viz = new $jit.Viz(options);
7433 var labelContainer = viz.labels.getLabelContainer();
7434 alert(labelContainer.innerHTML);
7437 getLabelContainer: function() {
7438 return this.labelContainer ?
7439 this.labelContainer :
7440 this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7446 Lazy fetcher for the label element.
7450 id - (string) The label id (which is also a <Graph.Node> id).
7459 var viz = new $jit.Viz(options);
7460 var label = viz.labels.getLabel('someid');
7461 alert(label.innerHTML);
7465 getLabel: function(id) {
7466 return (id in this.labels && this.labels[id] != null) ?
7468 this.labels[id] = document.getElementById(id);
7474 Hides all labels (by hiding the label container).
7478 hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7482 var viz = new $jit.Viz(options);
7483 rg.labels.hideLabels(true);
7487 hideLabels: function (hide) {
7488 var container = this.getLabelContainer();
7490 container.style.display = 'none';
7492 container.style.display = '';
7493 this.labelsHidden = hide;
7499 Clears the label container.
7501 Useful when using a new visualization with the same canvas element/widget.
7505 force - (boolean) Forces deletion of all labels.
7509 var viz = new $jit.Viz(options);
7510 viz.labels.clearLabels();
7513 clearLabels: function(force) {
7514 for(var id in this.labels) {
7515 if (force || !this.viz.graph.hasNode(id)) {
7516 this.disposeLabel(id);
7517 delete this.labels[id];
7523 Method: disposeLabel
7529 id - (string) A label id (which generally is also a <Graph.Node> id).
7533 var viz = new $jit.Viz(options);
7534 viz.labels.disposeLabel('labelid');
7537 disposeLabel: function(id) {
7538 var elem = this.getLabel(id);
7539 if(elem && elem.parentNode) {
7540 elem.parentNode.removeChild(elem);
7547 Hides the corresponding <Graph.Node> label.
7551 node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7552 show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7556 var rg = new $jit.Viz(options);
7557 viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7560 hideLabel: function(node, show) {
7561 node = $.splat(node);
7562 var st = show ? "" : "none", lab, that = this;
7563 $.each(node, function(n) {
7564 var lab = that.getLabel(n.id);
7566 lab.style.display = st;
7574 Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7578 pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7579 canvas - A <Canvas> instance.
7583 A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7586 fitsInCanvas: function(pos, canvas) {
7587 var size = canvas.getSize();
7588 if(pos.x >= size.width || pos.x < 0
7589 || pos.y >= size.height || pos.y < 0) return false;
7595 Class: Graph.Label.HTML
7597 Implements HTML labels.
7601 All <Graph.Label.DOM> methods.
7604 Graph.Label.HTML = new Class({
7605 Implements: Graph.Label.DOM,
7610 Plots a label for a given node.
7614 canvas - (object) A <Canvas> instance.
7615 node - (object) A <Graph.Node>.
7616 controller - (object) A configuration object.
7621 var viz = new $jit.Viz(options);
7622 var node = viz.graph.getNode('nodeId');
7623 viz.labels.plotLabel(viz.canvas, node, viz.config);
7628 plotLabel: function(canvas, node, controller) {
7629 var id = node.id, tag = this.getLabel(id);
7631 if(!tag && !(tag = document.getElementById(id))) {
7632 tag = document.createElement('div');
7633 var container = this.getLabelContainer();
7635 tag.className = 'node';
7636 tag.style.position = 'absolute';
7637 controller.onCreateLabel(tag, node);
7638 container.appendChild(tag);
7639 this.labels[node.id] = tag;
7642 this.placeLabel(tag, node, controller);
7647 Class: Graph.Label.SVG
7649 Implements SVG labels.
7653 All <Graph.Label.DOM> methods.
7655 Graph.Label.SVG = new Class({
7656 Implements: Graph.Label.DOM,
7661 Plots a label for a given node.
7665 canvas - (object) A <Canvas> instance.
7666 node - (object) A <Graph.Node>.
7667 controller - (object) A configuration object.
7672 var viz = new $jit.Viz(options);
7673 var node = viz.graph.getNode('nodeId');
7674 viz.labels.plotLabel(viz.canvas, node, viz.config);
7679 plotLabel: function(canvas, node, controller) {
7680 var id = node.id, tag = this.getLabel(id);
7681 if(!tag && !(tag = document.getElementById(id))) {
7682 var ns = 'http://www.w3.org/2000/svg';
7683 tag = document.createElementNS(ns, 'svg:text');
7684 var tspan = document.createElementNS(ns, 'svg:tspan');
7685 tag.appendChild(tspan);
7686 var container = this.getLabelContainer();
7687 tag.setAttribute('id', id);
7688 tag.setAttribute('class', 'node');
7689 container.appendChild(tag);
7690 controller.onCreateLabel(tag, node);
7691 this.labels[node.id] = tag;
7693 this.placeLabel(tag, node, controller);
7699 Graph.Geom = new Class({
7701 initialize: function(viz) {
7703 this.config = viz.config;
7704 this.node = viz.config.Node;
7705 this.edge = viz.config.Edge;
7708 Applies a translation to the tree.
7712 pos - A <Complex> number specifying translation vector.
7713 prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7718 st.geom.translate(new Complex(300, 100), 'end');
7721 translate: function(pos, prop) {
7722 prop = $.splat(prop);
7723 this.viz.graph.eachNode(function(elem) {
7724 $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7728 Hides levels of the tree until it properly fits in canvas.
7730 setRightLevelToShow: function(node, canvas, callback) {
7731 var level = this.getRightLevelToShow(node, canvas),
7732 fx = this.viz.labels,
7739 node.eachLevel(0, this.config.levelsToShow, function(n) {
7740 var d = n._depth - node._depth;
7746 fx.hideLabel(n, false);
7758 Returns the right level to show for the current tree in order to fit in canvas.
7760 getRightLevelToShow: function(node, canvas) {
7761 var config = this.config;
7762 var level = config.levelsToShow;
7763 var constrained = config.constrained;
7764 if(!constrained) return level;
7765 while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7778 Provides methods for loading and serving JSON data.
7781 construct: function(json) {
7782 var isGraph = ($.type(json) == 'array');
7783 var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7786 (function (ans, json) {
7789 for(var i=0, ch = json.children; i<ch.length; i++) {
7790 ans.addAdjacence(json, ch[i]);
7791 arguments.callee(ans, ch[i]);
7797 (function (ans, json) {
7798 var getNode = function(id) {
7799 for(var i=0, l=json.length; i<l; i++) {
7800 if(json[i].id == id) {
7804 // The node was not defined in the JSON
7810 return ans.addNode(newNode);
7813 for(var i=0, l=json.length; i<l; i++) {
7814 ans.addNode(json[i]);
7815 var adj = json[i].adjacencies;
7817 for(var j=0, lj=adj.length; j<lj; j++) {
7818 var node = adj[j], data = {};
7819 if(typeof adj[j] != 'string') {
7820 data = $.merge(node.data, {});
7823 ans.addAdjacence(json[i], getNode(node), data);
7835 Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7837 A JSON tree or graph structure consists of nodes, each having as properties
7839 id - (string) A unique identifier for the node
7840 name - (string) A node's name
7841 data - (object) The data optional property contains a hash (i.e {})
7842 where you can store all the information you want about this node.
7844 For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7850 "id": "aUniqueIdentifier",
7851 "name": "usually a nodes name",
7853 "some key": "some value",
7854 "some other key": "some other value"
7856 "children": [ *other nodes or empty* ]
7860 JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7861 For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7863 There are two types of *Graph* structures, *simple* and *extended* graph structures.
7865 For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7866 id of the node connected to the main node.
7873 "id": "aUniqueIdentifier",
7874 "name": "usually a nodes name",
7876 "some key": "some value",
7877 "some other key": "some other value"
7879 "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7882 'other nodes go here...'
7886 For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7888 nodeTo - (string) The other node connected by this adjacency.
7889 data - (object) A data property, where we can store custom key/value information.
7896 "id": "aUniqueIdentifier",
7897 "name": "usually a nodes name",
7899 "some key": "some value",
7900 "some other key": "some other value"
7905 data: {} //put whatever you want here
7907 'other adjacencies go here...'
7910 'other nodes go here...'
7914 About the data property:
7916 As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
7917 You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
7918 have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7920 For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
7921 <Options.Node> will override the general value for that option with that particular value. For this to work
7922 however, you do have to set *overridable = true* in <Options.Node>.
7924 The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
7925 if <Options.Edge> has *overridable = true*.
7927 When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
7928 since this is the value which will be taken into account when creating the layout.
7929 The same thing goes for the *$color* parameter.
7931 In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
7932 *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
7933 canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
7934 to the *shadowBlur* property.
7936 These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
7937 by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7939 Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
7940 information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7942 loadJSON Parameters:
7944 json - A JSON Tree or Graph structure.
7945 i - For Graph structures only. Sets the indexed node as root for the visualization.
7948 loadJSON: function(json, i) {
7950 //if they're canvas labels erase them.
7951 if(this.labels && this.labels.clearLabels) {
7952 this.labels.clearLabels(true);
7954 this.graph = this.construct(json);
7955 if($.type(json) != 'array'){
7956 this.root = json.id;
7958 this.root = json[i? i : 0].id;
7965 Returns a JSON tree/graph structure from the visualization's <Graph>.
7966 See <Loader.loadJSON> for the graph formats available.
7974 type - (string) Default's "tree". The type of the JSON structure to be returned.
7975 Possible options are "tree" or "graph".
7977 toJSON: function(type) {
7978 type = type || "tree";
7979 if(type == 'tree') {
7981 var rootNode = this.graph.getNode(this.root);
7982 var ans = (function recTree(node) {
7985 ans.name = node.name;
7986 ans.data = node.data;
7988 node.eachSubnode(function(n) {
7989 ch.push(recTree(n));
7997 var T = !!this.graph.getNode(this.root).visited;
7998 this.graph.eachNode(function(node) {
8000 ansNode.id = node.id;
8001 ansNode.name = node.name;
8002 ansNode.data = node.data;
8004 node.eachAdjacency(function(adj) {
8005 var nodeTo = adj.nodeTo;
8006 if(!!nodeTo.visited === T) {
8008 ansAdj.nodeTo = nodeTo.id;
8009 ansAdj.data = adj.data;
8013 ansNode.adjacencies = adjs;
8027 * Implements base Tree and Graph layouts.
8031 * Implements base Tree and Graph layouts like Radial, Tree, etc.
8038 * Parent object for common layouts.
8041 var Layouts = $jit.Layouts = {};
8044 //Some util shared layout functions are defined here.
8048 compute: function(graph, prop, opt) {
8049 this.initializeLabel(opt);
8050 var label = this.label, style = label.style;
8051 graph.eachNode(function(n) {
8052 var autoWidth = n.getData('autoWidth'),
8053 autoHeight = n.getData('autoHeight');
8054 if(autoWidth || autoHeight) {
8055 //delete dimensions since these are
8056 //going to be overridden now.
8057 delete n.data.$width;
8058 delete n.data.$height;
8061 var width = n.getData('width'),
8062 height = n.getData('height');
8063 //reset label dimensions
8064 style.width = autoWidth? 'auto' : width + 'px';
8065 style.height = autoHeight? 'auto' : height + 'px';
8067 //TODO(nico) should let the user choose what to insert here.
8068 label.innerHTML = n.name;
8070 var offsetWidth = label.offsetWidth,
8071 offsetHeight = label.offsetHeight;
8072 var type = n.getData('type');
8073 if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8074 n.setData('width', offsetWidth);
8075 n.setData('height', offsetHeight);
8077 var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8078 n.setData('width', dim);
8079 n.setData('height', dim);
8080 n.setData('dim', dim);
8086 initializeLabel: function(opt) {
8088 this.label = document.createElement('div');
8089 document.body.appendChild(this.label);
8091 this.setLabelStyles(opt);
8094 setLabelStyles: function(opt) {
8095 $.extend(this.label.style, {
8096 'visibility': 'hidden',
8097 'position': 'absolute',
8101 this.label.className = 'jit-autoadjust-label';
8107 * Class: Layouts.Tree
8109 * Implements a Tree Layout.
8117 * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8120 Layouts.Tree = (function() {
8122 var slice = Array.prototype.slice;
8125 Calculates the max width and height nodes for a tree level
8127 function getBoundaries(graph, config, level, orn, prop) {
8128 var dim = config.Node;
8129 var multitree = config.multitree;
8130 if (dim.overridable) {
8132 graph.eachNode(function(n) {
8133 if (n._depth == level
8134 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8135 var dw = n.getData('width', prop);
8136 var dh = n.getData('height', prop);
8137 w = (w < dw) ? dw : w;
8138 h = (h < dh) ? dh : h;
8142 'width' : w < 0 ? dim.width : w,
8143 'height' : h < 0 ? dim.height : h
8151 function movetree(node, prop, val, orn) {
8152 var p = (orn == "left" || orn == "right") ? "y" : "x";
8153 node.getPos(prop)[p] += val;
8157 function moveextent(extent, val) {
8159 $.each(extent, function(elem) {
8160 elem = slice.call(elem);
8169 function merge(ps, qs) {
8174 var p = ps.shift(), q = qs.shift();
8175 return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8179 function mergelist(ls, def) {
8184 return mergelist(ls, merge(ps, def));
8188 function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8189 if (ext1.length <= i || ext2.length <= i)
8192 var p = ext1[i][1], q = ext2[i][0];
8193 return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8194 + subtreeOffset, p - q + siblingOffset);
8198 function fitlistl(es, subtreeOffset, siblingOffset) {
8199 function $fitlistl(acc, es, i) {
8202 var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8203 return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8206 return $fitlistl( [], es, 0);
8210 function fitlistr(es, subtreeOffset, siblingOffset) {
8211 function $fitlistr(acc, es, i) {
8214 var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8215 return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8218 es = slice.call(es);
8219 var ans = $fitlistr( [], es.reverse(), 0);
8220 return ans.reverse();
8224 function fitlist(es, subtreeOffset, siblingOffset, align) {
8225 var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8226 subtreeOffset, siblingOffset);
8228 if (align == "left")
8230 else if (align == "right")
8233 for ( var i = 0, ans = []; i < esl.length; i++) {
8234 ans[i] = (esl[i] + esr[i]) / 2;
8240 function design(graph, node, prop, config, orn) {
8241 var multitree = config.multitree;
8242 var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8243 var ind = +(orn == "left" || orn == "right");
8244 var p = auxp[ind], notp = auxp[1 - ind];
8246 var cnode = config.Node;
8247 var s = auxs[ind], nots = auxs[1 - ind];
8249 var siblingOffset = config.siblingOffset;
8250 var subtreeOffset = config.subtreeOffset;
8251 var align = config.align;
8253 function $design(node, maxsize, acum) {
8254 var sval = node.getData(s, prop);
8255 var notsval = maxsize
8256 || (node.getData(nots, prop));
8258 var trees = [], extents = [], chmaxsize = false;
8259 var chacum = notsval + config.levelDistance;
8260 node.eachSubnode(function(n) {
8262 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8265 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8267 var s = $design(n, chmaxsize[nots], acum + chacum);
8269 extents.push(s.extent);
8272 var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8273 for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8274 movetree(trees[i], prop, positions[i], orn);
8275 pextents.push(moveextent(extents[i], positions[i]));
8277 var resultextent = [ [ -sval / 2, sval / 2 ] ]
8278 .concat(mergelist(pextents));
8279 node.getPos(prop)[p] = 0;
8281 if (orn == "top" || orn == "left") {
8282 node.getPos(prop)[notp] = acum;
8284 node.getPos(prop)[notp] = -acum;
8289 extent : resultextent
8293 $design(node, false, 0);
8301 Computes nodes' positions.
8304 compute : function(property, computeLevels) {
8305 var prop = property || 'start';
8306 var node = this.graph.getNode(this.root);
8312 NodeDim.compute(this.graph, prop, this.config);
8313 if (!!computeLevels || !("_depth" in node)) {
8314 this.graph.computeLevels(this.root, 0, "ignore");
8317 this.computePositions(node, prop);
8320 computePositions : function(node, prop) {
8321 var config = this.config;
8322 var multitree = config.multitree;
8323 var align = config.align;
8324 var indent = align !== 'center' && config.indent;
8325 var orn = config.orientation;
8326 var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8328 $.each(orns, function(orn) {
8330 design(that.graph, node, prop, that.config, orn, prop);
8331 var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8333 (function red(node) {
8334 node.eachSubnode(function(n) {
8336 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8338 n.getPos(prop)[i] += node.getPos(prop)[i];
8340 n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8353 * File: Spacetree.js
8359 A Tree layout with advanced contraction and expansion animations.
8363 SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8364 <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8366 Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8370 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.
8374 All <Loader> methods
8376 Constructor Options:
8378 Inherits options from
8381 - <Options.Controller>
8388 - <Options.NodeStyles>
8389 - <Options.Navigation>
8391 Additionally, there are other parameters and some default values changed
8393 constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8394 levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8395 levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8396 Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8397 offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8398 offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8399 duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8401 Instance Properties:
8403 canvas - Access a <Canvas> instance.
8404 graph - Access a <Graph> instance.
8405 op - Access a <ST.Op> instance.
8406 fx - Access a <ST.Plot> instance.
8407 labels - Access a <ST.Label> interface implementation.
8411 $jit.ST= (function() {
8412 // Define some private methods first...
8414 var nodesInPath = [];
8415 // Nodes to contract
8416 function getNodesToHide(node) {
8417 node = node || this.clickedNode;
8418 if(!this.config.constrained) {
8421 var Geom = this.geom;
8422 var graph = this.graph;
8423 var canvas = this.canvas;
8424 var level = node._depth, nodeArray = [];
8425 graph.eachNode(function(n) {
8426 if(n.exist && !n.selected) {
8427 if(n.isDescendantOf(node.id)) {
8428 if(n._depth <= level) nodeArray.push(n);
8434 var leafLevel = Geom.getRightLevelToShow(node, canvas);
8435 node.eachLevel(leafLevel, leafLevel, function(n) {
8436 if(n.exist && !n.selected) nodeArray.push(n);
8439 for (var i = 0; i < nodesInPath.length; i++) {
8440 var n = this.graph.getNode(nodesInPath[i]);
8441 if(!n.isDescendantOf(node.id)) {
8448 function getNodesToShow(node) {
8449 var nodeArray = [], config = this.config;
8450 node = node || this.clickedNode;
8451 this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8452 if(config.multitree && !('$orn' in n.data)
8453 && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8455 } else if(n.drawn && !n.anySubnode("drawn")) {
8461 // Now define the actual class.
8464 Implements: [Loader, Extras, Layouts.Tree],
8466 initialize: function(controller) {
8481 this.controller = this.config = $.merge(
8482 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8483 "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8485 var canvasConfig = this.config;
8486 if(canvasConfig.useCanvas) {
8487 this.canvas = canvasConfig.useCanvas;
8488 this.config.labelContainer = this.canvas.id + '-label';
8490 if(canvasConfig.background) {
8491 canvasConfig.background = $.merge({
8493 colorStop1: this.config.colorStop1,
8494 colorStop2: this.config.colorStop2
8495 }, canvasConfig.background);
8497 this.canvas = new Canvas(this, canvasConfig);
8498 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8501 this.graphOptions = {
8504 this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8505 this.labels = new $ST.Label[canvasConfig.Label.type](this);
8506 this.fx = new $ST.Plot(this, $ST);
8507 this.op = new $ST.Op(this);
8508 this.group = new $ST.Group(this);
8509 this.geom = new $ST.Geom(this);
8510 this.clickedNode= null;
8511 // initialize extras
8512 this.initializeExtras();
8518 Plots the <ST>. This is a shortcut to *fx.plot*.
8521 plot: function() { this.fx.plot(this.controller); },
8525 Method: switchPosition
8527 Switches the tree orientation.
8531 pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8532 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.
8533 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8538 st.switchPosition("right", "animate", {
8539 onComplete: function() {
8540 alert('completed!');
8545 switchPosition: function(pos, method, onComplete) {
8546 var Geom = this.geom, Plot = this.fx, that = this;
8550 onComplete: function() {
8551 Geom.switchOrientation(pos);
8552 that.compute('end', false);
8554 if(method == 'animate') {
8555 that.onClick(that.clickedNode.id, onComplete);
8556 } else if(method == 'replot') {
8557 that.select(that.clickedNode.id, onComplete);
8565 Method: switchAlignment
8567 Switches the tree alignment.
8571 align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8572 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.
8573 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8578 st.switchAlignment("right", "animate", {
8579 onComplete: function() {
8580 alert('completed!');
8585 switchAlignment: function(align, method, onComplete) {
8586 this.config.align = align;
8587 if(method == 'animate') {
8588 this.select(this.clickedNode.id, onComplete);
8589 } else if(method == 'replot') {
8590 this.onClick(this.clickedNode.id, onComplete);
8595 Method: addNodeInPath
8597 Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8602 id - (string) A <Graph.Node> id.
8607 st.addNodeInPath("nodeId");
8610 addNodeInPath: function(id) {
8611 nodesInPath.push(id);
8612 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8616 Method: clearNodesInPath
8618 Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8627 st.clearNodesInPath();
8630 clearNodesInPath: function(id) {
8631 nodesInPath.length = 0;
8632 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8638 Computes positions and plots the tree.
8641 refresh: function() {
8643 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8646 reposition: function() {
8647 this.graph.computeLevels(this.root, 0, "ignore");
8648 this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8649 this.graph.eachNode(function(n) {
8650 if(n.exist) n.drawn = true;
8652 this.compute('end');
8655 requestNodes: function(node, onComplete) {
8656 var handler = $.merge(this.controller, onComplete),
8657 lev = this.config.levelsToShow;
8658 if(handler.request) {
8659 var leaves = [], d = node._depth;
8660 node.eachLevel(0, lev, function(n) {
8664 n._level = lev - (n._depth - d);
8667 this.group.requestNodes(leaves, handler);
8670 handler.onComplete();
8673 contract: function(onComplete, switched) {
8674 var orn = this.config.orientation;
8675 var Geom = this.geom, Group = this.group;
8676 if(switched) Geom.switchOrientation(switched);
8677 var nodes = getNodesToHide.call(this);
8678 if(switched) Geom.switchOrientation(orn);
8679 Group.contract(nodes, $.merge(this.controller, onComplete));
8682 move: function(node, onComplete) {
8683 this.compute('end', false);
8684 var move = onComplete.Move, offset = {
8689 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8691 this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8694 expand: function (node, onComplete) {
8695 var nodeArray = getNodesToShow.call(this, node);
8696 this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8699 selectPath: function(node) {
8701 this.graph.eachNode(function(n) { n.selected = false; });
8702 function path(node) {
8703 if(node == null || node.selected) return;
8704 node.selected = true;
8705 $.each(that.group.getSiblings([node])[node.id],
8710 var parents = node.getParents();
8711 parents = (parents.length > 0)? parents[0] : null;
8714 for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8715 path(this.graph.getNode(ns[i]));
8722 Switches the current root node. Changes the topology of the Tree.
8725 id - (string) The id of the node to be set as root.
8726 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.
8727 onComplete - (optional|object) An action to perform after the animation (if any).
8732 st.setRoot('nodeId', 'animate', {
8733 onComplete: function() {
8739 setRoot: function(id, method, onComplete) {
8740 if(this.busy) return;
8742 var that = this, canvas = this.canvas;
8743 var rootNode = this.graph.getNode(this.root);
8744 var clickedNode = this.graph.getNode(id);
8745 function $setRoot() {
8746 if(this.config.multitree && clickedNode.data.$orn) {
8747 var orn = clickedNode.data.$orn;
8754 rootNode.data.$orn = opp;
8755 (function tag(rootNode) {
8756 rootNode.eachSubnode(function(n) {
8763 delete clickedNode.data.$orn;
8766 this.clickedNode = clickedNode;
8767 this.graph.computeLevels(this.root, 0, "ignore");
8768 this.geom.setRightLevelToShow(clickedNode, canvas, {
8770 onShow: function(node) {
8773 node.setData('alpha', 1, 'end');
8774 node.setData('alpha', 0);
8775 node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8779 this.compute('end');
8782 modes: ['linear', 'node-property:alpha'],
8783 onComplete: function() {
8786 onComplete: function() {
8787 onComplete && onComplete.onComplete();
8794 // delete previous orientations (if any)
8795 delete rootNode.data.$orns;
8797 if(method == 'animate') {
8798 $setRoot.call(this);
8799 that.selectPath(clickedNode);
8800 } else if(method == 'replot') {
8801 $setRoot.call(this);
8802 this.select(this.root);
8812 subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8813 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.
8814 onComplete - (optional|object) An action to perform after the animation (if any).
8819 st.addSubtree(json, 'animate', {
8820 onComplete: function() {
8826 addSubtree: function(subtree, method, onComplete) {
8827 if(method == 'replot') {
8828 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8829 } else if (method == 'animate') {
8830 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8835 Method: removeSubtree
8840 id - (string) The _id_ of the subtree to be removed.
8841 removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8842 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.
8843 onComplete - (optional|object) An action to perform after the animation (if any).
8848 st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8849 onComplete: function() {
8856 removeSubtree: function(id, removeRoot, method, onComplete) {
8857 var node = this.graph.getNode(id), subids = [];
8858 node.eachLevel(+!removeRoot, false, function(n) {
8861 if(method == 'replot') {
8862 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8863 } else if (method == 'animate') {
8864 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8871 Selects a node in the <ST> without performing an animation. Useful when selecting
8872 nodes which are currently hidden or deep inside the tree.
8875 id - (string) The id of the node to select.
8876 onComplete - (optional|object) an onComplete callback.
8880 st.select('mynodeid', {
8881 onComplete: function() {
8887 select: function(id, onComplete) {
8888 var group = this.group, geom = this.geom;
8889 var node= this.graph.getNode(id), canvas = this.canvas;
8890 var root = this.graph.getNode(this.root);
8891 var complete = $.merge(this.controller, onComplete);
8894 complete.onBeforeCompute(node);
8895 this.selectPath(node);
8896 this.clickedNode= node;
8897 this.requestNodes(node, {
8898 onComplete: function(){
8899 group.hide(group.prepare(getNodesToHide.call(that)), complete);
8900 geom.setRightLevelToShow(node, canvas);
8901 that.compute("current");
8902 that.graph.eachNode(function(n) {
8903 var pos = n.pos.getc(true);
8904 n.startPos.setc(pos.x, pos.y);
8905 n.endPos.setc(pos.x, pos.y);
8908 var offset = { x: complete.offsetX, y: complete.offsetY };
8909 that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8910 group.show(getNodesToShow.call(that));
8912 complete.onAfterCompute(that.clickedNode);
8913 complete.onComplete();
8921 Animates the <ST> to center the node specified by *id*.
8925 id - (string) A node id.
8926 options - (optional|object) A group of options and callbacks described below.
8927 onComplete - (object) An object callback called when the animation finishes.
8928 Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8933 st.onClick('mynodeid', {
8939 onComplete: function() {
8946 onClick: function (id, options) {
8947 var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8948 var innerController = {
8951 offsetX: config.offsetX || 0,
8952 offsetY: config.offsetY || 0
8954 setRightLevelToShowConfig: false,
8955 onBeforeRequest: $.empty,
8956 onBeforeContract: $.empty,
8957 onBeforeMove: $.empty,
8958 onBeforeExpand: $.empty
8960 var complete = $.merge(this.controller, innerController, options);
8964 var node = this.graph.getNode(id);
8965 this.selectPath(node, this.clickedNode);
8966 this.clickedNode = node;
8967 complete.onBeforeCompute(node);
8968 complete.onBeforeRequest(node);
8969 this.requestNodes(node, {
8970 onComplete: function() {
8971 complete.onBeforeContract(node);
8973 onComplete: function() {
8974 Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8975 complete.onBeforeMove(node);
8977 Move: complete.Move,
8978 onComplete: function() {
8979 complete.onBeforeExpand(node);
8981 onComplete: function() {
8983 complete.onAfterCompute(id);
8984 complete.onComplete();
8999 $jit.ST.$extend = true;
9004 Custom extension of <Graph.Op>.
9008 All <Graph.Op> methods
9015 $jit.ST.Op = new Class({
9017 Implements: Graph.Op
9023 Performs operations on group of nodes.
9026 $jit.ST.Group = new Class({
9028 initialize: function(viz) {
9030 this.canvas = viz.canvas;
9031 this.config = viz.config;
9032 this.animation = new Animation;
9038 Calls the request method on the controller to request a subtree for each node.
9040 requestNodes: function(nodes, controller) {
9041 var counter = 0, len = nodes.length, nodeSelected = {};
9042 var complete = function() { controller.onComplete(); };
9044 if(len == 0) complete();
9045 for(var i=0; i<len; i++) {
9046 nodeSelected[nodes[i].id] = nodes[i];
9047 controller.request(nodes[i].id, nodes[i]._level, {
9048 onComplete: function(nodeId, data) {
9049 if(data && data.children) {
9051 viz.op.sum(data, { type: 'nothing' });
9053 if(++counter == len) {
9054 viz.graph.computeLevels(viz.root, 0);
9064 Collapses group of nodes.
9066 contract: function(nodes, controller) {
9070 nodes = this.prepare(nodes);
9071 this.animation.setOptions($.merge(controller, {
9073 compute: function(delta) {
9074 if(delta == 1) delta = 0.99;
9075 that.plotStep(1 - delta, controller, this.$animating);
9076 this.$animating = 'contract';
9079 complete: function() {
9080 that.hide(nodes, controller);
9085 hide: function(nodes, controller) {
9087 for(var i=0; i<nodes.length; i++) {
9088 // TODO nodes are requested on demand, but not
9089 // deleted when hidden. Would that be a good feature?
9090 // Currently that feature is buggy, so I'll turn it off
9091 // Actually this feature is buggy because trimming should take
9092 // place onAfterCompute and not right after collapsing nodes.
9093 if (true || !controller || !controller.request) {
9094 nodes[i].eachLevel(1, false, function(elem){
9104 nodes[i].eachLevel(1, false, function(n) {
9107 viz.op.removeNode(ids, { 'type': 'nothing' });
9108 viz.labels.clearLabels();
9111 controller.onComplete();
9116 Expands group of nodes.
9118 expand: function(nodes, controller) {
9121 this.animation.setOptions($.merge(controller, {
9123 compute: function(delta) {
9124 that.plotStep(delta, controller, this.$animating);
9125 this.$animating = 'expand';
9128 complete: function() {
9129 that.plotStep(undefined, controller, false);
9130 controller.onComplete();
9136 show: function(nodes) {
9137 var config = this.config;
9138 this.prepare(nodes);
9139 $.each(nodes, function(n) {
9140 // check for root nodes if multitree
9141 if(config.multitree && !('$orn' in n.data)) {
9142 delete n.data.$orns;
9144 n.eachSubnode(function(ch) {
9145 if(('$orn' in ch.data)
9146 && orns.indexOf(ch.data.$orn) < 0
9147 && ch.exist && !ch.drawn) {
9148 orns += ch.data.$orn + ' ';
9151 n.data.$orns = orns;
9153 n.eachLevel(0, config.levelsToShow, function(n) {
9154 if(n.exist) n.drawn = true;
9159 prepare: function(nodes) {
9160 this.nodes = this.getNodesWithChildren(nodes);
9165 Filters an array of nodes leaving only nodes with children.
9167 getNodesWithChildren: function(nodes) {
9168 var ans = [], config = this.config, root = this.viz.root;
9169 nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9170 for(var i=0; i<nodes.length; i++) {
9171 if(nodes[i].anySubnode("exist")) {
9172 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9173 if(!config.multitree || '$orn' in nodes[j].data) {
9174 desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9177 if(!desc) ans.push(nodes[i]);
9183 plotStep: function(delta, controller, animating) {
9185 config = this.config,
9186 canvas = viz.canvas,
9187 ctx = canvas.getCtx(),
9190 // hide nodes that are meant to be collapsed/expanded
9192 for(i=0; i<nodes.length; i++) {
9195 var root = config.multitree && !('$orn' in node.data);
9196 var orns = root && node.data.$orns;
9197 node.eachSubgraph(function(n) {
9198 // TODO(nico): Cleanup
9199 // special check for root node subnodes when
9200 // multitree is checked.
9201 if(root && orns && orns.indexOf(n.data.$orn) > 0
9204 nds[node.id].push(n);
9205 } else if((!root || !orns) && n.drawn) {
9207 nds[node.id].push(n);
9212 // plot the whole (non-scaled) tree
9213 if(nodes.length > 0) viz.fx.plot();
9214 // show nodes that were previously hidden
9216 $.each(nds[i], function(n) { n.drawn = true; });
9218 // plot each scaled subtree
9219 for(i=0; i<nodes.length; i++) {
9222 viz.fx.plotSubtree(node, controller, delta, animating);
9227 getSiblings: function(nodes) {
9229 $.each(nodes, function(n) {
9230 var par = n.getParents();
9231 if (par.length == 0) {
9232 siblings[n.id] = [n];
9235 par[0].eachSubnode(function(sn) {
9238 siblings[n.id] = ans;
9248 Performs low level geometrical computations.
9252 This instance can be accessed with the _geom_ parameter of the st instance created.
9257 var st = new ST(canvas, config);
9258 st.geom.translate //or can also call any other <ST.Geom> method
9263 $jit.ST.Geom = new Class({
9264 Implements: Graph.Geom,
9266 Changes the tree current orientation to the one specified.
9268 You should usually use <ST.switchPosition> instead.
9270 switchOrientation: function(orn) {
9271 this.config.orientation = orn;
9275 Makes a value dispatch according to the current layout
9276 Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9278 dispatch: function() {
9279 // TODO(nico) should store Array.prototype.slice.call somewhere.
9280 var args = Array.prototype.slice.call(arguments);
9281 var s = args.shift(), len = args.length;
9282 var val = function(a) { return typeof a == 'function'? a() : a; };
9284 return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9285 } else if(len == 4) {
9287 case "top": return val(args[0]);
9288 case "right": return val(args[1]);
9289 case "bottom": return val(args[2]);
9290 case "left": return val(args[3]);
9297 Returns label height or with, depending on the tree current orientation.
9299 getSize: function(n, invert) {
9300 var data = n.data, config = this.config;
9301 var siblingOffset = config.siblingOffset;
9302 var s = (config.multitree
9304 && data.$orn) || config.orientation;
9305 var w = n.getData('width') + siblingOffset;
9306 var h = n.getData('height') + siblingOffset;
9308 return this.dispatch(s, h, w);
9310 return this.dispatch(s, w, h);
9314 Calculates a subtree base size. This is an utility function used by _getBaseSize_
9316 getTreeBaseSize: function(node, level, leaf) {
9317 var size = this.getSize(node, true), baseHeight = 0, that = this;
9318 if(leaf(level, node)) return size;
9319 if(level === 0) return 0;
9320 node.eachSubnode(function(elem) {
9321 baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9323 return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9330 Returns a Complex instance with the begin or end position of the edge to be plotted.
9334 node - A <Graph.Node> that is connected to this edge.
9335 type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9339 A <Complex> number specifying the begin or end position.
9341 getEdge: function(node, type, s) {
9342 var $C = function(a, b) {
9344 return node.pos.add(new Complex(a, b));
9347 var dim = this.node;
9348 var w = node.getData('width');
9349 var h = node.getData('height');
9351 if(type == 'begin') {
9352 if(dim.align == "center") {
9353 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9354 $C(0, -h/2),$C(w/2, 0));
9355 } else if(dim.align == "left") {
9356 return this.dispatch(s, $C(0, h), $C(0, 0),
9357 $C(0, 0), $C(w, 0));
9358 } else if(dim.align == "right") {
9359 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9360 $C(0, -h),$C(0, 0));
9361 } else throw "align: not implemented";
9364 } else if(type == 'end') {
9365 if(dim.align == "center") {
9366 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9367 $C(0, h/2), $C(-w/2, 0));
9368 } else if(dim.align == "left") {
9369 return this.dispatch(s, $C(0, 0), $C(w, 0),
9370 $C(0, h), $C(0, 0));
9371 } else if(dim.align == "right") {
9372 return this.dispatch(s, $C(0, -h),$C(0, 0),
9373 $C(0, 0), $C(-w, 0));
9374 } else throw "align: not implemented";
9379 Adjusts the tree position due to canvas scaling or translation.
9381 getScaledTreePosition: function(node, scale) {
9382 var dim = this.node;
9383 var w = node.getData('width');
9384 var h = node.getData('height');
9385 var s = (this.config.multitree
9386 && ('$orn' in node.data)
9387 && node.data.$orn) || this.config.orientation;
9389 var $C = function(a, b) {
9391 return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9394 if(dim.align == "left") {
9395 return this.dispatch(s, $C(0, h), $C(0, 0),
9396 $C(0, 0), $C(w, 0));
9397 } else if(dim.align == "center") {
9398 return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9399 $C(0, -h / 2),$C(w / 2, 0));
9400 } else if(dim.align == "right") {
9401 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9402 $C(0, -h),$C(0, 0));
9403 } else throw "align: not implemented";
9409 Returns a Boolean if the current subtree fits in canvas.
9413 node - A <Graph.Node> which is the current root of the subtree.
9414 canvas - The <Canvas> object.
9415 level - The depth of the subtree to be considered.
9417 treeFitsInCanvas: function(node, canvas, level) {
9418 var csize = canvas.getSize();
9419 var s = (this.config.multitree
9420 && ('$orn' in node.data)
9421 && node.data.$orn) || this.config.orientation;
9423 var size = this.dispatch(s, csize.width, csize.height);
9424 var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9425 return level === 0 || !node.anySubnode();
9427 return (baseSize < size);
9434 Custom extension of <Graph.Plot>.
9438 All <Graph.Plot> methods
9445 $jit.ST.Plot = new Class({
9447 Implements: Graph.Plot,
9450 Plots a subtree from the spacetree.
9452 plotSubtree: function(node, opt, scale, animating) {
9453 var viz = this.viz, canvas = viz.canvas, config = viz.config;
9454 scale = Math.min(Math.max(0.001, scale), 1);
9457 var ctx = canvas.getCtx();
9458 var diff = viz.geom.getScaledTreePosition(node, scale);
9459 ctx.translate(diff.x, diff.y);
9460 ctx.scale(scale, scale);
9462 this.plotTree(node, $.merge(opt, {
9464 'hideLabels': !!scale,
9465 'plotSubtree': function(n, ch) {
9466 var root = config.multitree && !('$orn' in node.data);
9467 var orns = root && node.getData('orns');
9468 return !root || orns.indexOf(elem.getData('orn')) > -1;
9471 if(scale >= 0) node.drawn = true;
9475 Method: getAlignedPos
9477 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9481 pos - (object) A <Graph.Node> position.
9482 width - (number) The width of the node.
9483 height - (number) The height of the node.
9486 getAlignedPos: function(pos, width, height) {
9487 var nconfig = this.node;
9489 if(nconfig.align == "center") {
9491 x: pos.x - width / 2,
9492 y: pos.y - height / 2
9494 } else if (nconfig.align == "left") {
9495 orn = this.config.orientation;
9496 if(orn == "bottom" || orn == "top") {
9498 x: pos.x - width / 2,
9504 y: pos.y - height / 2
9507 } else if(nconfig.align == "right") {
9508 orn = this.config.orientation;
9509 if(orn == "bottom" || orn == "top") {
9511 x: pos.x - width / 2,
9517 y: pos.y - height / 2
9520 } else throw "align: not implemented";
9525 getOrientation: function(adj) {
9526 var config = this.config;
9527 var orn = config.orientation;
9529 if(config.multitree) {
9530 var nodeFrom = adj.nodeFrom;
9531 var nodeTo = adj.nodeTo;
9532 orn = (('$orn' in nodeFrom.data)
9533 && nodeFrom.data.$orn)
9534 || (('$orn' in nodeTo.data)
9535 && nodeTo.data.$orn);
9545 Custom extension of <Graph.Label>.
9546 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9550 All <Graph.Label> methods and subclasses.
9554 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9561 Custom extension of <Graph.Label.Native>.
9565 All <Graph.Label.Native> methods
9569 <Graph.Label.Native>
9571 $jit.ST.Label.Native = new Class({
9572 Implements: Graph.Label.Native,
9574 renderLabel: function(canvas, node, controller) {
9575 var ctx = canvas.getCtx();
9576 var coord = node.pos.getc(true);
9577 ctx.fillText(node.name, coord.x, coord.y);
9581 $jit.ST.Label.DOM = new Class({
9582 Implements: Graph.Label.DOM,
9587 Overrides abstract method placeLabel in <Graph.Plot>.
9591 tag - A DOM label element.
9592 node - A <Graph.Node>.
9593 controller - A configuration/controller object passed to the visualization.
9596 placeLabel: function(tag, node, controller) {
9597 var pos = node.pos.getc(true),
9598 config = this.viz.config,
9600 canvas = this.viz.canvas,
9601 w = node.getData('width'),
9602 h = node.getData('height'),
9603 radius = canvas.getSize(),
9606 var ox = canvas.translateOffsetX,
9607 oy = canvas.translateOffsetY,
9608 sx = canvas.scaleOffsetX,
9609 sy = canvas.scaleOffsetY,
9610 posx = pos.x * sx + ox,
9611 posy = pos.y * sy + oy;
9613 if(dim.align == "center") {
9615 x: Math.round(posx - w / 2 + radius.width/2),
9616 y: Math.round(posy - h / 2 + radius.height/2)
9618 } else if (dim.align == "left") {
9619 orn = config.orientation;
9620 if(orn == "bottom" || orn == "top") {
9622 x: Math.round(posx - w / 2 + radius.width/2),
9623 y: Math.round(posy + radius.height/2)
9627 x: Math.round(posx + radius.width/2),
9628 y: Math.round(posy - h / 2 + radius.height/2)
9631 } else if(dim.align == "right") {
9632 orn = config.orientation;
9633 if(orn == "bottom" || orn == "top") {
9635 x: Math.round(posx - w / 2 + radius.width/2),
9636 y: Math.round(posy - h + radius.height/2)
9640 x: Math.round(posx - w + radius.width/2),
9641 y: Math.round(posy - h / 2 + radius.height/2)
9644 } else throw "align: not implemented";
9646 var style = tag.style;
9647 style.left = labelPos.x + 'px';
9648 style.top = labelPos.y + 'px';
9649 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9650 controller.onPlaceLabel(tag, node);
9657 Custom extension of <Graph.Label.SVG>.
9661 All <Graph.Label.SVG> methods
9667 $jit.ST.Label.SVG = new Class({
9668 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9670 initialize: function(viz) {
9678 Custom extension of <Graph.Label.HTML>.
9682 All <Graph.Label.HTML> methods.
9689 $jit.ST.Label.HTML = new Class({
9690 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9692 initialize: function(viz) {
9699 Class: ST.Plot.NodeTypes
9701 This class contains a list of <Graph.Node> built-in types.
9702 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9704 You can add your custom node types, customizing your visualization to the extreme.
9709 ST.Plot.NodeTypes.implement({
9711 'render': function(node, canvas) {
9712 //print your custom node to canvas
9715 'contains': function(node, pos) {
9716 //return true if pos is inside the node or false otherwise
9723 $jit.ST.Plot.NodeTypes = new Class({
9726 'contains': $.lambda(false)
9729 'render': function(node, canvas) {
9730 var dim = node.getData('dim'),
9731 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9733 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9735 'contains': function(node, pos) {
9736 var dim = node.getData('dim'),
9737 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9739 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9743 'render': function(node, canvas) {
9744 var dim = node.getData('dim'),
9746 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9747 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9749 'contains': function(node, pos) {
9750 var dim = node.getData('dim'),
9751 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9753 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9757 'render': function(node, canvas) {
9758 var width = node.getData('width'),
9759 height = node.getData('height'),
9760 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9761 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9763 'contains': function(node, pos) {
9764 var width = node.getData('width'),
9765 height = node.getData('height'),
9766 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9767 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9771 'render': function(node, canvas) {
9772 var width = node.getData('width'),
9773 height = node.getData('height'),
9774 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9775 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9777 'contains': function(node, pos) {
9778 var width = node.getData('width'),
9779 height = node.getData('height'),
9780 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9781 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9787 Class: ST.Plot.EdgeTypes
9789 This class contains a list of <Graph.Adjacence> built-in types.
9790 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9792 You can add your custom edge types, customizing your visualization to the extreme.
9797 ST.Plot.EdgeTypes.implement({
9799 'render': function(adj, canvas) {
9800 //print your custom edge to canvas
9803 'contains': function(adj, pos) {
9804 //return true if pos is inside the arc or false otherwise
9811 $jit.ST.Plot.EdgeTypes = new Class({
9814 'render': function(adj, canvas) {
9815 var orn = this.getOrientation(adj),
9816 nodeFrom = adj.nodeFrom,
9817 nodeTo = adj.nodeTo,
9818 rel = nodeFrom._depth < nodeTo._depth,
9819 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9820 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9821 this.edgeHelper.line.render(from, to, canvas);
9823 'contains': function(adj, pos) {
9824 var orn = this.getOrientation(adj),
9825 nodeFrom = adj.nodeFrom,
9826 nodeTo = adj.nodeTo,
9827 rel = nodeFrom._depth < nodeTo._depth,
9828 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9829 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9830 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9834 'render': function(adj, canvas) {
9835 var orn = this.getOrientation(adj),
9836 node = adj.nodeFrom,
9838 dim = adj.getData('dim'),
9839 from = this.viz.geom.getEdge(node, 'begin', orn),
9840 to = this.viz.geom.getEdge(child, 'end', orn),
9841 direction = adj.data.$direction,
9842 inv = (direction && direction.length>1 && direction[0] != node.id);
9843 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9845 'contains': function(adj, pos) {
9846 var orn = this.getOrientation(adj),
9847 nodeFrom = adj.nodeFrom,
9848 nodeTo = adj.nodeTo,
9849 rel = nodeFrom._depth < nodeTo._depth,
9850 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9851 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9852 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9855 'quadratic:begin': {
9856 'render': function(adj, canvas) {
9857 var orn = this.getOrientation(adj);
9858 var nodeFrom = adj.nodeFrom,
9859 nodeTo = adj.nodeTo,
9860 rel = nodeFrom._depth < nodeTo._depth,
9861 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9862 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9863 dim = adj.getData('dim'),
9864 ctx = canvas.getCtx();
9866 ctx.moveTo(begin.x, begin.y);
9869 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9872 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9875 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9878 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9885 'render': function(adj, canvas) {
9886 var orn = this.getOrientation(adj);
9887 var nodeFrom = adj.nodeFrom,
9888 nodeTo = adj.nodeTo,
9889 rel = nodeFrom._depth < nodeTo._depth,
9890 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9891 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9892 dim = adj.getData('dim'),
9893 ctx = canvas.getCtx();
9895 ctx.moveTo(begin.x, begin.y);
9898 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9901 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9904 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9907 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9914 'render': function(adj, canvas) {
9915 var orn = this.getOrientation(adj),
9916 nodeFrom = adj.nodeFrom,
9917 nodeTo = adj.nodeTo,
9918 rel = nodeFrom._depth < nodeTo._depth,
9919 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9920 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9921 dim = adj.getData('dim'),
9922 ctx = canvas.getCtx();
9924 ctx.moveTo(begin.x, begin.y);
9927 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9930 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9933 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9936 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9945 Options.LineChart = {
9949 labelOffset: 3, // label offset
9950 type: 'basic', // gradient
9966 selectOnHover: true,
9967 showAggregates: true,
9969 filterOnClick: false,
9970 restoreOnRightClick: false
9975 * File: LineChart.js
9979 $jit.ST.Plot.NodeTypes.implement({
9980 'linechart-basic' : {
9981 'render' : function(node, canvas) {
9982 var pos = node.pos.getc(true),
9983 width = node.getData('width'),
9984 height = node.getData('height'),
9985 algnPos = this.getAlignedPos(pos, width, height),
9986 x = algnPos.x + width/2 , y = algnPos.y,
9987 stringArray = node.getData('stringArray'),
9988 lastNode = node.getData('lastNode'),
9989 dimArray = node.getData('dimArray'),
9990 valArray = node.getData('valueArray'),
9991 colorArray = node.getData('colorArray'),
9992 colorLength = colorArray.length,
9993 config = node.getData('config'),
9994 gradient = node.getData('gradient'),
9995 showLabels = config.showLabels,
9996 aggregates = config.showAggregates,
9997 label = config.Label,
9998 prev = node.getData('prev'),
9999 dataPointSize = config.dataPointSize;
10001 var ctx = canvas.getCtx(), border = node.getData('border');
10002 if (colorArray && dimArray && stringArray) {
10004 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10005 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10007 ctx.lineCap = "round";
10011 //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
10013 ctx.moveTo(x, y - dimArray[i][0]);
10014 ctx.lineTo(x + width, y - dimArray[i][1]);
10018 //render data point
10019 ctx.fillRect(x - (dataPointSize/2), y - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10023 if(label.type == 'Native' && showLabels) {
10025 ctx.fillStyle = ctx.strokeStyle = label.color;
10026 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10027 ctx.textAlign = 'center';
10028 ctx.textBaseline = 'middle';
10029 ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10035 'contains': function(node, mpos) {
10036 var pos = node.pos.getc(true),
10037 width = node.getData('width'),
10038 height = node.getData('height'),
10039 config = node.getData('config'),
10040 dataPointSize = config.dataPointSize,
10041 dataPointMidPoint = dataPointSize/2,
10042 algnPos = this.getAlignedPos(pos, width, height),
10043 x = algnPos.x + width/2, y = algnPos.y,
10044 dimArray = node.getData('dimArray');
10045 //bounding box check
10046 if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10050 for(var i=0, l=dimArray.length; i<l; i++) {
10051 var dimi = dimArray[i];
10052 var url = Url.decode(node.getData('linkArray')[i]);
10053 if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10054 var valArrayCur = node.getData('valArrayCur');
10055 var results = array_match(valArrayCur[i],valArrayCur);
10056 var matches = results[0];
10057 var indexValues = results[1];
10059 var names = new Array(),
10060 values = new Array(),
10061 percentages = new Array(),
10062 linksArr = new Array();
10063 for(var j=0, il=indexValues.length; j<il; j++) {
10064 names[j] = node.getData('stringArray')[indexValues[j]];
10065 values[j] = valArrayCur[indexValues[j]];
10066 percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10067 linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10072 'color': node.getData('colorArray')[i],
10074 'percentage': percentages,
10081 'name': node.getData('stringArray')[i],
10082 'color': node.getData('colorArray')[i],
10083 'value': node.getData('valueArray')[i][0],
10084 // 'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10085 'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10100 A visualization that displays line charts.
10102 Constructor Options:
10104 See <Options.Line>.
10107 $jit.LineChart = new Class({
10109 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10113 initialize: function(opt) {
10114 this.controller = this.config =
10115 $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10116 Label: { type: 'Native' }
10118 //set functions for showLabels and showAggregates
10119 var showLabels = this.config.showLabels,
10120 typeLabels = $.type(showLabels),
10121 showAggregates = this.config.showAggregates,
10122 typeAggregates = $.type(showAggregates);
10123 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10124 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10125 Options.Fx.clearCanvas = false;
10126 this.initializeViz();
10129 initializeViz: function() {
10130 var config = this.config,
10132 nodeType = config.type.split(":")[0],
10135 var st = new $jit.ST({
10136 injectInto: config.injectInto,
10137 orientation: "bottom",
10138 backgroundColor: config.backgroundColor,
10139 renderBackground: config.renderBackground,
10143 withLabels: config.Label.type != 'Native',
10144 useCanvas: config.useCanvas,
10146 type: config.Label.type
10150 type: 'linechart-' + nodeType,
10159 enable: config.Tips.enable,
10162 onShow: function(tip, node, contains) {
10163 var elem = contains;
10164 config.Tips.onShow(tip, elem, node);
10170 onClick: function(node, eventInfo, evt) {
10171 if(!config.filterOnClick && !config.Events.enable) return;
10172 var elem = eventInfo.getContains();
10173 if(elem) config.filterOnClick && that.filter(elem.name);
10174 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10176 onRightClick: function(node, eventInfo, evt) {
10177 if(!config.restoreOnRightClick) return;
10180 onMouseMove: function(node, eventInfo, evt) {
10181 if(!config.selectOnHover) return;
10183 var elem = eventInfo.getContains();
10184 that.select(node.id, elem.name, elem.index);
10186 that.select(false, false, false);
10190 onCreateLabel: function(domElement, node) {
10191 var labelConf = config.Label,
10192 valueArray = node.getData('valueArray'),
10193 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10194 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10195 if(node.getData('prev')) {
10197 wrapper: document.createElement('div'),
10198 aggregate: document.createElement('div'),
10199 label: document.createElement('div')
10201 var wrapper = nlbs.wrapper,
10202 label = nlbs.label,
10203 aggregate = nlbs.aggregate,
10204 wrapperStyle = wrapper.style,
10205 labelStyle = label.style,
10206 aggregateStyle = aggregate.style;
10207 //store node labels
10208 nodeLabels[node.id] = nlbs;
10210 wrapper.appendChild(label);
10211 wrapper.appendChild(aggregate);
10212 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10213 label.style.display = 'none';
10215 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10216 aggregate.style.display = 'none';
10218 wrapperStyle.position = 'relative';
10219 wrapperStyle.overflow = 'visible';
10220 wrapperStyle.fontSize = labelConf.size + 'px';
10221 wrapperStyle.fontFamily = labelConf.family;
10222 wrapperStyle.color = labelConf.color;
10223 wrapperStyle.textAlign = 'center';
10224 aggregateStyle.position = labelStyle.position = 'absolute';
10226 domElement.style.width = node.getData('width') + 'px';
10227 domElement.style.height = node.getData('height') + 'px';
10228 label.innerHTML = node.name;
10230 domElement.appendChild(wrapper);
10233 onPlaceLabel: function(domElement, node) {
10234 if(!node.getData('prev')) return;
10235 var labels = nodeLabels[node.id],
10236 wrapperStyle = labels.wrapper.style,
10237 labelStyle = labels.label.style,
10238 aggregateStyle = labels.aggregate.style,
10239 width = node.getData('width'),
10240 height = node.getData('height'),
10241 dimArray = node.getData('dimArray'),
10242 valArray = node.getData('valueArray'),
10243 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10244 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10245 font = parseInt(wrapperStyle.fontSize, 10),
10246 domStyle = domElement.style;
10248 if(dimArray && valArray) {
10249 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10250 labelStyle.display = '';
10252 labelStyle.display = 'none';
10254 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10255 aggregateStyle.display = '';
10257 aggregateStyle.display = 'none';
10259 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10260 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10261 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10262 if(dimArray[i][0] > 0) {
10263 acum+= valArray[i][0];
10264 leftAcum+= dimArray[i][0];
10267 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10268 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10269 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10270 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10271 labels.aggregate.innerHTML = acum;
10276 var size = st.canvas.getSize(),
10277 margin = config.Margin;
10278 st.config.offsetY = -size.height/2 + margin.bottom
10279 + (config.showLabels && (config.labelOffset + config.Label.size));
10280 st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10282 this.canvas = this.st.canvas;
10285 renderTitle: function() {
10286 var canvas = this.canvas,
10287 size = canvas.getSize(),
10288 config = this.config,
10289 margin = config.Margin,
10290 label = config.Label,
10291 title = config.Title;
10292 ctx = canvas.getCtx();
10293 ctx.fillStyle = title.color;
10294 ctx.textAlign = 'left';
10295 ctx.textBaseline = 'top';
10296 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10297 if(label.type == 'Native') {
10298 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10302 renderTicks: function() {
10304 var canvas = this.canvas,
10305 size = canvas.getSize(),
10306 config = this.config,
10307 margin = config.Margin,
10308 ticks = config.Ticks,
10309 title = config.Title,
10310 subtitle = config.Subtitle,
10311 label = config.Label,
10312 maxValue = this.maxValue,
10313 maxTickValue = Math.ceil(maxValue*.1)*10;
10314 if(maxTickValue == maxValue) {
10315 var length = maxTickValue.toString().length;
10316 maxTickValue = maxTickValue + parseInt(pad(1,length));
10321 labelIncrement = maxTickValue/ticks.segments,
10322 ctx = canvas.getCtx();
10323 ctx.strokeStyle = ticks.color;
10324 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10325 ctx.textAlign = 'center';
10326 ctx.textBaseline = 'middle';
10328 idLabel = canvas.id + "-label";
10330 container = document.getElementById(idLabel);
10333 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10334 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10335 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)),
10336 segmentLength = grid/ticks.segments;
10337 ctx.fillStyle = ticks.color;
10338 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));
10340 while(axis>=grid) {
10342 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10343 ctx.rotate(Math.PI / 2);
10344 ctx.fillStyle = label.color;
10345 if(config.showLabels) {
10346 if(label.type == 'Native') {
10347 ctx.fillText(labelValue, 0, 0);
10349 //html labels on y axis
10350 labelDiv = document.createElement('div');
10351 labelDiv.innerHTML = labelValue;
10352 labelDiv.className = "rotatedLabel";
10353 // labelDiv.class = "rotatedLabel";
10354 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10355 labelDiv.style.left = margin.left + "px";
10356 labelDiv.style.width = labelDim + "px";
10357 labelDiv.style.height = labelDim + "px";
10358 labelDiv.style.textAlign = "center";
10359 labelDiv.style.verticalAlign = "middle";
10360 labelDiv.style.position = "absolute";
10361 container.appendChild(labelDiv);
10365 ctx.fillStyle = ticks.color;
10366 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 );
10367 htmlOrigin += segmentLength;
10368 axis += segmentLength;
10369 labelValue += labelIncrement;
10379 renderBackground: function() {
10380 var canvas = this.canvas,
10381 config = this.config,
10382 backgroundColor = config.backgroundColor,
10383 size = canvas.getSize(),
10384 ctx = canvas.getCtx();
10385 //ctx.globalCompositeOperation = "destination-over";
10386 ctx.fillStyle = backgroundColor;
10387 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10389 clear: function() {
10390 var canvas = this.canvas;
10391 var ctx = canvas.getCtx(),
10392 size = canvas.getSize();
10393 ctx.fillStyle = "rgba(255,255,255,0)";
10394 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10395 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
10397 resizeGraph: function(json,width) {
10398 var canvas = this.canvas,
10399 size = canvas.getSize(),
10400 orgHeight = size.height;
10402 canvas.resize(width,orgHeight);
10403 if(typeof FlashCanvas == "undefined") {
10406 this.clear();// hack for flashcanvas bug not properly clearing rectangle
10408 this.loadJSON(json);
10414 Loads JSON data into the visualization.
10418 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>.
10422 var areaChart = new $jit.AreaChart(options);
10423 areaChart.loadJSON(json);
10426 loadJSON: function(json) {
10427 var prefix = $.time(),
10430 name = $.splat(json.label),
10431 color = $.splat(json.color || this.colors),
10432 config = this.config,
10433 ticks = config.Ticks,
10434 renderBackground = config.renderBackground,
10435 gradient = !!config.type.split(":")[1],
10436 animate = config.animate,
10437 title = config.Title,
10438 groupTotalValue = 0;
10440 var valArrayAll = new Array();
10442 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10443 var val = values[i];
10444 var valArray = $.splat(val.values);
10445 for (var j=0, len=valArray.length; j<len; j++) {
10446 valArrayAll.push(parseInt(valArray[j]));
10448 groupTotalValue += parseInt(valArray.sum());
10451 this.maxValue = Math.max.apply(null, valArrayAll);
10453 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10454 var val = values[i], prev = values[i-1];
10456 var next = (i+1 < l) ? values[i+1] : 0;
10457 var valLeft = $.splat(values[i].values);
10458 var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10459 var valArray = $.zip(valLeft, valRight);
10460 var valArrayCur = $.splat(values[i].values);
10461 var linkArray = $.splat(values[i].links);
10462 var acumLeft = 0, acumRight = 0;
10463 var lastNode = (l-1 == i) ? true : false;
10465 'id': prefix + val.label,
10469 '$valueArray': valArray,
10470 '$valArrayCur': valArrayCur,
10471 '$colorArray': color,
10472 '$linkArray': linkArray,
10473 '$stringArray': name,
10474 '$next': next? next.label:false,
10475 '$prev': prev? prev.label:false,
10477 '$lastNode': lastNode,
10478 '$groupTotalValue': groupTotalValue,
10479 '$gradient': gradient
10485 'id': prefix + '$root',
10496 this.normalizeDims();
10498 if(renderBackground) {
10499 this.renderBackground();
10502 if(!animate && ticks.enable) {
10503 this.renderTicks();
10508 this.renderTitle();
10512 st.select(st.root);
10515 modes: ['node-property:height:dimArray'],
10524 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.
10528 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10529 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10534 areaChart.updateJSON(json, {
10535 onComplete: function() {
10536 alert('update complete!');
10541 updateJSON: function(json, onComplete) {
10542 if(this.busy) return;
10547 labels = json.label && $.splat(json.label),
10548 values = json.values,
10549 animate = this.config.animate,
10551 $.each(values, function(v) {
10552 var n = graph.getByName(v.label);
10554 v.values = $.splat(v.values);
10555 var stringArray = n.getData('stringArray'),
10556 valArray = n.getData('valueArray');
10557 $.each(valArray, function(a, i) {
10558 a[0] = v.values[i];
10559 if(labels) stringArray[i] = labels[i];
10561 n.setData('valueArray', valArray);
10562 var prev = n.getData('prev'),
10563 next = n.getData('next'),
10564 nextNode = graph.getByName(next);
10566 var p = graph.getByName(prev);
10568 var valArray = p.getData('valueArray');
10569 $.each(valArray, function(a, i) {
10570 a[1] = v.values[i];
10575 var valArray = n.getData('valueArray');
10576 $.each(valArray, function(a, i) {
10577 a[1] = v.values[i];
10582 this.normalizeDims();
10585 st.select(st.root);
10588 modes: ['node-property:height:dimArray'],
10590 onComplete: function() {
10592 onComplete && onComplete.onComplete();
10601 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10605 Variable strings arguments with the name of the stacks.
10610 areaChart.filter('label A', 'label C');
10615 <AreaChart.restore>.
10617 filter: function() {
10618 if(this.busy) return;
10620 if(this.config.Tips.enable) this.st.tips.hide();
10621 this.select(false, false, false);
10622 var args = Array.prototype.slice.call(arguments);
10623 var rt = this.st.graph.getNode(this.st.root);
10625 rt.eachAdjacency(function(adj) {
10626 var n = adj.nodeTo,
10627 dimArray = n.getData('dimArray'),
10628 stringArray = n.getData('stringArray');
10629 n.setData('dimArray', $.map(dimArray, function(d, i) {
10630 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10633 this.st.fx.animate({
10634 modes: ['node-property:dimArray'],
10636 onComplete: function() {
10645 Sets all stacks that could have been filtered visible.
10650 areaChart.restore();
10655 <AreaChart.filter>.
10657 restore: function() {
10658 if(this.busy) return;
10660 if(this.config.Tips.enable) this.st.tips.hide();
10661 this.select(false, false, false);
10662 this.normalizeDims();
10664 this.st.fx.animate({
10665 modes: ['node-property:height:dimArray'],
10667 onComplete: function() {
10672 //adds the little brown bar when hovering the node
10673 select: function(id, name, index) {
10674 if(!this.config.selectOnHover) return;
10675 var s = this.selected;
10676 if(s.id != id || s.name != name
10677 || s.index != index) {
10681 this.st.graph.eachNode(function(n) {
10682 n.setData('border', false);
10685 var n = this.st.graph.getNode(id);
10686 n.setData('border', s);
10687 var link = index === 0? 'prev':'next';
10688 link = n.getData(link);
10690 n = this.st.graph.getByName(link);
10692 n.setData('border', {
10706 Returns an object containing as keys the legend names and as values hex strings with color values.
10711 var legend = areaChart.getLegend();
10714 getLegend: function() {
10715 var legend = new Array();
10716 var name = new Array();
10717 var color = new Array();
10719 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10722 var colors = n.getData('colorArray'),
10723 len = colors.length;
10724 $.each(n.getData('stringArray'), function(s, i) {
10725 color[i] = colors[i % len];
10728 legend['name'] = name;
10729 legend['color'] = color;
10734 Method: getMaxValue
10736 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10741 var ans = areaChart.getMaxValue();
10744 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10749 //will return 100 for all AreaChart instances,
10750 //displaying all of them with the same scale
10751 $jit.AreaChart.implement({
10752 'getMaxValue': function() {
10760 normalizeDims: function() {
10761 //number of elements
10762 var root = this.st.graph.getNode(this.st.root), l=0;
10763 root.eachAdjacency(function() {
10768 var maxValue = this.maxValue || 1,
10769 size = this.st.canvas.getSize(),
10770 config = this.config,
10771 margin = config.Margin,
10772 labelOffset = config.labelOffset + config.Label.size,
10773 fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10774 animate = config.animate,
10775 ticks = config.Ticks,
10776 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10777 - (config.showLabels && labelOffset);
10780 var maxTickValue = Math.ceil(maxValue*.1)*10;
10781 if(maxTickValue == maxValue) {
10782 var length = maxTickValue.toString().length;
10783 maxTickValue = maxTickValue + parseInt(pad(1,length));
10788 this.st.graph.eachNode(function(n) {
10789 var acumLeft = 0, acumRight = 0, animateValue = [];
10790 $.each(n.getData('valueArray'), function(v) {
10792 acumRight += +v[1];
10793 animateValue.push([0, 0]);
10795 var acum = acumRight>acumLeft? acumRight:acumLeft;
10797 n.setData('width', fixedDim);
10799 n.setData('height', acum * height / maxValue, 'end');
10800 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10801 return [n[0] * height / maxValue, n[1] * height / maxValue];
10803 var dimArray = n.getData('dimArray');
10805 n.setData('dimArray', animateValue);
10810 n.setData('height', acum * height / maxValue);
10811 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10812 return [n[0] * height / maxTickValue, n[1] * height / maxTickValue];
10815 n.setData('height', acum * height / maxValue);
10816 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10817 return [n[0] * height / maxValue, n[1] * height / maxValue];
10832 * File: AreaChart.js
10836 $jit.ST.Plot.NodeTypes.implement({
10837 'areachart-stacked' : {
10838 'render' : function(node, canvas) {
10839 var pos = node.pos.getc(true),
10840 width = node.getData('width'),
10841 height = node.getData('height'),
10842 algnPos = this.getAlignedPos(pos, width, height),
10843 x = algnPos.x, y = algnPos.y,
10844 stringArray = node.getData('stringArray'),
10845 dimArray = node.getData('dimArray'),
10846 valArray = node.getData('valueArray'),
10847 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10848 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10849 colorArray = node.getData('colorArray'),
10850 colorLength = colorArray.length,
10851 config = node.getData('config'),
10852 gradient = node.getData('gradient'),
10853 showLabels = config.showLabels,
10854 aggregates = config.showAggregates,
10855 label = config.Label,
10856 prev = node.getData('prev');
10858 var ctx = canvas.getCtx(), border = node.getData('border');
10859 if (colorArray && dimArray && stringArray) {
10860 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10861 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10863 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10864 var h1 = acumLeft + dimArray[i][0],
10865 h2 = acumRight + dimArray[i][1],
10866 alpha = Math.atan((h2 - h1) / width),
10868 var linear = ctx.createLinearGradient(x + width/2,
10870 x + width/2 + delta * Math.sin(alpha),
10871 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10872 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10873 function(v) { return (v * 0.85) >> 0; }));
10874 linear.addColorStop(0, colorArray[i % colorLength]);
10875 linear.addColorStop(1, color);
10876 ctx.fillStyle = linear;
10879 ctx.moveTo(x, y - acumLeft);
10880 ctx.lineTo(x + width, y - acumRight);
10881 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10882 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10883 ctx.lineTo(x, y - acumLeft);
10887 var strong = border.name == stringArray[i];
10888 var perc = strong? 0.7 : 0.8;
10889 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10890 function(v) { return (v * perc) >> 0; }));
10891 ctx.strokeStyle = color;
10892 ctx.lineWidth = strong? 4 : 1;
10895 if(border.index === 0) {
10896 ctx.moveTo(x, y - acumLeft);
10897 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10899 ctx.moveTo(x + width, y - acumRight);
10900 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10905 acumLeft += (dimArray[i][0] || 0);
10906 acumRight += (dimArray[i][1] || 0);
10908 if(dimArray[i][0] > 0)
10909 valAcum += (valArray[i][0] || 0);
10911 if(prev && label.type == 'Native') {
10914 ctx.fillStyle = ctx.strokeStyle = label.color;
10915 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10916 ctx.textAlign = 'center';
10917 ctx.textBaseline = 'middle';
10918 if(aggregates(node.name, valLeft, valRight, node)) {
10919 ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10921 if(showLabels(node.name, valLeft, valRight, node)) {
10922 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10928 'contains': function(node, mpos) {
10929 var pos = node.pos.getc(true),
10930 width = node.getData('width'),
10931 height = node.getData('height'),
10932 algnPos = this.getAlignedPos(pos, width, height),
10933 x = algnPos.x, y = algnPos.y,
10934 dimArray = node.getData('dimArray'),
10936 //bounding box check
10937 if(mpos.x < x || mpos.x > x + width
10938 || mpos.y > y || mpos.y < y - height) {
10942 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10943 var dimi = dimArray[i];
10946 var intersec = lAcum + (rAcum - lAcum) * rx / width;
10947 if(mpos.y >= intersec) {
10948 var index = +(rx > width/2);
10950 'name': node.getData('stringArray')[i],
10951 'color': node.getData('colorArray')[i],
10952 'value': node.getData('valueArray')[i][index],
10965 A visualization that displays stacked area charts.
10967 Constructor Options:
10969 See <Options.AreaChart>.
10972 $jit.AreaChart = new Class({
10974 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10978 initialize: function(opt) {
10979 this.controller = this.config =
10980 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10981 Label: { type: 'Native' }
10983 //set functions for showLabels and showAggregates
10984 var showLabels = this.config.showLabels,
10985 typeLabels = $.type(showLabels),
10986 showAggregates = this.config.showAggregates,
10987 typeAggregates = $.type(showAggregates);
10988 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10989 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10991 this.initializeViz();
10994 initializeViz: function() {
10995 var config = this.config,
10997 nodeType = config.type.split(":")[0],
11000 var st = new $jit.ST({
11001 injectInto: config.injectInto,
11002 orientation: "bottom",
11006 withLabels: config.Label.type != 'Native',
11007 useCanvas: config.useCanvas,
11009 type: config.Label.type
11013 type: 'areachart-' + nodeType,
11022 enable: config.Tips.enable,
11025 onShow: function(tip, node, contains) {
11026 var elem = contains;
11027 config.Tips.onShow(tip, elem, node);
11033 onClick: function(node, eventInfo, evt) {
11034 if(!config.filterOnClick && !config.Events.enable) return;
11035 var elem = eventInfo.getContains();
11036 if(elem) config.filterOnClick && that.filter(elem.name);
11037 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11039 onRightClick: function(node, eventInfo, evt) {
11040 if(!config.restoreOnRightClick) return;
11043 onMouseMove: function(node, eventInfo, evt) {
11044 if(!config.selectOnHover) return;
11046 var elem = eventInfo.getContains();
11047 that.select(node.id, elem.name, elem.index);
11049 that.select(false, false, false);
11053 onCreateLabel: function(domElement, node) {
11054 var labelConf = config.Label,
11055 valueArray = node.getData('valueArray'),
11056 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11057 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11058 if(node.getData('prev')) {
11060 wrapper: document.createElement('div'),
11061 aggregate: document.createElement('div'),
11062 label: document.createElement('div')
11064 var wrapper = nlbs.wrapper,
11065 label = nlbs.label,
11066 aggregate = nlbs.aggregate,
11067 wrapperStyle = wrapper.style,
11068 labelStyle = label.style,
11069 aggregateStyle = aggregate.style;
11070 //store node labels
11071 nodeLabels[node.id] = nlbs;
11073 wrapper.appendChild(label);
11074 wrapper.appendChild(aggregate);
11075 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11076 label.style.display = 'none';
11078 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11079 aggregate.style.display = 'none';
11081 wrapperStyle.position = 'relative';
11082 wrapperStyle.overflow = 'visible';
11083 wrapperStyle.fontSize = labelConf.size + 'px';
11084 wrapperStyle.fontFamily = labelConf.family;
11085 wrapperStyle.color = labelConf.color;
11086 wrapperStyle.textAlign = 'center';
11087 aggregateStyle.position = labelStyle.position = 'absolute';
11089 domElement.style.width = node.getData('width') + 'px';
11090 domElement.style.height = node.getData('height') + 'px';
11091 label.innerHTML = node.name;
11093 domElement.appendChild(wrapper);
11096 onPlaceLabel: function(domElement, node) {
11097 if(!node.getData('prev')) return;
11098 var labels = nodeLabels[node.id],
11099 wrapperStyle = labels.wrapper.style,
11100 labelStyle = labels.label.style,
11101 aggregateStyle = labels.aggregate.style,
11102 width = node.getData('width'),
11103 height = node.getData('height'),
11104 dimArray = node.getData('dimArray'),
11105 valArray = node.getData('valueArray'),
11106 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11107 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11108 font = parseInt(wrapperStyle.fontSize, 10),
11109 domStyle = domElement.style;
11111 if(dimArray && valArray) {
11112 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11113 labelStyle.display = '';
11115 labelStyle.display = 'none';
11117 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11118 aggregateStyle.display = '';
11120 aggregateStyle.display = 'none';
11122 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11123 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11124 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11125 if(dimArray[i][0] > 0) {
11126 acum+= valArray[i][0];
11127 leftAcum+= dimArray[i][0];
11130 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11131 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11132 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11133 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11134 labels.aggregate.innerHTML = acum;
11139 var size = st.canvas.getSize(),
11140 margin = config.Margin;
11141 st.config.offsetY = -size.height/2 + margin.bottom
11142 + (config.showLabels && (config.labelOffset + config.Label.size));
11143 st.config.offsetX = (margin.right - margin.left)/2;
11145 this.canvas = this.st.canvas;
11151 Loads JSON data into the visualization.
11155 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>.
11159 var areaChart = new $jit.AreaChart(options);
11160 areaChart.loadJSON(json);
11163 loadJSON: function(json) {
11164 var prefix = $.time(),
11167 name = $.splat(json.label),
11168 color = $.splat(json.color || this.colors),
11169 config = this.config,
11170 gradient = !!config.type.split(":")[1],
11171 animate = config.animate;
11173 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11174 var val = values[i], prev = values[i-1], next = values[i+1];
11175 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11176 var valArray = $.zip(valLeft, valRight);
11177 var acumLeft = 0, acumRight = 0;
11179 'id': prefix + val.label,
11183 '$valueArray': valArray,
11184 '$colorArray': color,
11185 '$stringArray': name,
11186 '$next': next.label,
11187 '$prev': prev? prev.label:false,
11189 '$gradient': gradient
11195 'id': prefix + '$root',
11206 this.normalizeDims();
11208 st.select(st.root);
11211 modes: ['node-property:height:dimArray'],
11220 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.
11224 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11225 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11230 areaChart.updateJSON(json, {
11231 onComplete: function() {
11232 alert('update complete!');
11237 updateJSON: function(json, onComplete) {
11238 if(this.busy) return;
11243 labels = json.label && $.splat(json.label),
11244 values = json.values,
11245 animate = this.config.animate,
11247 $.each(values, function(v) {
11248 var n = graph.getByName(v.label);
11250 v.values = $.splat(v.values);
11251 var stringArray = n.getData('stringArray'),
11252 valArray = n.getData('valueArray');
11253 $.each(valArray, function(a, i) {
11254 a[0] = v.values[i];
11255 if(labels) stringArray[i] = labels[i];
11257 n.setData('valueArray', valArray);
11258 var prev = n.getData('prev'),
11259 next = n.getData('next'),
11260 nextNode = graph.getByName(next);
11262 var p = graph.getByName(prev);
11264 var valArray = p.getData('valueArray');
11265 $.each(valArray, function(a, i) {
11266 a[1] = v.values[i];
11271 var valArray = n.getData('valueArray');
11272 $.each(valArray, function(a, i) {
11273 a[1] = v.values[i];
11278 this.normalizeDims();
11280 st.select(st.root);
11283 modes: ['node-property:height:dimArray'],
11285 onComplete: function() {
11287 onComplete && onComplete.onComplete();
11296 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11300 Variable strings arguments with the name of the stacks.
11305 areaChart.filter('label A', 'label C');
11310 <AreaChart.restore>.
11312 filter: function() {
11313 if(this.busy) return;
11315 if(this.config.Tips.enable) this.st.tips.hide();
11316 this.select(false, false, false);
11317 var args = Array.prototype.slice.call(arguments);
11318 var rt = this.st.graph.getNode(this.st.root);
11320 rt.eachAdjacency(function(adj) {
11321 var n = adj.nodeTo,
11322 dimArray = n.getData('dimArray'),
11323 stringArray = n.getData('stringArray');
11324 n.setData('dimArray', $.map(dimArray, function(d, i) {
11325 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11328 this.st.fx.animate({
11329 modes: ['node-property:dimArray'],
11331 onComplete: function() {
11340 Sets all stacks that could have been filtered visible.
11345 areaChart.restore();
11350 <AreaChart.filter>.
11352 restore: function() {
11353 if(this.busy) return;
11355 if(this.config.Tips.enable) this.st.tips.hide();
11356 this.select(false, false, false);
11357 this.normalizeDims();
11359 this.st.fx.animate({
11360 modes: ['node-property:height:dimArray'],
11362 onComplete: function() {
11367 //adds the little brown bar when hovering the node
11368 select: function(id, name, index) {
11369 if(!this.config.selectOnHover) return;
11370 var s = this.selected;
11371 if(s.id != id || s.name != name
11372 || s.index != index) {
11376 this.st.graph.eachNode(function(n) {
11377 n.setData('border', false);
11380 var n = this.st.graph.getNode(id);
11381 n.setData('border', s);
11382 var link = index === 0? 'prev':'next';
11383 link = n.getData(link);
11385 n = this.st.graph.getByName(link);
11387 n.setData('border', {
11401 Returns an object containing as keys the legend names and as values hex strings with color values.
11406 var legend = areaChart.getLegend();
11409 getLegend: function() {
11412 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11415 var colors = n.getData('colorArray'),
11416 len = colors.length;
11417 $.each(n.getData('stringArray'), function(s, i) {
11418 legend[s] = colors[i % len];
11424 Method: getMaxValue
11426 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11431 var ans = areaChart.getMaxValue();
11434 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11439 //will return 100 for all AreaChart instances,
11440 //displaying all of them with the same scale
11441 $jit.AreaChart.implement({
11442 'getMaxValue': function() {
11449 getMaxValue: function() {
11451 this.st.graph.eachNode(function(n) {
11452 var valArray = n.getData('valueArray'),
11453 acumLeft = 0, acumRight = 0;
11454 $.each(valArray, function(v) {
11456 acumRight += +v[1];
11458 var acum = acumRight>acumLeft? acumRight:acumLeft;
11459 maxValue = maxValue>acum? maxValue:acum;
11464 normalizeDims: function() {
11465 //number of elements
11466 var root = this.st.graph.getNode(this.st.root), l=0;
11467 root.eachAdjacency(function() {
11470 var maxValue = this.getMaxValue() || 1,
11471 size = this.st.canvas.getSize(),
11472 config = this.config,
11473 margin = config.Margin,
11474 labelOffset = config.labelOffset + config.Label.size,
11475 fixedDim = (size.width - (margin.left + margin.right)) / l,
11476 animate = config.animate,
11477 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
11478 - (config.showLabels && labelOffset);
11479 this.st.graph.eachNode(function(n) {
11480 var acumLeft = 0, acumRight = 0, animateValue = [];
11481 $.each(n.getData('valueArray'), function(v) {
11483 acumRight += +v[1];
11484 animateValue.push([0, 0]);
11486 var acum = acumRight>acumLeft? acumRight:acumLeft;
11487 n.setData('width', fixedDim);
11489 n.setData('height', acum * height / maxValue, 'end');
11490 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11491 return [n[0] * height / maxValue, n[1] * height / maxValue];
11493 var dimArray = n.getData('dimArray');
11495 n.setData('dimArray', animateValue);
11498 n.setData('height', acum * height / maxValue);
11499 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11500 return [n[0] * height / maxValue, n[1] * height / maxValue];
11508 * File: Options.BarChart.js
11513 Object: Options.BarChart
11515 <BarChart> options.
11516 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11522 Options.BarChart = {
11527 hoveredColor: '#9fd4ff',
11528 orientation: 'horizontal',
11529 showAggregates: true,
11539 var barChart = new $jit.BarChart({
11542 type: 'stacked:gradient'
11549 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11550 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11551 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11552 barsOffset - (number) Default's *0*. Separation between bars.
11553 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11554 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11555 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11556 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11557 showLabels - (boolean) Default's *true*. Display the name of the slots.
11561 Options.BarChart = {
11565 type: 'stacked', //stacked, grouped, : gradient
11566 labelOffset: 3, //label offset
11567 barsOffset: 0, //distance between bars
11568 nodeCount: 0, //number of bars
11569 hoveredColor: '#9fd4ff',
11571 renderBackground: false,
11572 orientation: 'horizontal',
11573 showAggregates: true,
11592 * File: BarChart.js
11596 $jit.ST.Plot.NodeTypes.implement({
11597 'barchart-stacked' : {
11598 'render' : function(node, canvas) {
11599 var pos = node.pos.getc(true),
11600 width = node.getData('width'),
11601 height = node.getData('height'),
11602 algnPos = this.getAlignedPos(pos, width, height),
11603 x = algnPos.x, y = algnPos.y,
11604 dimArray = node.getData('dimArray'),
11605 valueArray = node.getData('valueArray'),
11606 stringArray = node.getData('stringArray'),
11607 linkArray = node.getData('linkArray'),
11608 gvl = node.getData('gvl'),
11609 colorArray = node.getData('colorArray'),
11610 colorLength = colorArray.length,
11611 nodeCount = node.getData('nodeCount');
11612 var ctx = canvas.getCtx(),
11613 canvasSize = canvas.getSize(),
11615 border = node.getData('border'),
11616 gradient = node.getData('gradient'),
11617 config = node.getData('config'),
11618 horz = config.orientation == 'horizontal',
11619 aggregates = config.showAggregates,
11620 showLabels = config.showLabels,
11621 label = config.Label,
11622 margin = config.Margin;
11625 if (colorArray && dimArray && stringArray) {
11626 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11627 acum += (dimArray[i] || 0);
11632 if(config.shadow.enable) {
11633 shadowThickness = config.shadow.size;
11634 ctx.fillStyle = "rgba(0,0,0,.2)";
11636 ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11638 ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11642 if (colorArray && dimArray && stringArray) {
11643 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11644 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11651 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
11652 x + acum + dimArray[i]/2, y + height);
11654 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
11655 x + width, y - acum- dimArray[i]/2);
11657 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11658 function(v) { return (v * 0.8) >> 0; }));
11659 linear.addColorStop(0, color);
11660 linear.addColorStop(0.3, colorArray[i % colorLength]);
11661 linear.addColorStop(0.7, colorArray[i % colorLength]);
11662 linear.addColorStop(1, color);
11663 ctx.fillStyle = linear;
11670 chartBarWidth = dimArray[i];
11671 chartBarHeight = height;
11676 yCoord = y - acum - dimArray[i];
11677 chartBarWidth = width;
11678 chartBarHeight = dimArray[i];
11680 ctx.fillRect(xCoord, yCoord, chartBarWidth, chartBarHeight);
11683 if (chartBarHeight > 0)
11685 ctx.font = label.style + ' ' + (label.size - 2) + 'px ' + label.family;
11686 labelText = valueArray[i].toString();
11687 mtxt = ctx.measureText(labelText);
11689 labelTextPaddingX = 10;
11690 labelTextPaddingY = 6;
11692 labelBoxWidth = mtxt.width + labelTextPaddingX;
11693 labelBoxHeight = label.size + labelTextPaddingY;
11695 // do NOT draw label if label box is smaller than chartBarHeight
11696 if ((horz && (labelBoxWidth < chartBarWidth)) || (!horz && (labelBoxHeight < chartBarHeight)))
11698 labelBoxX = xCoord + chartBarWidth/2 - mtxt.width/2 - labelTextPaddingX/2;
11699 labelBoxY = yCoord + chartBarHeight/2 - labelBoxHeight/2;
11701 ctx.fillStyle = "rgba(255,255,255,.2)";
11702 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "fill");
11703 ctx.fillStyle = "rgba(0,0,0,.8)";
11704 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "stroke");
11705 ctx.textAlign = 'center';
11706 ctx.fillStyle = "rgba(255,255,255,.6)";
11707 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2, labelBoxY + labelBoxHeight/2);
11708 ctx.fillStyle = "rgba(0,0,0,.6)";
11709 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2 + 1, labelBoxY + labelBoxHeight/2 + 1);
11713 if(border && border.name == stringArray[i]) {
11715 opt.dimValue = dimArray[i];
11717 acum += (dimArray[i] || 0);
11718 valAcum += (valueArray[i] || 0);
11723 ctx.strokeStyle = border.color;
11725 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11727 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11731 if(label.type == 'Native') {
11733 ctx.fillStyle = ctx.strokeStyle = label.color;
11734 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11735 ctx.textBaseline = 'middle';
11737 acumValueLabel = gvl;
11739 acumValueLabel = valAcum;
11741 if(aggregates(node.name, valAcum)) {
11743 ctx.textAlign = 'center';
11744 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11747 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11748 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11749 (label ? label.size + config.labelOffset : 0));
11750 mtxt = ctx.measureText(acumValueLabel);
11751 boxWidth = mtxt.width+10;
11753 boxHeight = label.size+6;
11755 if(boxHeight + acum + config.labelOffset > gridHeight) {
11756 bottomPadding = acum - config.labelOffset - boxHeight;
11758 bottomPadding = acum + config.labelOffset + inset;
11762 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11765 boxY = -boxHeight/2;
11767 ctx.rotate(0 * Math.PI / 180);
11768 ctx.fillStyle = "rgba(255,255,255,.8)";
11769 if(boxHeight + acum + config.labelOffset > gridHeight) {
11770 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11772 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11773 ctx.fillStyle = ctx.strokeStyle = label.color;
11774 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11779 if(showLabels(node.name, valAcum, node)) {
11784 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11787 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11788 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11789 boxWidth = mtxt.width+10;
11792 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11793 leftPadding = acum - config.labelOffset - boxWidth - inset;
11795 leftPadding = acum + config.labelOffset;
11799 ctx.textAlign = 'left';
11800 ctx.translate(x + inset + leftPadding, y + height/2);
11801 boxHeight = label.size+6;
11803 boxY = -boxHeight/2;
11804 ctx.fillStyle = "rgba(255,255,255,.8)";
11806 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11807 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11809 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11811 ctx.fillStyle = label.color;
11812 ctx.rotate(0 * Math.PI / 180);
11813 ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11817 //if the number of nodes greater than 8 rotate labels 45 degrees
11818 if(nodeCount > 8) {
11819 ctx.textAlign = 'left';
11820 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11821 ctx.rotate(45* Math.PI / 180);
11822 ctx.fillText(node.name, 0, 0);
11824 ctx.textAlign = 'center';
11825 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11833 'contains': function(node, mpos) {
11834 var pos = node.pos.getc(true),
11835 width = node.getData('width'),
11836 height = node.getData('height'),
11837 algnPos = this.getAlignedPos(pos, width, height),
11838 x = algnPos.x, y = algnPos.y,
11839 dimArray = node.getData('dimArray'),
11840 config = node.getData('config'),
11842 horz = config.orientation == 'horizontal';
11843 //bounding box check
11845 if(mpos.x < x || mpos.x > x + width
11846 || mpos.y > y + height || mpos.y < y) {
11850 if(mpos.x < x || mpos.x > x + width
11851 || mpos.y > y || mpos.y < y - height) {
11856 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11857 var dimi = dimArray[i];
11858 var url = Url.decode(node.getData('linkArray')[i]);
11861 var intersec = acum;
11862 if(mpos.x <= intersec) {
11864 'name': node.getData('stringArray')[i],
11865 'color': node.getData('colorArray')[i],
11866 'value': node.getData('valueArray')[i],
11867 'valuelabel': node.getData('valuelabelArray')[i],
11868 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11875 var intersec = acum;
11876 if(mpos.y >= intersec) {
11878 'name': node.getData('stringArray')[i],
11879 'color': node.getData('colorArray')[i],
11880 'value': node.getData('valueArray')[i],
11881 'valuelabel': node.getData('valuelabelArray')[i],
11882 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11892 'barchart-grouped' : {
11893 'render' : function(node, canvas) {
11894 var pos = node.pos.getc(true),
11895 width = node.getData('width'),
11896 height = node.getData('height'),
11897 algnPos = this.getAlignedPos(pos, width, height),
11898 x = algnPos.x, y = algnPos.y,
11899 dimArray = node.getData('dimArray'),
11900 valueArray = node.getData('valueArray'),
11901 valuelabelArray = node.getData('valuelabelArray'),
11902 linkArray = node.getData('linkArray'),
11903 valueLength = valueArray.length,
11904 colorArray = node.getData('colorArray'),
11905 colorLength = colorArray.length,
11906 stringArray = node.getData('stringArray');
11908 var ctx = canvas.getCtx(),
11909 canvasSize = canvas.getSize(),
11911 border = node.getData('border'),
11912 gradient = node.getData('gradient'),
11913 config = node.getData('config'),
11914 horz = config.orientation == 'horizontal',
11915 aggregates = config.showAggregates,
11916 showLabels = config.showLabels,
11917 label = config.Label,
11918 shadow = config.shadow,
11919 margin = config.Margin,
11920 fixedDim = (horz? height : width) / valueLength;
11924 maxValue = Math.max.apply(null, dimArray);
11928 ctx.fillStyle = "rgba(0,0,0,.2)";
11929 if (colorArray && dimArray && stringArray && shadow.enable) {
11930 shadowThickness = shadow.size;
11932 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11933 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11934 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11937 ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11942 if(nextBar && nextBar > dimArray[i]) {
11943 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11944 } else if (nextBar && nextBar < dimArray[i]){
11945 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11947 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11949 } else if (i> 0 && i<l-1) {
11950 if(nextBar && nextBar > dimArray[i]) {
11951 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
11952 } else if (nextBar && nextBar < dimArray[i]){
11953 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11955 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11957 } else if (i == l-1) {
11958 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11968 if (colorArray && dimArray && stringArray) {
11969 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11970 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11974 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
11975 x + dimArray[i]/2, y + fixedDim * (i + 1));
11977 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
11978 x + fixedDim * (i + 1), y - dimArray[i]/2);
11980 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11981 function(v) { return (v * 0.8) >> 0; }));
11982 linear.addColorStop(0, color);
11983 linear.addColorStop(0.3, colorArray[i % colorLength]);
11984 linear.addColorStop(0.7, colorArray[i % colorLength]);
11985 linear.addColorStop(1, color);
11986 ctx.fillStyle = linear;
11989 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11991 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11993 if(border && border.name == stringArray[i]) {
11994 opt.acum = fixedDim * i;
11995 opt.dimValue = dimArray[i];
11997 acum += (dimArray[i] || 0);
11998 valAcum += (valueArray[i] || 0);
11999 ctx.fillStyle = ctx.strokeStyle = label.color;
12000 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12003 if(aggregates(node.name, valAcum) && label.type == 'Native') {
12004 if(valuelabelArray[i]) {
12005 acumValueLabel = valuelabelArray[i];
12007 acumValueLabel = valueArray[i];
12010 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12011 ctx.textAlign = 'left';
12012 ctx.textBaseline = 'top';
12013 ctx.fillStyle = "rgba(255,255,255,.8)";
12015 gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
12016 mtxt = ctx.measureText(acumValueLabel);
12017 boxWidth = mtxt.width+10;
12019 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12020 leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
12022 leftPadding = dimArray[i] + config.labelOffset + inset;
12024 boxHeight = label.size+6;
12025 boxX = x + leftPadding;
12026 boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
12030 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12031 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12033 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12035 ctx.fillStyle = ctx.strokeStyle = label.color;
12036 ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12043 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12045 ctx.textAlign = 'center';
12048 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12049 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12050 (label ? label.size + config.labelOffset : 0));
12052 mtxt = ctx.measureText(acumValueLabel);
12053 boxWidth = mtxt.width+10;
12054 boxHeight = label.size+6;
12055 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12056 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12058 bottomPadding = dimArray[i] + config.labelOffset + inset;
12062 ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12064 boxX = -boxWidth/2;
12065 boxY = -boxHeight/2;
12066 ctx.fillStyle = "rgba(255,255,255,.8)";
12070 //ctx.rotate(270* Math.PI / 180);
12071 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12072 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12074 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12076 ctx.fillStyle = ctx.strokeStyle = label.color;
12077 ctx.fillText(acumValueLabel, 0,0);
12086 ctx.strokeStyle = border.color;
12088 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12090 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12094 if(label.type == 'Native') {
12096 ctx.fillStyle = ctx.strokeStyle = label.color;
12097 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12098 ctx.textBaseline = 'middle';
12100 if(showLabels(node.name, valAcum, node)) {
12102 ctx.textAlign = 'center';
12103 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12104 ctx.rotate(Math.PI / 2);
12105 ctx.fillText(node.name, 0, 0);
12107 ctx.textAlign = 'center';
12108 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12115 'contains': function(node, mpos) {
12116 var pos = node.pos.getc(true),
12117 width = node.getData('width'),
12118 height = node.getData('height'),
12119 algnPos = this.getAlignedPos(pos, width, height),
12120 x = algnPos.x, y = algnPos.y,
12121 dimArray = node.getData('dimArray'),
12122 len = dimArray.length,
12123 config = node.getData('config'),
12125 horz = config.orientation == 'horizontal',
12126 fixedDim = (horz? height : width) / len;
12127 //bounding box check
12129 if(mpos.x < x || mpos.x > x + width
12130 || mpos.y > y + height || mpos.y < y) {
12134 if(mpos.x < x || mpos.x > x + width
12135 || mpos.y > y || mpos.y < y - height) {
12140 for(var i=0, l=dimArray.length; i<l; i++) {
12141 var dimi = dimArray[i];
12142 var url = Url.decode(node.getData('linkArray')[i]);
12144 var limit = y + fixedDim * i;
12145 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12147 'name': node.getData('stringArray')[i],
12148 'color': node.getData('colorArray')[i],
12149 'value': node.getData('valueArray')[i],
12150 'valuelabel': node.getData('valuelabelArray')[i],
12151 'title': node.getData('titleArray')[i],
12152 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12158 var limit = x + fixedDim * i;
12159 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12161 'name': node.getData('stringArray')[i],
12162 'color': node.getData('colorArray')[i],
12163 'value': node.getData('valueArray')[i],
12164 'valuelabel': node.getData('valuelabelArray')[i],
12165 'title': node.getData('titleArray')[i],
12166 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12176 'barchart-basic' : {
12177 'render' : function(node, canvas) {
12178 var pos = node.pos.getc(true),
12179 width = node.getData('width'),
12180 height = node.getData('height'),
12181 algnPos = this.getAlignedPos(pos, width, height),
12182 x = algnPos.x, y = algnPos.y,
12183 dimArray = node.getData('dimArray'),
12184 valueArray = node.getData('valueArray'),
12185 valuelabelArray = node.getData('valuelabelArray'),
12186 linkArray = node.getData('linkArray'),
12187 valueLength = valueArray.length,
12188 colorArray = node.getData('colorMono'),
12189 colorLength = colorArray.length,
12190 stringArray = node.getData('stringArray');
12192 var ctx = canvas.getCtx(),
12193 canvasSize = canvas.getSize(),
12195 border = node.getData('border'),
12196 gradient = node.getData('gradient'),
12197 config = node.getData('config'),
12198 horz = config.orientation == 'horizontal',
12199 aggregates = config.showAggregates,
12200 showLabels = config.showLabels,
12201 label = config.Label,
12202 fixedDim = (horz? height : width) / valueLength,
12203 margin = config.Margin;
12205 if (colorArray && dimArray && stringArray) {
12206 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12207 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12212 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
12213 x + dimArray[i]/2, y + fixedDim * (i + 1));
12215 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
12216 x + fixedDim * (i + 1), y - dimArray[i]/2);
12219 if(config.shadow.size) {
12220 shadowThickness = config.shadow.size;
12221 ctx.fillStyle = "rgba(0,0,0,.2)";
12223 ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12225 ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12229 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
12230 function(v) { return (v * 0.8) >> 0; }));
12231 linear.addColorStop(0, color);
12232 linear.addColorStop(0.3, colorArray[i % colorLength]);
12233 linear.addColorStop(0.7, colorArray[i % colorLength]);
12234 linear.addColorStop(1, color);
12235 ctx.fillStyle = linear;
12238 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12240 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12242 if(border && border.name == stringArray[i]) {
12243 opt.acum = fixedDim * i;
12244 opt.dimValue = dimArray[i];
12246 acum += (dimArray[i] || 0);
12247 valAcum += (valueArray[i] || 0);
12249 if(label.type == 'Native') {
12250 ctx.fillStyle = ctx.strokeStyle = label.color;
12251 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12252 if(aggregates(node.name, valAcum)) {
12253 if(valuelabelArray[i]) {
12254 acumValueLabel = valuelabelArray[i];
12256 acumValueLabel = valueArray[i];
12259 ctx.textAlign = 'center';
12260 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12263 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12264 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12265 (label ? label.size + config.labelOffset : 0));
12266 mtxt = ctx.measureText(acumValueLabel);
12267 boxWidth = mtxt.width+10;
12269 boxHeight = label.size+6;
12271 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12272 bottomPadding = dimArray[i] - config.labelOffset - inset;
12274 bottomPadding = dimArray[i] + config.labelOffset + inset;
12278 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12281 boxY = -boxHeight/2;
12283 //ctx.rotate(270* Math.PI / 180);
12284 ctx.fillStyle = "rgba(255,255,255,.6)";
12285 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12286 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12288 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12289 ctx.fillStyle = ctx.strokeStyle = label.color;
12290 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12299 ctx.strokeStyle = border.color;
12301 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12303 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12307 if(label.type == 'Native') {
12309 ctx.fillStyle = ctx.strokeStyle = label.color;
12310 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12311 ctx.textBaseline = 'middle';
12312 if(showLabels(node.name, valAcum, node)) {
12316 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12317 mtxt = ctx.measureText(node.name + ": " + valAcum);
12318 boxWidth = mtxt.width+10;
12321 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12322 leftPadding = acum - config.labelOffset - boxWidth - inset;
12324 leftPadding = acum + config.labelOffset;
12328 ctx.textAlign = 'left';
12329 ctx.translate(x + inset + leftPadding, y + height/2);
12330 boxHeight = label.size+6;
12332 boxY = -boxHeight/2;
12333 ctx.fillStyle = "rgba(255,255,255,.8)";
12336 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12337 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12339 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12342 ctx.fillStyle = label.color;
12343 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12347 if(stringArray.length > 8) {
12348 ctx.textAlign = 'left';
12349 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12350 ctx.rotate(45* Math.PI / 180);
12351 ctx.fillText(node.name, 0, 0);
12353 ctx.textAlign = 'center';
12354 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12363 'contains': function(node, mpos) {
12364 var pos = node.pos.getc(true),
12365 width = node.getData('width'),
12366 height = node.getData('height'),
12367 config = node.getData('config'),
12368 algnPos = this.getAlignedPos(pos, width, height),
12369 x = algnPos.x, y = algnPos.y ,
12370 dimArray = node.getData('dimArray'),
12371 len = dimArray.length,
12373 horz = config.orientation == 'horizontal',
12374 fixedDim = (horz? height : width) / len;
12376 //bounding box check
12378 if(mpos.x < x || mpos.x > x + width
12379 || mpos.y > y + height || mpos.y < y) {
12383 if(mpos.x < x || mpos.x > x + width
12384 || mpos.y > y || mpos.y < y - height) {
12389 for(var i=0, l=dimArray.length; i<l; i++) {
12390 var dimi = dimArray[i];
12391 var url = Url.decode(node.getData('linkArray')[i]);
12393 var limit = y + fixedDim * i;
12394 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12396 'name': node.getData('stringArray')[i],
12397 'color': node.getData('colorArray')[i],
12398 'value': node.getData('valueArray')[i],
12399 'valuelabel': node.getData('valuelabelArray')[i],
12400 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12406 var limit = x + fixedDim * i;
12407 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12409 'name': node.getData('stringArray')[i],
12410 'color': node.getData('colorArray')[i],
12411 'value': node.getData('valueArray')[i],
12412 'valuelabel': node.getData('valuelabelArray')[i],
12413 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12428 A visualization that displays stacked bar charts.
12430 Constructor Options:
12432 See <Options.BarChart>.
12435 $jit.BarChart = new Class({
12437 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12441 initialize: function(opt) {
12442 this.controller = this.config =
12443 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12444 Label: { type: 'Native' }
12446 //set functions for showLabels and showAggregates
12447 var showLabels = this.config.showLabels,
12448 typeLabels = $.type(showLabels),
12449 showAggregates = this.config.showAggregates,
12450 typeAggregates = $.type(showAggregates);
12451 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12452 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12453 Options.Fx.clearCanvas = false;
12454 this.initializeViz();
12457 initializeViz: function() {
12458 var config = this.config, that = this;
12459 var nodeType = config.type.split(":")[0],
12460 horz = config.orientation == 'horizontal',
12462 var st = new $jit.ST({
12463 injectInto: config.injectInto,
12464 orientation: horz? 'left' : 'bottom',
12465 background: config.background,
12466 renderBackground: config.renderBackground,
12467 backgroundColor: config.backgroundColor,
12468 colorStop1: config.colorStop1,
12469 colorStop2: config.colorStop2,
12471 nodeCount: config.nodeCount,
12472 siblingOffset: config.barsOffset,
12474 withLabels: config.Label.type != 'Native',
12475 useCanvas: config.useCanvas,
12477 type: config.Label.type
12481 type: 'barchart-' + nodeType,
12490 enable: config.Tips.enable,
12493 onShow: function(tip, node, contains) {
12494 var elem = contains;
12495 config.Tips.onShow(tip, elem, node);
12496 if(elem.link != 'undefined' && elem.link != '') {
12497 document.body.style.cursor = 'pointer';
12500 onHide: function(call) {
12501 document.body.style.cursor = 'default';
12508 onClick: function(node, eventInfo, evt) {
12509 if(!config.Events.enable) return;
12510 var elem = eventInfo.getContains();
12511 config.Events.onClick(elem, eventInfo, evt);
12513 onMouseMove: function(node, eventInfo, evt) {
12514 if(!config.hoveredColor) return;
12516 var elem = eventInfo.getContains();
12517 that.select(node.id, elem.name, elem.index);
12519 that.select(false, false, false);
12523 onCreateLabel: function(domElement, node) {
12524 var labelConf = config.Label,
12525 valueArray = node.getData('valueArray'),
12526 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12527 grouped = config.type.split(':')[0] == 'grouped',
12528 horz = config.orientation == 'horizontal';
12530 wrapper: document.createElement('div'),
12531 aggregate: document.createElement('div'),
12532 label: document.createElement('div')
12535 var wrapper = nlbs.wrapper,
12536 label = nlbs.label,
12537 aggregate = nlbs.aggregate,
12538 wrapperStyle = wrapper.style,
12539 labelStyle = label.style,
12540 aggregateStyle = aggregate.style;
12541 //store node labels
12542 nodeLabels[node.id] = nlbs;
12544 wrapper.appendChild(label);
12545 wrapper.appendChild(aggregate);
12546 if(!config.showLabels(node.name, acum, node)) {
12547 labelStyle.display = 'none';
12549 if(!config.showAggregates(node.name, acum, node)) {
12550 aggregateStyle.display = 'none';
12552 wrapperStyle.position = 'relative';
12553 wrapperStyle.overflow = 'visible';
12554 wrapperStyle.fontSize = labelConf.size + 'px';
12555 wrapperStyle.fontFamily = labelConf.family;
12556 wrapperStyle.color = labelConf.color;
12557 wrapperStyle.textAlign = 'center';
12558 aggregateStyle.position = labelStyle.position = 'absolute';
12560 domElement.style.width = node.getData('width') + 'px';
12561 domElement.style.height = node.getData('height') + 'px';
12562 aggregateStyle.left = "0px";
12563 labelStyle.left = config.labelOffset + 'px';
12564 labelStyle.whiteSpace = "nowrap";
12565 label.innerHTML = node.name;
12567 domElement.appendChild(wrapper);
12569 onPlaceLabel: function(domElement, node) {
12570 if(!nodeLabels[node.id]) return;
12571 var labels = nodeLabels[node.id],
12572 wrapperStyle = labels.wrapper.style,
12573 labelStyle = labels.label.style,
12574 aggregateStyle = labels.aggregate.style,
12575 grouped = config.type.split(':')[0] == 'grouped',
12576 horz = config.orientation == 'horizontal',
12577 dimArray = node.getData('dimArray'),
12578 valArray = node.getData('valueArray'),
12579 nodeCount = node.getData('nodeCount'),
12580 valueLength = valArray.length;
12581 valuelabelArray = node.getData('valuelabelArray'),
12582 stringArray = node.getData('stringArray'),
12583 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12584 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12585 font = parseInt(wrapperStyle.fontSize, 10),
12586 domStyle = domElement.style,
12587 fixedDim = (horz? height : width) / valueLength;
12590 if(dimArray && valArray) {
12591 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12593 aggregateStyle.width = width - config.labelOffset + "px";
12594 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12595 if(dimArray[i] > 0) {
12596 acum+= valArray[i];
12599 if(config.showLabels(node.name, acum, node)) {
12600 labelStyle.display = '';
12602 labelStyle.display = 'none';
12604 if(config.showAggregates(node.name, acum, node)) {
12605 aggregateStyle.display = '';
12607 aggregateStyle.display = 'none';
12609 if(config.orientation == 'horizontal') {
12610 aggregateStyle.textAlign = 'right';
12611 labelStyle.textAlign = 'left';
12612 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12613 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12614 domElement.style.height = wrapperStyle.height = height + 'px';
12616 aggregateStyle.top = (-font - config.labelOffset) + 'px';
12617 labelStyle.top = (config.labelOffset + height) + 'px';
12618 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12619 domElement.style.height = wrapperStyle.height = height + 'px';
12620 if(stringArray.length > 8) {
12621 labels.label.className = "rotatedLabelReverse";
12622 labelStyle.textAlign = "left";
12623 labelStyle.top = config.labelOffset + height + width/2 + "px";
12629 labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12630 labels.aggregate.innerHTML = "";
12635 maxValue = Math.max.apply(null,dimArray);
12636 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12637 valueLabelDim = 50;
12638 valueLabel = document.createElement('div');
12639 valueLabel.innerHTML = valuelabelArray[i];
12640 // valueLabel.class = "rotatedLabel";
12641 valueLabel.className = "rotatedLabel";
12642 valueLabel.style.position = "absolute";
12643 valueLabel.style.textAlign = "left";
12644 valueLabel.style.verticalAlign = "middle";
12645 valueLabel.style.height = valueLabelDim + "px";
12646 valueLabel.style.width = valueLabelDim + "px";
12647 valueLabel.style.top = (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12648 valueLabel.style.left = (fixedDim * i) + "px";
12649 labels.wrapper.appendChild(valueLabel);
12652 labels.aggregate.innerHTML = acum;
12659 var size = st.canvas.getSize(),
12660 l = config.nodeCount,
12661 margin = config.Margin;
12662 title = config.Title;
12663 subtitle = config.Subtitle,
12664 grouped = config.type.split(':')[0] == 'grouped',
12665 margin = config.Margin,
12666 ticks = config.Ticks,
12667 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12668 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12669 horz = config.orientation == 'horizontal',
12670 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12671 fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12672 whiteSpace = size.width - (marginWidth + (fixedDim * l));
12673 //bug in IE7 when vertical bar charts load in dashlets where number of bars exceed a certain width, canvas renders with an incorrect width, a hard refresh fixes the problem
12674 if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12676 //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12677 if(!grouped && !horz) {
12678 st.config.siblingOffset = whiteSpace/(l+1);
12685 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12686 if(config.Ticks.enable) {
12687 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;
12689 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12692 st.config.offsetY = -size.height/2 + margin.bottom
12693 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12694 if(config.Ticks.enable) {
12695 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12697 st.config.offsetX = (margin.right - margin.left)/2;
12701 this.canvas = this.st.canvas;
12706 renderTitle: function() {
12707 var canvas = this.canvas,
12708 size = canvas.getSize(),
12709 config = this.config,
12710 margin = config.Margin,
12711 label = config.Label,
12712 title = config.Title;
12713 ctx = canvas.getCtx();
12714 ctx.fillStyle = title.color;
12715 ctx.textAlign = 'left';
12716 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12717 if(label.type == 'Native') {
12718 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12722 renderSubtitle: function() {
12723 var canvas = this.canvas,
12724 size = canvas.getSize(),
12725 config = this.config,
12726 margin = config.Margin,
12727 label = config.Label,
12728 subtitle = config.Subtitle,
12729 nodeCount = config.nodeCount,
12730 horz = config.orientation == 'horizontal' ? true : false,
12731 ctx = canvas.getCtx();
12732 ctx.fillStyle = title.color;
12733 ctx.textAlign = 'left';
12734 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12735 if(label.type == 'Native') {
12736 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12740 renderScrollNote: function() {
12741 var canvas = this.canvas,
12742 size = canvas.getSize(),
12743 config = this.config,
12744 margin = config.Margin,
12745 label = config.Label,
12746 note = config.ScrollNote;
12747 ctx = canvas.getCtx();
12748 ctx.fillStyle = title.color;
12749 title = config.Title;
12750 ctx.textAlign = 'center';
12751 ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12752 if(label.type == 'Native') {
12753 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12757 renderTicks: function() {
12759 var canvas = this.canvas,
12760 size = canvas.getSize(),
12761 config = this.config,
12762 margin = config.Margin,
12763 ticks = config.Ticks,
12764 title = config.Title,
12765 subtitle = config.Subtitle,
12766 label = config.Label,
12767 shadow = config.shadow;
12768 horz = config.orientation == 'horizontal',
12769 grouped = config.type.split(':')[0] == 'grouped',
12770 ctx = canvas.getCtx();
12771 ctx.strokeStyle = ticks.color;
12772 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12774 ctx.textAlign = 'center';
12775 ctx.textBaseline = 'middle';
12777 idLabel = canvas.id + "-label";
12779 container = document.getElementById(idLabel);
12783 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12784 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12785 segmentLength = grid/ticks.segments;
12786 ctx.fillStyle = ticks.color;
12788 // Main horizontal line
12790 var yTop = size.height / 2 - margin.bottom - config.labelOffset - label.size - (subtitle.text ? subtitle.size + subtitle.offset : 0) + (shadow.enable ? shadow.size : 0);
12791 var xLength = size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0);
12793 ctx.fillRect(xTop, yTop, xLength, yLength);
12795 maxTickValue = config.Ticks.maxValue;
12796 var humanNumber = config.Ticks.humanNumber;
12797 var segments = config.Ticks.segments;
12798 var tempHumanNumber = humanNumber;
12799 var humanNumberPow = 0;
12800 // Tries to find pow of humanNumber if it is less than 1. For 0.001 humanNumberPos will be 3. it means 0.001*10^3 = 1.
12801 // humanNumberPow is required for work with number less than 1.
12802 while (tempHumanNumber % 1 != 0)
12804 tempHumanNumber = tempHumanNumber * 10;
12808 // Tries convert length of line to steps in pixels. 4 steps, 160 length - 40 pixels per step
12809 var pixelsPerStep = xLength / maxTickValue;
12810 var 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);
12811 for (var i = 0; i <= segments; i++)
12813 var iX = Math.round(xTop + i * pixelsPerStep * humanNumber);
12815 ctx.translate(iX, yTop + yLength + margin.top);
12816 ctx.rotate(0 * Math.PI / 2 * 3);
12817 ctx.fillStyle = label.color;
12818 // Float numbers fix (0.45 can be displayed as 0.44466666)
12819 var labelText = humanNumber * Math.pow(10, humanNumberPow) * i;
12820 labelText = labelText * Math.pow(10, -humanNumberPow);
12821 if (config.showLabels)
12823 // Filling Text through canvas or html elements
12824 if (label.type == 'Native')
12826 ctx.fillText(labelText, 0, 0); // For coords see ctx.translate above
12830 //html labels on y axis
12831 labelDiv = document.createElement('div');
12832 labelDiv.innerHTML = labelText;
12833 labelDiv.style.top = Math.round(size.height - margin.bottom - config.labelOffset) + "px";
12834 labelDiv.style.left = Math.round(margin.left - labelDim / 2 + i * pixelsPerStep * humanNumber) + "px";
12835 labelDiv.style.width = labelDim + "px";
12836 labelDiv.style.height = labelDim + "px";
12837 labelDiv.style.textAlign = "center";
12838 labelDiv.style.verticalAlign = "middle";
12839 labelDiv.style.position = "absolute";
12840 labelDiv.style.background = '1px solid red';
12841 container.appendChild(labelDiv);
12845 ctx.fillStyle = ticks.color;
12847 ctx.fillRect(Math.round(axis) + i * pixelsPerStep * humanNumber, -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));
12851 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12852 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12853 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)),
12854 segmentLength = grid/ticks.segments;
12855 ctx.fillStyle = ticks.color;
12857 // Main horizontal line
12858 var xTop = -size.width / 2 + margin.left + config.labelOffset + label.size - 1;
12859 var yTop = -size.height / 2 + margin.top + (title.text ? title.size + title.offset : 0);
12861 var yLength = size.height - margin.top - margin.bottom - label.size - config.labelOffset - (title.text ? title.size + title.offset : 0) - (subtitle.text ? subtitle.size + subtitle.offset : 0);
12862 ctx.fillRect(xTop, yTop, xLength, yLength);
12864 maxTickValue = config.Ticks.maxValue;
12865 var humanNumber = config.Ticks.humanNumber;
12866 var segments = config.Ticks.segments;
12867 var tempHumanNumber = humanNumber;
12868 var humanNumberPow = 0;
12869 // Tries to find pow of humanNumber if it is less than 1. For 0.001 humanNumberPos will be 3. it means 0.001*10^3 = 1.
12870 // humanNumberPow is required for work with number less than 1.
12871 while (tempHumanNumber % 1 != 0)
12873 tempHumanNumber = tempHumanNumber * 10;
12877 // Tries convert length of line to steps in pixels. 4 steps, 160 length - 40 pixels per step
12878 var pixelsPerStep = yLength / maxTickValue;
12879 for (var i = 0; i <= segments; i++)
12881 var iY = Math.round(yTop + yLength - i * pixelsPerStep * humanNumber);
12883 ctx.translate(-size.width / 2 + margin.left, iY);
12884 ctx.rotate(0 * Math.PI / 2 * 3);
12885 ctx.fillStyle = label.color;
12886 // Float numbers fix (0.45 can be displayed as 0.44466666)
12887 var labelText = humanNumber * Math.pow(10, humanNumberPow) * i;
12888 labelText = labelText * Math.pow(10, -humanNumberPow);
12889 if (config.showLabels)
12891 // Filling Text through canvas or html elements
12892 if (label.type == 'Native')
12894 ctx.fillText(labelText, 0, 0); // For coords see ctx.translate above
12898 //html labels on y axis
12899 labelDiv = document.createElement('div');
12900 labelDiv.innerHTML = labelText;
12901 labelDiv.className = "rotatedLabel";
12902 // labelDiv.class = "rotatedLabel";
12903 labelDiv.style.top = Math.round(htmlOrigin - labelDim / 2 - i * pixelsPerStep * humanNumber) + "px";
12904 labelDiv.style.left = margin.left + "px";
12905 labelDiv.style.width = labelDim + "px";
12906 labelDiv.style.height = labelDim + "px";
12907 labelDiv.style.textAlign = "center";
12908 labelDiv.style.verticalAlign = "middle";
12909 labelDiv.style.position = "absolute";
12910 container.appendChild(labelDiv);
12914 ctx.fillStyle = ticks.color;
12915 ctx.fillRect(-size.width / 2 + margin.left + config.labelOffset + label.size, iY, size.width - margin.right - margin.left - config.labelOffset - label.size, 1);
12924 renderBackground: function() {
12925 var canvas = this.canvas,
12926 config = this.config,
12927 backgroundColor = config.backgroundColor,
12928 size = canvas.getSize(),
12929 ctx = canvas.getCtx();
12930 ctx.fillStyle = backgroundColor;
12931 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12934 clear: function() {
12935 var canvas = this.canvas;
12936 var ctx = canvas.getCtx(),
12937 size = canvas.getSize();
12938 ctx.fillStyle = "rgba(255,255,255,0)";
12939 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12940 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
12942 resizeGraph: function(json,width) {
12943 var canvas = this.canvas,
12944 size = canvas.getSize(),
12945 config = this.config,
12946 orgHeight = size.height,
12947 margin = config.Margin,
12949 grouped = config.type.split(':')[0] == 'grouped',
12950 horz = config.orientation == 'horizontal';
12952 canvas.resize(width,orgHeight);
12953 if(typeof FlashCanvas == "undefined") {
12956 this.clear();// hack for flashcanvas bug not properly clearing rectangle
12959 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12962 this.loadJSON(json);
12969 Loads JSON data into the visualization.
12973 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>.
12977 var barChart = new $jit.BarChart(options);
12978 barChart.loadJSON(json);
12981 loadJSON: function(json) {
12982 if(this.busy) return;
12985 var prefix = $.time(),
12988 name = $.splat(json.label),
12989 color = $.splat(json.color || this.colors),
12990 config = this.config,
12991 gradient = !!config.type.split(":")[1],
12992 renderBackground = config.renderBackground,
12993 animate = config.animate,
12994 ticks = config.Ticks,
12995 title = config.Title,
12996 note = config.ScrollNote,
12997 subtitle = config.Subtitle,
12998 horz = config.orientation == 'horizontal',
13000 colorLength = color.length,
13001 nameLength = name.length;
13002 groupTotalValue = 0;
13003 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13004 var val = values[i];
13005 var valArray = $.splat(val.values);
13006 groupTotalValue += parseFloat(valArray.sum());
13009 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13010 var val = values[i];
13011 var valArray = $.splat(values[i].values);
13012 var valuelabelArray = $.splat(values[i].valuelabels);
13013 var linkArray = $.splat(values[i].links);
13014 var titleArray = $.splat(values[i].titles);
13015 var barTotalValue = valArray.sum();
13018 'id': prefix + val.label,
13023 '$linkArray': linkArray,
13024 '$gvl': val.gvaluelabel,
13025 '$titleArray': titleArray,
13026 '$valueArray': valArray,
13027 '$valuelabelArray': valuelabelArray,
13028 '$colorArray': color,
13029 '$colorMono': $.splat(color[i % colorLength]),
13030 '$stringArray': name,
13031 '$barTotalValue': barTotalValue,
13032 '$groupTotalValue': groupTotalValue,
13033 '$nodeCount': values.length,
13034 '$gradient': gradient,
13041 'id': prefix + '$root',
13052 this.normalizeDims();
13054 if(renderBackground) {
13055 this.renderBackground();
13058 if(!animate && ticks.enable) {
13059 this.renderTicks();
13061 if(!animate && note.text) {
13062 this.renderScrollNote();
13064 if(!animate && title.text) {
13065 this.renderTitle();
13067 if(!animate && subtitle.text) {
13068 this.renderSubtitle();
13072 st.select(st.root);
13076 modes: ['node-property:width:dimArray'],
13078 onComplete: function() {
13084 modes: ['node-property:height:dimArray'],
13086 onComplete: function() {
13099 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.
13103 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13104 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13109 barChart.updateJSON(json, {
13110 onComplete: function() {
13111 alert('update complete!');
13116 updateJSON: function(json, onComplete) {
13117 if(this.busy) return;
13121 var graph = st.graph;
13122 var values = json.values;
13123 var animate = this.config.animate;
13125 var horz = this.config.orientation == 'horizontal';
13126 $.each(values, function(v) {
13127 var n = graph.getByName(v.label);
13129 n.setData('valueArray', $.splat(v.values));
13131 n.setData('stringArray', $.splat(json.label));
13135 this.normalizeDims();
13137 st.select(st.root);
13141 modes: ['node-property:width:dimArray'],
13143 onComplete: function() {
13145 onComplete && onComplete.onComplete();
13150 modes: ['node-property:height:dimArray'],
13152 onComplete: function() {
13154 onComplete && onComplete.onComplete();
13161 //adds the little brown bar when hovering the node
13162 select: function(id, name) {
13164 if(!this.config.hoveredColor) return;
13165 var s = this.selected;
13166 if(s.id != id || s.name != name) {
13169 s.color = this.config.hoveredColor;
13170 this.st.graph.eachNode(function(n) {
13172 n.setData('border', s);
13174 n.setData('border', false);
13184 Returns an object containing as keys the legend names and as values hex strings with color values.
13189 var legend = barChart.getLegend();
13192 getLegend: function() {
13193 var legend = new Array();
13194 var name = new Array();
13195 var color = new Array();
13197 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13200 var colors = n.getData('colorArray'),
13201 len = colors.length;
13202 $.each(n.getData('stringArray'), function(s, i) {
13203 color[i] = colors[i % len];
13206 legend['name'] = name;
13207 legend['color'] = color;
13212 Method: getMaxValue
13214 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13219 var ans = barChart.getMaxValue();
13222 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13227 //will return 100 for all BarChart instances,
13228 //displaying all of them with the same scale
13229 $jit.BarChart.implement({
13230 'getMaxValue': function() {
13237 getMaxValue: function() {
13238 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13239 this.st.graph.eachNode(function(n) {
13240 var valArray = n.getData('valueArray'),
13242 if(!valArray) return;
13244 $.each(valArray, function(v) {
13248 acum = Math.max.apply(null, valArray);
13250 maxValue = maxValue>acum? maxValue:acum;
13255 setBarType: function(type) {
13256 this.config.type = type;
13257 this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13260 normalizeDims: function() {
13261 //number of elements
13262 var root = this.st.graph.getNode(this.st.root), l=0;
13263 root.eachAdjacency(function() {
13266 var maxValue = this.getMaxValue() || 1,
13267 size = this.st.canvas.getSize(),
13268 config = this.config,
13269 margin = config.Margin,
13270 ticks = config.Ticks,
13271 title = config.Title,
13272 subtitle = config.Subtitle,
13273 grouped = config.type.split(':')[0] == 'grouped',
13274 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13275 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13276 horz = config.orientation == 'horizontal',
13277 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13278 animate = config.animate,
13279 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13281 - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13282 dim1 = horz? 'height':'width',
13283 dim2 = horz? 'width':'height',
13284 basic = config.type.split(':')[0] == 'basic';
13286 // Bug #47147 Correct detection of maxTickValue and step size for asix labels
13287 var iDirection = 10; // We need this var for convert value. 10^2 = 100
13288 var zeroCount = 0; // Pow for iDirection for detection of size of human step.
13289 var iNumber = maxValue; // This var will store two first digits from maxValue. For 1265848 it will be 12. For 0.0453 it will be 45.
13290 // Tries to get two first digits from maxValue
13293 // Tries to calculate zeroCount
13294 // if iNumber = 100 we will get zeroCount = 2, iNumber = 0.1
13295 while (iNumber >= 1)
13298 iNumber = iNumber / 10;
13300 iNumber = Math.floor(iNumber * 100); // We need to increase iNumber by 100 to get two first digits. for 0.1 it will be 0.1*100 = 10
13304 iDirection = 0.1; // if iNumber is less than 1 we should change iDirection. 0.1^2 = 0.01
13305 // Tries to calculate zeroCount
13306 // if iNumber = 0.01 we will get zeroCount = 2, iNumber = 1
13307 while (iNumber < 1)
13310 iNumber = iNumber * 10;
13312 iNumber = Math.floor(iNumber * 10); // We need to increase iNumber by 10 to get two first digits. for 1 it will be 1*10 = 10
13314 var humanNumber = 0;
13315 var iNumberTemp = iNumber + 1; // We need to add 1 for correct tick size detection. It means that tick always will be great than max value of chart.
13316 // 5 is human step. And we try to detect max value of tick. if maxValue is 1234567 it means iNumber = 12, iNumberTemp = 13 and as result we will get iNumberTemp = 15.
13317 while (iNumberTemp % 5 != 0)
13321 var isFound = false;
13322 zeroCount --; // We need to reduce zeroCount because of increase by 10 or 100 in steps above. It means iNumber = 10, zeroCount = 2 - 1 = 1. 10 * 10^1 = 100. 100 is original value of iNumber
13323 // Tries to find humanNumber. Our step is 5. ticks.segments is number of lines = 4 (for example)
13324 // iNumberTemp = 15 (for example). 15 % 4 = 3. It means that we should add 5 to iNumberTemp till division will equal 0. 20 % 4 = 0. Our result is iNumberTemp = 20
13325 while (isFound == false)
13327 if (iNumberTemp % ticks.segments == 0)
13329 humanNumber = iNumberTemp / ticks.segments;
13333 iNumberTemp = iNumberTemp + 5;
13335 // Getting real values
13336 var maxTickValue = config.Ticks.maxValue = maxTickValue = iNumberTemp * Math.pow(iDirection, zeroCount - 1);
13337 config.Ticks.humanNumber = humanNumber = humanNumber * Math.pow(iDirection, zeroCount - 1);
13338 config.Ticks.segments = Math.floor(maxTickValue / humanNumber);
13340 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13343 this.st.graph.eachNode(function(n) {
13344 var acum = 0, animateValue = [];
13345 $.each(n.getData('valueArray'), function(v) {
13347 animateValue.push(0);
13351 fixedDim = animateValue.length * 40;
13353 n.setData(dim1, fixedDim);
13357 n.setData(dim2, acum * height / maxValue, 'end');
13358 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13359 return n * height / maxValue;
13361 var dimArray = n.getData('dimArray');
13363 n.setData('dimArray', animateValue);
13369 n.setData(dim2, acum * height / maxTickValue);
13370 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13371 return n * height / maxTickValue;
13374 n.setData(dim2, acum * height / maxValue);
13375 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13376 return n * height / maxValue;
13384 //funnel chart options
13387 Options.FunnelChart = {
13391 type: 'stacked', //stacked, grouped, : gradient
13392 labelOffset: 3, //label offset
13393 barsOffset: 0, //distance between bars
13394 hoveredColor: '#9fd4ff',
13395 orientation: 'vertical',
13396 showAggregates: true,
13409 $jit.ST.Plot.NodeTypes.implement({
13410 'funnelchart-basic' : {
13411 'render' : function(node, canvas) {
13412 var pos = node.pos.getc(true),
13413 width = node.getData('width'),
13414 height = node.getData('height'),
13415 algnPos = this.getAlignedPos(pos, width, height),
13416 x = algnPos.x, y = algnPos.y,
13417 dimArray = node.getData('dimArray'),
13418 valueArray = node.getData('valueArray'),
13419 valuelabelArray = node.getData('valuelabelArray'),
13420 linkArray = node.getData('linkArray'),
13421 colorArray = node.getData('colorArray'),
13422 colorLength = colorArray.length,
13423 stringArray = node.getData('stringArray');
13424 var ctx = canvas.getCtx(),
13426 border = node.getData('border'),
13427 gradient = node.getData('gradient'),
13428 config = node.getData('config'),
13429 horz = config.orientation == 'horizontal',
13430 aggregates = config.showAggregates,
13431 showLabels = config.showLabels,
13432 label = config.Label,
13433 size = canvas.getSize(),
13434 labelOffset = config.labelOffset + 10;
13435 minWidth = width * .25;
13438 if (colorArray && dimArray && stringArray) {
13441 // horizontal lines
13442 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13443 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13445 if(label.type == 'Native') {
13446 if(showLabels(node.name, valAcum, node)) {
13447 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13448 var stringValue = stringArray[i];
13449 var valueLabel = String(valuelabelArray[i]);
13450 var mV = ctx.measureText(stringValue);
13451 var mVL = ctx.measureText(valueLabel);
13452 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13453 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13454 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13455 var bottomWidth = minWidth + ((acum) * ratio);
13456 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13457 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13458 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13459 // ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13463 ctx.moveTo(bottomWidth/2,y - acum); //
13464 ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight); // top right
13465 ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight); // bottom right
13469 ctx.moveTo(-bottomWidth/2,y - acum); //
13470 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight); // top right
13471 ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight); // bottom right
13476 acum += (dimArray[i] || 0);
13477 valAcum += (valueArray[i] || 0);
13484 //funnel segments and labels
13485 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13486 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13487 var colori = colorArray[i % colorLength];
13488 if(label.type == 'Native') {
13489 var stringValue = stringArray[i];
13490 var valueLabel = String(valuelabelArray[i]);
13491 var mV = ctx.measureText(stringValue);
13492 var mVL = ctx.measureText(valueLabel);
13497 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13498 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13499 var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13500 var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13502 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13503 var bottomWidth = minWidth + ((acum) * ratio);
13504 var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13509 linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13510 var colorRgb = $.hexToRgb(colori);
13511 var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
13512 function(v) { return (v * .5) >> 0; });
13513 linear.addColorStop(0, 'rgba('+color+',1)');
13514 linear.addColorStop(0.5, 'rgba('+colorRgb+',1)');
13515 linear.addColorStop(1, 'rgba('+color+',1)');
13516 ctx.fillStyle = linear;
13520 ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13521 ctx.lineTo(topWidth/2,y - acum - dimArray[i]); // top right
13522 ctx.lineTo(bottomWidth/2,y - acum); // bottom right
13523 ctx.lineTo(-bottomWidth/2,y - acum); // bottom left
13528 if(border && border.name == stringArray[i]) {
13530 opt.dimValue = dimArray[i];
13537 ctx.strokeStyle = border.color;
13539 //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13543 if(label.type == 'Native') {
13545 ctx.fillStyle = ctx.strokeStyle = label.color;
13546 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13547 ctx.textBaseline = 'middle';
13549 acumValueLabel = valAcum;
13551 if(showLabels(node.name, valAcum, node)) {
13554 ctx.textAlign = 'left';
13555 ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13556 ctx.textAlign = 'right';
13557 ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13562 acum += (dimArray[i] || 0);
13563 valAcum += (valueArray[i] || 0);
13569 'contains': function(node, mpos) {
13570 var pos = node.pos.getc(true),
13571 width = node.getData('width'),
13572 height = node.getData('height'),
13573 algnPos = this.getAlignedPos(pos, width, height),
13574 x = algnPos.x, y = algnPos.y,
13575 dimArray = node.getData('dimArray'),
13576 config = node.getData('config'),
13577 st = node.getData('st'),
13579 horz = config.orientation == 'horizontal',
13580 minWidth = width * .25;
13582 canvas = node.getData('canvas'),
13583 size = canvas.getSize(),
13584 offsetY = st.config.offsetY;
13585 //bounding box check
13587 if(mpos.y > y || mpos.y < y - height) {
13591 var newY = Math.abs(mpos.y + offsetY);
13592 var bound = minWidth + (newY * ratio);
13593 var boundLeft = -bound/2;
13594 var boundRight = bound/2;
13595 if(mpos.x < boundLeft || mpos.x > boundRight ) {
13601 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13602 var dimi = dimArray[i];
13606 var url = Url.decode(node.getData('linkArray')[i]);
13608 var intersec = acum;
13609 if(mpos.y >= intersec) {
13611 'name': node.getData('stringArray')[i],
13612 'color': node.getData('colorArray')[i],
13613 'value': node.getData('valueArray')[i],
13614 'percentage': node.getData('percentageArray')[i],
13615 'valuelabel': node.getData('valuelabelArray')[i],
13630 A visualization that displays funnel charts.
13632 Constructor Options:
13634 See <Options.FunnelChart>.
13637 $jit.FunnelChart = new Class({
13639 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13643 initialize: function(opt) {
13644 this.controller = this.config =
13645 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13646 Label: { type: 'Native' }
13648 //set functions for showLabels and showAggregates
13649 var showLabels = this.config.showLabels,
13650 typeLabels = $.type(showLabels),
13651 showAggregates = this.config.showAggregates,
13652 typeAggregates = $.type(showAggregates);
13653 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13654 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13655 Options.Fx.clearCanvas = false;
13656 this.initializeViz();
13659 initializeViz: function() {
13660 var config = this.config, that = this;
13661 var nodeType = config.type.split(":")[0],
13662 horz = config.orientation == 'horizontal',
13664 var st = new $jit.ST({
13665 injectInto: config.injectInto,
13666 orientation: horz? 'left' : 'bottom',
13668 background: config.background,
13669 renderBackground: config.renderBackground,
13670 backgroundColor: config.backgroundColor,
13671 colorStop1: config.colorStop1,
13672 colorStop2: config.colorStop2,
13673 siblingOffset: config.segmentOffset,
13675 withLabels: config.Label.type != 'Native',
13676 useCanvas: config.useCanvas,
13678 type: config.Label.type
13682 type: 'funnelchart-' + nodeType,
13691 enable: config.Tips.enable,
13694 onShow: function(tip, node, contains) {
13695 var elem = contains;
13696 config.Tips.onShow(tip, elem, node);
13697 if(elem.link != 'undefined' && elem.link != '') {
13698 document.body.style.cursor = 'pointer';
13701 onHide: function(call) {
13702 document.body.style.cursor = 'default';
13709 onClick: function(node, eventInfo, evt) {
13710 if(!config.Events.enable) return;
13711 var elem = eventInfo.getContains();
13712 config.Events.onClick(elem, eventInfo, evt);
13714 onMouseMove: function(node, eventInfo, evt) {
13715 if(!config.hoveredColor) return;
13717 var elem = eventInfo.getContains();
13718 that.select(node.id, elem.name, elem.index);
13720 that.select(false, false, false);
13724 onCreateLabel: function(domElement, node) {
13725 var labelConf = config.Label,
13726 valueArray = node.getData('valueArray'),
13727 idArray = node.getData('idArray'),
13728 valuelabelArray = node.getData('valuelabelArray'),
13729 stringArray = node.getData('stringArray');
13730 size = st.canvas.getSize()
13733 for(var i=0, l=valueArray.length; i<l; i++) {
13735 wrapper: document.createElement('div'),
13736 valueLabel: document.createElement('div'),
13737 label: document.createElement('div')
13739 var wrapper = nlbs.wrapper,
13740 label = nlbs.label,
13741 valueLabel = nlbs.valueLabel,
13742 wrapperStyle = wrapper.style,
13743 labelStyle = label.style,
13744 valueLabelStyle = valueLabel.style;
13745 //store node labels
13746 nodeLabels[idArray[i]] = nlbs;
13748 wrapper.appendChild(label);
13749 wrapper.appendChild(valueLabel);
13751 wrapperStyle.position = 'relative';
13752 wrapperStyle.overflow = 'visible';
13753 wrapperStyle.fontSize = labelConf.size + 'px';
13754 wrapperStyle.fontFamily = labelConf.family;
13755 wrapperStyle.color = labelConf.color;
13756 wrapperStyle.textAlign = 'center';
13757 wrapperStyle.width = size.width + 'px';
13758 valueLabelStyle.position = labelStyle.position = 'absolute';
13759 valueLabelStyle.left = labelStyle.left = '0px';
13760 valueLabelStyle.width = (size.width/3) + 'px';
13761 valueLabelStyle.textAlign = 'right';
13762 label.innerHTML = stringArray[i];
13763 valueLabel.innerHTML = valuelabelArray[i];
13764 domElement.id = prefix+'funnel';
13765 domElement.style.width = size.width + 'px';
13767 domElement.appendChild(wrapper);
13771 onPlaceLabel: function(domElement, node) {
13773 var dimArray = node.getData('dimArray'),
13774 idArray = node.getData('idArray'),
13775 valueArray = node.getData('valueArray'),
13776 valuelabelArray = node.getData('valuelabelArray'),
13777 stringArray = node.getData('stringArray');
13778 size = st.canvas.getSize(),
13779 pos = node.pos.getc(true),
13780 domElement.style.left = "0px",
13781 domElement.style.top = "0px",
13782 minWidth = node.getData('width') * .25,
13784 pos = node.pos.getc(true),
13785 labelConf = config.Label;
13788 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13790 var labels = nodeLabels[idArray[i]],
13791 wrapperStyle = labels.wrapper.style,
13792 labelStyle = labels.label.style,
13793 valueLabelStyle = labels.valueLabel.style;
13794 var bottomWidth = minWidth + (acum * ratio);
13796 font = parseInt(wrapperStyle.fontSize, 10),
13797 domStyle = domElement.style;
13801 wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13802 valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13803 labelStyle.left = (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13805 acum += (dimArray[i] || 0);
13813 var size = st.canvas.getSize(),
13814 margin = config.Margin;
13815 title = config.Title;
13816 subtitle = config.Subtitle;
13819 st.config.offsetY = -size.height/2 + margin.bottom
13820 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13822 st.config.offsetX = (margin.right - margin.left)/2;
13826 this.canvas = this.st.canvas;
13829 renderTitle: function() {
13830 var canvas = this.canvas,
13831 size = canvas.getSize(),
13832 config = this.config,
13833 margin = config.Margin,
13834 label = config.Label,
13835 title = config.Title;
13836 ctx = canvas.getCtx();
13837 ctx.fillStyle = title.color;
13838 ctx.textAlign = 'left';
13839 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13840 if(label.type == 'Native') {
13841 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13845 renderSubtitle: function() {
13846 var canvas = this.canvas,
13847 size = canvas.getSize(),
13848 config = this.config,
13849 margin = config.Margin,
13850 label = config.Label,
13851 subtitle = config.Subtitle;
13852 ctx = canvas.getCtx();
13853 ctx.fillStyle = title.color;
13854 ctx.textAlign = 'left';
13855 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13856 if(label.type == 'Native') {
13857 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13862 renderDropShadow: function() {
13863 var canvas = this.canvas,
13864 size = canvas.getSize(),
13865 config = this.config,
13866 margin = config.Margin,
13867 horz = config.orientation == 'horizontal',
13868 label = config.Label,
13869 title = config.Title,
13870 shadowThickness = 4,
13871 subtitle = config.Subtitle,
13872 ctx = canvas.getCtx(),
13873 minwidth = (size.width/8) * .25,
13874 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13875 topMargin = (title.text? title.size + title.offset : 0) + margin.top,
13876 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13877 - (config.showLabels && (config.Label.size + config.labelOffset)),
13879 topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13880 topY = (-size.height/2) + topMargin - shadowThickness;
13881 bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13882 bottomWidth = minwidth + shadowThickness;
13884 ctx.fillStyle = "rgba(0,0,0,.2)";
13885 ctx.moveTo(0,topY);
13886 ctx.lineTo(-topWidth/2,topY); //top left
13887 ctx.lineTo(-bottomWidth/2,bottomY); // bottom left
13888 ctx.lineTo(bottomWidth/2,bottomY); // bottom right
13889 ctx.lineTo(topWidth/2,topY); // top right
13896 renderBackground: function() {
13897 var canvas = this.canvas,
13898 config = this.config,
13899 backgroundColor = config.backgroundColor,
13900 size = canvas.getSize(),
13901 ctx = canvas.getCtx();
13902 //ctx.globalCompositeOperation = "destination-over";
13903 ctx.fillStyle = backgroundColor;
13904 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13906 clear: function() {
13907 var canvas = this.canvas;
13908 var ctx = canvas.getCtx(),
13909 size = canvas.getSize();
13910 ctx.fillStyle = "rgba(255,255,255,0)";
13911 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13912 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13914 resizeGraph: function(json,width) {
13915 var canvas = this.canvas,
13916 size = canvas.getSize(),
13917 config = this.config,
13918 orgHeight = size.height;
13921 canvas.resize(width,orgHeight);
13923 if(typeof FlashCanvas == "undefined") {
13926 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13928 this.loadJSON(json);
13932 loadJSON: function(json) {
13933 if(this.busy) return;
13935 var prefix = $.time(),
13938 name = $.splat(json.label),
13939 color = $.splat(json.color || this.colors),
13940 config = this.config,
13941 canvas = this.canvas,
13942 gradient = !!config.type.split(":")[1],
13943 animate = config.animate,
13944 title = config.Title,
13945 subtitle = config.Subtitle,
13946 renderBackground = config.renderBackground,
13947 horz = config.orientation == 'horizontal',
13949 colorLength = color.length,
13950 nameLength = name.length,
13952 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13953 var val = values[i];
13954 var valArray = $.splat(val.values);
13955 totalValue += parseFloat(valArray.sum());
13959 var nameArray = new Array();
13960 var idArray = new Array();
13961 var valArray = new Array();
13962 var valuelabelArray = new Array();
13963 var linkArray = new Array();
13964 var titleArray = new Array();
13965 var percentageArray = new Array();
13967 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13968 var val = values[i];
13969 nameArray[i] = $.splat(val.label);
13970 idArray[i] = $.splat(prefix + val.label);
13971 valArray[i] = $.splat(val.values);
13972 valuelabelArray[i] = $.splat(val.valuelabels);
13973 linkArray[i] = $.splat(val.links);
13974 titleArray[i] = $.splat(val.titles);
13975 percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13980 nameArray.reverse();
13981 valArray.reverse();
13982 valuelabelArray.reverse();
13983 linkArray.reverse();
13984 titleArray.reverse();
13985 percentageArray.reverse();
13988 'id': prefix + val.label,
13993 '$idArray': idArray,
13994 '$linkArray': linkArray,
13995 '$titleArray': titleArray,
13996 '$valueArray': valArray,
13997 '$valuelabelArray': valuelabelArray,
13998 '$colorArray': color,
13999 '$colorMono': $.splat(color[i % colorLength]),
14000 '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
14001 '$gradient': gradient,
14003 '$percentageArray' : percentageArray,
14011 'id': prefix + '$root',
14022 this.normalizeDims();
14024 if(renderBackground) {
14025 this.renderBackground();
14027 if(!animate && title.text) {
14028 this.renderTitle();
14030 if(!animate && subtitle.text) {
14031 this.renderSubtitle();
14033 if(typeof FlashCanvas == "undefined") {
14034 this.renderDropShadow();
14037 st.select(st.root);
14041 modes: ['node-property:width:dimArray'],
14043 onComplete: function() {
14049 modes: ['node-property:height:dimArray'],
14051 onComplete: function() {
14064 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.
14068 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
14069 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
14074 barChart.updateJSON(json, {
14075 onComplete: function() {
14076 alert('update complete!');
14081 updateJSON: function(json, onComplete) {
14082 if(this.busy) return;
14086 var graph = st.graph;
14087 var values = json.values;
14088 var animate = this.config.animate;
14090 var horz = this.config.orientation == 'horizontal';
14091 $.each(values, function(v) {
14092 var n = graph.getByName(v.label);
14094 n.setData('valueArray', $.splat(v.values));
14096 n.setData('stringArray', $.splat(json.label));
14100 this.normalizeDims();
14102 st.select(st.root);
14106 modes: ['node-property:width:dimArray'],
14108 onComplete: function() {
14110 onComplete && onComplete.onComplete();
14115 modes: ['node-property:height:dimArray'],
14117 onComplete: function() {
14119 onComplete && onComplete.onComplete();
14126 //adds the little brown bar when hovering the node
14127 select: function(id, name) {
14129 if(!this.config.hoveredColor) return;
14130 var s = this.selected;
14131 if(s.id != id || s.name != name) {
14134 s.color = this.config.hoveredColor;
14135 this.st.graph.eachNode(function(n) {
14137 n.setData('border', s);
14139 n.setData('border', false);
14149 Returns an object containing as keys the legend names and as values hex strings with color values.
14154 var legend = barChart.getLegend();
14157 getLegend: function() {
14158 var legend = new Array();
14159 var name = new Array();
14160 var color = new Array();
14162 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14165 var colors = n.getData('colorArray'),
14166 len = colors.length;
14167 $.each(n.getData('stringArray'), function(s, i) {
14168 color[i] = colors[i % len];
14171 legend['name'] = name;
14172 legend['color'] = color;
14177 Method: getMaxValue
14179 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14184 var ans = barChart.getMaxValue();
14187 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14192 //will return 100 for all BarChart instances,
14193 //displaying all of them with the same scale
14194 $jit.BarChart.implement({
14195 'getMaxValue': function() {
14202 getMaxValue: function() {
14203 var maxValue = 0, stacked = true;
14204 this.st.graph.eachNode(function(n) {
14205 var valArray = n.getData('valueArray'),
14207 if(!valArray) return;
14209 $.each(valArray, function(v) {
14213 acum = Math.max.apply(null, valArray);
14215 maxValue = maxValue>acum? maxValue:acum;
14220 setBarType: function(type) {
14221 this.config.type = type;
14222 this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14225 normalizeDims: function() {
14226 //number of elements
14227 var root = this.st.graph.getNode(this.st.root), l=0;
14228 root.eachAdjacency(function() {
14231 var maxValue = this.getMaxValue() || 1,
14232 size = this.st.canvas.getSize(),
14233 config = this.config,
14234 margin = config.Margin,
14235 title = config.Title,
14236 subtitle = config.Subtitle,
14237 marginWidth = margin.left + margin.right,
14238 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14239 horz = config.orientation == 'horizontal',
14240 animate = config.animate,
14241 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14243 - (config.showLabels && (config.Label.size + config.labelOffset)),
14244 dim1 = horz? 'height':'width',
14245 dim2 = horz? 'width':'height';
14248 minWidth = size.width/8;
14252 this.st.graph.eachNode(function(n) {
14253 var acum = 0, animateValue = [];
14254 $.each(n.getData('valueArray'), function(v) {
14256 animateValue.push(0);
14258 n.setData(dim1, minWidth);
14261 n.setData(dim2, acum * height / maxValue, 'end');
14262 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14263 return n * height / maxValue;
14265 var dimArray = n.getData('dimArray');
14267 n.setData('dimArray', animateValue);
14270 n.setData(dim2, acum * height / maxValue);
14271 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14272 return n * height / maxValue;
14283 * File: Options.PieChart.js
14287 Object: Options.PieChart
14289 <PieChart> options.
14290 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14296 Options.PieChart = {
14302 hoveredColor: '#9fd4ff',
14304 resizeLabels: false,
14305 updateHeights: false
14314 var pie = new $jit.PieChart({
14317 type: 'stacked:gradient'
14324 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14325 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14326 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14327 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14328 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14329 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14330 showLabels - (boolean) Default's *true*. Display the name of the slots.
14331 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.
14332 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.
14335 Options.PieChart = {
14339 offset: 25, // page offset
14341 labelOffset: 3, // label offset
14342 type: 'stacked', // gradient
14344 hoveredColor: '#9fd4ff',
14355 resizeLabels: false,
14357 //only valid for mono-valued datasets
14358 updateHeights: false
14362 * Class: Layouts.Radial
14364 * Implements a Radial Layout.
14368 * <RGraph>, <Hypertree>
14371 Layouts.Radial = new Class({
14376 * Computes nodes' positions.
14380 * property - _optional_ A <Graph.Node> position property to store the new
14381 * positions. Possible values are 'pos', 'end' or 'start'.
14384 compute : function(property) {
14385 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14386 NodeDim.compute(this.graph, prop, this.config);
14387 this.graph.computeLevels(this.root, 0, "ignore");
14388 var lengthFunc = this.createLevelDistanceFunc();
14389 this.computeAngularWidths(prop);
14390 this.computePositions(prop, lengthFunc);
14396 * Performs the main algorithm for computing node positions.
14398 computePositions : function(property, getLength) {
14399 var propArray = property;
14400 var graph = this.graph;
14401 var root = graph.getNode(this.root);
14402 var parent = this.parent;
14403 var config = this.config;
14405 for ( var i=0, l=propArray.length; i < l; i++) {
14406 var pi = propArray[i];
14407 root.setPos($P(0, 0), pi);
14408 root.setData('span', Math.PI * 2, pi);
14416 graph.eachBFS(this.root, function(elem) {
14417 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14418 var angleInit = elem.angleSpan.begin;
14419 var len = getLength(elem);
14420 //Calculate the sum of all angular widths
14421 var totalAngularWidths = 0, subnodes = [], maxDim = {};
14422 elem.eachSubnode(function(sib) {
14423 totalAngularWidths += sib._treeAngularWidth;
14425 for ( var i=0, l=propArray.length; i < l; i++) {
14426 var pi = propArray[i], dim = sib.getData('dim', pi);
14427 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14429 subnodes.push(sib);
14431 //Maintain children order
14432 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14433 if (parent && parent.id == elem.id && subnodes.length > 0
14434 && subnodes[0].dist) {
14435 subnodes.sort(function(a, b) {
14436 return (a.dist >= b.dist) - (a.dist <= b.dist);
14439 //Calculate nodes positions.
14440 for (var k = 0, ls=subnodes.length; k < ls; k++) {
14441 var child = subnodes[k];
14442 if (!child._flag) {
14443 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14444 var theta = angleInit + angleProportion / 2;
14446 for ( var i=0, l=propArray.length; i < l; i++) {
14447 var pi = propArray[i];
14448 child.setPos($P(theta, len), pi);
14449 child.setData('span', angleProportion, pi);
14450 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14453 child.angleSpan = {
14455 end : angleInit + angleProportion
14457 angleInit += angleProportion;
14464 * Method: setAngularWidthForNodes
14466 * Sets nodes angular widths.
14468 setAngularWidthForNodes : function(prop) {
14469 this.graph.eachBFS(this.root, function(elem, i) {
14470 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14471 elem._angularWidth = diamValue / i;
14476 * Method: setSubtreesAngularWidth
14478 * Sets subtrees angular widths.
14480 setSubtreesAngularWidth : function() {
14482 this.graph.eachNode(function(elem) {
14483 that.setSubtreeAngularWidth(elem);
14488 * Method: setSubtreeAngularWidth
14490 * Sets the angular width for a subtree.
14492 setSubtreeAngularWidth : function(elem) {
14493 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14494 elem.eachSubnode(function(child) {
14495 that.setSubtreeAngularWidth(child);
14496 sumAW += child._treeAngularWidth;
14498 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14502 * Method: computeAngularWidths
14504 * Computes nodes and subtrees angular widths.
14506 computeAngularWidths : function(prop) {
14507 this.setAngularWidthForNodes(prop);
14508 this.setSubtreesAngularWidth();
14515 * File: Sunburst.js
14521 A radial space filling tree visualization.
14525 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14529 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.
14533 All <Loader> methods
14535 Constructor Options:
14537 Inherits options from
14540 - <Options.Controller>
14546 - <Options.NodeStyles>
14547 - <Options.Navigation>
14549 Additionally, there are other parameters and some default values changed
14551 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14552 levelDistance - (number) Default's *100*. The distance between levels of the tree.
14553 Node.type - Described in <Options.Node>. Default's to *multipie*.
14554 Node.height - Described in <Options.Node>. Default's *0*.
14555 Edge.type - Described in <Options.Edge>. Default's *none*.
14556 Label.textAlign - Described in <Options.Label>. Default's *start*.
14557 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14559 Instance Properties:
14561 canvas - Access a <Canvas> instance.
14562 graph - Access a <Graph> instance.
14563 op - Access a <Sunburst.Op> instance.
14564 fx - Access a <Sunburst.Plot> instance.
14565 labels - Access a <Sunburst.Label> interface implementation.
14569 $jit.Sunburst = new Class({
14571 Implements: [ Loader, Extras, Layouts.Radial ],
14573 initialize: function(controller) {
14574 var $Sunburst = $jit.Sunburst;
14577 interpolation: 'linear',
14578 levelDistance: 100,
14580 'type': 'multipie',
14587 textAlign: 'start',
14588 textBaseline: 'middle'
14592 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14593 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14595 var canvasConfig = this.config;
14596 if(canvasConfig.useCanvas) {
14597 this.canvas = canvasConfig.useCanvas;
14598 this.config.labelContainer = this.canvas.id + '-label';
14600 if(canvasConfig.background) {
14601 canvasConfig.background = $.merge({
14603 colorStop1: this.config.colorStop1,
14604 colorStop2: this.config.colorStop2
14605 }, canvasConfig.background);
14607 this.canvas = new Canvas(this, canvasConfig);
14608 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14611 this.graphOptions = {
14619 this.graph = new Graph(this.graphOptions, this.config.Node,
14621 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14622 this.fx = new $Sunburst.Plot(this, $Sunburst);
14623 this.op = new $Sunburst.Op(this);
14626 this.rotated = null;
14628 // initialize extras
14629 this.initializeExtras();
14634 createLevelDistanceFunc
14636 Returns the levelDistance function used for calculating a node distance
14637 to its origin. This function returns a function that is computed
14638 per level and not per node, such that all nodes with the same depth will have the
14639 same distance to the origin. The resulting function gets the
14640 parent node as parameter and returns a float.
14643 createLevelDistanceFunc: function() {
14644 var ld = this.config.levelDistance;
14645 return function(elem) {
14646 return (elem._depth + 1) * ld;
14653 Computes positions and plots the tree.
14656 refresh: function() {
14664 An alias for computing new positions to _endPos_
14671 reposition: function() {
14672 this.compute('end');
14678 Rotates the graph so that the selected node is horizontal on the right.
14682 node - (object) A <Graph.Node>.
14683 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14684 opt - (object) Configuration options merged with this visualization configuration options.
14688 <Sunburst.rotateAngle>
14691 rotate: function(node, method, opt) {
14692 var theta = node.getPos(opt.property || 'current').getp(true).theta;
14693 this.rotated = node;
14694 this.rotateAngle(-theta, method, opt);
14698 Method: rotateAngle
14700 Rotates the graph of an angle theta.
14704 node - (object) A <Graph.Node>.
14705 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14706 opt - (object) Configuration options merged with this visualization configuration options.
14713 rotateAngle: function(theta, method, opt) {
14715 var options = $.merge(this.config, opt || {}, {
14718 var prop = opt.property || (method === "animate" ? 'end' : 'current');
14719 if(method === 'animate') {
14720 this.fx.animation.pause();
14722 this.graph.eachNode(function(n) {
14723 var p = n.getPos(prop);
14726 p.theta += Math.PI * 2;
14729 if (method == 'animate') {
14730 this.fx.animate(options);
14731 } else if (method == 'replot') {
14740 Plots the Sunburst. This is a shortcut to *fx.plot*.
14747 $jit.Sunburst.$extend = true;
14749 (function(Sunburst) {
14754 Custom extension of <Graph.Op>.
14758 All <Graph.Op> methods
14765 Sunburst.Op = new Class( {
14767 Implements: Graph.Op
14772 Class: Sunburst.Plot
14774 Custom extension of <Graph.Plot>.
14778 All <Graph.Plot> methods
14785 Sunburst.Plot = new Class( {
14787 Implements: Graph.Plot
14792 Class: Sunburst.Label
14794 Custom extension of <Graph.Label>.
14795 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14799 All <Graph.Label> methods and subclasses.
14803 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14806 Sunburst.Label = {};
14809 Sunburst.Label.Native
14811 Custom extension of <Graph.Label.Native>.
14815 All <Graph.Label.Native> methods
14819 <Graph.Label.Native>
14821 Sunburst.Label.Native = new Class( {
14822 Implements: Graph.Label.Native,
14824 initialize: function(viz) {
14826 this.label = viz.config.Label;
14827 this.config = viz.config;
14830 renderLabel: function(canvas, node, controller) {
14831 var span = node.getData('span');
14832 if(span < Math.PI /2 && Math.tan(span) *
14833 this.config.levelDistance * node._depth < 10) {
14836 var ctx = canvas.getCtx();
14837 var measure = ctx.measureText(node.name);
14838 if (node.id == this.viz.root) {
14839 var x = -measure.width / 2, y = 0, thetap = 0;
14843 var ld = controller.levelDistance - indent;
14844 var clone = node.pos.clone();
14845 clone.rho += indent;
14846 var p = clone.getp(true);
14847 var ct = clone.getc(true);
14848 var x = ct.x, y = ct.y;
14849 // get angle in degrees
14851 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14852 var thetap = cond ? p.theta + pi : p.theta;
14854 x -= Math.abs(Math.cos(p.theta) * measure.width);
14855 y += Math.sin(p.theta) * measure.width;
14856 } else if (node.id == this.viz.root) {
14857 x -= measure.width / 2;
14861 ctx.translate(x, y);
14862 ctx.rotate(thetap);
14863 ctx.fillText(node.name, 0, 0);
14871 Custom extension of <Graph.Label.SVG>.
14875 All <Graph.Label.SVG> methods
14882 Sunburst.Label.SVG = new Class( {
14883 Implements: Graph.Label.SVG,
14885 initialize: function(viz) {
14892 Overrides abstract method placeLabel in <Graph.Plot>.
14896 tag - A DOM label element.
14897 node - A <Graph.Node>.
14898 controller - A configuration/controller object passed to the visualization.
14901 placeLabel: function(tag, node, controller) {
14902 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14903 var radius = canvas.getSize();
14905 x: Math.round(pos.x + radius.width / 2),
14906 y: Math.round(pos.y + radius.height / 2)
14908 tag.setAttribute('x', labelPos.x);
14909 tag.setAttribute('y', labelPos.y);
14911 var bb = tag.getBBox();
14913 // center the label
14914 var x = tag.getAttribute('x');
14915 var y = tag.getAttribute('y');
14916 // get polar coordinates
14917 var p = node.pos.getp(true);
14918 // get angle in degrees
14920 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14922 tag.setAttribute('x', x - bb.width);
14923 tag.setAttribute('y', y - bb.height);
14924 } else if (node.id == viz.root) {
14925 tag.setAttribute('x', x - bb.width / 2);
14928 var thetap = cond ? p.theta + pi : p.theta;
14930 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14934 controller.onPlaceLabel(tag, node);
14939 Sunburst.Label.HTML
14941 Custom extension of <Graph.Label.HTML>.
14945 All <Graph.Label.HTML> methods.
14952 Sunburst.Label.HTML = new Class( {
14953 Implements: Graph.Label.HTML,
14955 initialize: function(viz) {
14961 Overrides abstract method placeLabel in <Graph.Plot>.
14965 tag - A DOM label element.
14966 node - A <Graph.Node>.
14967 controller - A configuration/controller object passed to the visualization.
14970 placeLabel: function(tag, node, controller) {
14971 var pos = node.pos.clone(),
14972 canvas = this.viz.canvas,
14973 height = node.getData('height'),
14974 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14975 radius = canvas.getSize();
14977 pos = pos.getc(true);
14980 x: Math.round(pos.x + radius.width / 2),
14981 y: Math.round(pos.y + radius.height / 2)
14984 var style = tag.style;
14985 style.left = labelPos.x + 'px';
14986 style.top = labelPos.y + 'px';
14987 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14989 controller.onPlaceLabel(tag, node);
14994 Class: Sunburst.Plot.NodeTypes
14996 This class contains a list of <Graph.Node> built-in types.
14997 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14999 You can add your custom node types, customizing your visualization to the extreme.
15004 Sunburst.Plot.NodeTypes.implement({
15006 'render': function(node, canvas) {
15007 //print your custom node to canvas
15010 'contains': function(node, pos) {
15011 //return true if pos is inside the node or false otherwise
15018 Sunburst.Plot.NodeTypes = new Class( {
15021 'contains': $.lambda(false),
15022 'anglecontains': function(node, pos) {
15023 var span = node.getData('span') / 2, theta = node.pos.theta;
15024 var begin = theta - span, end = theta + span;
15026 begin += Math.PI * 2;
15027 var atan = Math.atan2(pos.y, pos.x);
15029 atan += Math.PI * 2;
15031 return (atan > begin && atan <= Math.PI * 2) || atan < end;
15033 return atan > begin && atan < end;
15036 'anglecontainsgauge': function(node, pos) {
15037 var span = node.getData('span') / 2, theta = node.pos.theta;
15038 var config = node.getData('config');
15039 var ld = this.config.levelDistance;
15040 var yOffset = pos.y-(ld/2);
15041 var begin = ((theta - span)/2)+Math.PI,
15042 end = ((theta + span)/2)+Math.PI;
15045 begin += Math.PI * 2;
15046 var atan = Math.atan2(yOffset, pos.x);
15050 atan += Math.PI * 2;
15054 return (atan > begin && atan <= Math.PI * 2) || atan < end;
15056 return atan > begin && atan < end;
15062 'render': function(node, canvas) {
15063 var span = node.getData('span') / 2, theta = node.pos.theta;
15064 var begin = theta - span, end = theta + span;
15065 var polarNode = node.pos.getp(true);
15066 var polar = new Polar(polarNode.rho, begin);
15067 var p1coord = polar.getc(true);
15069 var p2coord = polar.getc(true);
15071 var ctx = canvas.getCtx();
15074 ctx.lineTo(p1coord.x, p1coord.y);
15076 ctx.lineTo(p2coord.x, p2coord.y);
15078 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
15082 'contains': function(node, pos) {
15083 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15084 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15085 var ld = this.config.levelDistance, d = node._depth;
15086 return (rho <= ld * d);
15092 'render': function(node, canvas) {
15093 var height = node.getData('height');
15094 var ldist = height? height : this.config.levelDistance;
15095 var span = node.getData('span') / 2, theta = node.pos.theta;
15096 var begin = theta - span, end = theta + span;
15097 var polarNode = node.pos.getp(true);
15099 var polar = new Polar(polarNode.rho, begin);
15100 var p1coord = polar.getc(true);
15103 var p2coord = polar.getc(true);
15105 polar.rho += ldist;
15106 var p3coord = polar.getc(true);
15108 polar.theta = begin;
15109 var p4coord = polar.getc(true);
15111 var ctx = canvas.getCtx();
15114 ctx.arc(0, 0, polarNode.rho, begin, end, false);
15115 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15116 ctx.moveTo(p1coord.x, p1coord.y);
15117 ctx.lineTo(p4coord.x, p4coord.y);
15118 ctx.moveTo(p2coord.x, p2coord.y);
15119 ctx.lineTo(p3coord.x, p3coord.y);
15122 if (node.collapsed) {
15127 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15133 'contains': function(node, pos) {
15134 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15135 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15136 var height = node.getData('height');
15137 var ldist = height? height : this.config.levelDistance;
15138 var ld = this.config.levelDistance, d = node._depth;
15139 return (rho >= ld * d) && (rho <= (ld * d + ldist));
15145 'gradient-multipie': {
15146 'render': function(node, canvas) {
15147 var ctx = canvas.getCtx();
15148 var height = node.getData('height');
15149 var ldist = height? height : this.config.levelDistance;
15150 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15151 0, 0, node.getPos().rho + ldist);
15153 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15154 $.each(colorArray, function(i) {
15155 ans.push(parseInt(i * 0.5, 10));
15157 var endColor = $.rgbToHex(ans);
15158 radialGradient.addColorStop(0, endColor);
15159 radialGradient.addColorStop(1, node.getData('color'));
15160 ctx.fillStyle = radialGradient;
15161 this.nodeTypes['multipie'].render.call(this, node, canvas);
15163 'contains': function(node, pos) {
15164 return this.nodeTypes['multipie'].contains.call(this, node, pos);
15169 'render': function(node, canvas) {
15170 var ctx = canvas.getCtx();
15171 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15174 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15175 $.each(colorArray, function(i) {
15176 ans.push(parseInt(i * 0.5, 10));
15178 var endColor = $.rgbToHex(ans);
15179 radialGradient.addColorStop(1, endColor);
15180 radialGradient.addColorStop(0, node.getData('color'));
15181 ctx.fillStyle = radialGradient;
15182 this.nodeTypes['pie'].render.call(this, node, canvas);
15184 'contains': function(node, pos) {
15185 return this.nodeTypes['pie'].contains.call(this, node, pos);
15191 Class: Sunburst.Plot.EdgeTypes
15193 This class contains a list of <Graph.Adjacence> built-in types.
15194 Edge types implemented are 'none', 'line' and 'arrow'.
15196 You can add your custom edge types, customizing your visualization to the extreme.
15201 Sunburst.Plot.EdgeTypes.implement({
15203 'render': function(adj, canvas) {
15204 //print your custom edge to canvas
15207 'contains': function(adj, pos) {
15208 //return true if pos is inside the arc or false otherwise
15215 Sunburst.Plot.EdgeTypes = new Class({
15218 'render': function(adj, canvas) {
15219 var from = adj.nodeFrom.pos.getc(true),
15220 to = adj.nodeTo.pos.getc(true);
15221 this.edgeHelper.line.render(from, to, canvas);
15223 'contains': function(adj, pos) {
15224 var from = adj.nodeFrom.pos.getc(true),
15225 to = adj.nodeTo.pos.getc(true);
15226 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15230 'render': function(adj, canvas) {
15231 var from = adj.nodeFrom.pos.getc(true),
15232 to = adj.nodeTo.pos.getc(true),
15233 dim = adj.getData('dim'),
15234 direction = adj.data.$direction,
15235 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15236 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15238 'contains': function(adj, pos) {
15239 var from = adj.nodeFrom.pos.getc(true),
15240 to = adj.nodeTo.pos.getc(true);
15241 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15245 'render': function(adj, canvas) {
15246 var from = adj.nodeFrom.pos.getc(),
15247 to = adj.nodeTo.pos.getc(),
15248 dim = Math.max(from.norm(), to.norm());
15249 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15251 'contains': $.lambda(false) //TODO(nico): Implement this!
15259 * File: PieChart.js
15263 $jit.Sunburst.Plot.NodeTypes.implement({
15264 'piechart-stacked' : {
15265 'render' : function(node, canvas) {
15266 var pos = node.pos.getp(true),
15267 dimArray = node.getData('dimArray'),
15268 valueArray = node.getData('valueArray'),
15269 colorArray = node.getData('colorArray'),
15270 colorLength = colorArray.length,
15271 stringArray = node.getData('stringArray'),
15272 span = node.getData('span') / 2,
15273 theta = node.pos.theta,
15274 begin = theta - span,
15275 end = theta + span,
15278 var ctx = canvas.getCtx(),
15280 gradient = node.getData('gradient'),
15281 border = node.getData('border'),
15282 config = node.getData('config'),
15283 showLabels = config.showLabels,
15284 resizeLabels = config.resizeLabels,
15285 label = config.Label;
15287 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15288 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15290 if (colorArray && dimArray && stringArray) {
15291 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15292 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15293 if(dimi <= 0) continue;
15294 ctx.fillStyle = ctx.strokeStyle = colori;
15295 if(gradient && dimi) {
15296 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15297 xpos, ypos, acum + dimi + config.sliceOffset);
15298 var colorRgb = $.hexToRgb(colori),
15299 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15300 endColor = $.rgbToHex(ans);
15302 radialGradient.addColorStop(0, colori);
15303 radialGradient.addColorStop(0.5, colori);
15304 radialGradient.addColorStop(1, endColor);
15305 ctx.fillStyle = radialGradient;
15308 polar.rho = acum + config.sliceOffset;
15309 polar.theta = begin;
15310 var p1coord = polar.getc(true);
15312 var p2coord = polar.getc(true);
15314 var p3coord = polar.getc(true);
15315 polar.theta = begin;
15316 var p4coord = polar.getc(true);
15319 //fixing FF arc method + fill
15320 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15321 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15323 if(border && border.name == stringArray[i]) {
15325 opt.dimValue = dimArray[i];
15329 acum += (dimi || 0);
15330 valAcum += (valueArray[i] || 0);
15334 ctx.globalCompositeOperation = "source-over";
15336 ctx.strokeStyle = border.color;
15337 var s = begin < end? 1 : -1;
15339 //fixing FF arc method + fill
15340 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15341 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15346 if(showLabels && label.type == 'Native') {
15348 ctx.fillStyle = ctx.strokeStyle = label.color;
15349 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15350 fontSize = (label.size * scale) >> 0;
15351 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15353 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15354 ctx.textBaseline = 'middle';
15355 ctx.textAlign = 'center';
15357 polar.rho = acum + config.labelOffset + config.sliceOffset;
15358 polar.theta = node.pos.theta;
15359 var cart = polar.getc(true);
15361 ctx.fillText(node.name, cart.x, cart.y);
15366 'contains': function(node, pos) {
15367 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15368 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15369 var ld = this.config.levelDistance, d = node._depth;
15370 var config = node.getData('config');
15371 if(rho <=ld * d + config.sliceOffset) {
15372 var dimArray = node.getData('dimArray');
15373 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15374 var dimi = dimArray[i];
15375 if(rho >= acum && rho <= acum + dimi) {
15377 name: node.getData('stringArray')[i],
15378 color: node.getData('colorArray')[i],
15379 value: node.getData('valueArray')[i],
15392 'piechart-basic' : {
15393 'render' : function(node, canvas) {
15394 var pos = node.pos.getp(true),
15395 dimArray = node.getData('dimArray'),
15396 valueArray = node.getData('valueArray'),
15397 colorArray = node.getData('colorMono'),
15398 colorLength = colorArray.length,
15399 stringArray = node.getData('stringArray'),
15400 percentage = node.getData('percentage'),
15401 iteration = node.getData('iteration'),
15402 span = node.getData('span') / 2,
15403 theta = node.pos.theta,
15404 begin = theta - span,
15405 end = theta + span,
15408 var ctx = canvas.getCtx(),
15410 gradient = node.getData('gradient'),
15411 border = node.getData('border'),
15412 config = node.getData('config'),
15413 renderSubtitle = node.getData('renderSubtitle'),
15414 renderBackground = config.renderBackground,
15415 showLabels = config.showLabels,
15416 resizeLabels = config.resizeLabels,
15417 label = config.Label;
15419 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15420 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15421 //background rendering for IE
15422 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15423 backgroundColor = config.backgroundColor,
15424 size = canvas.getSize();
15426 ctx.fillStyle = backgroundColor;
15427 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15431 var margin = config.Margin,
15432 title = config.Title,
15433 subtitle = config.Subtitle;
15434 ctx.fillStyle = title.color;
15435 ctx.textAlign = 'left';
15437 if(title.text != "") {
15438 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15440 if(label.type == 'Native') {
15441 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15445 if(subtitle.text != "") {
15446 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15447 if(label.type == 'Native') {
15448 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15453 if (colorArray && dimArray && stringArray) {
15454 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15455 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15456 if(dimi <= 0) continue;
15457 ctx.fillStyle = ctx.strokeStyle = colori;
15459 polar.rho = acum + config.sliceOffset;
15460 polar.theta = begin;
15461 var p1coord = polar.getc(true);
15463 var p2coord = polar.getc(true);
15465 var p3coord = polar.getc(true);
15466 polar.theta = begin;
15467 var p4coord = polar.getc(true);
15469 if(typeof FlashCanvas == "undefined") {
15472 ctx.fillStyle = "rgba(0,0,0,.2)";
15473 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15474 ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);
15477 if(gradient && dimi) {
15478 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15479 xpos, ypos, acum + dimi + config.sliceOffset);
15480 var colorRgb = $.hexToRgb(colori),
15481 endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15482 endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15484 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15485 radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15486 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15487 radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15488 ctx.fillStyle = radialGradient;
15493 //fixing FF arc method + fill
15495 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15496 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15498 if(border && border.name == stringArray[i]) {
15500 opt.dimValue = dimArray[i];
15503 opt.sliceValue = valueArray[i];
15505 acum += (dimi || 0);
15506 valAcum += (valueArray[i] || 0);
15510 ctx.globalCompositeOperation = "source-over";
15512 ctx.strokeStyle = border.color;
15513 var s = begin < end? 1 : -1;
15515 //fixing FF arc method + fill
15516 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15517 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15522 if(showLabels && label.type == 'Native') {
15524 ctx.fillStyle = ctx.strokeStyle = label.color;
15525 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15526 fontSize = (label.size * scale) >> 0;
15527 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15529 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15530 ctx.textBaseline = 'middle';
15531 ctx.textAlign = 'center';
15533 angle = theta * 360 / (2 * pi);
15534 polar.rho = acum + config.labelOffset + config.sliceOffset;
15535 polar.theta = node.pos.theta;
15536 var cart = polar.getc(true);
15537 if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15540 if(config.labelType == 'name') {
15541 ctx.fillText(node.name, cart.x, cart.y);
15543 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15550 'contains': function(node, pos) {
15551 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15552 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15553 var ld = this.config.levelDistance, d = node._depth;
15554 var config = node.getData('config');
15556 if(rho <=ld * d + config.sliceOffset) {
15557 var dimArray = node.getData('dimArray');
15558 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15559 var dimi = dimArray[i];
15560 if(rho >= acum && rho <= acum + dimi) {
15561 var url = Url.decode(node.getData('linkArray')[i]);
15563 name: node.getData('stringArray')[i],
15565 color: node.getData('colorArray')[i],
15566 value: node.getData('valueArray')[i],
15567 percentage: node.getData('percentage'),
15568 valuelabel: node.getData('valuelabelsArray')[i],
15586 A visualization that displays stacked bar charts.
15588 Constructor Options:
15590 See <Options.PieChart>.
15593 $jit.PieChart = new Class({
15595 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15599 initialize: function(opt) {
15600 this.controller = this.config =
15601 $.merge(Options("Canvas", "PieChart", "Label"), {
15602 Label: { type: 'Native' }
15604 this.initializeViz();
15607 initializeViz: function() {
15608 var config = this.config, that = this;
15609 var nodeType = config.type.split(":")[0];
15610 var sb = new $jit.Sunburst({
15611 injectInto: config.injectInto,
15612 useCanvas: config.useCanvas,
15613 withLabels: config.Label.type != 'Native',
15614 background: config.background,
15615 renderBackground: config.renderBackground,
15616 backgroundColor: config.backgroundColor,
15617 colorStop1: config.colorStop1,
15618 colorStop2: config.colorStop2,
15620 type: config.Label.type
15624 type: 'piechart-' + nodeType,
15632 enable: config.Tips.enable,
15635 onShow: function(tip, node, contains) {
15636 var elem = contains;
15637 config.Tips.onShow(tip, elem, node);
15638 if(elem.link != 'undefined' && elem.link != '') {
15639 document.body.style.cursor = 'pointer';
15642 onHide: function() {
15643 document.body.style.cursor = 'default';
15649 onClick: function(node, eventInfo, evt) {
15650 if(!config.Events.enable) return;
15651 var elem = eventInfo.getContains();
15652 config.Events.onClick(elem, eventInfo, evt);
15654 onMouseMove: function(node, eventInfo, evt) {
15655 if(!config.hoveredColor) return;
15657 var elem = eventInfo.getContains();
15658 that.select(node.id, elem.name, elem.index);
15660 that.select(false, false, false);
15664 onCreateLabel: function(domElement, node) {
15665 var labelConf = config.Label;
15666 if(config.showLabels) {
15667 var style = domElement.style;
15668 style.fontSize = labelConf.size + 'px';
15669 style.fontFamily = labelConf.family;
15670 style.color = labelConf.color;
15671 style.textAlign = 'center';
15672 if(config.labelType == 'name') {
15673 domElement.innerHTML = node.name;
15675 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15677 domElement.style.width = '400px';
15680 onPlaceLabel: function(domElement, node) {
15681 if(!config.showLabels) return;
15682 var pos = node.pos.getp(true),
15683 dimArray = node.getData('dimArray'),
15684 span = node.getData('span') / 2,
15685 theta = node.pos.theta,
15686 begin = theta - span,
15687 end = theta + span,
15690 var showLabels = config.showLabels,
15691 resizeLabels = config.resizeLabels,
15692 label = config.Label;
15695 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15696 acum += dimArray[i];
15698 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15699 fontSize = (label.size * scale) >> 0;
15700 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15701 domElement.style.fontSize = fontSize + 'px';
15702 polar.rho = acum + config.labelOffset + config.sliceOffset;
15703 polar.theta = (begin + end) / 2;
15704 var pos = polar.getc(true);
15705 var radius = that.canvas.getSize();
15707 x: Math.round(pos.x + radius.width / 2),
15708 y: Math.round(pos.y + radius.height / 2)
15710 domElement.style.left = (labelPos.x - 200) + 'px';
15711 domElement.style.top = labelPos.y + 'px';
15716 var size = sb.canvas.getSize(),
15718 sb.config.levelDistance = min(size.width, size.height)/2
15719 - config.offset - config.sliceOffset;
15721 this.canvas = this.sb.canvas;
15722 this.canvas.getCtx().globalCompositeOperation = 'lighter';
15724 renderBackground: function() {
15725 var canvas = this.canvas,
15726 config = this.config,
15727 backgroundColor = config.backgroundColor,
15728 size = canvas.getSize(),
15729 ctx = canvas.getCtx();
15730 ctx.globalCompositeOperation = "destination-over";
15731 ctx.fillStyle = backgroundColor;
15732 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15734 renderTitle: function() {
15735 var canvas = this.canvas,
15736 size = canvas.getSize(),
15737 config = this.config,
15738 margin = config.Margin,
15739 radius = this.sb.config.levelDistance,
15740 title = config.Title,
15741 label = config.Label,
15742 subtitle = config.Subtitle;
15743 ctx = canvas.getCtx();
15744 ctx.fillStyle = title.color;
15745 ctx.textAlign = 'left';
15746 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15748 if(label.type == 'Native') {
15749 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15752 renderSubtitle: function() {
15753 var canvas = this.canvas,
15754 size = canvas.getSize(),
15755 config = this.config,
15756 margin = config.Margin,
15757 radius = this.sb.config.levelDistance,
15758 title = config.Title,
15759 label = config.Label,
15760 subtitle = config.Subtitle;
15761 ctx = canvas.getCtx();
15762 ctx.fillStyle = title.color;
15763 ctx.textAlign = 'left';
15764 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15766 if(label.type == 'Native') {
15767 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15770 clear: function() {
15771 var canvas = this.canvas;
15772 var ctx = canvas.getCtx(),
15773 size = canvas.getSize();
15774 ctx.fillStyle = "rgba(255,255,255,0)";
15775 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15776 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15778 resizeGraph: function(json,width) {
15779 var canvas = this.canvas,
15780 size = canvas.getSize(),
15781 config = this.config,
15782 orgHeight = size.height;
15784 canvas.resize(width,orgHeight);
15785 if(typeof FlashCanvas == "undefined") {
15788 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15790 this.loadJSON(json);
15796 Loads JSON data into the visualization.
15800 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>.
15804 var pieChart = new $jit.PieChart(options);
15805 pieChart.loadJSON(json);
15808 loadJSON: function(json) {
15809 var prefix = $.time(),
15812 name = $.splat(json.label),
15813 nameLength = name.length,
15814 color = $.splat(json.color || this.colors),
15815 colorLength = color.length,
15816 config = this.config,
15817 renderBackground = config.renderBackground,
15818 title = config.Title,
15819 subtitle = config.Subtitle,
15820 gradient = !!config.type.split(":")[1],
15821 animate = config.animate,
15822 mono = nameLength == 1;
15824 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15825 var val = values[i];
15826 var valArray = $.splat(val.values);
15827 totalValue += parseFloat(valArray.sum());
15830 for(var i=0, values=json.values, l=values.length; i<l; i++) {
15831 var val = values[i];
15832 var valArray = $.splat(val.values);
15833 var percentage = (valArray.sum()/totalValue) * 100;
15835 var linkArray = $.splat(val.links);
15836 var valuelabelsArray = $.splat(val.valuelabels);
15840 'id': prefix + val.label,
15844 'valuelabel': valuelabelsArray,
15845 '$linkArray': linkArray,
15846 '$valuelabelsArray': valuelabelsArray,
15847 '$valueArray': valArray,
15848 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15849 '$colorMono': $.splat(color[i % colorLength]),
15850 '$stringArray': name,
15851 '$gradient': gradient,
15854 '$percentage': percentage.toFixed(1),
15855 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15861 'id': prefix + '$root',
15873 this.normalizeDims();
15877 if(title.text != "") {
15878 this.renderTitle();
15881 if(subtitle.text != "") {
15882 this.renderSubtitle();
15884 if(renderBackground && typeof FlashCanvas == "undefined") {
15885 this.renderBackground();
15890 modes: ['node-property:dimArray'],
15899 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.
15903 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15904 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15909 pieChart.updateJSON(json, {
15910 onComplete: function() {
15911 alert('update complete!');
15916 updateJSON: function(json, onComplete) {
15917 if(this.busy) return;
15921 var graph = sb.graph;
15922 var values = json.values;
15923 var animate = this.config.animate;
15925 $.each(values, function(v) {
15926 var n = graph.getByName(v.label),
15927 vals = $.splat(v.values);
15929 n.setData('valueArray', vals);
15930 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15932 n.setData('stringArray', $.splat(json.label));
15936 this.normalizeDims();
15940 modes: ['node-property:dimArray:span', 'linear'],
15942 onComplete: function() {
15944 onComplete && onComplete.onComplete();
15952 //adds the little brown bar when hovering the node
15953 select: function(id, name) {
15954 if(!this.config.hoveredColor) return;
15955 var s = this.selected;
15956 if(s.id != id || s.name != name) {
15959 s.color = this.config.hoveredColor;
15960 this.sb.graph.eachNode(function(n) {
15962 n.setData('border', s);
15964 n.setData('border', false);
15974 Returns an object containing as keys the legend names and as values hex strings with color values.
15979 var legend = pieChart.getLegend();
15982 getLegend: function() {
15983 var legend = new Array();
15984 var name = new Array();
15985 var color = new Array();
15987 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15990 var colors = n.getData('colorArray'),
15991 len = colors.length;
15992 $.each(n.getData('stringArray'), function(s, i) {
15993 color[i] = colors[i % len];
15996 legend['name'] = name;
15997 legend['color'] = color;
16002 Method: getMaxValue
16004 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16009 var ans = pieChart.getMaxValue();
16012 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16017 //will return 100 for all PieChart instances,
16018 //displaying all of them with the same scale
16019 $jit.PieChart.implement({
16020 'getMaxValue': function() {
16027 getMaxValue: function() {
16029 this.sb.graph.eachNode(function(n) {
16030 var valArray = n.getData('valueArray'),
16032 $.each(valArray, function(v) {
16035 maxValue = maxValue>acum? maxValue:acum;
16040 normalizeDims: function() {
16041 //number of elements
16042 var root = this.sb.graph.getNode(this.sb.root), l=0;
16043 root.eachAdjacency(function() {
16046 var maxValue = this.getMaxValue() || 1,
16047 config = this.config,
16048 animate = config.animate,
16049 rho = this.sb.config.levelDistance;
16050 this.sb.graph.eachNode(function(n) {
16051 var acum = 0, animateValue = [];
16052 $.each(n.getData('valueArray'), function(v) {
16054 animateValue.push(1);
16056 var stat = (animateValue.length == 1) && !config.updateHeights;
16058 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16059 return stat? rho: (n * rho / maxValue);
16061 var dimArray = n.getData('dimArray');
16063 n.setData('dimArray', animateValue);
16066 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16067 return stat? rho : (n * rho / maxValue);
16070 n.setData('normalizedDim', acum / maxValue);
16078 Options.GaugeChart = {
16082 offset: 25, // page offset
16084 labelOffset: 3, // label offset
16085 type: 'stacked', // gradient
16087 hoveredColor: '#9fd4ff',
16098 resizeLabels: false,
16100 //only valid for mono-valued datasets
16101 updateHeights: false
16106 $jit.Sunburst.Plot.NodeTypes.implement({
16107 'gaugechart-basic' : {
16108 'render' : function(node, canvas) {
16109 var pos = node.pos.getp(true),
16110 dimArray = node.getData('dimArray'),
16111 valueArray = node.getData('valueArray'),
16112 valuelabelsArray = node.getData('valuelabelsArray'),
16113 gaugeTarget = node.getData('gaugeTarget'),
16114 nodeIteration = node.getData('nodeIteration'),
16115 nodeLength = node.getData('nodeLength'),
16116 colorArray = node.getData('colorMono'),
16117 colorLength = colorArray.length,
16118 stringArray = node.getData('stringArray'),
16119 span = node.getData('span') / 2,
16120 theta = node.pos.theta,
16121 begin = ((theta - span)/2)+Math.PI,
16122 end = ((theta + span)/2)+Math.PI,
16126 var ctx = canvas.getCtx(),
16128 gradient = node.getData('gradient'),
16129 border = node.getData('border'),
16130 config = node.getData('config'),
16131 showLabels = config.showLabels,
16132 resizeLabels = config.resizeLabels,
16133 label = config.Label;
16135 var xpos = Math.cos((begin + end) /2);
16136 var ypos = Math.sin((begin + end) /2);
16138 if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16139 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16140 var dimi = dimArray[i], colori = colorArray[i % colorLength];
16141 if(dimi <= 0) continue;
16142 ctx.fillStyle = ctx.strokeStyle = colori;
16143 if(gradient && dimi) {
16144 var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16145 xpos, (ypos + dimi/2), acum + dimi);
16146 var colorRgb = $.hexToRgb(colori),
16147 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16148 endColor = $.rgbToHex(ans);
16150 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16151 radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16152 radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16153 radialGradient.addColorStop(1, 'rgba('+ans+',1)');
16154 ctx.fillStyle = radialGradient;
16158 polar.theta = begin;
16159 var p1coord = polar.getc(true);
16161 var p2coord = polar.getc(true);
16163 var p3coord = polar.getc(true);
16164 polar.theta = begin;
16165 var p4coord = polar.getc(true);
16169 //fixing FF arc method + fill
16170 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16171 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16175 acum += (dimi || 0);
16176 valAcum += (valueArray[i] || 0);
16179 if(showLabels && label.type == 'Native') {
16181 ctx.fillStyle = ctx.strokeStyle = label.color;
16184 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16185 ctx.textBaseline = 'bottom';
16186 ctx.textAlign = 'center';
16188 polar.rho = acum * .65;
16189 polar.theta = begin;
16190 var cart = polar.getc(true);
16192 //changes y pos of first label
16193 if(nodeIteration == 1) {
16194 textY = cart.y - (label.size/2) + acum /2;
16196 textY = cart.y + acum/2;
16199 if(config.labelType == 'name') {
16200 ctx.fillText(node.name, cart.x, textY);
16202 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16206 if(nodeIteration == nodeLength) {
16208 var cart = polar.getc(true);
16209 if(config.labelType == 'name') {
16210 ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16212 ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16221 'contains': function(node, pos) {
16225 if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16226 var config = node.getData('config');
16227 var ld = this.config.levelDistance , d = node._depth;
16228 var yOffset = pos.y - (ld/2);
16229 var xOffset = pos.x;
16230 var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16231 if(rho <=parseInt(ld * d)) {
16232 var dimArray = node.getData('dimArray');
16233 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16234 var dimi = dimArray[i];
16235 if(rho >= ld * .8 && rho <= acum + dimi) {
16237 var url = Url.decode(node.getData('linkArray')[i]);
16239 name: node.getData('stringArray')[i],
16241 color: node.getData('colorArray')[i],
16242 value: node.getData('valueArray')[i],
16243 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16263 A visualization that displays gauge charts
16265 Constructor Options:
16267 See <Options.Gauge>.
16270 $jit.GaugeChart = new Class({
16272 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16276 initialize: function(opt) {
16277 this.controller = this.config =
16278 $.merge(Options("Canvas", "GaugeChart", "Label"), {
16279 Label: { type: 'Native' }
16281 this.initializeViz();
16284 initializeViz: function() {
16285 var config = this.config, that = this;
16286 var nodeType = config.type.split(":")[0];
16287 var sb = new $jit.Sunburst({
16288 injectInto: config.injectInto,
16289 useCanvas: config.useCanvas,
16290 withLabels: config.Label.type != 'Native',
16291 background: config.background,
16292 renderBackground: config.renderBackground,
16293 backgroundColor: config.backgroundColor,
16294 colorStop1: config.colorStop1,
16295 colorStop2: config.colorStop2,
16297 type: config.Label.type
16301 type: 'gaugechart-' + nodeType,
16309 enable: config.Tips.enable,
16312 onShow: function(tip, node, contains) {
16313 var elem = contains;
16314 config.Tips.onShow(tip, elem, node);
16315 if(elem.link != 'undefined' && elem.link != '') {
16316 document.body.style.cursor = 'pointer';
16319 onHide: function() {
16320 document.body.style.cursor = 'default';
16326 onClick: function(node, eventInfo, evt) {
16327 if(!config.Events.enable) return;
16328 var elem = eventInfo.getContains();
16329 config.Events.onClick(elem, eventInfo, evt);
16332 onCreateLabel: function(domElement, node) {
16333 var labelConf = config.Label;
16334 if(config.showLabels) {
16335 var style = domElement.style;
16336 style.fontSize = labelConf.size + 'px';
16337 style.fontFamily = labelConf.family;
16338 style.color = labelConf.color;
16339 style.textAlign = 'center';
16340 valuelabelsArray = node.getData('valuelabelsArray'),
16341 nodeIteration = node.getData('nodeIteration'),
16342 nodeLength = node.getData('nodeLength'),
16343 canvas = sb.canvas,
16346 if(config.labelType == 'name') {
16347 domElement.innerHTML = node.name;
16349 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16352 domElement.style.width = '400px';
16355 if(nodeIteration == nodeLength && nodeLength != 0) {
16356 idLabel = canvas.id + "-label";
16357 container = document.getElementById(idLabel);
16358 finalLabel = document.createElement('div');
16359 finalLabelStyle = finalLabel.style;
16360 finalLabel.id = prefix + "finalLabel";
16361 finalLabelStyle.position = "absolute";
16362 finalLabelStyle.width = "400px";
16363 finalLabelStyle.left = "0px";
16364 container.appendChild(finalLabel);
16365 if(config.labelType == 'name') {
16366 finalLabel.innerHTML = node.name;
16368 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16374 onPlaceLabel: function(domElement, node) {
16375 if(!config.showLabels) return;
16376 var pos = node.pos.getp(true),
16377 dimArray = node.getData('dimArray'),
16378 nodeIteration = node.getData('nodeIteration'),
16379 nodeLength = node.getData('nodeLength'),
16380 span = node.getData('span') / 2,
16381 theta = node.pos.theta,
16382 begin = ((theta - span)/2)+Math.PI,
16383 end = ((theta + span)/2)+Math.PI,
16386 var showLabels = config.showLabels,
16387 resizeLabels = config.resizeLabels,
16388 label = config.Label,
16389 radiusOffset = sb.config.levelDistance;
16392 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16393 acum += dimArray[i];
16395 var scale = resizeLabels? node.getData('normalizedDim') : 1,
16396 fontSize = (label.size * scale) >> 0;
16397 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16398 domElement.style.fontSize = fontSize + 'px';
16399 polar.rho = acum * .65;
16400 polar.theta = begin;
16401 var pos = polar.getc(true);
16402 var radius = that.canvas.getSize();
16404 x: Math.round(pos.x + radius.width / 2),
16405 y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16410 domElement.style.left = (labelPos.x - 200) + 'px';
16411 domElement.style.top = labelPos.y + 'px';
16413 //reposition first label
16414 if(nodeIteration == 1) {
16415 domElement.style.top = labelPos.y - label.size + 'px';
16419 //position final label
16420 if(nodeIteration == nodeLength && nodeLength != 0) {
16422 var final = polar.getc(true);
16424 x: Math.round(final.x + radius.width / 2),
16425 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16427 finalLabel.style.left = (finalPos.x - 200) + "px";
16428 finalLabel.style.top = finalPos.y - label.size + "px";
16436 this.canvas = this.sb.canvas;
16437 var size = sb.canvas.getSize(),
16439 sb.config.levelDistance = min(size.width, size.height)/2
16440 - config.offset - config.sliceOffset;
16445 renderBackground: function() {
16446 var canvas = this.sb.canvas,
16447 config = this.config,
16448 style = config.gaugeStyle,
16449 ctx = canvas.getCtx(),
16450 size = canvas.getSize(),
16451 radius = this.sb.config.levelDistance,
16452 startAngle = (Math.PI/180)*1,
16453 endAngle = (Math.PI/180)*179;
16456 //background border
16457 ctx.fillStyle = style.borderColor;
16459 ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true);
16463 var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16464 radialGradient.addColorStop(0, '#ffffff');
16465 radialGradient.addColorStop(0.3, style.backgroundColor);
16466 radialGradient.addColorStop(0.6, style.backgroundColor);
16467 radialGradient.addColorStop(1, '#FFFFFF');
16468 ctx.fillStyle = radialGradient;
16471 startAngle = (Math.PI/180)*0;
16472 endAngle = (Math.PI/180)*180;
16474 ctx.arc(0,radius/2,radius,startAngle,endAngle, true);
16482 renderNeedle: function(gaugePosition,target) {
16483 var canvas = this.sb.canvas,
16484 config = this.config,
16485 style = config.gaugeStyle,
16486 ctx = canvas.getCtx(),
16487 size = canvas.getSize(),
16488 radius = this.sb.config.levelDistance;
16489 gaugeCenter = (radius/2);
16491 endAngle = (Math.PI/180)*180;
16495 ctx.fillStyle = style.needleColor;
16496 var segments = 180/target;
16497 needleAngle = gaugePosition * segments;
16498 ctx.translate(0, gaugeCenter);
16500 ctx.rotate(needleAngle * Math.PI / 180);
16504 ctx.lineTo(-radius*.9,-1);
16505 ctx.lineTo(-radius*.9,1);
16515 ctx.strokeStyle = '#aa0000';
16517 ctx.rotate(needleAngle * Math.PI / 180);
16521 ctx.lineTo(-radius*.8,-1);
16522 ctx.lineTo(-radius*.8,1);
16530 ctx.fillStyle = "#000000";
16531 ctx.lineWidth = style.borderSize;
16532 ctx.strokeStyle = style.borderColor;
16533 var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16534 radialGradient.addColorStop(0, '#666666');
16535 radialGradient.addColorStop(0.8, '#444444');
16536 radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
16537 ctx.fillStyle = radialGradient;
16538 ctx.translate(0,5);
16541 ctx.arc(0,0,radius*.2,startAngle,endAngle, true);
16548 renderTicks: function(values) {
16549 var canvas = this.sb.canvas,
16550 config = this.config,
16551 style = config.gaugeStyle,
16552 ctx = canvas.getCtx(),
16553 size = canvas.getSize(),
16554 radius = this.sb.config.levelDistance,
16555 gaugeCenter = (radius/2);
16558 ctx.strokeStyle = style.borderColor;
16560 ctx.lineCap = "round";
16561 for(var i=0, total = 0, l=values.length; i<l; i++) {
16562 var val = values[i];
16563 if(val.label != 'GaugePosition') {
16564 total += (parseInt(val.values) || 0);
16568 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16569 var val = values[i];
16570 if(val.label != 'GaugePosition') {
16571 acum += (parseInt(val.values) || 0);
16573 var segments = 180/total;
16574 angle = acum * segments;
16578 ctx.translate(0, gaugeCenter);
16580 ctx.rotate(angle * (Math.PI/180));
16581 ctx.moveTo(-radius,0);
16582 ctx.lineTo(-radius*.75,0);
16590 renderPositionLabel: function(position) {
16591 var canvas = this.sb.canvas,
16592 config = this.config,
16593 label = config.Label,
16594 style = config.gaugeStyle,
16595 ctx = canvas.getCtx(),
16596 size = canvas.getSize(),
16597 radius = this.sb.config.levelDistance,
16598 gaugeCenter = (radius/2);
16599 ctx.textBaseline = 'middle';
16600 ctx.textAlign = 'center';
16601 ctx.font = style.positionFontSize + 'px ' + label.family;
16602 ctx.fillStyle = "#ffffff";
16604 height = style.positionFontSize + 10,
16606 idLabel = canvas.id + "-label";
16607 container = document.getElementById(idLabel);
16608 if(label.type == 'Native') {
16609 var m = ctx.measureText(position),
16610 width = m.width + 40;
16614 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16615 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16616 if(label.type == 'Native') {
16617 ctx.fillStyle = label.color;
16618 ctx.fillText(position, 0, (height/2) + style.positionOffset);
16620 var labelDiv = document.createElement('div');
16621 labelDivStyle = labelDiv.style;
16622 labelDivStyle.color = label.color;
16623 labelDivStyle.fontSize = style.positionFontSize + "px";
16624 labelDivStyle.position = "absolute";
16625 labelDivStyle.width = width + "px";
16626 labelDivStyle.left = (size.width/2) - (width/2) + "px";
16627 labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16628 labelDiv.innerHTML = position;
16629 container.appendChild(labelDiv);
16634 renderSubtitle: function() {
16635 var canvas = this.canvas,
16636 size = canvas.getSize(),
16637 config = this.config,
16638 margin = config.Margin,
16639 radius = this.sb.config.levelDistance,
16640 title = config.Title,
16641 label = config.Label,
16642 subtitle = config.Subtitle;
16643 ctx = canvas.getCtx();
16644 ctx.fillStyle = title.color;
16645 ctx.textAlign = 'left';
16646 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16648 if(label.type == 'Native') {
16649 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2));
16653 renderChartBackground: function() {
16654 var canvas = this.canvas,
16655 config = this.config,
16656 backgroundColor = config.backgroundColor,
16657 size = canvas.getSize(),
16658 ctx = canvas.getCtx();
16659 //ctx.globalCompositeOperation = "destination-over";
16660 ctx.fillStyle = backgroundColor;
16661 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16663 clear: function() {
16664 var canvas = this.canvas;
16665 var ctx = canvas.getCtx(),
16666 size = canvas.getSize();
16667 ctx.fillStyle = "rgba(255,255,255,0)";
16668 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16669 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16671 resizeGraph: function(json,width) {
16672 var canvas = this.canvas,
16673 size = canvas.getSize(),
16674 orgHeight = size.height;
16676 canvas.resize(width,orgHeight);
16677 if(typeof FlashCanvas == "undefined") {
16680 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16682 this.loadJSON(json);
16685 loadJSON: function(json) {
16687 var prefix = $.time(),
16690 name = $.splat(json.label),
16691 nameLength = name.length,
16692 color = $.splat(json.color || this.colors),
16693 colorLength = color.length,
16694 config = this.config,
16695 renderBackground = config.renderBackground,
16696 gradient = !!config.type.split(":")[1],
16697 animate = config.animate,
16698 mono = nameLength == 1;
16699 var props = $.splat(json.properties)[0];
16701 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16703 var val = values[i];
16704 if(val.label != 'GaugePosition') {
16705 var valArray = $.splat(val.values);
16706 var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16707 var valuelabelsArray = $.splat(val.valuelabels);
16710 'id': prefix + val.label,
16714 'valuelabel': valuelabelsArray,
16715 '$linkArray': linkArray,
16716 '$valuelabelsArray': valuelabelsArray,
16717 '$valueArray': valArray,
16718 '$nodeIteration': i,
16719 '$nodeLength': l-1,
16720 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16721 '$colorMono': $.splat(color[i % colorLength]),
16722 '$stringArray': name,
16723 '$gradient': gradient,
16725 '$gaugeTarget': props['gaugeTarget'],
16726 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16731 var gaugePosition = val.gvalue;
16732 var gaugePositionLabel = val.gvaluelabel;
16736 'id': prefix + '$root',
16749 if(renderBackground) {
16750 this.renderChartBackground();
16753 this.renderBackground();
16754 this.renderSubtitle();
16756 this.normalizeDims();
16761 modes: ['node-property:dimArray'],
16767 this.renderPositionLabel(gaugePositionLabel);
16768 if (props['gaugeTarget'] != 0) {
16769 this.renderTicks(json.values);
16770 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16777 updateJSON: function(json, onComplete) {
16778 if(this.busy) return;
16782 var graph = sb.graph;
16783 var values = json.values;
16784 var animate = this.config.animate;
16786 $.each(values, function(v) {
16787 var n = graph.getByName(v.label),
16788 vals = $.splat(v.values);
16790 n.setData('valueArray', vals);
16791 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16793 n.setData('stringArray', $.splat(json.label));
16797 this.normalizeDims();
16801 modes: ['node-property:dimArray:span', 'linear'],
16803 onComplete: function() {
16805 onComplete && onComplete.onComplete();
16813 //adds the little brown bar when hovering the node
16814 select: function(id, name) {
16815 if(!this.config.hoveredColor) return;
16816 var s = this.selected;
16817 if(s.id != id || s.name != name) {
16820 s.color = this.config.hoveredColor;
16821 this.sb.graph.eachNode(function(n) {
16823 n.setData('border', s);
16825 n.setData('border', false);
16835 Returns an object containing as keys the legend names and as values hex strings with color values.
16840 var legend = pieChart.getLegend();
16843 getLegend: function() {
16844 var legend = new Array();
16845 var name = new Array();
16846 var color = new Array();
16848 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16851 var colors = n.getData('colorArray'),
16852 len = colors.length;
16853 $.each(n.getData('stringArray'), function(s, i) {
16854 color[i] = colors[i % len];
16857 legend['name'] = name;
16858 legend['color'] = color;
16863 Method: getMaxValue
16865 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16870 var ans = pieChart.getMaxValue();
16873 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16878 //will return 100 for all PieChart instances,
16879 //displaying all of them with the same scale
16880 $jit.PieChart.implement({
16881 'getMaxValue': function() {
16888 getMaxValue: function() {
16890 this.sb.graph.eachNode(function(n) {
16891 var valArray = n.getData('valueArray'),
16893 $.each(valArray, function(v) {
16896 maxValue = maxValue>acum? maxValue:acum;
16901 normalizeDims: function() {
16902 //number of elements
16903 var root = this.sb.graph.getNode(this.sb.root), l=0;
16904 root.eachAdjacency(function() {
16907 var maxValue = this.getMaxValue() || 1,
16908 config = this.config,
16909 animate = config.animate,
16910 rho = this.sb.config.levelDistance;
16911 this.sb.graph.eachNode(function(n) {
16912 var acum = 0, animateValue = [];
16913 $.each(n.getData('valueArray'), function(v) {
16915 animateValue.push(1);
16917 var stat = (animateValue.length == 1) && !config.updateHeights;
16919 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16920 return stat? rho: (n * rho / maxValue);
16922 var dimArray = n.getData('dimArray');
16924 n.setData('dimArray', animateValue);
16927 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16928 return stat? rho : (n * rho / maxValue);
16931 n.setData('normalizedDim', acum / maxValue);
16938 * Class: Layouts.TM
16940 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16949 Layouts.TM.SliceAndDice = new Class({
16950 compute: function(prop) {
16951 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16952 this.controller.onBeforeCompute(root);
16953 var size = this.canvas.getSize(),
16954 config = this.config,
16955 width = size.width,
16956 height = size.height;
16957 this.graph.computeLevels(this.root, 0, "ignore");
16958 //set root position and dimensions
16959 root.getPos(prop).setc(-width/2, -height/2);
16960 root.setData('width', width, prop);
16961 root.setData('height', height + config.titleHeight, prop);
16962 this.computePositions(root, root, this.layout.orientation, prop);
16963 this.controller.onAfterCompute(root);
16966 computePositions: function(par, ch, orn, prop) {
16967 //compute children areas
16969 par.eachSubnode(function(n) {
16970 totalArea += n.getData('area', prop);
16973 var config = this.config,
16974 offst = config.offset,
16975 width = par.getData('width', prop),
16976 height = par.getData('height', prop) - config.titleHeight,
16977 fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16979 var otherSize, size, dim, pos, pos2, posth, pos2th;
16980 var horizontal = (orn == "h");
16983 otherSize = height;
16984 size = width * fact;
16988 posth = config.titleHeight;
16992 otherSize = height * fact;
16998 pos2th = config.titleHeight;
17000 var cpos = ch.getPos(prop);
17001 ch.setData('width', size, prop);
17002 ch.setData('height', otherSize, prop);
17003 var offsetSize = 0, tm = this;
17004 ch.eachSubnode(function(n) {
17005 var p = n.getPos(prop);
17006 p[pos] = offsetSize + cpos[pos] + posth;
17007 p[pos2] = cpos[pos2] + pos2th;
17008 tm.computePositions(ch, n, orn, prop);
17009 offsetSize += n.getData(dim, prop);
17015 Layouts.TM.Area = {
17019 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17023 json - A JSON tree. See also <Loader.loadJSON>.
17024 coord - A coordinates object specifying width, height, left and top style properties.
17026 compute: function(prop) {
17027 prop = prop || "current";
17028 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17029 this.controller.onBeforeCompute(root);
17030 var config = this.config,
17031 size = this.canvas.getSize(),
17032 width = size.width,
17033 height = size.height,
17034 offst = config.offset,
17035 offwdth = width - offst,
17036 offhght = height - offst;
17037 this.graph.computeLevels(this.root, 0, "ignore");
17038 //set root position and dimensions
17039 root.getPos(prop).setc(-width/2, -height/2);
17040 root.setData('width', width, prop);
17041 root.setData('height', height, prop);
17042 //create a coordinates object
17044 'top': -height/2 + config.titleHeight,
17047 'height': offhght - config.titleHeight
17049 this.computePositions(root, coord, prop);
17050 this.controller.onAfterCompute(root);
17056 Computes dimensions and positions of a group of nodes
17057 according to a custom layout row condition.
17061 tail - An array of nodes.
17062 initElem - An array of nodes (containing the initial node to be laid).
17063 w - A fixed dimension where nodes will be layed out.
17064 coord - A coordinates object specifying width, height, left and top style properties.
17065 comp - A custom comparison function
17067 computeDim: function(tail, initElem, w, coord, comp, prop) {
17068 if(tail.length + initElem.length == 1) {
17069 var l = (tail.length == 1)? tail : initElem;
17070 this.layoutLast(l, w, coord, prop);
17073 if(tail.length >= 2 && initElem.length == 0) {
17074 initElem = [tail.shift()];
17076 if(tail.length == 0) {
17077 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17081 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17082 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17084 var newCoords = this.layoutRow(initElem, w, coord, prop);
17085 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17091 Method: worstAspectRatio
17093 Calculates the worst aspect ratio of a group of rectangles.
17097 <http://en.wikipedia.org/wiki/Aspect_ratio>
17101 ch - An array of nodes.
17102 w - The fixed dimension where rectangles are being laid out.
17106 The worst aspect ratio.
17110 worstAspectRatio: function(ch, w) {
17111 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17112 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17113 for(var i=0, l=ch.length; i<l; i++) {
17114 var area = ch[i]._area;
17116 minArea = minArea < area? minArea : area;
17117 maxArea = maxArea > area? maxArea : area;
17119 var sqw = w * w, sqAreaSum = areaSum * areaSum;
17120 return Math.max(sqw * maxArea / sqAreaSum,
17121 sqAreaSum / (sqw * minArea));
17125 Method: avgAspectRatio
17127 Calculates the average aspect ratio of a group of rectangles.
17131 <http://en.wikipedia.org/wiki/Aspect_ratio>
17135 ch - An array of nodes.
17136 w - The fixed dimension where rectangles are being laid out.
17140 The average aspect ratio.
17144 avgAspectRatio: function(ch, w) {
17145 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17147 for(var i=0, l=ch.length; i<l; i++) {
17148 var area = ch[i]._area;
17150 arSum += w > h? w / h : h / w;
17158 Performs the layout of the last computed sibling.
17162 ch - An array of nodes.
17163 w - A fixed dimension where nodes will be layed out.
17164 coord - A coordinates object specifying width, height, left and top style properties.
17166 layoutLast: function(ch, w, coord, prop) {
17168 child.getPos(prop).setc(coord.left, coord.top);
17169 child.setData('width', coord.width, prop);
17170 child.setData('height', coord.height, prop);
17175 Layouts.TM.Squarified = new Class({
17176 Implements: Layouts.TM.Area,
17178 computePositions: function(node, coord, prop) {
17179 var config = this.config;
17181 if (coord.width >= coord.height)
17182 this.layout.orientation = 'h';
17184 this.layout.orientation = 'v';
17186 var ch = node.getSubnodes([1, 1], "ignore");
17187 if(ch.length > 0) {
17188 this.processChildrenLayout(node, ch, coord, prop);
17189 for(var i=0, l=ch.length; i<l; i++) {
17191 var offst = config.offset,
17192 height = chi.getData('height', prop) - offst - config.titleHeight,
17193 width = chi.getData('width', prop) - offst;
17194 var chipos = chi.getPos(prop);
17198 'top': chipos.y + config.titleHeight,
17201 this.computePositions(chi, coord, prop);
17207 Method: processChildrenLayout
17209 Computes children real areas and other useful parameters for performing the Squarified algorithm.
17213 par - The parent node of the json subtree.
17214 ch - An Array of nodes
17215 coord - A coordinates object specifying width, height, left and top style properties.
17217 processChildrenLayout: function(par, ch, coord, prop) {
17218 //compute children real areas
17219 var parentArea = coord.width * coord.height;
17220 var i, l=ch.length, totalChArea=0, chArea = [];
17221 for(i=0; i<l; i++) {
17222 chArea[i] = parseFloat(ch[i].getData('area', prop));
17223 totalChArea += chArea[i];
17225 for(i=0; i<l; i++) {
17226 ch[i]._area = parentArea * chArea[i] / totalChArea;
17228 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17229 ch.sort(function(a, b) {
17230 var diff = b._area - a._area;
17231 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
17233 var initElem = [ch[0]];
17234 var tail = ch.slice(1);
17235 this.squarify(tail, initElem, minimumSideValue, coord, prop);
17241 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17245 tail - An array of nodes.
17246 initElem - An array of nodes, containing the initial node to be laid out.
17247 w - A fixed dimension where nodes will be laid out.
17248 coord - A coordinates object specifying width, height, left and top style properties.
17250 squarify: function(tail, initElem, w, coord, prop) {
17251 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17257 Performs the layout of an array of nodes.
17261 ch - An array of nodes.
17262 w - A fixed dimension where nodes will be laid out.
17263 coord - A coordinates object specifying width, height, left and top style properties.
17265 layoutRow: function(ch, w, coord, prop) {
17266 if(this.layout.horizontal()) {
17267 return this.layoutV(ch, w, coord, prop);
17269 return this.layoutH(ch, w, coord, prop);
17273 layoutV: function(ch, w, coord, prop) {
17274 var totalArea = 0, rnd = function(x) { return x; };
17275 $.each(ch, function(elem) { totalArea += elem._area; });
17276 var width = rnd(totalArea / w), top = 0;
17277 for(var i=0, l=ch.length; i<l; i++) {
17278 var h = rnd(ch[i]._area / width);
17280 chi.getPos(prop).setc(coord.left, coord.top + top);
17281 chi.setData('width', width, prop);
17282 chi.setData('height', h, prop);
17286 'height': coord.height,
17287 'width': coord.width - width,
17289 'left': coord.left + width
17291 //take minimum side value.
17292 ans.dim = Math.min(ans.width, ans.height);
17293 if(ans.dim != ans.height) this.layout.change();
17297 layoutH: function(ch, w, coord, prop) {
17299 $.each(ch, function(elem) { totalArea += elem._area; });
17300 var height = totalArea / w,
17304 for(var i=0, l=ch.length; i<l; i++) {
17306 var w = chi._area / height;
17307 chi.getPos(prop).setc(coord.left + left, top);
17308 chi.setData('width', w, prop);
17309 chi.setData('height', height, prop);
17313 'height': coord.height - height,
17314 'width': coord.width,
17315 'top': coord.top + height,
17318 ans.dim = Math.min(ans.width, ans.height);
17319 if(ans.dim != ans.width) this.layout.change();
17324 Layouts.TM.Strip = new Class({
17325 Implements: Layouts.TM.Area,
17330 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17334 json - A JSON subtree. See also <Loader.loadJSON>.
17335 coord - A coordinates object specifying width, height, left and top style properties.
17337 computePositions: function(node, coord, prop) {
17338 var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17339 if(ch.length > 0) {
17340 this.processChildrenLayout(node, ch, coord, prop);
17341 for(var i=0, l=ch.length; i<l; i++) {
17343 var offst = config.offset,
17344 height = chi.getData('height', prop) - offst - config.titleHeight,
17345 width = chi.getData('width', prop) - offst;
17346 var chipos = chi.getPos(prop);
17350 'top': chipos.y + config.titleHeight,
17353 this.computePositions(chi, coord, prop);
17359 Method: processChildrenLayout
17361 Computes children real areas and other useful parameters for performing the Strip algorithm.
17365 par - The parent node of the json subtree.
17366 ch - An Array of nodes
17367 coord - A coordinates object specifying width, height, left and top style properties.
17369 processChildrenLayout: function(par, ch, coord, prop) {
17370 //compute children real areas
17371 var parentArea = coord.width * coord.height;
17372 var i, l=ch.length, totalChArea=0, chArea = [];
17373 for(i=0; i<l; i++) {
17374 chArea[i] = +ch[i].getData('area', prop);
17375 totalChArea += chArea[i];
17377 for(i=0; i<l; i++) {
17378 ch[i]._area = parentArea * chArea[i] / totalChArea;
17380 var side = this.layout.horizontal()? coord.width : coord.height;
17381 var initElem = [ch[0]];
17382 var tail = ch.slice(1);
17383 this.stripify(tail, initElem, side, coord, prop);
17389 Performs an heuristic method to calculate div elements sizes in order to have
17390 a good compromise between aspect ratio and order.
17394 tail - An array of nodes.
17395 initElem - An array of nodes.
17396 w - A fixed dimension where nodes will be layed out.
17397 coord - A coordinates object specifying width, height, left and top style properties.
17399 stripify: function(tail, initElem, w, coord, prop) {
17400 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17406 Performs the layout of an array of nodes.
17410 ch - An array of nodes.
17411 w - A fixed dimension where nodes will be laid out.
17412 coord - A coordinates object specifying width, height, left and top style properties.
17414 layoutRow: function(ch, w, coord, prop) {
17415 if(this.layout.horizontal()) {
17416 return this.layoutH(ch, w, coord, prop);
17418 return this.layoutV(ch, w, coord, prop);
17422 layoutV: function(ch, w, coord, prop) {
17424 $.each(ch, function(elem) { totalArea += elem._area; });
17425 var width = totalArea / w, top = 0;
17426 for(var i=0, l=ch.length; i<l; i++) {
17428 var h = chi._area / width;
17429 chi.getPos(prop).setc(coord.left,
17430 coord.top + (w - h - top));
17431 chi.setData('width', width, prop);
17432 chi.setData('height', h, prop);
17437 'height': coord.height,
17438 'width': coord.width - width,
17440 'left': coord.left + width,
17445 layoutH: function(ch, w, coord, prop) {
17447 $.each(ch, function(elem) { totalArea += elem._area; });
17448 var height = totalArea / w,
17449 top = coord.height - height,
17452 for(var i=0, l=ch.length; i<l; i++) {
17454 var s = chi._area / height;
17455 chi.getPos(prop).setc(coord.left + left, coord.top + top);
17456 chi.setData('width', s, prop);
17457 chi.setData('height', height, prop);
17461 'height': coord.height - height,
17462 'width': coord.width,
17464 'left': coord.left,
17471 * Class: Layouts.Icicle
17473 * Implements the icicle tree layout.
17481 Layouts.Icicle = new Class({
17485 * Called by loadJSON to calculate all node positions.
17489 * posType - The nodes' position to compute. Either "start", "end" or
17490 * "current". Defaults to "current".
17492 compute: function(posType) {
17493 posType = posType || "current";
17494 var root = this.graph.getNode(this.root),
17495 config = this.config,
17496 size = this.canvas.getSize(),
17497 width = size.width,
17498 height = size.height,
17499 offset = config.offset,
17500 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17502 this.controller.onBeforeCompute(root);
17504 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17508 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17510 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17511 var maxDepth = Math.min(treeDepth, levelsToShow-1);
17512 var initialDepth = startNode._depth;
17513 if(this.layout.horizontal()) {
17514 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17516 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17520 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17521 root.getPos(posType).setc(x, y);
17522 root.setData('width', width, posType);
17523 root.setData('height', height, posType);
17525 var nodeLength, prevNodeLength = 0, totalDim = 0;
17526 var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17528 if(!children.length)
17531 $.each(children, function(e) { totalDim += e.getData('dim'); });
17533 for(var i=0, l=children.length; i < l; i++) {
17534 if(this.layout.horizontal()) {
17535 nodeLength = height * children[i].getData('dim') / totalDim;
17536 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17539 nodeLength = width * children[i].getData('dim') / totalDim;
17540 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17557 Icicle space filling visualization.
17561 All <Loader> methods
17563 Constructor Options:
17565 Inherits options from
17568 - <Options.Controller>
17574 - <Options.NodeStyles>
17575 - <Options.Navigation>
17577 Additionally, there are other parameters and some default values changed
17579 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17580 offset - (number) Default's *2*. Boxes offset.
17581 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17582 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17583 animate - (boolean) Default's *false*. Whether to animate transitions.
17584 Node.type - Described in <Options.Node>. Default's *rectangle*.
17585 Label.type - Described in <Options.Label>. Default's *Native*.
17586 duration - Described in <Options.Fx>. Default's *700*.
17587 fps - Described in <Options.Fx>. Default's *45*.
17589 Instance Properties:
17591 canvas - Access a <Canvas> instance.
17592 graph - Access a <Graph> instance.
17593 op - Access a <Icicle.Op> instance.
17594 fx - Access a <Icicle.Plot> instance.
17595 labels - Access a <Icicle.Label> interface implementation.
17599 $jit.Icicle = new Class({
17600 Implements: [ Loader, Extras, Layouts.Icicle ],
17604 vertical: function(){
17605 return this.orientation == "v";
17607 horizontal: function(){
17608 return this.orientation == "h";
17610 change: function(){
17611 this.orientation = this.vertical()? "h" : "v";
17615 initialize: function(controller) {
17620 levelsToShow: Number.MAX_VALUE,
17621 constrained: false,
17636 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17637 "Events", "Navigation", "Controller", "Label");
17638 this.controller = this.config = $.merge(opts, config, controller);
17639 this.layout.orientation = this.config.orientation;
17641 var canvasConfig = this.config;
17642 if (canvasConfig.useCanvas) {
17643 this.canvas = canvasConfig.useCanvas;
17644 this.config.labelContainer = this.canvas.id + '-label';
17646 this.canvas = new Canvas(this, canvasConfig);
17647 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17650 this.graphOptions = {
17659 this.graph = new Graph(
17660 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17662 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17663 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17664 this.op = new $jit.Icicle.Op(this);
17665 this.group = new $jit.Icicle.Group(this);
17666 this.clickedNode = null;
17668 this.initializeExtras();
17674 Computes positions and plots the tree.
17676 refresh: function(){
17677 var labelType = this.config.Label.type;
17678 if(labelType != 'Native') {
17680 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17689 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
17693 this.fx.plot(this.config);
17699 Sets the node as root.
17703 node - (object) A <Graph.Node>.
17706 enter: function (node) {
17712 config = this.config;
17715 onComplete: function() {
17716 //compute positions of newly inserted nodes
17720 if(config.animate) {
17721 that.graph.nodeList.setDataset(['current', 'end'], {
17722 'alpha': [1, 0] //fade nodes
17725 Graph.Util.eachSubgraph(node, function(n) {
17726 n.setData('alpha', 1, 'end');
17731 modes:['node-property:alpha'],
17732 onComplete: function() {
17733 that.clickedNode = node;
17734 that.compute('end');
17737 modes:['linear', 'node-property:width:height'],
17739 onComplete: function() {
17741 that.clickedNode = node;
17747 that.clickedNode = node;
17754 if(config.request) {
17755 this.requestNodes(clickedNode, callback);
17757 callback.onComplete();
17764 Sets the parent node of the current selected node as root.
17772 GUtil = Graph.Util,
17773 config = this.config,
17774 graph = this.graph,
17775 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17776 parent = parents[0],
17777 clickedNode = parent,
17778 previousClickedNode = this.clickedNode;
17781 this.events.hoveredNode = false;
17788 //final plot callback
17790 onComplete: function() {
17791 that.clickedNode = parent;
17792 if(config.request) {
17793 that.requestNodes(parent, {
17794 onComplete: function() {
17808 //animate node positions
17809 if(config.animate) {
17810 this.clickedNode = clickedNode;
17811 this.compute('end');
17812 //animate the visible subtree only
17813 this.clickedNode = previousClickedNode;
17815 modes:['linear', 'node-property:width:height'],
17817 onComplete: function() {
17818 //animate the parent subtree
17819 that.clickedNode = clickedNode;
17820 //change nodes alpha
17821 graph.nodeList.setDataset(['current', 'end'], {
17824 GUtil.eachSubgraph(previousClickedNode, function(node) {
17825 node.setData('alpha', 1);
17829 modes:['node-property:alpha'],
17830 onComplete: function() {
17831 callback.onComplete();
17837 callback.onComplete();
17840 requestNodes: function(node, onComplete){
17841 var handler = $.merge(this.controller, onComplete),
17842 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17844 if (handler.request) {
17845 var leaves = [], d = node._depth;
17846 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17847 if (n.drawn && !Graph.Util.anySubnode(n)) {
17849 n._level = n._depth - d;
17850 if (this.config.constrained)
17851 n._level = levelsToShow - n._level;
17855 this.group.requestNodes(leaves, handler);
17857 handler.onComplete();
17865 Custom extension of <Graph.Op>.
17869 All <Graph.Op> methods
17876 $jit.Icicle.Op = new Class({
17878 Implements: Graph.Op
17883 * Performs operations on group of nodes.
17885 $jit.Icicle.Group = new Class({
17887 initialize: function(viz){
17889 this.canvas = viz.canvas;
17890 this.config = viz.config;
17894 * Calls the request method on the controller to request a subtree for each node.
17896 requestNodes: function(nodes, controller){
17897 var counter = 0, len = nodes.length, nodeSelected = {};
17898 var complete = function(){
17899 controller.onComplete();
17901 var viz = this.viz;
17904 for(var i = 0; i < len; i++) {
17905 nodeSelected[nodes[i].id] = nodes[i];
17906 controller.request(nodes[i].id, nodes[i]._level, {
17907 onComplete: function(nodeId, data){
17908 if (data && data.children) {
17914 if (++counter == len) {
17915 Graph.Util.computeLevels(viz.graph, viz.root, 0);
17927 Custom extension of <Graph.Plot>.
17931 All <Graph.Plot> methods
17938 $jit.Icicle.Plot = new Class({
17939 Implements: Graph.Plot,
17941 plot: function(opt, animating){
17942 opt = opt || this.viz.controller;
17943 var viz = this.viz,
17945 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17946 initialDepth = root._depth;
17948 viz.canvas.clear();
17949 this.plotTree(root, $.merge(opt, {
17950 'withLabels': true,
17951 'hideLabels': false,
17952 'plotSubtree': function(root, node) {
17953 return !viz.config.constrained ||
17954 (node._depth - initialDepth < viz.config.levelsToShow);
17961 Class: Icicle.Label
17963 Custom extension of <Graph.Label>.
17964 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17968 All <Graph.Label> methods and subclasses.
17972 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17975 $jit.Icicle.Label = {};
17978 Icicle.Label.Native
17980 Custom extension of <Graph.Label.Native>.
17984 All <Graph.Label.Native> methods
17988 <Graph.Label.Native>
17991 $jit.Icicle.Label.Native = new Class({
17992 Implements: Graph.Label.Native,
17994 renderLabel: function(canvas, node, controller) {
17995 var ctx = canvas.getCtx(),
17996 width = node.getData('width'),
17997 height = node.getData('height'),
17998 size = node.getLabelData('size'),
17999 m = ctx.measureText(node.name);
18001 // Guess as much as possible if the label will fit in the node
18002 if(height < (size * 1.5) || width < m.width)
18005 var pos = node.pos.getc(true);
18006 ctx.fillText(node.name,
18008 pos.y + height / 2);
18015 Custom extension of <Graph.Label.SVG>.
18019 All <Graph.Label.SVG> methods
18025 $jit.Icicle.Label.SVG = new Class( {
18026 Implements: Graph.Label.SVG,
18028 initialize: function(viz){
18035 Overrides abstract method placeLabel in <Graph.Plot>.
18039 tag - A DOM label element.
18040 node - A <Graph.Node>.
18041 controller - A configuration/controller object passed to the visualization.
18043 placeLabel: function(tag, node, controller){
18044 var pos = node.pos.getc(true), canvas = this.viz.canvas;
18045 var radius = canvas.getSize();
18047 x: Math.round(pos.x + radius.width / 2),
18048 y: Math.round(pos.y + radius.height / 2)
18050 tag.setAttribute('x', labelPos.x);
18051 tag.setAttribute('y', labelPos.y);
18053 controller.onPlaceLabel(tag, node);
18060 Custom extension of <Graph.Label.HTML>.
18064 All <Graph.Label.HTML> methods.
18071 $jit.Icicle.Label.HTML = new Class( {
18072 Implements: Graph.Label.HTML,
18074 initialize: function(viz){
18081 Overrides abstract method placeLabel in <Graph.Plot>.
18085 tag - A DOM label element.
18086 node - A <Graph.Node>.
18087 controller - A configuration/controller object passed to the visualization.
18089 placeLabel: function(tag, node, controller){
18090 var pos = node.pos.getc(true), canvas = this.viz.canvas;
18091 var radius = canvas.getSize();
18093 x: Math.round(pos.x + radius.width / 2),
18094 y: Math.round(pos.y + radius.height / 2)
18097 var style = tag.style;
18098 style.left = labelPos.x + 'px';
18099 style.top = labelPos.y + 'px';
18100 style.display = '';
18102 controller.onPlaceLabel(tag, node);
18107 Class: Icicle.Plot.NodeTypes
18109 This class contains a list of <Graph.Node> built-in types.
18110 Node types implemented are 'none', 'rectangle'.
18112 You can add your custom node types, customizing your visualization to the extreme.
18117 Icicle.Plot.NodeTypes.implement({
18119 'render': function(node, canvas) {
18120 //print your custom node to canvas
18123 'contains': function(node, pos) {
18124 //return true if pos is inside the node or false otherwise
18131 $jit.Icicle.Plot.NodeTypes = new Class( {
18137 'render': function(node, canvas, animating) {
18138 var config = this.viz.config;
18139 var offset = config.offset;
18140 var width = node.getData('width');
18141 var height = node.getData('height');
18142 var border = node.getData('border');
18143 var pos = node.pos.getc(true);
18144 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18145 var ctx = canvas.getCtx();
18147 if(width - offset < 2 || height - offset < 2) return;
18149 if(config.cushion) {
18150 var color = node.getData('color');
18151 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
18152 posy + (height - offset)/2, 1,
18153 posx + (width-offset)/2, posy + (height-offset)/2,
18154 width < height? height : width);
18155 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
18156 function(r) { return r * 0.3 >> 0; }));
18157 lg.addColorStop(0, color);
18158 lg.addColorStop(1, colorGrad);
18159 ctx.fillStyle = lg;
18163 ctx.strokeStyle = border;
18167 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18168 border && ctx.strokeRect(pos.x, pos.y, width, height);
18171 'contains': function(node, pos) {
18172 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18173 var npos = node.pos.getc(true),
18174 width = node.getData('width'),
18175 height = node.getData('height');
18176 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18181 $jit.Icicle.Plot.EdgeTypes = new Class( {
18188 * File: Layouts.ForceDirected.js
18193 * Class: Layouts.ForceDirected
18195 * Implements a Force Directed Layout.
18203 * Marcus Cobden <http://marcuscobden.co.uk>
18206 Layouts.ForceDirected = new Class({
18208 getOptions: function(random) {
18209 var s = this.canvas.getSize();
18210 var w = s.width, h = s.height;
18213 this.graph.eachNode(function(n) {
18216 var k2 = w * h / count, k = Math.sqrt(k2);
18217 var l = this.config.levelDistance;
18223 nodef: function(x) { return k2 / (x || 1); },
18224 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18228 compute: function(property, incremental) {
18229 var prop = $.splat(property || ['current', 'start', 'end']);
18230 var opt = this.getOptions();
18231 NodeDim.compute(this.graph, prop, this.config);
18232 this.graph.computeLevels(this.root, 0, "ignore");
18233 this.graph.eachNode(function(n) {
18234 $.each(prop, function(p) {
18235 var pos = n.getPos(p);
18236 if(pos.equals(Complex.KER)) {
18237 pos.x = opt.width/5 * (Math.random() - 0.5);
18238 pos.y = opt.height/5 * (Math.random() - 0.5);
18240 //initialize disp vector
18242 $.each(prop, function(p) {
18243 n.disp[p] = $C(0, 0);
18247 this.computePositions(prop, opt, incremental);
18250 computePositions: function(property, opt, incremental) {
18251 var times = this.config.iterations, i = 0, that = this;
18254 for(var total=incremental.iter, j=0; j<total; j++) {
18255 opt.t = opt.tstart * (1 - i++/(times -1));
18256 that.computePositionStep(property, opt);
18258 incremental.onComplete();
18262 incremental.onStep(Math.round(i / (times -1) * 100));
18263 setTimeout(iter, 1);
18266 for(; i < times; i++) {
18267 opt.t = opt.tstart * (1 - i/(times -1));
18268 this.computePositionStep(property, opt);
18273 computePositionStep: function(property, opt) {
18274 var graph = this.graph;
18275 var min = Math.min, max = Math.max;
18276 var dpos = $C(0, 0);
18277 //calculate repulsive forces
18278 graph.eachNode(function(v) {
18280 $.each(property, function(p) {
18281 v.disp[p].x = 0; v.disp[p].y = 0;
18283 graph.eachNode(function(u) {
18285 $.each(property, function(p) {
18286 var vp = v.getPos(p), up = u.getPos(p);
18287 dpos.x = vp.x - up.x;
18288 dpos.y = vp.y - up.y;
18289 var norm = dpos.norm() || 1;
18290 v.disp[p].$add(dpos
18291 .$scale(opt.nodef(norm) / norm));
18296 //calculate attractive forces
18297 var T = !!graph.getNode(this.root).visited;
18298 graph.eachNode(function(node) {
18299 node.eachAdjacency(function(adj) {
18300 var nodeTo = adj.nodeTo;
18301 if(!!nodeTo.visited === T) {
18302 $.each(property, function(p) {
18303 var vp = node.getPos(p), up = nodeTo.getPos(p);
18304 dpos.x = vp.x - up.x;
18305 dpos.y = vp.y - up.y;
18306 var norm = dpos.norm() || 1;
18307 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18308 nodeTo.disp[p].$add(dpos.$scale(-1));
18314 //arrange positions to fit the canvas
18315 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18316 graph.eachNode(function(u) {
18317 $.each(property, function(p) {
18318 var disp = u.disp[p];
18319 var norm = disp.norm() || 1;
18320 var p = u.getPos(p);
18321 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
18322 disp.y * min(Math.abs(disp.y), t) / norm));
18323 p.x = min(w2, max(-w2, p.x));
18324 p.y = min(h2, max(-h2, p.y));
18331 * File: ForceDirected.js
18335 Class: ForceDirected
18337 A visualization that lays graphs using a Force-Directed layout algorithm.
18341 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18345 All <Loader> methods
18347 Constructor Options:
18349 Inherits options from
18352 - <Options.Controller>
18358 - <Options.NodeStyles>
18359 - <Options.Navigation>
18361 Additionally, there are two parameters
18363 levelDistance - (number) Default's *50*. The natural length desired for the edges.
18364 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*.
18366 Instance Properties:
18368 canvas - Access a <Canvas> instance.
18369 graph - Access a <Graph> instance.
18370 op - Access a <ForceDirected.Op> instance.
18371 fx - Access a <ForceDirected.Plot> instance.
18372 labels - Access a <ForceDirected.Label> interface implementation.
18376 $jit.ForceDirected = new Class( {
18378 Implements: [ Loader, Extras, Layouts.ForceDirected ],
18380 initialize: function(controller) {
18381 var $ForceDirected = $jit.ForceDirected;
18388 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18389 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18391 var canvasConfig = this.config;
18392 if(canvasConfig.useCanvas) {
18393 this.canvas = canvasConfig.useCanvas;
18394 this.config.labelContainer = this.canvas.id + '-label';
18396 if(canvasConfig.background) {
18397 canvasConfig.background = $.merge({
18399 }, canvasConfig.background);
18401 this.canvas = new Canvas(this, canvasConfig);
18402 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18405 this.graphOptions = {
18413 this.graph = new Graph(this.graphOptions, this.config.Node,
18415 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18416 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18417 this.op = new $ForceDirected.Op(this);
18420 // initialize extras
18421 this.initializeExtras();
18427 Computes positions and plots the tree.
18429 refresh: function() {
18434 reposition: function() {
18435 this.compute('end');
18439 Method: computeIncremental
18441 Performs the Force Directed algorithm incrementally.
18445 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
18446 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
18447 avoiding browser messages such as "This script is taking too long to complete".
18451 opt - (object) The object properties are described below
18453 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
18454 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18456 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
18457 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
18458 computations for final animation positions then you can just choose 'end'.
18460 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
18461 parameter a percentage value.
18463 onComplete - A callback function called when the algorithm completed.
18467 In this example I calculate the end positions and then animate the graph to those positions
18470 var fd = new $jit.ForceDirected(...);
18471 fd.computeIncremental({
18474 onStep: function(perc) {
18475 Log.write("loading " + perc + "%");
18477 onComplete: function() {
18484 In this example I calculate all positions and (re)plot the graph
18487 var fd = new ForceDirected(...);
18488 fd.computeIncremental({
18490 property: ['end', 'start', 'current'],
18491 onStep: function(perc) {
18492 Log.write("loading " + perc + "%");
18494 onComplete: function() {
18502 computeIncremental: function(opt) {
18507 onComplete: $.empty
18510 this.config.onBeforeCompute(this.graph.getNode(this.root));
18511 this.compute(opt.property, opt);
18517 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18526 Animates the graph from the current positions to the 'end' node positions.
18528 animate: function(opt) {
18529 this.fx.animate($.merge( {
18530 modes: [ 'linear' ]
18535 $jit.ForceDirected.$extend = true;
18537 (function(ForceDirected) {
18540 Class: ForceDirected.Op
18542 Custom extension of <Graph.Op>.
18546 All <Graph.Op> methods
18553 ForceDirected.Op = new Class( {
18555 Implements: Graph.Op
18560 Class: ForceDirected.Plot
18562 Custom extension of <Graph.Plot>.
18566 All <Graph.Plot> methods
18573 ForceDirected.Plot = new Class( {
18575 Implements: Graph.Plot
18580 Class: ForceDirected.Label
18582 Custom extension of <Graph.Label>.
18583 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18587 All <Graph.Label> methods and subclasses.
18591 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18594 ForceDirected.Label = {};
18597 ForceDirected.Label.Native
18599 Custom extension of <Graph.Label.Native>.
18603 All <Graph.Label.Native> methods
18607 <Graph.Label.Native>
18610 ForceDirected.Label.Native = new Class( {
18611 Implements: Graph.Label.Native
18615 ForceDirected.Label.SVG
18617 Custom extension of <Graph.Label.SVG>.
18621 All <Graph.Label.SVG> methods
18628 ForceDirected.Label.SVG = new Class( {
18629 Implements: Graph.Label.SVG,
18631 initialize: function(viz) {
18638 Overrides abstract method placeLabel in <Graph.Label>.
18642 tag - A DOM label element.
18643 node - A <Graph.Node>.
18644 controller - A configuration/controller object passed to the visualization.
18647 placeLabel: function(tag, node, controller) {
18648 var pos = node.pos.getc(true),
18649 canvas = this.viz.canvas,
18650 ox = canvas.translateOffsetX,
18651 oy = canvas.translateOffsetY,
18652 sx = canvas.scaleOffsetX,
18653 sy = canvas.scaleOffsetY,
18654 radius = canvas.getSize();
18656 x: Math.round(pos.x * sx + ox + radius.width / 2),
18657 y: Math.round(pos.y * sy + oy + radius.height / 2)
18659 tag.setAttribute('x', labelPos.x);
18660 tag.setAttribute('y', labelPos.y);
18662 controller.onPlaceLabel(tag, node);
18667 ForceDirected.Label.HTML
18669 Custom extension of <Graph.Label.HTML>.
18673 All <Graph.Label.HTML> methods.
18680 ForceDirected.Label.HTML = new Class( {
18681 Implements: Graph.Label.HTML,
18683 initialize: function(viz) {
18689 Overrides abstract method placeLabel in <Graph.Plot>.
18693 tag - A DOM label element.
18694 node - A <Graph.Node>.
18695 controller - A configuration/controller object passed to the visualization.
18698 placeLabel: function(tag, node, controller) {
18699 var pos = node.pos.getc(true),
18700 canvas = this.viz.canvas,
18701 ox = canvas.translateOffsetX,
18702 oy = canvas.translateOffsetY,
18703 sx = canvas.scaleOffsetX,
18704 sy = canvas.scaleOffsetY,
18705 radius = canvas.getSize();
18707 x: Math.round(pos.x * sx + ox + radius.width / 2),
18708 y: Math.round(pos.y * sy + oy + radius.height / 2)
18710 var style = tag.style;
18711 style.left = labelPos.x + 'px';
18712 style.top = labelPos.y + 'px';
18713 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18715 controller.onPlaceLabel(tag, node);
18720 Class: ForceDirected.Plot.NodeTypes
18722 This class contains a list of <Graph.Node> built-in types.
18723 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18725 You can add your custom node types, customizing your visualization to the extreme.
18730 ForceDirected.Plot.NodeTypes.implement({
18732 'render': function(node, canvas) {
18733 //print your custom node to canvas
18736 'contains': function(node, pos) {
18737 //return true if pos is inside the node or false otherwise
18744 ForceDirected.Plot.NodeTypes = new Class({
18747 'contains': $.lambda(false)
18750 'render': function(node, canvas){
18751 var pos = node.pos.getc(true),
18752 dim = node.getData('dim');
18753 this.nodeHelper.circle.render('fill', pos, dim, canvas);
18755 'contains': function(node, pos){
18756 var npos = node.pos.getc(true),
18757 dim = node.getData('dim');
18758 return this.nodeHelper.circle.contains(npos, pos, dim);
18762 'render': function(node, canvas){
18763 var pos = node.pos.getc(true),
18764 width = node.getData('width'),
18765 height = node.getData('height');
18766 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18768 // TODO(nico): be more precise...
18769 'contains': function(node, pos){
18770 var npos = node.pos.getc(true),
18771 width = node.getData('width'),
18772 height = node.getData('height');
18773 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18777 'render': function(node, canvas){
18778 var pos = node.pos.getc(true),
18779 dim = node.getData('dim');
18780 this.nodeHelper.square.render('fill', pos, dim, canvas);
18782 'contains': function(node, pos){
18783 var npos = node.pos.getc(true),
18784 dim = node.getData('dim');
18785 return this.nodeHelper.square.contains(npos, pos, dim);
18789 'render': function(node, canvas){
18790 var pos = node.pos.getc(true),
18791 width = node.getData('width'),
18792 height = node.getData('height');
18793 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18795 'contains': function(node, pos){
18796 var npos = node.pos.getc(true),
18797 width = node.getData('width'),
18798 height = node.getData('height');
18799 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18803 'render': function(node, canvas){
18804 var pos = node.pos.getc(true),
18805 dim = node.getData('dim');
18806 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18808 'contains': function(node, pos) {
18809 var npos = node.pos.getc(true),
18810 dim = node.getData('dim');
18811 return this.nodeHelper.triangle.contains(npos, pos, dim);
18815 'render': function(node, canvas){
18816 var pos = node.pos.getc(true),
18817 dim = node.getData('dim');
18818 this.nodeHelper.star.render('fill', pos, dim, canvas);
18820 'contains': function(node, pos) {
18821 var npos = node.pos.getc(true),
18822 dim = node.getData('dim');
18823 return this.nodeHelper.star.contains(npos, pos, dim);
18829 Class: ForceDirected.Plot.EdgeTypes
18831 This class contains a list of <Graph.Adjacence> built-in types.
18832 Edge types implemented are 'none', 'line' and 'arrow'.
18834 You can add your custom edge types, customizing your visualization to the extreme.
18839 ForceDirected.Plot.EdgeTypes.implement({
18841 'render': function(adj, canvas) {
18842 //print your custom edge to canvas
18845 'contains': function(adj, pos) {
18846 //return true if pos is inside the arc or false otherwise
18853 ForceDirected.Plot.EdgeTypes = new Class({
18856 'render': function(adj, canvas) {
18857 var from = adj.nodeFrom.pos.getc(true),
18858 to = adj.nodeTo.pos.getc(true);
18859 this.edgeHelper.line.render(from, to, canvas);
18861 'contains': function(adj, pos) {
18862 var from = adj.nodeFrom.pos.getc(true),
18863 to = adj.nodeTo.pos.getc(true);
18864 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18868 'render': function(adj, canvas) {
18869 var from = adj.nodeFrom.pos.getc(true),
18870 to = adj.nodeTo.pos.getc(true),
18871 dim = adj.getData('dim'),
18872 direction = adj.data.$direction,
18873 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18874 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18876 'contains': function(adj, pos) {
18877 var from = adj.nodeFrom.pos.getc(true),
18878 to = adj.nodeTo.pos.getc(true);
18879 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18884 })($jit.ForceDirected);
18896 $jit.TM.$extend = true;
18901 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18905 All <Loader> methods
18907 Constructor Options:
18909 Inherits options from
18912 - <Options.Controller>
18918 - <Options.NodeStyles>
18919 - <Options.Navigation>
18921 Additionally, there are other parameters and some default values changed
18923 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18924 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18925 offset - (number) Default's *2*. Boxes offset.
18926 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18927 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18928 animate - (boolean) Default's *false*. Whether to animate transitions.
18929 Node.type - Described in <Options.Node>. Default's *rectangle*.
18930 duration - Described in <Options.Fx>. Default's *700*.
18931 fps - Described in <Options.Fx>. Default's *45*.
18933 Instance Properties:
18935 canvas - Access a <Canvas> instance.
18936 graph - Access a <Graph> instance.
18937 op - Access a <TM.Op> instance.
18938 fx - Access a <TM.Plot> instance.
18939 labels - Access a <TM.Label> interface implementation.
18943 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18945 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18949 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.
18955 vertical: function(){
18956 return this.orientation == "v";
18958 horizontal: function(){
18959 return this.orientation == "h";
18961 change: function(){
18962 this.orientation = this.vertical()? "h" : "v";
18966 initialize: function(controller){
18972 constrained: false,
18977 //we all know why this is not zero,
18984 textAlign: 'center',
18985 textBaseline: 'top'
18994 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18995 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18996 this.layout.orientation = this.config.orientation;
18998 var canvasConfig = this.config;
18999 if (canvasConfig.useCanvas) {
19000 this.canvas = canvasConfig.useCanvas;
19001 this.config.labelContainer = this.canvas.id + '-label';
19003 if(canvasConfig.background) {
19004 canvasConfig.background = $.merge({
19006 }, canvasConfig.background);
19008 this.canvas = new Canvas(this, canvasConfig);
19009 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19012 this.graphOptions = {
19020 this.graph = new Graph(this.graphOptions, this.config.Node,
19022 this.labels = new TM.Label[canvasConfig.Label.type](this);
19023 this.fx = new TM.Plot(this);
19024 this.op = new TM.Op(this);
19025 this.group = new TM.Group(this);
19026 this.geom = new TM.Geom(this);
19027 this.clickedNode = null;
19029 // initialize extras
19030 this.initializeExtras();
19036 Computes positions and plots the tree.
19038 refresh: function(){
19039 if(this.busy) return;
19042 if(this.config.animate) {
19043 this.compute('end');
19044 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
19045 && this.clickedNode.id || this.root));
19046 this.fx.animate($.merge(this.config, {
19047 modes: ['linear', 'node-property:width:height'],
19048 onComplete: function() {
19053 var labelType = this.config.Label.type;
19054 if(labelType != 'Native') {
19056 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
19060 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
19061 && this.clickedNode.id || this.root));
19069 Plots the TreeMap. This is a shortcut to *fx.plot*.
19079 Returns whether the node is a leaf.
19083 n - (object) A <Graph.Node>.
19087 return n.getSubnodes([
19089 ], "ignore").length == 0;
19095 Sets the node as root.
19099 n - (object) A <Graph.Node>.
19102 enter: function(n){
19103 if(this.busy) return;
19107 config = this.config,
19108 graph = this.graph,
19110 previousClickedNode = this.clickedNode;
19113 onComplete: function() {
19114 //ensure that nodes are shown for that level
19115 if(config.levelsToShow > 0) {
19116 that.geom.setRightLevelToShow(n);
19118 //compute positions of newly inserted nodes
19119 if(config.levelsToShow > 0 || config.request) that.compute();
19120 if(config.animate) {
19122 graph.nodeList.setData('alpha', 0, 'end');
19123 n.eachSubgraph(function(n) {
19124 n.setData('alpha', 1, 'end');
19128 modes:['node-property:alpha'],
19129 onComplete: function() {
19130 //compute end positions
19131 that.clickedNode = clickedNode;
19132 that.compute('end');
19133 //animate positions
19134 //TODO(nico) commenting this line didn't seem to throw errors...
19135 that.clickedNode = previousClickedNode;
19137 modes:['linear', 'node-property:width:height'],
19139 onComplete: function() {
19141 //TODO(nico) check comment above
19142 that.clickedNode = clickedNode;
19149 that.clickedNode = n;
19154 if(config.request) {
19155 this.requestNodes(clickedNode, callback);
19157 callback.onComplete();
19164 Sets the parent node of the current selected node as root.
19168 if(this.busy) return;
19170 this.events.hoveredNode = false;
19172 config = this.config,
19173 graph = this.graph,
19174 parents = graph.getNode(this.clickedNode
19175 && this.clickedNode.id || this.root).getParents(),
19176 parent = parents[0],
19177 clickedNode = parent,
19178 previousClickedNode = this.clickedNode;
19180 //if no parents return
19185 //final plot callback
19187 onComplete: function() {
19188 that.clickedNode = parent;
19189 if(config.request) {
19190 that.requestNodes(parent, {
19191 onComplete: function() {
19205 if (config.levelsToShow > 0)
19206 this.geom.setRightLevelToShow(parent);
19207 //animate node positions
19208 if(config.animate) {
19209 this.clickedNode = clickedNode;
19210 this.compute('end');
19211 //animate the visible subtree only
19212 this.clickedNode = previousClickedNode;
19214 modes:['linear', 'node-property:width:height'],
19216 onComplete: function() {
19217 //animate the parent subtree
19218 that.clickedNode = clickedNode;
19219 //change nodes alpha
19220 graph.eachNode(function(n) {
19221 n.setDataset(['current', 'end'], {
19225 previousClickedNode.eachSubgraph(function(node) {
19226 node.setData('alpha', 1);
19230 modes:['node-property:alpha'],
19231 onComplete: function() {
19232 callback.onComplete();
19238 callback.onComplete();
19242 requestNodes: function(node, onComplete){
19243 var handler = $.merge(this.controller, onComplete),
19244 lev = this.config.levelsToShow;
19245 if (handler.request) {
19246 var leaves = [], d = node._depth;
19247 node.eachLevel(0, lev, function(n){
19248 var nodeLevel = lev - (n._depth - d);
19249 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19251 n._level = nodeLevel;
19254 this.group.requestNodes(leaves, handler);
19256 handler.onComplete();
19264 Custom extension of <Graph.Op>.
19268 All <Graph.Op> methods
19275 TM.Op = new Class({
19276 Implements: Graph.Op,
19278 initialize: function(viz){
19283 //extend level methods of Graph.Geom
19284 TM.Geom = new Class({
19285 Implements: Graph.Geom,
19287 getRightLevelToShow: function() {
19288 return this.viz.config.levelsToShow;
19291 setRightLevelToShow: function(node) {
19292 var level = this.getRightLevelToShow(),
19293 fx = this.viz.labels;
19294 node.eachLevel(0, level+1, function(n) {
19295 var d = n._depth - node._depth;
19300 fx.hideLabel(n, false);
19308 delete node.ignore;
19314 Performs operations on group of nodes.
19317 TM.Group = new Class( {
19319 initialize: function(viz){
19321 this.canvas = viz.canvas;
19322 this.config = viz.config;
19327 Calls the request method on the controller to request a subtree for each node.
19329 requestNodes: function(nodes, controller){
19330 var counter = 0, len = nodes.length, nodeSelected = {};
19331 var complete = function(){
19332 controller.onComplete();
19334 var viz = this.viz;
19337 for ( var i = 0; i < len; i++) {
19338 nodeSelected[nodes[i].id] = nodes[i];
19339 controller.request(nodes[i].id, nodes[i]._level, {
19340 onComplete: function(nodeId, data){
19341 if (data && data.children) {
19347 if (++counter == len) {
19348 viz.graph.computeLevels(viz.root, 0);
19360 Custom extension of <Graph.Plot>.
19364 All <Graph.Plot> methods
19371 TM.Plot = new Class({
19373 Implements: Graph.Plot,
19375 initialize: function(viz){
19377 this.config = viz.config;
19378 this.node = this.config.Node;
19379 this.edge = this.config.Edge;
19380 this.animation = new Animation;
19381 this.nodeTypes = new TM.Plot.NodeTypes;
19382 this.edgeTypes = new TM.Plot.EdgeTypes;
19383 this.labels = viz.labels;
19386 plot: function(opt, animating){
19387 var viz = this.viz,
19389 viz.canvas.clear();
19390 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19391 'withLabels': true,
19392 'hideLabels': false,
19393 'plotSubtree': function(n, ch){
19394 return n.anySubnode("exist");
19403 Custom extension of <Graph.Label>.
19404 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19408 All <Graph.Label> methods and subclasses.
19412 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19420 Custom extension of <Graph.Label.Native>.
19424 All <Graph.Label.Native> methods
19428 <Graph.Label.Native>
19430 TM.Label.Native = new Class({
19431 Implements: Graph.Label.Native,
19433 initialize: function(viz) {
19434 this.config = viz.config;
19435 this.leaf = viz.leaf;
19438 renderLabel: function(canvas, node, controller){
19439 if(!this.leaf(node) && !this.config.titleHeight) return;
19440 var pos = node.pos.getc(true),
19441 ctx = canvas.getCtx(),
19442 width = node.getData('width'),
19443 height = node.getData('height'),
19444 x = pos.x + width/2,
19447 ctx.fillText(node.name, x, y, width);
19454 Custom extension of <Graph.Label.SVG>.
19458 All <Graph.Label.SVG> methods
19464 TM.Label.SVG = new Class( {
19465 Implements: Graph.Label.SVG,
19467 initialize: function(viz){
19469 this.leaf = viz.leaf;
19470 this.config = viz.config;
19476 Overrides abstract method placeLabel in <Graph.Plot>.
19480 tag - A DOM label element.
19481 node - A <Graph.Node>.
19482 controller - A configuration/controller object passed to the visualization.
19485 placeLabel: function(tag, node, controller){
19486 var pos = node.pos.getc(true),
19487 canvas = this.viz.canvas,
19488 ox = canvas.translateOffsetX,
19489 oy = canvas.translateOffsetY,
19490 sx = canvas.scaleOffsetX,
19491 sy = canvas.scaleOffsetY,
19492 radius = canvas.getSize();
19494 x: Math.round(pos.x * sx + ox + radius.width / 2),
19495 y: Math.round(pos.y * sy + oy + radius.height / 2)
19497 tag.setAttribute('x', labelPos.x);
19498 tag.setAttribute('y', labelPos.y);
19500 if(!this.leaf(node) && !this.config.titleHeight) {
19501 tag.style.display = 'none';
19503 controller.onPlaceLabel(tag, node);
19510 Custom extension of <Graph.Label.HTML>.
19514 All <Graph.Label.HTML> methods.
19521 TM.Label.HTML = new Class( {
19522 Implements: Graph.Label.HTML,
19524 initialize: function(viz){
19526 this.leaf = viz.leaf;
19527 this.config = viz.config;
19533 Overrides abstract method placeLabel in <Graph.Plot>.
19537 tag - A DOM label element.
19538 node - A <Graph.Node>.
19539 controller - A configuration/controller object passed to the visualization.
19542 placeLabel: function(tag, node, controller){
19543 var pos = node.pos.getc(true),
19544 canvas = this.viz.canvas,
19545 ox = canvas.translateOffsetX,
19546 oy = canvas.translateOffsetY,
19547 sx = canvas.scaleOffsetX,
19548 sy = canvas.scaleOffsetY,
19549 radius = canvas.getSize();
19551 x: Math.round(pos.x * sx + ox + radius.width / 2),
19552 y: Math.round(pos.y * sy + oy + radius.height / 2)
19555 var style = tag.style;
19556 style.left = labelPos.x + 'px';
19557 style.top = labelPos.y + 'px';
19558 style.width = node.getData('width') * sx + 'px';
19559 style.height = node.getData('height') * sy + 'px';
19560 style.zIndex = node._depth * 100;
19561 style.display = '';
19563 if(!this.leaf(node) && !this.config.titleHeight) {
19564 tag.style.display = 'none';
19566 controller.onPlaceLabel(tag, node);
19571 Class: TM.Plot.NodeTypes
19573 This class contains a list of <Graph.Node> built-in types.
19574 Node types implemented are 'none', 'rectangle'.
19576 You can add your custom node types, customizing your visualization to the extreme.
19581 TM.Plot.NodeTypes.implement({
19583 'render': function(node, canvas) {
19584 //print your custom node to canvas
19587 'contains': function(node, pos) {
19588 //return true if pos is inside the node or false otherwise
19595 TM.Plot.NodeTypes = new Class( {
19601 'render': function(node, canvas, animating){
19602 var leaf = this.viz.leaf(node),
19603 config = this.config,
19604 offst = config.offset,
19605 titleHeight = config.titleHeight,
19606 pos = node.pos.getc(true),
19607 width = node.getData('width'),
19608 height = node.getData('height'),
19609 border = node.getData('border'),
19610 ctx = canvas.getCtx(),
19611 posx = pos.x + offst / 2,
19612 posy = pos.y + offst / 2;
19613 if(width <= offst || height <= offst) return;
19615 if(config.cushion) {
19616 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
19617 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19618 var color = node.getData('color');
19619 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
19620 function(r) { return r * 0.2 >> 0; }));
19621 lg.addColorStop(0, color);
19622 lg.addColorStop(1, colorGrad);
19623 ctx.fillStyle = lg;
19625 ctx.fillRect(posx, posy, width - offst, height - offst);
19628 ctx.strokeStyle = border;
19629 ctx.strokeRect(posx, posy, width - offst, height - offst);
19632 } else if(titleHeight > 0){
19633 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19634 titleHeight - offst);
19637 ctx.strokeStyle = border;
19638 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19644 'contains': function(node, pos) {
19645 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19646 var npos = node.pos.getc(true),
19647 width = node.getData('width'),
19648 leaf = this.viz.leaf(node),
19649 height = leaf? node.getData('height') : this.config.titleHeight;
19650 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19655 TM.Plot.EdgeTypes = new Class( {
19660 Class: TM.SliceAndDice
19662 A slice and dice TreeMap visualization.
19666 All <TM.Base> methods and properties.
19668 TM.SliceAndDice = new Class( {
19670 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19675 Class: TM.Squarified
19677 A squarified TreeMap visualization.
19681 All <TM.Base> methods and properties.
19683 TM.Squarified = new Class( {
19685 Loader, Extras, TM.Base, Layouts.TM.Squarified
19692 A strip TreeMap visualization.
19696 All <TM.Base> methods and properties.
19698 TM.Strip = new Class( {
19700 Loader, Extras, TM.Base, Layouts.TM.Strip
19713 A radial graph visualization with advanced animations.
19717 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>
19721 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.
19725 All <Loader> methods
19727 Constructor Options:
19729 Inherits options from
19732 - <Options.Controller>
19738 - <Options.NodeStyles>
19739 - <Options.Navigation>
19741 Additionally, there are other parameters and some default values changed
19743 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19744 levelDistance - (number) Default's *100*. The distance between levels of the tree.
19746 Instance Properties:
19748 canvas - Access a <Canvas> instance.
19749 graph - Access a <Graph> instance.
19750 op - Access a <RGraph.Op> instance.
19751 fx - Access a <RGraph.Plot> instance.
19752 labels - Access a <RGraph.Label> interface implementation.
19755 $jit.RGraph = new Class( {
19758 Loader, Extras, Layouts.Radial
19761 initialize: function(controller){
19762 var $RGraph = $jit.RGraph;
19765 interpolation: 'linear',
19769 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19770 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19772 var canvasConfig = this.config;
19773 if(canvasConfig.useCanvas) {
19774 this.canvas = canvasConfig.useCanvas;
19775 this.config.labelContainer = this.canvas.id + '-label';
19777 if(canvasConfig.background) {
19778 canvasConfig.background = $.merge({
19780 }, canvasConfig.background);
19782 this.canvas = new Canvas(this, canvasConfig);
19783 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19786 this.graphOptions = {
19794 this.graph = new Graph(this.graphOptions, this.config.Node,
19796 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19797 this.fx = new $RGraph.Plot(this, $RGraph);
19798 this.op = new $RGraph.Op(this);
19802 this.parent = false;
19803 // initialize extras
19804 this.initializeExtras();
19809 createLevelDistanceFunc
19811 Returns the levelDistance function used for calculating a node distance
19812 to its origin. This function returns a function that is computed
19813 per level and not per node, such that all nodes with the same depth will have the
19814 same distance to the origin. The resulting function gets the
19815 parent node as parameter and returns a float.
19818 createLevelDistanceFunc: function(){
19819 var ld = this.config.levelDistance;
19820 return function(elem){
19821 return (elem._depth + 1) * ld;
19828 Computes positions and plots the tree.
19831 refresh: function(){
19836 reposition: function(){
19837 this.compute('end');
19843 Plots the RGraph. This is a shortcut to *fx.plot*.
19849 getNodeAndParentAngle
19851 Returns the _parent_ of the given node, also calculating its angle span.
19853 getNodeAndParentAngle: function(id){
19855 var n = this.graph.getNode(id);
19856 var ps = n.getParents();
19857 var p = (ps.length > 0)? ps[0] : false;
19859 var posParent = p.pos.getc(), posChild = n.pos.getc();
19860 var newPos = posParent.add(posChild.scale(-1));
19861 theta = Math.atan2(newPos.y, newPos.x);
19863 theta += 2 * Math.PI;
19873 Enumerates the children in order to maintain child ordering (second constraint of the paper).
19875 tagChildren: function(par, id){
19876 if (par.angleSpan) {
19878 par.eachAdjacency(function(elem){
19879 adjs.push(elem.nodeTo);
19881 var len = adjs.length;
19882 for ( var i = 0; i < len && id != adjs[i].id; i++)
19884 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19885 adjs[j].dist = k++;
19892 Animates the <RGraph> to center the node specified by *id*.
19896 id - A <Graph.Node> id.
19897 opt - (optional|object) An object containing some extra properties described below
19898 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19903 rgraph.onClick('someid');
19905 rgraph.onClick('someid', {
19911 onClick: function(id, opt){
19912 if (this.root != id && !this.busy) {
19916 this.controller.onBeforeCompute(this.graph.getNode(id));
19917 var obj = this.getNodeAndParentAngle(id);
19919 // second constraint
19920 this.tagChildren(obj.parent, id);
19921 this.parent = obj.parent;
19922 this.compute('end');
19924 // first constraint
19925 var thetaDiff = obj.theta - obj.parent.endPos.theta;
19926 this.graph.eachNode(function(elem){
19927 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19930 var mode = this.config.interpolation;
19932 onComplete: $.empty
19935 this.fx.animate($.merge( {
19941 onComplete: function(){
19950 $jit.RGraph.$extend = true;
19957 Custom extension of <Graph.Op>.
19961 All <Graph.Op> methods
19968 RGraph.Op = new Class( {
19970 Implements: Graph.Op
19977 Custom extension of <Graph.Plot>.
19981 All <Graph.Plot> methods
19988 RGraph.Plot = new Class( {
19990 Implements: Graph.Plot
19995 Object: RGraph.Label
19997 Custom extension of <Graph.Label>.
19998 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20002 All <Graph.Label> methods and subclasses.
20006 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20012 RGraph.Label.Native
20014 Custom extension of <Graph.Label.Native>.
20018 All <Graph.Label.Native> methods
20022 <Graph.Label.Native>
20025 RGraph.Label.Native = new Class( {
20026 Implements: Graph.Label.Native
20032 Custom extension of <Graph.Label.SVG>.
20036 All <Graph.Label.SVG> methods
20043 RGraph.Label.SVG = new Class( {
20044 Implements: Graph.Label.SVG,
20046 initialize: function(viz){
20053 Overrides abstract method placeLabel in <Graph.Plot>.
20057 tag - A DOM label element.
20058 node - A <Graph.Node>.
20059 controller - A configuration/controller object passed to the visualization.
20062 placeLabel: function(tag, node, controller){
20063 var pos = node.pos.getc(true),
20064 canvas = this.viz.canvas,
20065 ox = canvas.translateOffsetX,
20066 oy = canvas.translateOffsetY,
20067 sx = canvas.scaleOffsetX,
20068 sy = canvas.scaleOffsetY,
20069 radius = canvas.getSize();
20071 x: Math.round(pos.x * sx + ox + radius.width / 2),
20072 y: Math.round(pos.y * sy + oy + radius.height / 2)
20074 tag.setAttribute('x', labelPos.x);
20075 tag.setAttribute('y', labelPos.y);
20077 controller.onPlaceLabel(tag, node);
20084 Custom extension of <Graph.Label.HTML>.
20088 All <Graph.Label.HTML> methods.
20095 RGraph.Label.HTML = new Class( {
20096 Implements: Graph.Label.HTML,
20098 initialize: function(viz){
20104 Overrides abstract method placeLabel in <Graph.Plot>.
20108 tag - A DOM label element.
20109 node - A <Graph.Node>.
20110 controller - A configuration/controller object passed to the visualization.
20113 placeLabel: function(tag, node, controller){
20114 var pos = node.pos.getc(true),
20115 canvas = this.viz.canvas,
20116 ox = canvas.translateOffsetX,
20117 oy = canvas.translateOffsetY,
20118 sx = canvas.scaleOffsetX,
20119 sy = canvas.scaleOffsetY,
20120 radius = canvas.getSize();
20122 x: Math.round(pos.x * sx + ox + radius.width / 2),
20123 y: Math.round(pos.y * sy + oy + radius.height / 2)
20126 var style = tag.style;
20127 style.left = labelPos.x + 'px';
20128 style.top = labelPos.y + 'px';
20129 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20131 controller.onPlaceLabel(tag, node);
20136 Class: RGraph.Plot.NodeTypes
20138 This class contains a list of <Graph.Node> built-in types.
20139 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20141 You can add your custom node types, customizing your visualization to the extreme.
20146 RGraph.Plot.NodeTypes.implement({
20148 'render': function(node, canvas) {
20149 //print your custom node to canvas
20152 'contains': function(node, pos) {
20153 //return true if pos is inside the node or false otherwise
20160 RGraph.Plot.NodeTypes = new Class({
20163 'contains': $.lambda(false)
20166 'render': function(node, canvas){
20167 var pos = node.pos.getc(true),
20168 dim = node.getData('dim');
20169 this.nodeHelper.circle.render('fill', pos, dim, canvas);
20171 'contains': function(node, pos){
20172 var npos = node.pos.getc(true),
20173 dim = node.getData('dim');
20174 return this.nodeHelper.circle.contains(npos, pos, dim);
20178 'render': function(node, canvas){
20179 var pos = node.pos.getc(true),
20180 width = node.getData('width'),
20181 height = node.getData('height');
20182 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20184 // TODO(nico): be more precise...
20185 'contains': function(node, pos){
20186 var npos = node.pos.getc(true),
20187 width = node.getData('width'),
20188 height = node.getData('height');
20189 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20193 'render': function(node, canvas){
20194 var pos = node.pos.getc(true),
20195 dim = node.getData('dim');
20196 this.nodeHelper.square.render('fill', pos, dim, canvas);
20198 'contains': function(node, pos){
20199 var npos = node.pos.getc(true),
20200 dim = node.getData('dim');
20201 return this.nodeHelper.square.contains(npos, pos, dim);
20205 'render': function(node, canvas){
20206 var pos = node.pos.getc(true),
20207 width = node.getData('width'),
20208 height = node.getData('height');
20209 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20211 'contains': function(node, pos){
20212 var npos = node.pos.getc(true),
20213 width = node.getData('width'),
20214 height = node.getData('height');
20215 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20219 'render': function(node, canvas){
20220 var pos = node.pos.getc(true),
20221 dim = node.getData('dim');
20222 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20224 'contains': function(node, pos) {
20225 var npos = node.pos.getc(true),
20226 dim = node.getData('dim');
20227 return this.nodeHelper.triangle.contains(npos, pos, dim);
20231 'render': function(node, canvas){
20232 var pos = node.pos.getc(true),
20233 dim = node.getData('dim');
20234 this.nodeHelper.star.render('fill', pos, dim, canvas);
20236 'contains': function(node, pos) {
20237 var npos = node.pos.getc(true),
20238 dim = node.getData('dim');
20239 return this.nodeHelper.star.contains(npos, pos, dim);
20245 Class: RGraph.Plot.EdgeTypes
20247 This class contains a list of <Graph.Adjacence> built-in types.
20248 Edge types implemented are 'none', 'line' and 'arrow'.
20250 You can add your custom edge types, customizing your visualization to the extreme.
20255 RGraph.Plot.EdgeTypes.implement({
20257 'render': function(adj, canvas) {
20258 //print your custom edge to canvas
20261 'contains': function(adj, pos) {
20262 //return true if pos is inside the arc or false otherwise
20269 RGraph.Plot.EdgeTypes = new Class({
20272 'render': function(adj, canvas) {
20273 var from = adj.nodeFrom.pos.getc(true),
20274 to = adj.nodeTo.pos.getc(true);
20275 this.edgeHelper.line.render(from, to, canvas);
20277 'contains': function(adj, pos) {
20278 var from = adj.nodeFrom.pos.getc(true),
20279 to = adj.nodeTo.pos.getc(true);
20280 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20284 'render': function(adj, canvas) {
20285 var from = adj.nodeFrom.pos.getc(true),
20286 to = adj.nodeTo.pos.getc(true),
20287 dim = adj.getData('dim'),
20288 direction = adj.data.$direction,
20289 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20290 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20292 'contains': function(adj, pos) {
20293 var from = adj.nodeFrom.pos.getc(true),
20294 to = adj.nodeTo.pos.getc(true);
20295 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20304 * File: Hypertree.js
20311 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
20315 moebiusTransformation
20317 Calculates a moebius transformation for this point / complex.
20318 For more information go to:
20319 http://en.wikipedia.org/wiki/Moebius_transformation.
20323 c - An initialized Complex instance representing a translation Vector.
20326 Complex.prototype.moebiusTransformation = function(c) {
20327 var num = this.add(c);
20328 var den = c.$conjugate().$prod(this);
20330 return num.$div(den);
20334 moebiusTransformation
20336 Calculates a moebius transformation for the hyperbolic tree.
20338 <http://en.wikipedia.org/wiki/Moebius_transformation>
20342 graph - A <Graph> instance.
20344 prop - A property array.
20345 theta - Rotation angle.
20346 startPos - _optional_ start position.
20348 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20349 this.eachNode(graph, function(elem) {
20350 for ( var i = 0; i < prop.length; i++) {
20351 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20352 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20360 A Hyperbolic Tree/Graph visualization.
20364 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
20365 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20369 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.
20373 All <Loader> methods
20375 Constructor Options:
20377 Inherits options from
20380 - <Options.Controller>
20386 - <Options.NodeStyles>
20387 - <Options.Navigation>
20389 Additionally, there are other parameters and some default values changed
20391 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*.
20392 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.
20393 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20394 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20395 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
20397 Instance Properties:
20399 canvas - Access a <Canvas> instance.
20400 graph - Access a <Graph> instance.
20401 op - Access a <Hypertree.Op> instance.
20402 fx - Access a <Hypertree.Plot> instance.
20403 labels - Access a <Hypertree.Label> interface implementation.
20407 $jit.Hypertree = new Class( {
20409 Implements: [ Loader, Extras, Layouts.Radial ],
20411 initialize: function(controller) {
20412 var $Hypertree = $jit.Hypertree;
20423 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20424 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20426 var canvasConfig = this.config;
20427 if(canvasConfig.useCanvas) {
20428 this.canvas = canvasConfig.useCanvas;
20429 this.config.labelContainer = this.canvas.id + '-label';
20431 if(canvasConfig.background) {
20432 canvasConfig.background = $.merge({
20434 }, canvasConfig.background);
20436 this.canvas = new Canvas(this, canvasConfig);
20437 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20440 this.graphOptions = {
20448 this.graph = new Graph(this.graphOptions, this.config.Node,
20450 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20451 this.fx = new $Hypertree.Plot(this, $Hypertree);
20452 this.op = new $Hypertree.Op(this);
20456 // initialize extras
20457 this.initializeExtras();
20462 createLevelDistanceFunc
20464 Returns the levelDistance function used for calculating a node distance
20465 to its origin. This function returns a function that is computed
20466 per level and not per node, such that all nodes with the same depth will have the
20467 same distance to the origin. The resulting function gets the
20468 parent node as parameter and returns a float.
20471 createLevelDistanceFunc: function() {
20472 // get max viz. length.
20473 var r = this.getRadius();
20475 var depth = 0, max = Math.max, config = this.config;
20476 this.graph.eachNode(function(node) {
20477 depth = max(node._depth, depth);
20480 // node distance generator
20481 var genDistFunc = function(a) {
20482 return function(node) {
20484 var d = node._depth + 1;
20485 var acum = 0, pow = Math.pow;
20487 acum += pow(a, d--);
20489 return acum - config.offset;
20492 // estimate better edge length.
20493 for ( var i = 0.51; i <= 1; i += 0.01) {
20494 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20495 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20497 return genDistFunc(0.75);
20503 Returns the current radius of the visualization. If *config.radius* is *auto* then it
20504 calculates the radius by taking the smaller size of the <Canvas> widget.
20511 getRadius: function() {
20512 var rad = this.config.radius;
20513 if (rad !== "auto") { return rad; }
20514 var s = this.canvas.getSize();
20515 return Math.min(s.width, s.height) / 2;
20521 Computes positions and plots the tree.
20525 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20528 refresh: function(reposition) {
20531 this.graph.eachNode(function(node) {
20532 node.startPos.rho = node.pos.rho = node.endPos.rho;
20533 node.startPos.theta = node.pos.theta = node.endPos.theta;
20544 Computes nodes' positions and restores the tree to its previous position.
20546 For calculating nodes' positions the root must be placed on its origin. This method does this
20547 and then attemps to restore the hypertree to its previous position.
20550 reposition: function() {
20551 this.compute('end');
20552 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20553 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20555 this.graph.eachNode(function(node) {
20557 node.endPos.rho = node.pos.rho;
20558 node.endPos.theta = node.pos.theta;
20566 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
20576 Animates the <Hypertree> to center the node specified by *id*.
20580 id - A <Graph.Node> id.
20581 opt - (optional|object) An object containing some extra properties described below
20582 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20587 ht.onClick('someid');
20589 ht.onClick('someid', {
20595 onClick: function(id, opt) {
20596 var pos = this.graph.getNode(id).pos.getc(true);
20597 this.move(pos, opt);
20603 Translates the tree to the given position.
20607 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20608 opt - This object has been defined in <Hypertree.onClick>
20613 ht.move({ x: 0, y: 0.7 }, {
20619 move: function(pos, opt) {
20620 var versor = $C(pos.x, pos.y);
20621 if (this.busy === false && versor.norm() < 1) {
20623 var root = this.graph.getClosestNodeToPos(versor), that = this;
20624 this.graph.computeLevels(root.id, 0);
20625 this.controller.onBeforeCompute(root);
20627 onComplete: $.empty
20629 this.fx.animate($.merge( {
20630 modes: [ 'moebius' ],
20633 onComplete: function() {
20642 $jit.Hypertree.$extend = true;
20644 (function(Hypertree) {
20647 Class: Hypertree.Op
20649 Custom extension of <Graph.Op>.
20653 All <Graph.Op> methods
20660 Hypertree.Op = new Class( {
20662 Implements: Graph.Op
20667 Class: Hypertree.Plot
20669 Custom extension of <Graph.Plot>.
20673 All <Graph.Plot> methods
20680 Hypertree.Plot = new Class( {
20682 Implements: Graph.Plot
20687 Object: Hypertree.Label
20689 Custom extension of <Graph.Label>.
20690 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20694 All <Graph.Label> methods and subclasses.
20698 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20701 Hypertree.Label = {};
20704 Hypertree.Label.Native
20706 Custom extension of <Graph.Label.Native>.
20710 All <Graph.Label.Native> methods
20714 <Graph.Label.Native>
20717 Hypertree.Label.Native = new Class( {
20718 Implements: Graph.Label.Native,
20720 initialize: function(viz) {
20724 renderLabel: function(canvas, node, controller) {
20725 var ctx = canvas.getCtx();
20726 var coord = node.pos.getc(true);
20727 var s = this.viz.getRadius();
20728 ctx.fillText(node.name, coord.x * s, coord.y * s);
20733 Hypertree.Label.SVG
20735 Custom extension of <Graph.Label.SVG>.
20739 All <Graph.Label.SVG> methods
20746 Hypertree.Label.SVG = new Class( {
20747 Implements: Graph.Label.SVG,
20749 initialize: function(viz) {
20756 Overrides abstract method placeLabel in <Graph.Plot>.
20760 tag - A DOM label element.
20761 node - A <Graph.Node>.
20762 controller - A configuration/controller object passed to the visualization.
20765 placeLabel: function(tag, node, controller) {
20766 var pos = node.pos.getc(true),
20767 canvas = this.viz.canvas,
20768 ox = canvas.translateOffsetX,
20769 oy = canvas.translateOffsetY,
20770 sx = canvas.scaleOffsetX,
20771 sy = canvas.scaleOffsetY,
20772 radius = canvas.getSize(),
20773 r = this.viz.getRadius();
20775 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20776 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20778 tag.setAttribute('x', labelPos.x);
20779 tag.setAttribute('y', labelPos.y);
20780 controller.onPlaceLabel(tag, node);
20785 Hypertree.Label.HTML
20787 Custom extension of <Graph.Label.HTML>.
20791 All <Graph.Label.HTML> methods.
20798 Hypertree.Label.HTML = new Class( {
20799 Implements: Graph.Label.HTML,
20801 initialize: function(viz) {
20807 Overrides abstract method placeLabel in <Graph.Plot>.
20811 tag - A DOM label element.
20812 node - A <Graph.Node>.
20813 controller - A configuration/controller object passed to the visualization.
20816 placeLabel: function(tag, node, controller) {
20817 var pos = node.pos.getc(true),
20818 canvas = this.viz.canvas,
20819 ox = canvas.translateOffsetX,
20820 oy = canvas.translateOffsetY,
20821 sx = canvas.scaleOffsetX,
20822 sy = canvas.scaleOffsetY,
20823 radius = canvas.getSize(),
20824 r = this.viz.getRadius();
20826 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20827 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20829 var style = tag.style;
20830 style.left = labelPos.x + 'px';
20831 style.top = labelPos.y + 'px';
20832 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20834 controller.onPlaceLabel(tag, node);
20839 Class: Hypertree.Plot.NodeTypes
20841 This class contains a list of <Graph.Node> built-in types.
20842 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20844 You can add your custom node types, customizing your visualization to the extreme.
20849 Hypertree.Plot.NodeTypes.implement({
20851 'render': function(node, canvas) {
20852 //print your custom node to canvas
20855 'contains': function(node, pos) {
20856 //return true if pos is inside the node or false otherwise
20863 Hypertree.Plot.NodeTypes = new Class({
20866 'contains': $.lambda(false)
20869 'render': function(node, canvas) {
20870 var nconfig = this.node,
20871 dim = node.getData('dim'),
20872 p = node.pos.getc();
20873 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20874 p.$scale(node.scale);
20876 this.nodeHelper.circle.render('fill', p, dim, canvas);
20879 'contains': function(node, pos) {
20880 var dim = node.getData('dim'),
20881 npos = node.pos.getc().$scale(node.scale);
20882 return this.nodeHelper.circle.contains(npos, pos, dim);
20886 'render': function(node, canvas) {
20887 var pos = node.pos.getc().$scale(node.scale),
20888 width = node.getData('width'),
20889 height = node.getData('height');
20890 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20892 'contains': function(node, pos) {
20893 var width = node.getData('width'),
20894 height = node.getData('height'),
20895 npos = node.pos.getc().$scale(node.scale);
20896 return this.nodeHelper.circle.contains(npos, pos, width, height);
20900 'render': function(node, canvas) {
20901 var nconfig = this.node,
20902 dim = node.getData('dim'),
20903 p = node.pos.getc();
20904 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20905 p.$scale(node.scale);
20907 this.nodeHelper.square.render('fill', p, dim, canvas);
20910 'contains': function(node, pos) {
20911 var dim = node.getData('dim'),
20912 npos = node.pos.getc().$scale(node.scale);
20913 return this.nodeHelper.square.contains(npos, pos, dim);
20917 'render': function(node, canvas) {
20918 var nconfig = this.node,
20919 width = node.getData('width'),
20920 height = node.getData('height'),
20921 pos = node.pos.getc();
20922 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20923 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20924 pos.$scale(node.scale);
20925 if (width > 0.2 && height > 0.2) {
20926 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20929 'contains': function(node, pos) {
20930 var width = node.getData('width'),
20931 height = node.getData('height'),
20932 npos = node.pos.getc().$scale(node.scale);
20933 return this.nodeHelper.square.contains(npos, pos, width, height);
20937 'render': function(node, canvas) {
20938 var nconfig = this.node,
20939 dim = node.getData('dim'),
20940 p = node.pos.getc();
20941 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20942 p.$scale(node.scale);
20944 this.nodeHelper.triangle.render('fill', p, dim, canvas);
20947 'contains': function(node, pos) {
20948 var dim = node.getData('dim'),
20949 npos = node.pos.getc().$scale(node.scale);
20950 return this.nodeHelper.triangle.contains(npos, pos, dim);
20954 'render': function(node, canvas) {
20955 var nconfig = this.node,
20956 dim = node.getData('dim'),
20957 p = node.pos.getc();
20958 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20959 p.$scale(node.scale);
20961 this.nodeHelper.star.render('fill', p, dim, canvas);
20964 'contains': function(node, pos) {
20965 var dim = node.getData('dim'),
20966 npos = node.pos.getc().$scale(node.scale);
20967 return this.nodeHelper.star.contains(npos, pos, dim);
20973 Class: Hypertree.Plot.EdgeTypes
20975 This class contains a list of <Graph.Adjacence> built-in types.
20976 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20978 You can add your custom edge types, customizing your visualization to the extreme.
20983 Hypertree.Plot.EdgeTypes.implement({
20985 'render': function(adj, canvas) {
20986 //print your custom edge to canvas
20989 'contains': function(adj, pos) {
20990 //return true if pos is inside the arc or false otherwise
20997 Hypertree.Plot.EdgeTypes = new Class({
21000 'render': function(adj, canvas) {
21001 var from = adj.nodeFrom.pos.getc(true),
21002 to = adj.nodeTo.pos.getc(true),
21003 r = adj.nodeFrom.scale;
21004 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
21006 'contains': function(adj, pos) {
21007 var from = adj.nodeFrom.pos.getc(true),
21008 to = adj.nodeTo.pos.getc(true),
21009 r = adj.nodeFrom.scale;
21010 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21014 'render': function(adj, canvas) {
21015 var from = adj.nodeFrom.pos.getc(true),
21016 to = adj.nodeTo.pos.getc(true),
21017 r = adj.nodeFrom.scale,
21018 dim = adj.getData('dim'),
21019 direction = adj.data.$direction,
21020 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
21021 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
21023 'contains': function(adj, pos) {
21024 var from = adj.nodeFrom.pos.getc(true),
21025 to = adj.nodeTo.pos.getc(true),
21026 r = adj.nodeFrom.scale;
21027 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21031 'render': function(adj, canvas) {
21032 var from = adj.nodeFrom.pos.getc(),
21033 to = adj.nodeTo.pos.getc(),
21034 dim = this.viz.getRadius();
21035 this.edgeHelper.hyperline.render(from, to, dim, canvas);
21037 'contains': $.lambda(false)
21041 })($jit.Hypertree);