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 * Return array with correct positions for each element
9477 * @param {Array} dimArray
9478 * @param {Number} fontHeight
9481 positions: function(dimArray, fontHeight)
9484 var isLastElem = false;
9491 for (i = 0; i < dimArray.length; i++)
9493 currentState = {type: 'element', position: position, height: dimArray[i], font: fontHeight, filament: true};
9494 if (dimArray[i] <= fontHeight)
9500 group.push(currentState);
9505 group.push(currentState);
9506 newArray.push({type: 'group', val:group, groupHeight: 0, groupPosition: group[0].position});
9510 position += dimArray[i];
9512 if (group.length > 0)
9514 newArray.push({type: 'group', val: group, groupHeight: 0, groupPosition: group[0].position});
9517 var figureHeight = position;
9519 for (i = 0; i < newArray.length; i++)
9521 newArray[i] = this.pipelineGetHeight(newArray[i]);
9524 newArray = this.pipelineMoveBlocks(newArray, figureHeight, fontHeight);
9527 for (i = 0; i < newArray.length; i++)
9529 group = newArray[i].val;
9530 for (var k = 0; k < group.length; k++)
9539 * Return recalculation group height(groupHeight) and positions of elements
9541 * @param {Array} group
9544 pipelineGetHeight: function(group)
9548 var count = group.val.length;
9549 var fontHeight = group.val[0].font;
9550 var positionStart = group.val[0].position;
9554 group.groupHeight = group.val[0].font;
9555 group.val[0].filament = false;
9561 group.groupHeight = fontHeight * 2 + between;
9562 group.val[1].position = positionStart + fontHeight + between;
9563 group.val[0].filament = false;
9564 group.val[1].filament = false;
9569 for (var i = 0; i < group.val.length; i++)
9571 group.val[i].position = positionStart + position;
9573 position += between;
9576 group.val[i].filament = false;
9577 position += fontHeight;
9581 group.val[i].filament = true;
9584 group.groupHeight = (group.val[group.val.length - 1].position - group.val[0].position) + fontHeight + between;
9589 * Return array with new group and elements positions relation figure layout border
9591 * @param {Array} block
9592 * @param {Number} figureheight
9593 * @param {Number} fontHeight
9596 pipelineMoveBlocks: function(block, figureheight, fontHeight)
9600 if (block.length < 2)
9605 var lastValue = block[block.length - 1];
9607 if ((lastValue.groupPosition + lastValue.groupHeight) > figureheight)
9609 offset = (figureheight - lastValue.groupHeight) - lastValue.groupPosition;
9610 lastValue.groupPosition += offset;
9611 for (var li = 0; li < lastValue.val.length; li++)
9613 lastValue.val[li].position += offset;
9615 prelastValue = block[block.length - 2];
9616 if (prelastValue.groupPosition + fontHeight > lastValue.groupPosition)
9618 block[block.length - 2] = this.pipelineMergeGroup(lastValue, prelastValue);
9622 if (block.length < 3)
9627 for (var i = 1; i < block.length; i++)
9629 if ( (block[i - 1].groupPosition + block[i - 1].groupHeight) > block[i].groupPosition)
9631 block[i - 1] = this.pipelineMergeGroup(block[i], block[i - 1]);
9638 block = this.pipelineMoveBlocks(block, figureheight, fontHeight);
9646 * @param {Array} lastValue
9647 * @param {Array} prelastValue
9650 pipelineMergeGroup: function(lastValue, prelastValue)
9653 newGroup = prelastValue;
9654 newGroup.val = newGroup.val.concat(lastValue.val);
9655 newGroup = this.pipelineGetHeight(newGroup);
9656 newGroup.groupPosition = prelastValue.groupPosition;
9661 Method: getAlignedPos
9663 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9667 pos - (object) A <Graph.Node> position.
9668 width - (number) The width of the node.
9669 height - (number) The height of the node.
9672 getAlignedPos: function(pos, width, height) {
9673 var nconfig = this.node;
9675 if(nconfig.align == "center") {
9677 x: pos.x - width / 2,
9678 y: pos.y - height / 2
9680 } else if (nconfig.align == "left") {
9681 orn = this.config.orientation;
9682 if(orn == "bottom" || orn == "top") {
9684 x: pos.x - width / 2,
9690 y: pos.y - height / 2
9693 } else if(nconfig.align == "right") {
9694 orn = this.config.orientation;
9695 if(orn == "bottom" || orn == "top") {
9697 x: pos.x - width / 2,
9703 y: pos.y - height / 2
9706 } else throw "align: not implemented";
9711 getOrientation: function(adj) {
9712 var config = this.config;
9713 var orn = config.orientation;
9715 if(config.multitree) {
9716 var nodeFrom = adj.nodeFrom;
9717 var nodeTo = adj.nodeTo;
9718 orn = (('$orn' in nodeFrom.data)
9719 && nodeFrom.data.$orn)
9720 || (('$orn' in nodeTo.data)
9721 && nodeTo.data.$orn);
9731 Custom extension of <Graph.Label>.
9732 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9736 All <Graph.Label> methods and subclasses.
9740 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9747 Custom extension of <Graph.Label.Native>.
9751 All <Graph.Label.Native> methods
9755 <Graph.Label.Native>
9757 $jit.ST.Label.Native = new Class({
9758 Implements: Graph.Label.Native,
9760 renderLabel: function(canvas, node, controller) {
9761 var ctx = canvas.getCtx();
9762 var coord = node.pos.getc(true);
9763 ctx.fillText(node.name, coord.x, coord.y);
9767 $jit.ST.Label.DOM = new Class({
9768 Implements: Graph.Label.DOM,
9773 Overrides abstract method placeLabel in <Graph.Plot>.
9777 tag - A DOM label element.
9778 node - A <Graph.Node>.
9779 controller - A configuration/controller object passed to the visualization.
9782 placeLabel: function(tag, node, controller) {
9783 var pos = node.pos.getc(true),
9784 config = this.viz.config,
9786 canvas = this.viz.canvas,
9787 w = node.getData('width'),
9788 h = node.getData('height'),
9789 radius = canvas.getSize(),
9792 var ox = canvas.translateOffsetX,
9793 oy = canvas.translateOffsetY,
9794 sx = canvas.scaleOffsetX,
9795 sy = canvas.scaleOffsetY,
9796 posx = pos.x * sx + ox,
9797 posy = pos.y * sy + oy;
9799 if(dim.align == "center") {
9801 x: Math.round(posx - w / 2 + radius.width/2),
9802 y: Math.round(posy - h / 2 + radius.height/2)
9804 } else if (dim.align == "left") {
9805 orn = config.orientation;
9806 if(orn == "bottom" || orn == "top") {
9808 x: Math.round(posx - w / 2 + radius.width/2),
9809 y: Math.round(posy + radius.height/2)
9813 x: Math.round(posx + radius.width/2),
9814 y: Math.round(posy - h / 2 + radius.height/2)
9817 } else if(dim.align == "right") {
9818 orn = config.orientation;
9819 if(orn == "bottom" || orn == "top") {
9821 x: Math.round(posx - w / 2 + radius.width/2),
9822 y: Math.round(posy - h + radius.height/2)
9826 x: Math.round(posx - w + radius.width/2),
9827 y: Math.round(posy - h / 2 + radius.height/2)
9830 } else throw "align: not implemented";
9832 var style = tag.style;
9833 style.left = labelPos.x + 'px';
9834 style.top = labelPos.y + 'px';
9835 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9836 controller.onPlaceLabel(tag, node);
9843 Custom extension of <Graph.Label.SVG>.
9847 All <Graph.Label.SVG> methods
9853 $jit.ST.Label.SVG = new Class({
9854 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9856 initialize: function(viz) {
9864 Custom extension of <Graph.Label.HTML>.
9868 All <Graph.Label.HTML> methods.
9875 $jit.ST.Label.HTML = new Class({
9876 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9878 initialize: function(viz) {
9885 Class: ST.Plot.NodeTypes
9887 This class contains a list of <Graph.Node> built-in types.
9888 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9890 You can add your custom node types, customizing your visualization to the extreme.
9895 ST.Plot.NodeTypes.implement({
9897 'render': function(node, canvas) {
9898 //print your custom node to canvas
9901 'contains': function(node, pos) {
9902 //return true if pos is inside the node or false otherwise
9909 $jit.ST.Plot.NodeTypes = new Class({
9912 'contains': $.lambda(false)
9915 'render': function(node, canvas) {
9916 var dim = node.getData('dim'),
9917 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9919 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9921 'contains': function(node, pos) {
9922 var dim = node.getData('dim'),
9923 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9925 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9929 'render': function(node, canvas) {
9930 var dim = node.getData('dim'),
9932 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9933 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9935 'contains': function(node, pos) {
9936 var dim = node.getData('dim'),
9937 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9939 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9943 'render': function(node, canvas) {
9944 var width = node.getData('width'),
9945 height = node.getData('height'),
9946 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9947 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9949 'contains': function(node, pos) {
9950 var width = node.getData('width'),
9951 height = node.getData('height'),
9952 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9953 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9957 'render': function(node, canvas) {
9958 var width = node.getData('width'),
9959 height = node.getData('height'),
9960 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9961 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9963 'contains': function(node, pos) {
9964 var width = node.getData('width'),
9965 height = node.getData('height'),
9966 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9967 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9973 Class: ST.Plot.EdgeTypes
9975 This class contains a list of <Graph.Adjacence> built-in types.
9976 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9978 You can add your custom edge types, customizing your visualization to the extreme.
9983 ST.Plot.EdgeTypes.implement({
9985 'render': function(adj, canvas) {
9986 //print your custom edge to canvas
9989 'contains': function(adj, pos) {
9990 //return true if pos is inside the arc or false otherwise
9997 $jit.ST.Plot.EdgeTypes = new Class({
10000 'render': function(adj, canvas) {
10001 var orn = this.getOrientation(adj),
10002 nodeFrom = adj.nodeFrom,
10003 nodeTo = adj.nodeTo,
10004 rel = nodeFrom._depth < nodeTo._depth,
10005 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10006 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
10007 this.edgeHelper.line.render(from, to, canvas);
10009 'contains': function(adj, pos) {
10010 var orn = this.getOrientation(adj),
10011 nodeFrom = adj.nodeFrom,
10012 nodeTo = adj.nodeTo,
10013 rel = nodeFrom._depth < nodeTo._depth,
10014 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10015 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
10016 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
10020 'render': function(adj, canvas) {
10021 var orn = this.getOrientation(adj),
10022 node = adj.nodeFrom,
10023 child = adj.nodeTo,
10024 dim = adj.getData('dim'),
10025 from = this.viz.geom.getEdge(node, 'begin', orn),
10026 to = this.viz.geom.getEdge(child, 'end', orn),
10027 direction = adj.data.$direction,
10028 inv = (direction && direction.length>1 && direction[0] != node.id);
10029 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
10031 'contains': function(adj, pos) {
10032 var orn = this.getOrientation(adj),
10033 nodeFrom = adj.nodeFrom,
10034 nodeTo = adj.nodeTo,
10035 rel = nodeFrom._depth < nodeTo._depth,
10036 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10037 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
10038 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
10041 'quadratic:begin': {
10042 'render': function(adj, canvas) {
10043 var orn = this.getOrientation(adj);
10044 var nodeFrom = adj.nodeFrom,
10045 nodeTo = adj.nodeTo,
10046 rel = nodeFrom._depth < nodeTo._depth,
10047 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10048 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10049 dim = adj.getData('dim'),
10050 ctx = canvas.getCtx();
10052 ctx.moveTo(begin.x, begin.y);
10055 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
10058 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
10061 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
10064 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
10071 'render': function(adj, canvas) {
10072 var orn = this.getOrientation(adj);
10073 var nodeFrom = adj.nodeFrom,
10074 nodeTo = adj.nodeTo,
10075 rel = nodeFrom._depth < nodeTo._depth,
10076 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10077 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10078 dim = adj.getData('dim'),
10079 ctx = canvas.getCtx();
10081 ctx.moveTo(begin.x, begin.y);
10084 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
10087 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
10090 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
10093 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
10100 'render': function(adj, canvas) {
10101 var orn = this.getOrientation(adj),
10102 nodeFrom = adj.nodeFrom,
10103 nodeTo = adj.nodeTo,
10104 rel = nodeFrom._depth < nodeTo._depth,
10105 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10106 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10107 dim = adj.getData('dim'),
10108 ctx = canvas.getCtx();
10110 ctx.moveTo(begin.x, begin.y);
10113 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
10116 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
10119 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
10122 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
10131 Options.LineChart = {
10135 labelOffset: 3, // label offset
10136 type: 'basic', // gradient
10152 selectOnHover: true,
10153 showAggregates: true,
10155 filterOnClick: false,
10156 restoreOnRightClick: false
10161 * File: LineChart.js
10165 $jit.ST.Plot.NodeTypes.implement({
10166 'linechart-basic' : {
10167 'render' : function(node, canvas) {
10168 var pos = node.pos.getc(true),
10169 width = node.getData('width'),
10170 height = node.getData('height'),
10171 algnPos = this.getAlignedPos(pos, width, height),
10172 x = algnPos.x + width/2 , y = algnPos.y,
10173 stringArray = node.getData('stringArray'),
10174 lastNode = node.getData('lastNode'),
10175 dimArray = node.getData('dimArray'),
10176 valArray = node.getData('valueArray'),
10177 colorArray = node.getData('colorArray'),
10178 colorLength = colorArray.length,
10179 config = node.getData('config'),
10180 gradient = node.getData('gradient'),
10181 showLabels = config.showLabels,
10182 aggregates = config.showAggregates,
10183 label = config.Label,
10184 prev = node.getData('prev'),
10185 dataPointSize = config.dataPointSize;
10187 var ctx = canvas.getCtx(), border = node.getData('border');
10188 if (colorArray && dimArray && stringArray) {
10190 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10191 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10193 ctx.lineCap = "round";
10197 //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
10199 ctx.moveTo(x, y - dimArray[i][0]);
10200 ctx.lineTo(x + width, y - dimArray[i][1]);
10204 //render data point
10205 ctx.fillRect(x - (dataPointSize/2), y - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10209 if(label.type == 'Native' && showLabels) {
10211 ctx.fillStyle = ctx.strokeStyle = label.color;
10212 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10213 ctx.textAlign = 'center';
10214 ctx.textBaseline = 'middle';
10215 ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10221 'contains': function(node, mpos) {
10222 var pos = node.pos.getc(true),
10223 width = node.getData('width'),
10224 height = node.getData('height'),
10225 config = node.getData('config'),
10226 dataPointSize = config.dataPointSize,
10227 dataPointMidPoint = dataPointSize/2,
10228 algnPos = this.getAlignedPos(pos, width, height),
10229 x = algnPos.x + width/2, y = algnPos.y,
10230 dimArray = node.getData('dimArray');
10231 //bounding box check
10232 if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10236 for(var i=0, l=dimArray.length; i<l; i++) {
10237 var dimi = dimArray[i];
10238 var url = Url.decode(node.getData('linkArray')[i]);
10239 if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10240 var valArrayCur = node.getData('valArrayCur');
10241 var results = array_match(valArrayCur[i],valArrayCur);
10242 var matches = results[0];
10243 var indexValues = results[1];
10245 var names = new Array(),
10246 values = new Array(),
10247 percentages = new Array(),
10248 linksArr = new Array();
10249 for(var j=0, il=indexValues.length; j<il; j++) {
10250 names[j] = node.getData('stringArray')[indexValues[j]];
10251 values[j] = valArrayCur[indexValues[j]];
10252 percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10253 linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10258 'color': node.getData('colorArray')[i],
10260 'percentage': percentages,
10267 'name': node.getData('stringArray')[i],
10268 'color': node.getData('colorArray')[i],
10269 'value': node.getData('valueArray')[i][0],
10270 // 'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10271 'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10286 A visualization that displays line charts.
10288 Constructor Options:
10290 See <Options.Line>.
10293 $jit.LineChart = new Class({
10295 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10299 initialize: function(opt) {
10300 this.controller = this.config =
10301 $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10302 Label: { type: 'Native' }
10304 //set functions for showLabels and showAggregates
10305 var showLabels = this.config.showLabels,
10306 typeLabels = $.type(showLabels),
10307 showAggregates = this.config.showAggregates,
10308 typeAggregates = $.type(showAggregates);
10309 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10310 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10311 Options.Fx.clearCanvas = false;
10312 this.initializeViz();
10315 initializeViz: function() {
10316 var config = this.config,
10318 nodeType = config.type.split(":")[0],
10321 var st = new $jit.ST({
10322 injectInto: config.injectInto,
10323 orientation: "bottom",
10324 backgroundColor: config.backgroundColor,
10325 renderBackground: config.renderBackground,
10329 withLabels: config.Label.type != 'Native',
10330 useCanvas: config.useCanvas,
10332 type: config.Label.type
10336 type: 'linechart-' + nodeType,
10345 enable: config.Tips.enable,
10348 onShow: function(tip, node, contains) {
10349 var elem = contains;
10350 config.Tips.onShow(tip, elem, node);
10356 onClick: function(node, eventInfo, evt) {
10357 if(!config.filterOnClick && !config.Events.enable) return;
10358 var elem = eventInfo.getContains();
10359 if(elem) config.filterOnClick && that.filter(elem.name);
10360 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10362 onRightClick: function(node, eventInfo, evt) {
10363 if(!config.restoreOnRightClick) return;
10366 onMouseMove: function(node, eventInfo, evt) {
10367 if(!config.selectOnHover) return;
10369 var elem = eventInfo.getContains();
10370 that.select(node.id, elem.name, elem.index);
10372 that.select(false, false, false);
10376 onCreateLabel: function(domElement, node) {
10377 var labelConf = config.Label,
10378 valueArray = node.getData('valueArray'),
10379 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10380 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10381 if(node.getData('prev')) {
10383 wrapper: document.createElement('div'),
10384 aggregate: document.createElement('div'),
10385 label: document.createElement('div')
10387 var wrapper = nlbs.wrapper,
10388 label = nlbs.label,
10389 aggregate = nlbs.aggregate,
10390 wrapperStyle = wrapper.style,
10391 labelStyle = label.style,
10392 aggregateStyle = aggregate.style;
10393 //store node labels
10394 nodeLabels[node.id] = nlbs;
10396 wrapper.appendChild(label);
10397 wrapper.appendChild(aggregate);
10398 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10399 label.style.display = 'none';
10401 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10402 aggregate.style.display = 'none';
10404 wrapperStyle.position = 'relative';
10405 wrapperStyle.overflow = 'visible';
10406 wrapperStyle.fontSize = labelConf.size + 'px';
10407 wrapperStyle.fontFamily = labelConf.family;
10408 wrapperStyle.color = labelConf.color;
10409 wrapperStyle.textAlign = 'center';
10410 aggregateStyle.position = labelStyle.position = 'absolute';
10412 domElement.style.width = node.getData('width') + 'px';
10413 domElement.style.height = node.getData('height') + 'px';
10414 label.innerHTML = node.name;
10416 domElement.appendChild(wrapper);
10419 onPlaceLabel: function(domElement, node) {
10420 if(!node.getData('prev')) return;
10421 var labels = nodeLabels[node.id],
10422 wrapperStyle = labels.wrapper.style,
10423 labelStyle = labels.label.style,
10424 aggregateStyle = labels.aggregate.style,
10425 width = node.getData('width'),
10426 height = node.getData('height'),
10427 dimArray = node.getData('dimArray'),
10428 valArray = node.getData('valueArray'),
10429 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10430 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10431 font = parseInt(wrapperStyle.fontSize, 10),
10432 domStyle = domElement.style;
10434 if(dimArray && valArray) {
10435 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10436 labelStyle.display = '';
10438 labelStyle.display = 'none';
10440 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10441 aggregateStyle.display = '';
10443 aggregateStyle.display = 'none';
10445 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10446 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10447 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10448 if(dimArray[i][0] > 0) {
10449 acum+= valArray[i][0];
10450 leftAcum+= dimArray[i][0];
10453 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10454 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10455 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10456 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10457 labels.aggregate.innerHTML = acum;
10462 var size = st.canvas.getSize(),
10463 margin = config.Margin;
10464 st.config.offsetY = -size.height/2 + margin.bottom
10465 + (config.showLabels && (config.labelOffset + config.Label.size));
10466 st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10468 this.canvas = this.st.canvas;
10471 renderTitle: function() {
10472 var canvas = this.canvas,
10473 size = canvas.getSize(),
10474 config = this.config,
10475 margin = config.Margin,
10476 label = config.Label,
10477 title = config.Title;
10478 ctx = canvas.getCtx();
10479 ctx.fillStyle = title.color;
10480 ctx.textAlign = 'left';
10481 ctx.textBaseline = 'top';
10482 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10483 if(label.type == 'Native') {
10484 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10488 renderTicks: function() {
10490 var canvas = this.canvas,
10491 size = canvas.getSize(),
10492 config = this.config,
10493 margin = config.Margin,
10494 ticks = config.Ticks,
10495 title = config.Title,
10496 subtitle = config.Subtitle,
10497 label = config.Label,
10498 maxValue = this.maxValue,
10499 maxTickValue = Math.ceil(maxValue*.1)*10;
10500 if(maxTickValue == maxValue) {
10501 var length = maxTickValue.toString().length;
10502 maxTickValue = maxTickValue + parseInt(pad(1,length));
10507 labelIncrement = maxTickValue/ticks.segments,
10508 ctx = canvas.getCtx();
10509 ctx.strokeStyle = ticks.color;
10510 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10511 ctx.textAlign = 'center';
10512 ctx.textBaseline = 'middle';
10514 idLabel = canvas.id + "-label";
10516 container = document.getElementById(idLabel);
10519 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10520 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10521 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)),
10522 segmentLength = grid/ticks.segments;
10523 ctx.fillStyle = ticks.color;
10524 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));
10526 while(axis>=grid) {
10528 ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10529 ctx.rotate(Math.PI / 2);
10530 ctx.fillStyle = label.color;
10531 if(config.showLabels) {
10532 if(label.type == 'Native') {
10533 ctx.fillText(labelValue, 0, 0);
10535 //html labels on y axis
10536 labelDiv = document.createElement('div');
10537 labelDiv.innerHTML = labelValue;
10538 labelDiv.className = "rotatedLabel";
10539 // labelDiv.class = "rotatedLabel";
10540 labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10541 labelDiv.style.left = margin.left + "px";
10542 labelDiv.style.width = labelDim + "px";
10543 labelDiv.style.height = labelDim + "px";
10544 labelDiv.style.textAlign = "center";
10545 labelDiv.style.verticalAlign = "middle";
10546 labelDiv.style.position = "absolute";
10547 container.appendChild(labelDiv);
10551 ctx.fillStyle = ticks.color;
10552 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 );
10553 htmlOrigin += segmentLength;
10554 axis += segmentLength;
10555 labelValue += labelIncrement;
10565 renderBackground: function() {
10566 var canvas = this.canvas,
10567 config = this.config,
10568 backgroundColor = config.backgroundColor,
10569 size = canvas.getSize(),
10570 ctx = canvas.getCtx();
10571 //ctx.globalCompositeOperation = "destination-over";
10572 ctx.fillStyle = backgroundColor;
10573 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10575 clear: function() {
10576 var canvas = this.canvas;
10577 var ctx = canvas.getCtx(),
10578 size = canvas.getSize();
10579 ctx.fillStyle = "rgba(255,255,255,0)";
10580 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10581 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
10583 resizeGraph: function(json,width) {
10584 var canvas = this.canvas,
10585 size = canvas.getSize(),
10586 orgHeight = size.height;
10588 canvas.resize(width,orgHeight);
10589 if(typeof FlashCanvas == "undefined") {
10592 this.clear();// hack for flashcanvas bug not properly clearing rectangle
10594 this.loadJSON(json);
10600 Loads JSON data into the visualization.
10604 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>.
10608 var areaChart = new $jit.AreaChart(options);
10609 areaChart.loadJSON(json);
10612 loadJSON: function(json) {
10613 var prefix = $.time(),
10616 name = $.splat(json.label),
10617 color = $.splat(json.color || this.colors),
10618 config = this.config,
10619 ticks = config.Ticks,
10620 renderBackground = config.renderBackground,
10621 gradient = !!config.type.split(":")[1],
10622 animate = config.animate,
10623 title = config.Title,
10624 groupTotalValue = 0;
10626 var valArrayAll = new Array();
10628 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10629 var val = values[i];
10630 var valArray = $.splat(val.values);
10631 for (var j=0, len=valArray.length; j<len; j++) {
10632 valArrayAll.push(parseInt(valArray[j]));
10634 groupTotalValue += parseInt(valArray.sum());
10637 this.maxValue = Math.max.apply(null, valArrayAll);
10639 for(var i=0, values=json.values, l=values.length; i<l; i++) {
10640 var val = values[i], prev = values[i-1];
10642 var next = (i+1 < l) ? values[i+1] : 0;
10643 var valLeft = $.splat(values[i].values);
10644 var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10645 var valArray = $.zip(valLeft, valRight);
10646 var valArrayCur = $.splat(values[i].values);
10647 var linkArray = $.splat(values[i].links);
10648 var acumLeft = 0, acumRight = 0;
10649 var lastNode = (l-1 == i) ? true : false;
10651 'id': prefix + val.label,
10655 '$valueArray': valArray,
10656 '$valArrayCur': valArrayCur,
10657 '$colorArray': color,
10658 '$linkArray': linkArray,
10659 '$stringArray': name,
10660 '$next': next? next.label:false,
10661 '$prev': prev? prev.label:false,
10663 '$lastNode': lastNode,
10664 '$groupTotalValue': groupTotalValue,
10665 '$gradient': gradient
10671 'id': prefix + '$root',
10682 this.normalizeDims();
10684 if(renderBackground) {
10685 this.renderBackground();
10688 if(!animate && ticks.enable) {
10689 this.renderTicks();
10694 this.renderTitle();
10698 st.select(st.root);
10701 modes: ['node-property:height:dimArray'],
10710 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.
10714 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10715 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10720 areaChart.updateJSON(json, {
10721 onComplete: function() {
10722 alert('update complete!');
10727 updateJSON: function(json, onComplete) {
10728 if(this.busy) return;
10733 labels = json.label && $.splat(json.label),
10734 values = json.values,
10735 animate = this.config.animate,
10737 $.each(values, function(v) {
10738 var n = graph.getByName(v.label);
10740 v.values = $.splat(v.values);
10741 var stringArray = n.getData('stringArray'),
10742 valArray = n.getData('valueArray');
10743 $.each(valArray, function(a, i) {
10744 a[0] = v.values[i];
10745 if(labels) stringArray[i] = labels[i];
10747 n.setData('valueArray', valArray);
10748 var prev = n.getData('prev'),
10749 next = n.getData('next'),
10750 nextNode = graph.getByName(next);
10752 var p = graph.getByName(prev);
10754 var valArray = p.getData('valueArray');
10755 $.each(valArray, function(a, i) {
10756 a[1] = v.values[i];
10761 var valArray = n.getData('valueArray');
10762 $.each(valArray, function(a, i) {
10763 a[1] = v.values[i];
10768 this.normalizeDims();
10771 st.select(st.root);
10774 modes: ['node-property:height:dimArray'],
10776 onComplete: function() {
10778 onComplete && onComplete.onComplete();
10787 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10791 Variable strings arguments with the name of the stacks.
10796 areaChart.filter('label A', 'label C');
10801 <AreaChart.restore>.
10803 filter: function() {
10804 if(this.busy) return;
10806 if(this.config.Tips.enable) this.st.tips.hide();
10807 this.select(false, false, false);
10808 var args = Array.prototype.slice.call(arguments);
10809 var rt = this.st.graph.getNode(this.st.root);
10811 rt.eachAdjacency(function(adj) {
10812 var n = adj.nodeTo,
10813 dimArray = n.getData('dimArray'),
10814 stringArray = n.getData('stringArray');
10815 n.setData('dimArray', $.map(dimArray, function(d, i) {
10816 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10819 this.st.fx.animate({
10820 modes: ['node-property:dimArray'],
10822 onComplete: function() {
10831 Sets all stacks that could have been filtered visible.
10836 areaChart.restore();
10841 <AreaChart.filter>.
10843 restore: function() {
10844 if(this.busy) return;
10846 if(this.config.Tips.enable) this.st.tips.hide();
10847 this.select(false, false, false);
10848 this.normalizeDims();
10850 this.st.fx.animate({
10851 modes: ['node-property:height:dimArray'],
10853 onComplete: function() {
10858 //adds the little brown bar when hovering the node
10859 select: function(id, name, index) {
10860 if(!this.config.selectOnHover) return;
10861 var s = this.selected;
10862 if(s.id != id || s.name != name
10863 || s.index != index) {
10867 this.st.graph.eachNode(function(n) {
10868 n.setData('border', false);
10871 var n = this.st.graph.getNode(id);
10872 n.setData('border', s);
10873 var link = index === 0? 'prev':'next';
10874 link = n.getData(link);
10876 n = this.st.graph.getByName(link);
10878 n.setData('border', {
10892 Returns an object containing as keys the legend names and as values hex strings with color values.
10897 var legend = areaChart.getLegend();
10900 getLegend: function() {
10901 var legend = new Array();
10902 var name = new Array();
10903 var color = new Array();
10905 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10908 var colors = n.getData('colorArray'),
10909 len = colors.length;
10910 $.each(n.getData('stringArray'), function(s, i) {
10911 color[i] = colors[i % len];
10914 legend['name'] = name;
10915 legend['color'] = color;
10920 Method: getMaxValue
10922 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10927 var ans = areaChart.getMaxValue();
10930 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10935 //will return 100 for all AreaChart instances,
10936 //displaying all of them with the same scale
10937 $jit.AreaChart.implement({
10938 'getMaxValue': function() {
10946 normalizeDims: function() {
10947 //number of elements
10948 var root = this.st.graph.getNode(this.st.root), l=0;
10949 root.eachAdjacency(function() {
10954 var maxValue = this.maxValue || 1,
10955 size = this.st.canvas.getSize(),
10956 config = this.config,
10957 margin = config.Margin,
10958 labelOffset = config.labelOffset + config.Label.size,
10959 fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10960 animate = config.animate,
10961 ticks = config.Ticks,
10962 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10963 - (config.showLabels && labelOffset);
10966 var maxTickValue = Math.ceil(maxValue*.1)*10;
10967 if(maxTickValue == maxValue) {
10968 var length = maxTickValue.toString().length;
10969 maxTickValue = maxTickValue + parseInt(pad(1,length));
10974 this.st.graph.eachNode(function(n) {
10975 var acumLeft = 0, acumRight = 0, animateValue = [];
10976 $.each(n.getData('valueArray'), function(v) {
10978 acumRight += +v[1];
10979 animateValue.push([0, 0]);
10981 var acum = acumRight>acumLeft? acumRight:acumLeft;
10983 n.setData('width', fixedDim);
10985 n.setData('height', acum * height / maxValue, 'end');
10986 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10987 return [n[0] * height / maxValue, n[1] * height / maxValue];
10989 var dimArray = n.getData('dimArray');
10991 n.setData('dimArray', animateValue);
10996 n.setData('height', acum * height / maxValue);
10997 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10998 return [n[0] * height / maxTickValue, n[1] * height / maxTickValue];
11001 n.setData('height', acum * height / maxValue);
11002 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11003 return [n[0] * height / maxValue, n[1] * height / maxValue];
11018 * File: AreaChart.js
11022 $jit.ST.Plot.NodeTypes.implement({
11023 'areachart-stacked' : {
11024 'render' : function(node, canvas) {
11025 var pos = node.pos.getc(true),
11026 width = node.getData('width'),
11027 height = node.getData('height'),
11028 algnPos = this.getAlignedPos(pos, width, height),
11029 x = algnPos.x, y = algnPos.y,
11030 stringArray = node.getData('stringArray'),
11031 dimArray = node.getData('dimArray'),
11032 valArray = node.getData('valueArray'),
11033 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11034 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11035 colorArray = node.getData('colorArray'),
11036 colorLength = colorArray.length,
11037 config = node.getData('config'),
11038 gradient = node.getData('gradient'),
11039 showLabels = config.showLabels,
11040 aggregates = config.showAggregates,
11041 label = config.Label,
11042 prev = node.getData('prev');
11044 var ctx = canvas.getCtx(), border = node.getData('border');
11045 if (colorArray && dimArray && stringArray) {
11046 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
11047 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11049 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
11050 var h1 = acumLeft + dimArray[i][0],
11051 h2 = acumRight + dimArray[i][1],
11052 alpha = Math.atan((h2 - h1) / width),
11054 var linear = ctx.createLinearGradient(x + width/2,
11056 x + width/2 + delta * Math.sin(alpha),
11057 y - (h1 + h2)/2 + delta * Math.cos(alpha));
11058 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11059 function(v) { return (v * 0.85) >> 0; }));
11060 linear.addColorStop(0, colorArray[i % colorLength]);
11061 linear.addColorStop(1, color);
11062 ctx.fillStyle = linear;
11065 ctx.moveTo(x, y - acumLeft);
11066 ctx.lineTo(x + width, y - acumRight);
11067 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
11068 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
11069 ctx.lineTo(x, y - acumLeft);
11073 var strong = border.name == stringArray[i];
11074 var perc = strong? 0.7 : 0.8;
11075 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11076 function(v) { return (v * perc) >> 0; }));
11077 ctx.strokeStyle = color;
11078 ctx.lineWidth = strong? 4 : 1;
11081 if(border.index === 0) {
11082 ctx.moveTo(x, y - acumLeft);
11083 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
11085 ctx.moveTo(x + width, y - acumRight);
11086 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
11091 acumLeft += (dimArray[i][0] || 0);
11092 acumRight += (dimArray[i][1] || 0);
11094 if(dimArray[i][0] > 0)
11095 valAcum += (valArray[i][0] || 0);
11097 if(prev && label.type == 'Native') {
11100 ctx.fillStyle = ctx.strokeStyle = label.color;
11101 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11102 ctx.textAlign = 'center';
11103 ctx.textBaseline = 'middle';
11104 if(aggregates(node.name, valLeft, valRight, node)) {
11105 ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
11107 if(showLabels(node.name, valLeft, valRight, node)) {
11108 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
11114 'contains': function(node, mpos) {
11115 var pos = node.pos.getc(true),
11116 width = node.getData('width'),
11117 height = node.getData('height'),
11118 algnPos = this.getAlignedPos(pos, width, height),
11119 x = algnPos.x, y = algnPos.y,
11120 dimArray = node.getData('dimArray'),
11122 //bounding box check
11123 if(mpos.x < x || mpos.x > x + width
11124 || mpos.y > y || mpos.y < y - height) {
11128 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
11129 var dimi = dimArray[i];
11132 var intersec = lAcum + (rAcum - lAcum) * rx / width;
11133 if(mpos.y >= intersec) {
11134 var index = +(rx > width/2);
11136 'name': node.getData('stringArray')[i],
11137 'color': node.getData('colorArray')[i],
11138 'value': node.getData('valueArray')[i][index],
11151 A visualization that displays stacked area charts.
11153 Constructor Options:
11155 See <Options.AreaChart>.
11158 $jit.AreaChart = new Class({
11160 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
11164 initialize: function(opt) {
11165 this.controller = this.config =
11166 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
11167 Label: { type: 'Native' }
11169 //set functions for showLabels and showAggregates
11170 var showLabels = this.config.showLabels,
11171 typeLabels = $.type(showLabels),
11172 showAggregates = this.config.showAggregates,
11173 typeAggregates = $.type(showAggregates);
11174 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
11175 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
11177 this.initializeViz();
11180 initializeViz: function() {
11181 var config = this.config,
11183 nodeType = config.type.split(":")[0],
11186 var st = new $jit.ST({
11187 injectInto: config.injectInto,
11188 orientation: "bottom",
11192 withLabels: config.Label.type != 'Native',
11193 useCanvas: config.useCanvas,
11195 type: config.Label.type
11199 type: 'areachart-' + nodeType,
11208 enable: config.Tips.enable,
11211 onShow: function(tip, node, contains) {
11212 var elem = contains;
11213 config.Tips.onShow(tip, elem, node);
11219 onClick: function(node, eventInfo, evt) {
11220 if(!config.filterOnClick && !config.Events.enable) return;
11221 var elem = eventInfo.getContains();
11222 if(elem) config.filterOnClick && that.filter(elem.name);
11223 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11225 onRightClick: function(node, eventInfo, evt) {
11226 if(!config.restoreOnRightClick) return;
11229 onMouseMove: function(node, eventInfo, evt) {
11230 if(!config.selectOnHover) return;
11232 var elem = eventInfo.getContains();
11233 that.select(node.id, elem.name, elem.index);
11235 that.select(false, false, false);
11239 onCreateLabel: function(domElement, node) {
11240 var labelConf = config.Label,
11241 valueArray = node.getData('valueArray'),
11242 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11243 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11244 if(node.getData('prev')) {
11246 wrapper: document.createElement('div'),
11247 aggregate: document.createElement('div'),
11248 label: document.createElement('div')
11250 var wrapper = nlbs.wrapper,
11251 label = nlbs.label,
11252 aggregate = nlbs.aggregate,
11253 wrapperStyle = wrapper.style,
11254 labelStyle = label.style,
11255 aggregateStyle = aggregate.style;
11256 //store node labels
11257 nodeLabels[node.id] = nlbs;
11259 wrapper.appendChild(label);
11260 wrapper.appendChild(aggregate);
11261 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11262 label.style.display = 'none';
11264 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11265 aggregate.style.display = 'none';
11267 wrapperStyle.position = 'relative';
11268 wrapperStyle.overflow = 'visible';
11269 wrapperStyle.fontSize = labelConf.size + 'px';
11270 wrapperStyle.fontFamily = labelConf.family;
11271 wrapperStyle.color = labelConf.color;
11272 wrapperStyle.textAlign = 'center';
11273 aggregateStyle.position = labelStyle.position = 'absolute';
11275 domElement.style.width = node.getData('width') + 'px';
11276 domElement.style.height = node.getData('height') + 'px';
11277 label.innerHTML = node.name;
11279 domElement.appendChild(wrapper);
11282 onPlaceLabel: function(domElement, node) {
11283 if(!node.getData('prev')) return;
11284 var labels = nodeLabels[node.id],
11285 wrapperStyle = labels.wrapper.style,
11286 labelStyle = labels.label.style,
11287 aggregateStyle = labels.aggregate.style,
11288 width = node.getData('width'),
11289 height = node.getData('height'),
11290 dimArray = node.getData('dimArray'),
11291 valArray = node.getData('valueArray'),
11292 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11293 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11294 font = parseInt(wrapperStyle.fontSize, 10),
11295 domStyle = domElement.style;
11297 if(dimArray && valArray) {
11298 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11299 labelStyle.display = '';
11301 labelStyle.display = 'none';
11303 if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11304 aggregateStyle.display = '';
11306 aggregateStyle.display = 'none';
11308 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11309 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11310 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11311 if(dimArray[i][0] > 0) {
11312 acum+= valArray[i][0];
11313 leftAcum+= dimArray[i][0];
11316 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11317 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11318 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11319 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11320 labels.aggregate.innerHTML = acum;
11325 var size = st.canvas.getSize(),
11326 margin = config.Margin;
11327 st.config.offsetY = -size.height/2 + margin.bottom
11328 + (config.showLabels && (config.labelOffset + config.Label.size));
11329 st.config.offsetX = (margin.right - margin.left)/2;
11331 this.canvas = this.st.canvas;
11337 Loads JSON data into the visualization.
11341 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>.
11345 var areaChart = new $jit.AreaChart(options);
11346 areaChart.loadJSON(json);
11349 loadJSON: function(json) {
11350 var prefix = $.time(),
11353 name = $.splat(json.label),
11354 color = $.splat(json.color || this.colors),
11355 config = this.config,
11356 gradient = !!config.type.split(":")[1],
11357 animate = config.animate;
11359 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11360 var val = values[i], prev = values[i-1], next = values[i+1];
11361 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11362 var valArray = $.zip(valLeft, valRight);
11363 var acumLeft = 0, acumRight = 0;
11365 'id': prefix + val.label,
11369 '$valueArray': valArray,
11370 '$colorArray': color,
11371 '$stringArray': name,
11372 '$next': next.label,
11373 '$prev': prev? prev.label:false,
11375 '$gradient': gradient
11381 'id': prefix + '$root',
11392 this.normalizeDims();
11394 st.select(st.root);
11397 modes: ['node-property:height:dimArray'],
11406 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.
11410 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11411 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11416 areaChart.updateJSON(json, {
11417 onComplete: function() {
11418 alert('update complete!');
11423 updateJSON: function(json, onComplete) {
11424 if(this.busy) return;
11429 labels = json.label && $.splat(json.label),
11430 values = json.values,
11431 animate = this.config.animate,
11433 $.each(values, function(v) {
11434 var n = graph.getByName(v.label);
11436 v.values = $.splat(v.values);
11437 var stringArray = n.getData('stringArray'),
11438 valArray = n.getData('valueArray');
11439 $.each(valArray, function(a, i) {
11440 a[0] = v.values[i];
11441 if(labels) stringArray[i] = labels[i];
11443 n.setData('valueArray', valArray);
11444 var prev = n.getData('prev'),
11445 next = n.getData('next'),
11446 nextNode = graph.getByName(next);
11448 var p = graph.getByName(prev);
11450 var valArray = p.getData('valueArray');
11451 $.each(valArray, function(a, i) {
11452 a[1] = v.values[i];
11457 var valArray = n.getData('valueArray');
11458 $.each(valArray, function(a, i) {
11459 a[1] = v.values[i];
11464 this.normalizeDims();
11466 st.select(st.root);
11469 modes: ['node-property:height:dimArray'],
11471 onComplete: function() {
11473 onComplete && onComplete.onComplete();
11482 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11486 Variable strings arguments with the name of the stacks.
11491 areaChart.filter('label A', 'label C');
11496 <AreaChart.restore>.
11498 filter: function() {
11499 if(this.busy) return;
11501 if(this.config.Tips.enable) this.st.tips.hide();
11502 this.select(false, false, false);
11503 var args = Array.prototype.slice.call(arguments);
11504 var rt = this.st.graph.getNode(this.st.root);
11506 rt.eachAdjacency(function(adj) {
11507 var n = adj.nodeTo,
11508 dimArray = n.getData('dimArray'),
11509 stringArray = n.getData('stringArray');
11510 n.setData('dimArray', $.map(dimArray, function(d, i) {
11511 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11514 this.st.fx.animate({
11515 modes: ['node-property:dimArray'],
11517 onComplete: function() {
11526 Sets all stacks that could have been filtered visible.
11531 areaChart.restore();
11536 <AreaChart.filter>.
11538 restore: function() {
11539 if(this.busy) return;
11541 if(this.config.Tips.enable) this.st.tips.hide();
11542 this.select(false, false, false);
11543 this.normalizeDims();
11545 this.st.fx.animate({
11546 modes: ['node-property:height:dimArray'],
11548 onComplete: function() {
11553 //adds the little brown bar when hovering the node
11554 select: function(id, name, index) {
11555 if(!this.config.selectOnHover) return;
11556 var s = this.selected;
11557 if(s.id != id || s.name != name
11558 || s.index != index) {
11562 this.st.graph.eachNode(function(n) {
11563 n.setData('border', false);
11566 var n = this.st.graph.getNode(id);
11567 n.setData('border', s);
11568 var link = index === 0? 'prev':'next';
11569 link = n.getData(link);
11571 n = this.st.graph.getByName(link);
11573 n.setData('border', {
11587 Returns an object containing as keys the legend names and as values hex strings with color values.
11592 var legend = areaChart.getLegend();
11595 getLegend: function() {
11598 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11601 var colors = n.getData('colorArray'),
11602 len = colors.length;
11603 $.each(n.getData('stringArray'), function(s, i) {
11604 legend[s] = colors[i % len];
11610 Method: getMaxValue
11612 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11617 var ans = areaChart.getMaxValue();
11620 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11625 //will return 100 for all AreaChart instances,
11626 //displaying all of them with the same scale
11627 $jit.AreaChart.implement({
11628 'getMaxValue': function() {
11635 getMaxValue: function() {
11637 this.st.graph.eachNode(function(n) {
11638 var valArray = n.getData('valueArray'),
11639 acumLeft = 0, acumRight = 0;
11640 $.each(valArray, function(v) {
11642 acumRight += +v[1];
11644 var acum = acumRight>acumLeft? acumRight:acumLeft;
11645 maxValue = maxValue>acum? maxValue:acum;
11650 normalizeDims: function() {
11651 //number of elements
11652 var root = this.st.graph.getNode(this.st.root), l=0;
11653 root.eachAdjacency(function() {
11656 var maxValue = this.getMaxValue() || 1,
11657 size = this.st.canvas.getSize(),
11658 config = this.config,
11659 margin = config.Margin,
11660 labelOffset = config.labelOffset + config.Label.size,
11661 fixedDim = (size.width - (margin.left + margin.right)) / l,
11662 animate = config.animate,
11663 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
11664 - (config.showLabels && labelOffset);
11665 this.st.graph.eachNode(function(n) {
11666 var acumLeft = 0, acumRight = 0, animateValue = [];
11667 $.each(n.getData('valueArray'), function(v) {
11669 acumRight += +v[1];
11670 animateValue.push([0, 0]);
11672 var acum = acumRight>acumLeft? acumRight:acumLeft;
11673 n.setData('width', fixedDim);
11675 n.setData('height', acum * height / maxValue, 'end');
11676 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11677 return [n[0] * height / maxValue, n[1] * height / maxValue];
11679 var dimArray = n.getData('dimArray');
11681 n.setData('dimArray', animateValue);
11684 n.setData('height', acum * height / maxValue);
11685 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11686 return [n[0] * height / maxValue, n[1] * height / maxValue];
11694 * File: Options.BarChart.js
11699 Object: Options.BarChart
11701 <BarChart> options.
11702 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11708 Options.BarChart = {
11713 hoveredColor: '#9fd4ff',
11714 orientation: 'horizontal',
11715 showAggregates: true,
11725 var barChart = new $jit.BarChart({
11728 type: 'stacked:gradient'
11735 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11736 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11737 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11738 barsOffset - (number) Default's *0*. Separation between bars.
11739 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11740 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11741 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11742 showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11743 showLabels - (boolean) Default's *true*. Display the name of the slots.
11747 Options.BarChart = {
11751 type: 'stacked', //stacked, grouped, : gradient
11752 labelOffset: 3, //label offset
11753 barsOffset: 0, //distance between bars
11754 nodeCount: 0, //number of bars
11755 hoveredColor: '#9fd4ff',
11757 renderBackground: false,
11758 orientation: 'horizontal',
11759 showAggregates: true,
11778 * File: BarChart.js
11782 $jit.ST.Plot.NodeTypes.implement({
11783 'barchart-stacked' : {
11784 'render' : function(node, canvas) {
11785 var pos = node.pos.getc(true),
11786 width = node.getData('width'),
11787 height = node.getData('height'),
11788 algnPos = this.getAlignedPos(pos, width, height),
11789 x = algnPos.x, y = algnPos.y,
11790 dimArray = node.getData('dimArray'),
11791 valueArray = node.getData('valueArray'),
11792 stringArray = node.getData('stringArray'),
11793 linkArray = node.getData('linkArray'),
11794 gvl = node.getData('gvl'),
11795 colorArray = node.getData('colorArray'),
11796 colorLength = colorArray.length,
11797 nodeCount = node.getData('nodeCount');
11798 var ctx = canvas.getCtx(),
11799 canvasSize = canvas.getSize(),
11801 border = node.getData('border'),
11802 gradient = node.getData('gradient'),
11803 config = node.getData('config'),
11804 horz = config.orientation == 'horizontal',
11805 aggregates = config.showAggregates,
11806 showLabels = config.showLabels,
11807 label = config.Label,
11808 margin = config.Margin;
11811 if (colorArray && dimArray && stringArray) {
11812 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11813 acum += (dimArray[i] || 0);
11818 if(config.shadow.enable) {
11819 shadowThickness = config.shadow.size;
11820 ctx.fillStyle = "rgba(0,0,0,.2)";
11822 ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11824 ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11828 if (colorArray && dimArray && stringArray) {
11829 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11830 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11837 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
11838 x + acum + dimArray[i]/2, y + height);
11840 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
11841 x + width, y - acum- dimArray[i]/2);
11843 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
11844 function(v) { return (v * 0.8) >> 0; }));
11845 linear.addColorStop(0, color);
11846 linear.addColorStop(0.3, colorArray[i % colorLength]);
11847 linear.addColorStop(0.7, colorArray[i % colorLength]);
11848 linear.addColorStop(1, color);
11849 ctx.fillStyle = linear;
11856 chartBarWidth = dimArray[i];
11857 chartBarHeight = height;
11862 yCoord = y - acum - dimArray[i];
11863 chartBarWidth = width;
11864 chartBarHeight = dimArray[i];
11866 ctx.fillRect(xCoord, yCoord, chartBarWidth, chartBarHeight);
11869 if (chartBarHeight > 0)
11871 ctx.font = label.style + ' ' + (label.size - 2) + 'px ' + label.family;
11872 labelText = valueArray[i].toString();
11873 mtxt = ctx.measureText(labelText);
11875 labelTextPaddingX = 10;
11876 labelTextPaddingY = 6;
11878 labelBoxWidth = mtxt.width + labelTextPaddingX;
11879 labelBoxHeight = label.size + labelTextPaddingY;
11881 // do NOT draw label if label box is smaller than chartBarHeight
11882 if ((horz && (labelBoxWidth < chartBarWidth)) || (!horz && (labelBoxHeight < chartBarHeight)))
11884 labelBoxX = xCoord + chartBarWidth/2 - mtxt.width/2 - labelTextPaddingX/2;
11885 labelBoxY = yCoord + chartBarHeight/2 - labelBoxHeight/2;
11887 ctx.fillStyle = "rgba(255,255,255,.2)";
11888 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "fill");
11889 ctx.fillStyle = "rgba(0,0,0,.8)";
11890 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "stroke");
11891 ctx.textAlign = 'center';
11892 ctx.fillStyle = "rgba(255,255,255,.6)";
11893 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2, labelBoxY + labelBoxHeight/2);
11894 ctx.fillStyle = "rgba(0,0,0,.6)";
11895 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2 + 1, labelBoxY + labelBoxHeight/2 + 1);
11899 if(border && border.name == stringArray[i]) {
11901 opt.dimValue = dimArray[i];
11903 acum += (dimArray[i] || 0);
11904 valAcum += (valueArray[i] || 0);
11909 ctx.strokeStyle = border.color;
11911 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11913 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11917 if(label.type == 'Native') {
11919 ctx.fillStyle = ctx.strokeStyle = label.color;
11920 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11921 ctx.textBaseline = 'middle';
11923 acumValueLabel = gvl;
11925 acumValueLabel = valAcum;
11927 if(aggregates(node.name, valAcum)) {
11929 ctx.textAlign = 'center';
11930 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11933 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11934 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11935 (label ? label.size + config.labelOffset : 0));
11936 mtxt = ctx.measureText(acumValueLabel);
11937 boxWidth = mtxt.width+10;
11939 boxHeight = label.size+6;
11941 if(boxHeight + acum + config.labelOffset > gridHeight) {
11942 bottomPadding = acum - config.labelOffset - boxHeight;
11944 bottomPadding = acum + config.labelOffset + inset;
11948 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11951 boxY = -boxHeight/2;
11953 ctx.rotate(0 * Math.PI / 180);
11954 ctx.fillStyle = "rgba(255,255,255,.8)";
11955 if(boxHeight + acum + config.labelOffset > gridHeight) {
11956 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11958 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11959 ctx.fillStyle = ctx.strokeStyle = label.color;
11960 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11965 if(showLabels(node.name, valAcum, node)) {
11970 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11973 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11974 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11975 boxWidth = mtxt.width+10;
11978 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11979 leftPadding = acum - config.labelOffset - boxWidth - inset;
11981 leftPadding = acum + config.labelOffset;
11985 ctx.textAlign = 'left';
11986 ctx.translate(x + inset + leftPadding, y + height/2);
11987 boxHeight = label.size+6;
11989 boxY = -boxHeight/2;
11990 ctx.fillStyle = "rgba(255,255,255,.8)";
11992 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11993 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11995 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11997 ctx.fillStyle = label.color;
11998 ctx.rotate(0 * Math.PI / 180);
11999 ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
12003 //if the number of nodes greater than 8 rotate labels 45 degrees
12004 if(nodeCount > 8) {
12005 ctx.textAlign = 'left';
12006 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12007 ctx.rotate(45* Math.PI / 180);
12008 ctx.fillText(node.name, 0, 0);
12010 ctx.textAlign = 'center';
12011 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12019 'contains': function(node, mpos) {
12020 var pos = node.pos.getc(true),
12021 width = node.getData('width'),
12022 height = node.getData('height'),
12023 algnPos = this.getAlignedPos(pos, width, height),
12024 x = algnPos.x, y = algnPos.y,
12025 dimArray = node.getData('dimArray'),
12026 config = node.getData('config'),
12028 horz = config.orientation == 'horizontal';
12029 //bounding box check
12031 if(mpos.x < x || mpos.x > x + width
12032 || mpos.y > y + height || mpos.y < y) {
12036 if(mpos.x < x || mpos.x > x + width
12037 || mpos.y > y || mpos.y < y - height) {
12042 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
12043 var dimi = dimArray[i];
12044 var url = Url.decode(node.getData('linkArray')[i]);
12047 var intersec = acum;
12048 if(mpos.x <= intersec) {
12050 'name': node.getData('stringArray')[i],
12051 'color': node.getData('colorArray')[i],
12052 'value': node.getData('valueArray')[i],
12053 'valuelabel': node.getData('valuelabelArray')[i],
12054 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12061 var intersec = acum;
12062 if(mpos.y >= intersec) {
12064 'name': node.getData('stringArray')[i],
12065 'color': node.getData('colorArray')[i],
12066 'value': node.getData('valueArray')[i],
12067 'valuelabel': node.getData('valuelabelArray')[i],
12068 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12078 'barchart-grouped' : {
12079 'render' : function(node, canvas) {
12080 var pos = node.pos.getc(true),
12081 width = node.getData('width'),
12082 height = node.getData('height'),
12083 algnPos = this.getAlignedPos(pos, width, height),
12084 x = algnPos.x, y = algnPos.y,
12085 dimArray = node.getData('dimArray'),
12086 valueArray = node.getData('valueArray'),
12087 valuelabelArray = node.getData('valuelabelArray'),
12088 linkArray = node.getData('linkArray'),
12089 valueLength = valueArray.length,
12090 colorArray = node.getData('colorArray'),
12091 colorLength = colorArray.length,
12092 stringArray = node.getData('stringArray');
12094 var ctx = canvas.getCtx(),
12095 canvasSize = canvas.getSize(),
12097 border = node.getData('border'),
12098 gradient = node.getData('gradient'),
12099 config = node.getData('config'),
12100 horz = config.orientation == 'horizontal',
12101 aggregates = config.showAggregates,
12102 showLabels = config.showLabels,
12103 label = config.Label,
12104 shadow = config.shadow,
12105 margin = config.Margin,
12106 fixedDim = (horz? height : width) / valueLength;
12110 maxValue = Math.max.apply(null, dimArray);
12114 ctx.fillStyle = "rgba(0,0,0,.2)";
12115 if (colorArray && dimArray && stringArray && shadow.enable) {
12116 shadowThickness = shadow.size;
12118 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12119 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
12120 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
12123 ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
12128 if(nextBar && nextBar > dimArray[i]) {
12129 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
12130 } else if (nextBar && nextBar < dimArray[i]){
12131 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
12133 ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
12135 } else if (i> 0 && i<l-1) {
12136 if(nextBar && nextBar > dimArray[i]) {
12137 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);
12138 } else if (nextBar && nextBar < dimArray[i]){
12139 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
12141 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
12143 } else if (i == l-1) {
12144 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
12154 if (colorArray && dimArray && stringArray) {
12155 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12156 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12160 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
12161 x + dimArray[i]/2, y + fixedDim * (i + 1));
12163 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
12164 x + fixedDim * (i + 1), y - dimArray[i]/2);
12166 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
12167 function(v) { return (v * 0.8) >> 0; }));
12168 linear.addColorStop(0, color);
12169 linear.addColorStop(0.3, colorArray[i % colorLength]);
12170 linear.addColorStop(0.7, colorArray[i % colorLength]);
12171 linear.addColorStop(1, color);
12172 ctx.fillStyle = linear;
12175 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12177 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12179 if(border && border.name == stringArray[i]) {
12180 opt.acum = fixedDim * i;
12181 opt.dimValue = dimArray[i];
12183 acum += (dimArray[i] || 0);
12184 valAcum += (valueArray[i] || 0);
12185 ctx.fillStyle = ctx.strokeStyle = label.color;
12186 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12189 if(aggregates(node.name, valAcum) && label.type == 'Native') {
12190 if(valuelabelArray[i]) {
12191 acumValueLabel = valuelabelArray[i];
12193 acumValueLabel = valueArray[i];
12196 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12197 ctx.textAlign = 'left';
12198 ctx.textBaseline = 'top';
12199 ctx.fillStyle = "rgba(255,255,255,.8)";
12201 gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
12202 mtxt = ctx.measureText(acumValueLabel);
12203 boxWidth = mtxt.width+10;
12205 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12206 leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
12208 leftPadding = dimArray[i] + config.labelOffset + inset;
12210 boxHeight = label.size+6;
12211 boxX = x + leftPadding;
12212 boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
12216 if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12217 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12219 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12221 ctx.fillStyle = ctx.strokeStyle = label.color;
12222 ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12229 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12231 ctx.textAlign = 'center';
12234 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12235 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12236 (label ? label.size + config.labelOffset : 0));
12238 mtxt = ctx.measureText(acumValueLabel);
12239 boxWidth = mtxt.width+10;
12240 boxHeight = label.size+6;
12241 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12242 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12244 bottomPadding = dimArray[i] + config.labelOffset + inset;
12248 ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12250 boxX = -boxWidth/2;
12251 boxY = -boxHeight/2;
12252 ctx.fillStyle = "rgba(255,255,255,.8)";
12256 //ctx.rotate(270* Math.PI / 180);
12257 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12258 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12260 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12262 ctx.fillStyle = ctx.strokeStyle = label.color;
12263 ctx.fillText(acumValueLabel, 0,0);
12272 ctx.strokeStyle = border.color;
12274 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12276 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12280 if(label.type == 'Native') {
12282 ctx.fillStyle = ctx.strokeStyle = label.color;
12283 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12284 ctx.textBaseline = 'middle';
12286 if(showLabels(node.name, valAcum, node)) {
12288 ctx.textAlign = 'center';
12289 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12290 ctx.rotate(Math.PI / 2);
12291 ctx.fillText(node.name, 0, 0);
12293 ctx.textAlign = 'center';
12294 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12301 'contains': function(node, mpos) {
12302 var pos = node.pos.getc(true),
12303 width = node.getData('width'),
12304 height = node.getData('height'),
12305 algnPos = this.getAlignedPos(pos, width, height),
12306 x = algnPos.x, y = algnPos.y,
12307 dimArray = node.getData('dimArray'),
12308 len = dimArray.length,
12309 config = node.getData('config'),
12311 horz = config.orientation == 'horizontal',
12312 fixedDim = (horz? height : width) / len;
12313 //bounding box check
12315 if(mpos.x < x || mpos.x > x + width
12316 || mpos.y > y + height || mpos.y < y) {
12320 if(mpos.x < x || mpos.x > x + width
12321 || mpos.y > y || mpos.y < y - height) {
12326 for(var i=0, l=dimArray.length; i<l; i++) {
12327 var dimi = dimArray[i];
12328 var url = Url.decode(node.getData('linkArray')[i]);
12330 var limit = y + fixedDim * i;
12331 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12333 'name': node.getData('stringArray')[i],
12334 'color': node.getData('colorArray')[i],
12335 'value': node.getData('valueArray')[i],
12336 'valuelabel': node.getData('valuelabelArray')[i],
12337 'title': node.getData('titleArray')[i],
12338 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12344 var limit = x + fixedDim * i;
12345 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12347 'name': node.getData('stringArray')[i],
12348 'color': node.getData('colorArray')[i],
12349 'value': node.getData('valueArray')[i],
12350 'valuelabel': node.getData('valuelabelArray')[i],
12351 'title': node.getData('titleArray')[i],
12352 'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12362 'barchart-basic' : {
12363 'render' : function(node, canvas) {
12364 var pos = node.pos.getc(true),
12365 width = node.getData('width'),
12366 height = node.getData('height'),
12367 algnPos = this.getAlignedPos(pos, width, height),
12368 x = algnPos.x, y = algnPos.y,
12369 dimArray = node.getData('dimArray'),
12370 valueArray = node.getData('valueArray'),
12371 valuelabelArray = node.getData('valuelabelArray'),
12372 linkArray = node.getData('linkArray'),
12373 valueLength = valueArray.length,
12374 colorArray = node.getData('colorMono'),
12375 colorLength = colorArray.length,
12376 stringArray = node.getData('stringArray');
12378 var ctx = canvas.getCtx(),
12379 canvasSize = canvas.getSize(),
12381 border = node.getData('border'),
12382 gradient = node.getData('gradient'),
12383 config = node.getData('config'),
12384 horz = config.orientation == 'horizontal',
12385 aggregates = config.showAggregates,
12386 showLabels = config.showLabels,
12387 label = config.Label,
12388 fixedDim = (horz? height : width) / valueLength,
12389 margin = config.Margin;
12391 if (colorArray && dimArray && stringArray) {
12392 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12393 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12398 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
12399 x + dimArray[i]/2, y + fixedDim * (i + 1));
12401 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
12402 x + fixedDim * (i + 1), y - dimArray[i]/2);
12405 if(config.shadow.size) {
12406 shadowThickness = config.shadow.size;
12407 ctx.fillStyle = "rgba(0,0,0,.2)";
12409 ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12411 ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12415 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
12416 function(v) { return (v * 0.8) >> 0; }));
12417 linear.addColorStop(0, color);
12418 linear.addColorStop(0.3, colorArray[i % colorLength]);
12419 linear.addColorStop(0.7, colorArray[i % colorLength]);
12420 linear.addColorStop(1, color);
12421 ctx.fillStyle = linear;
12424 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12426 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12428 if(border && border.name == stringArray[i]) {
12429 opt.acum = fixedDim * i;
12430 opt.dimValue = dimArray[i];
12432 acum += (dimArray[i] || 0);
12433 valAcum += (valueArray[i] || 0);
12435 if(label.type == 'Native') {
12436 ctx.fillStyle = ctx.strokeStyle = label.color;
12437 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12438 if(aggregates(node.name, valAcum)) {
12439 if(valuelabelArray[i]) {
12440 acumValueLabel = valuelabelArray[i];
12442 acumValueLabel = valueArray[i];
12445 ctx.textAlign = 'center';
12446 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12449 gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12450 (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12451 (label ? label.size + config.labelOffset : 0));
12452 mtxt = ctx.measureText(acumValueLabel);
12453 boxWidth = mtxt.width+10;
12455 boxHeight = label.size+6;
12457 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12458 bottomPadding = dimArray[i] - config.labelOffset - inset;
12460 bottomPadding = dimArray[i] + config.labelOffset + inset;
12464 ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12467 boxY = -boxHeight/2;
12469 //ctx.rotate(270* Math.PI / 180);
12470 ctx.fillStyle = "rgba(255,255,255,.6)";
12471 if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12472 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12474 // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12475 ctx.fillStyle = ctx.strokeStyle = label.color;
12476 ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12485 ctx.strokeStyle = border.color;
12487 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12489 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12493 if(label.type == 'Native') {
12495 ctx.fillStyle = ctx.strokeStyle = label.color;
12496 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12497 ctx.textBaseline = 'middle';
12498 if(showLabels(node.name, valAcum, node)) {
12502 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12503 mtxt = ctx.measureText(node.name + ": " + valAcum);
12504 boxWidth = mtxt.width+10;
12507 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12508 leftPadding = acum - config.labelOffset - boxWidth - inset;
12510 leftPadding = acum + config.labelOffset;
12514 ctx.textAlign = 'left';
12515 ctx.translate(x + inset + leftPadding, y + height/2);
12516 boxHeight = label.size+6;
12518 boxY = -boxHeight/2;
12519 ctx.fillStyle = "rgba(255,255,255,.8)";
12522 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12523 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12525 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12528 ctx.fillStyle = label.color;
12529 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12533 if(stringArray.length > 8) {
12534 ctx.textAlign = 'left';
12535 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12536 ctx.rotate(45* Math.PI / 180);
12537 ctx.fillText(node.name, 0, 0);
12539 ctx.textAlign = 'center';
12540 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12549 'contains': function(node, mpos) {
12550 var pos = node.pos.getc(true),
12551 width = node.getData('width'),
12552 height = node.getData('height'),
12553 config = node.getData('config'),
12554 algnPos = this.getAlignedPos(pos, width, height),
12555 x = algnPos.x, y = algnPos.y ,
12556 dimArray = node.getData('dimArray'),
12557 len = dimArray.length,
12559 horz = config.orientation == 'horizontal',
12560 fixedDim = (horz? height : width) / len;
12562 //bounding box check
12564 if(mpos.x < x || mpos.x > x + width
12565 || mpos.y > y + height || mpos.y < y) {
12569 if(mpos.x < x || mpos.x > x + width
12570 || mpos.y > y || mpos.y < y - height) {
12575 for(var i=0, l=dimArray.length; i<l; i++) {
12576 var dimi = dimArray[i];
12577 var url = Url.decode(node.getData('linkArray')[i]);
12579 var limit = y + fixedDim * i;
12580 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12582 'name': node.getData('stringArray')[i],
12583 'color': node.getData('colorArray')[i],
12584 'value': node.getData('valueArray')[i],
12585 'valuelabel': node.getData('valuelabelArray')[i],
12586 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12592 var limit = x + fixedDim * i;
12593 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12595 'name': node.getData('stringArray')[i],
12596 'color': node.getData('colorArray')[i],
12597 'value': node.getData('valueArray')[i],
12598 'valuelabel': node.getData('valuelabelArray')[i],
12599 'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12614 A visualization that displays stacked bar charts.
12616 Constructor Options:
12618 See <Options.BarChart>.
12621 $jit.BarChart = new Class({
12623 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12627 initialize: function(opt) {
12628 this.controller = this.config =
12629 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12630 Label: { type: 'Native' }
12632 //set functions for showLabels and showAggregates
12633 var showLabels = this.config.showLabels,
12634 typeLabels = $.type(showLabels),
12635 showAggregates = this.config.showAggregates,
12636 typeAggregates = $.type(showAggregates);
12637 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12638 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12639 Options.Fx.clearCanvas = false;
12640 this.initializeViz();
12643 initializeViz: function() {
12644 var config = this.config, that = this;
12645 var nodeType = config.type.split(":")[0],
12646 horz = config.orientation == 'horizontal',
12648 var st = new $jit.ST({
12649 injectInto: config.injectInto,
12650 orientation: horz? 'left' : 'bottom',
12651 background: config.background,
12652 renderBackground: config.renderBackground,
12653 backgroundColor: config.backgroundColor,
12654 colorStop1: config.colorStop1,
12655 colorStop2: config.colorStop2,
12657 nodeCount: config.nodeCount,
12658 siblingOffset: config.barsOffset,
12660 withLabels: config.Label.type != 'Native',
12661 useCanvas: config.useCanvas,
12663 type: config.Label.type
12667 type: 'barchart-' + nodeType,
12676 enable: config.Tips.enable,
12679 onShow: function(tip, node, contains) {
12680 var elem = contains;
12681 config.Tips.onShow(tip, elem, node);
12682 if(elem.link != 'undefined' && elem.link != '') {
12683 document.body.style.cursor = 'pointer';
12686 onHide: function(call) {
12687 document.body.style.cursor = 'default';
12694 onClick: function(node, eventInfo, evt) {
12695 if(!config.Events.enable) return;
12696 var elem = eventInfo.getContains();
12697 config.Events.onClick(elem, eventInfo, evt);
12699 onMouseMove: function(node, eventInfo, evt) {
12700 if(!config.hoveredColor) return;
12702 var elem = eventInfo.getContains();
12703 that.select(node.id, elem.name, elem.index);
12705 that.select(false, false, false);
12709 onCreateLabel: function(domElement, node) {
12710 var labelConf = config.Label,
12711 valueArray = node.getData('valueArray'),
12712 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12713 grouped = config.type.split(':')[0] == 'grouped',
12714 horz = config.orientation == 'horizontal';
12716 wrapper: document.createElement('div'),
12717 aggregate: document.createElement('div'),
12718 label: document.createElement('div')
12721 var wrapper = nlbs.wrapper,
12722 label = nlbs.label,
12723 aggregate = nlbs.aggregate,
12724 wrapperStyle = wrapper.style,
12725 labelStyle = label.style,
12726 aggregateStyle = aggregate.style;
12727 //store node labels
12728 nodeLabels[node.id] = nlbs;
12730 wrapper.appendChild(label);
12731 wrapper.appendChild(aggregate);
12732 if(!config.showLabels(node.name, acum, node)) {
12733 labelStyle.display = 'none';
12735 if(!config.showAggregates(node.name, acum, node)) {
12736 aggregateStyle.display = 'none';
12738 wrapperStyle.position = 'relative';
12739 wrapperStyle.overflow = 'visible';
12740 wrapperStyle.fontSize = labelConf.size + 'px';
12741 wrapperStyle.fontFamily = labelConf.family;
12742 wrapperStyle.color = labelConf.color;
12743 wrapperStyle.textAlign = 'center';
12744 aggregateStyle.position = labelStyle.position = 'absolute';
12746 domElement.style.width = node.getData('width') + 'px';
12747 domElement.style.height = node.getData('height') + 'px';
12748 aggregateStyle.left = "0px";
12749 labelStyle.left = config.labelOffset + 'px';
12750 labelStyle.whiteSpace = "nowrap";
12751 label.innerHTML = node.name;
12753 domElement.appendChild(wrapper);
12755 onPlaceLabel: function(domElement, node) {
12756 if(!nodeLabels[node.id]) return;
12757 var labels = nodeLabels[node.id],
12758 wrapperStyle = labels.wrapper.style,
12759 labelStyle = labels.label.style,
12760 aggregateStyle = labels.aggregate.style,
12761 grouped = config.type.split(':')[0] == 'grouped',
12762 horz = config.orientation == 'horizontal',
12763 dimArray = node.getData('dimArray'),
12764 valArray = node.getData('valueArray'),
12765 nodeCount = node.getData('nodeCount'),
12766 valueLength = valArray.length;
12767 valuelabelArray = node.getData('valuelabelArray'),
12768 stringArray = node.getData('stringArray'),
12769 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12770 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12771 font = parseInt(wrapperStyle.fontSize, 10),
12772 domStyle = domElement.style,
12773 fixedDim = (horz? height : width) / valueLength;
12776 if(dimArray && valArray) {
12777 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12779 aggregateStyle.width = width - config.labelOffset + "px";
12780 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12781 if(dimArray[i] > 0) {
12782 acum+= valArray[i];
12785 if(config.showLabels(node.name, acum, node)) {
12786 labelStyle.display = '';
12788 labelStyle.display = 'none';
12790 if(config.showAggregates(node.name, acum, node)) {
12791 aggregateStyle.display = '';
12793 aggregateStyle.display = 'none';
12795 if(config.orientation == 'horizontal') {
12796 aggregateStyle.textAlign = 'right';
12797 labelStyle.textAlign = 'left';
12798 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12799 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12800 domElement.style.height = wrapperStyle.height = height + 'px';
12802 aggregateStyle.top = (-font - config.labelOffset) + 'px';
12803 labelStyle.top = (config.labelOffset + height) + 'px';
12804 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12805 domElement.style.height = wrapperStyle.height = height + 'px';
12806 if(stringArray.length > 8) {
12807 labels.label.className = "rotatedLabelReverse";
12808 labelStyle.textAlign = "left";
12809 labelStyle.top = config.labelOffset + height + width/2 + "px";
12815 labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12816 labels.aggregate.innerHTML = "";
12821 maxValue = Math.max.apply(null,dimArray);
12822 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12823 valueLabelDim = 50;
12824 valueLabel = document.createElement('div');
12825 valueLabel.innerHTML = valuelabelArray[i];
12826 // valueLabel.class = "rotatedLabel";
12827 valueLabel.className = "rotatedLabel";
12828 valueLabel.style.position = "absolute";
12829 valueLabel.style.textAlign = "left";
12830 valueLabel.style.verticalAlign = "middle";
12831 valueLabel.style.height = valueLabelDim + "px";
12832 valueLabel.style.width = valueLabelDim + "px";
12833 valueLabel.style.top = (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12834 valueLabel.style.left = (fixedDim * i) + "px";
12835 labels.wrapper.appendChild(valueLabel);
12838 labels.aggregate.innerHTML = acum;
12845 var size = st.canvas.getSize(),
12846 l = config.nodeCount,
12847 margin = config.Margin;
12848 title = config.Title;
12849 subtitle = config.Subtitle,
12850 grouped = config.type.split(':')[0] == 'grouped',
12851 margin = config.Margin,
12852 ticks = config.Ticks,
12853 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12854 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12855 horz = config.orientation == 'horizontal',
12856 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12857 fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12858 whiteSpace = size.width - (marginWidth + (fixedDim * l));
12859 //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
12860 if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12862 //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12863 if(!grouped && !horz) {
12864 st.config.siblingOffset = whiteSpace/(l+1);
12871 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12872 if(config.Ticks.enable) {
12873 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;
12875 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12878 st.config.offsetY = -size.height/2 + margin.bottom
12879 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12880 if(config.Ticks.enable) {
12881 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12883 st.config.offsetX = (margin.right - margin.left)/2;
12887 this.canvas = this.st.canvas;
12892 renderTitle: function() {
12893 var canvas = this.canvas,
12894 size = canvas.getSize(),
12895 config = this.config,
12896 margin = config.Margin,
12897 label = config.Label,
12898 title = config.Title;
12899 ctx = canvas.getCtx();
12900 ctx.fillStyle = title.color;
12901 ctx.textAlign = 'left';
12902 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12903 if(label.type == 'Native') {
12904 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12908 renderSubtitle: function() {
12909 var canvas = this.canvas,
12910 size = canvas.getSize(),
12911 config = this.config,
12912 margin = config.Margin,
12913 label = config.Label,
12914 subtitle = config.Subtitle,
12915 nodeCount = config.nodeCount,
12916 horz = config.orientation == 'horizontal' ? true : false,
12917 ctx = canvas.getCtx();
12918 ctx.fillStyle = title.color;
12919 ctx.textAlign = 'left';
12920 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12921 if(label.type == 'Native') {
12922 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12926 renderScrollNote: function() {
12927 var canvas = this.canvas,
12928 size = canvas.getSize(),
12929 config = this.config,
12930 margin = config.Margin,
12931 label = config.Label,
12932 note = config.ScrollNote;
12933 ctx = canvas.getCtx();
12934 ctx.fillStyle = title.color;
12935 title = config.Title;
12936 ctx.textAlign = 'center';
12937 ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12938 if(label.type == 'Native') {
12939 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12943 renderTicks: function() {
12945 var canvas = this.canvas,
12946 size = canvas.getSize(),
12947 config = this.config,
12948 margin = config.Margin,
12949 ticks = config.Ticks,
12950 title = config.Title,
12951 subtitle = config.Subtitle,
12952 label = config.Label,
12953 shadow = config.shadow;
12954 horz = config.orientation == 'horizontal',
12955 grouped = config.type.split(':')[0] == 'grouped',
12956 ctx = canvas.getCtx();
12957 ctx.strokeStyle = ticks.color;
12958 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12960 ctx.textAlign = 'center';
12961 ctx.textBaseline = 'middle';
12963 idLabel = canvas.id + "-label";
12965 container = document.getElementById(idLabel);
12969 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12970 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12971 segmentLength = grid/ticks.segments;
12972 ctx.fillStyle = ticks.color;
12974 // Main horizontal line
12976 var yTop = size.height / 2 - margin.bottom - config.labelOffset - label.size - (subtitle.text ? subtitle.size + subtitle.offset : 0) + (shadow.enable ? shadow.size : 0);
12977 var xLength = size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0);
12979 ctx.fillRect(xTop, yTop, xLength, yLength);
12981 maxTickValue = config.Ticks.maxValue;
12982 var humanNumber = config.Ticks.humanNumber;
12983 var segments = config.Ticks.segments;
12984 var tempHumanNumber = humanNumber;
12985 var humanNumberPow = 0;
12986 // 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.
12987 // humanNumberPow is required for work with number less than 1.
12988 while (tempHumanNumber % 1 != 0)
12990 tempHumanNumber = tempHumanNumber * 10;
12994 // Tries convert length of line to steps in pixels. 4 steps, 160 length - 40 pixels per step
12995 var pixelsPerStep = xLength / maxTickValue;
12996 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);
12997 for (var i = 0; i <= segments; i++)
12999 var iX = Math.round(xTop + i * pixelsPerStep * humanNumber);
13001 ctx.translate(iX, yTop + yLength + margin.top);
13002 ctx.rotate(0 * Math.PI / 2 * 3);
13003 ctx.fillStyle = label.color;
13004 // Float numbers fix (0.45 can be displayed as 0.44466666)
13005 var labelText = humanNumber * Math.pow(10, humanNumberPow) * i;
13006 labelText = labelText * Math.pow(10, -humanNumberPow);
13007 if (config.showLabels)
13009 // Filling Text through canvas or html elements
13010 if (label.type == 'Native')
13012 ctx.fillText(labelText, 0, 0); // For coords see ctx.translate above
13016 //html labels on y axis
13017 labelDiv = document.createElement('div');
13018 labelDiv.innerHTML = labelText;
13019 labelDiv.style.top = Math.round(size.height - margin.bottom - config.labelOffset) + "px";
13020 labelDiv.style.left = Math.round(margin.left - labelDim / 2 + i * pixelsPerStep * humanNumber) + "px";
13021 labelDiv.style.width = labelDim + "px";
13022 labelDiv.style.height = labelDim + "px";
13023 labelDiv.style.textAlign = "center";
13024 labelDiv.style.verticalAlign = "middle";
13025 labelDiv.style.position = "absolute";
13026 labelDiv.style.background = '1px solid red';
13027 container.appendChild(labelDiv);
13031 ctx.fillStyle = ticks.color;
13033 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));
13037 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
13038 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
13039 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)),
13040 segmentLength = grid/ticks.segments;
13041 ctx.fillStyle = ticks.color;
13043 // Main horizontal line
13044 var xTop = -size.width / 2 + margin.left + config.labelOffset + label.size - 1;
13045 var yTop = -size.height / 2 + margin.top + (title.text ? title.size + title.offset : 0);
13047 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);
13048 ctx.fillRect(xTop, yTop, xLength, yLength);
13050 maxTickValue = config.Ticks.maxValue;
13051 var humanNumber = config.Ticks.humanNumber;
13052 var segments = config.Ticks.segments;
13053 var tempHumanNumber = humanNumber;
13054 var humanNumberPow = 0;
13055 // 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.
13056 // humanNumberPow is required for work with number less than 1.
13057 while (tempHumanNumber % 1 != 0)
13059 tempHumanNumber = tempHumanNumber * 10;
13063 // Tries convert length of line to steps in pixels. 4 steps, 160 length - 40 pixels per step
13064 var pixelsPerStep = yLength / maxTickValue;
13065 for (var i = 0; i <= segments; i++)
13067 var iY = Math.round(yTop + yLength - i * pixelsPerStep * humanNumber);
13069 ctx.translate(-size.width / 2 + margin.left, iY);
13070 ctx.rotate(0 * Math.PI / 2 * 3);
13071 ctx.fillStyle = label.color;
13072 // Float numbers fix (0.45 can be displayed as 0.44466666)
13073 var labelText = humanNumber * Math.pow(10, humanNumberPow) * i;
13074 labelText = labelText * Math.pow(10, -humanNumberPow);
13075 if (config.showLabels)
13077 // Filling Text through canvas or html elements
13078 if (label.type == 'Native')
13080 ctx.fillText(labelText, 0, 0); // For coords see ctx.translate above
13084 //html labels on y axis
13085 labelDiv = document.createElement('div');
13086 labelDiv.innerHTML = labelText;
13087 labelDiv.className = "rotatedLabel";
13088 // labelDiv.class = "rotatedLabel";
13089 labelDiv.style.top = Math.round(htmlOrigin - labelDim / 2 - i * pixelsPerStep * humanNumber) + "px";
13090 labelDiv.style.left = margin.left + "px";
13091 labelDiv.style.width = labelDim + "px";
13092 labelDiv.style.height = labelDim + "px";
13093 labelDiv.style.textAlign = "center";
13094 labelDiv.style.verticalAlign = "middle";
13095 labelDiv.style.position = "absolute";
13096 container.appendChild(labelDiv);
13100 ctx.fillStyle = ticks.color;
13101 ctx.fillRect(-size.width / 2 + margin.left + config.labelOffset + label.size, iY, size.width - margin.right - margin.left - config.labelOffset - label.size, 1);
13110 renderBackground: function() {
13111 var canvas = this.canvas,
13112 config = this.config,
13113 backgroundColor = config.backgroundColor,
13114 size = canvas.getSize(),
13115 ctx = canvas.getCtx();
13116 ctx.fillStyle = backgroundColor;
13117 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13120 clear: function() {
13121 var canvas = this.canvas;
13122 var ctx = canvas.getCtx(),
13123 size = canvas.getSize();
13124 ctx.fillStyle = "rgba(255,255,255,0)";
13125 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13126 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13128 resizeGraph: function(json,width) {
13129 var canvas = this.canvas,
13130 size = canvas.getSize(),
13131 config = this.config,
13132 orgHeight = size.height,
13133 margin = config.Margin,
13135 grouped = config.type.split(':')[0] == 'grouped',
13136 horz = config.orientation == 'horizontal';
13138 canvas.resize(width,orgHeight);
13139 if(typeof FlashCanvas == "undefined") {
13142 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13145 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
13148 this.loadJSON(json);
13155 Loads JSON data into the visualization.
13159 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>.
13163 var barChart = new $jit.BarChart(options);
13164 barChart.loadJSON(json);
13167 loadJSON: function(json) {
13168 if(this.busy) return;
13171 var prefix = $.time(),
13174 name = $.splat(json.label),
13175 color = $.splat(json.color || this.colors),
13176 config = this.config,
13177 gradient = !!config.type.split(":")[1],
13178 renderBackground = config.renderBackground,
13179 animate = config.animate,
13180 ticks = config.Ticks,
13181 title = config.Title,
13182 note = config.ScrollNote,
13183 subtitle = config.Subtitle,
13184 horz = config.orientation == 'horizontal',
13186 colorLength = color.length,
13187 nameLength = name.length;
13188 groupTotalValue = 0;
13189 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13190 var val = values[i];
13191 var valArray = $.splat(val.values);
13192 groupTotalValue += parseFloat(valArray.sum());
13195 for(var i=0, values=json.values, l=values.length; i<l; i++) {
13196 var val = values[i];
13197 var valArray = $.splat(values[i].values);
13198 var valuelabelArray = $.splat(values[i].valuelabels);
13199 var linkArray = $.splat(values[i].links);
13200 var titleArray = $.splat(values[i].titles);
13201 var barTotalValue = valArray.sum();
13204 'id': prefix + val.label,
13209 '$linkArray': linkArray,
13210 '$gvl': val.gvaluelabel,
13211 '$titleArray': titleArray,
13212 '$valueArray': valArray,
13213 '$valuelabelArray': valuelabelArray,
13214 '$colorArray': color,
13215 '$colorMono': $.splat(color[i % colorLength]),
13216 '$stringArray': name,
13217 '$barTotalValue': barTotalValue,
13218 '$groupTotalValue': groupTotalValue,
13219 '$nodeCount': values.length,
13220 '$gradient': gradient,
13227 'id': prefix + '$root',
13238 this.normalizeDims();
13240 if(renderBackground) {
13241 this.renderBackground();
13244 if(!animate && ticks.enable) {
13245 this.renderTicks();
13247 if(!animate && note.text) {
13248 this.renderScrollNote();
13250 if(!animate && title.text) {
13251 this.renderTitle();
13253 if(!animate && subtitle.text) {
13254 this.renderSubtitle();
13258 st.select(st.root);
13262 modes: ['node-property:width:dimArray'],
13264 onComplete: function() {
13270 modes: ['node-property:height:dimArray'],
13272 onComplete: function() {
13285 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.
13289 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13290 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13295 barChart.updateJSON(json, {
13296 onComplete: function() {
13297 alert('update complete!');
13302 updateJSON: function(json, onComplete) {
13303 if(this.busy) return;
13307 var graph = st.graph;
13308 var values = json.values;
13309 var animate = this.config.animate;
13311 var horz = this.config.orientation == 'horizontal';
13312 $.each(values, function(v) {
13313 var n = graph.getByName(v.label);
13315 n.setData('valueArray', $.splat(v.values));
13317 n.setData('stringArray', $.splat(json.label));
13321 this.normalizeDims();
13323 st.select(st.root);
13327 modes: ['node-property:width:dimArray'],
13329 onComplete: function() {
13331 onComplete && onComplete.onComplete();
13336 modes: ['node-property:height:dimArray'],
13338 onComplete: function() {
13340 onComplete && onComplete.onComplete();
13347 //adds the little brown bar when hovering the node
13348 select: function(id, name) {
13350 if(!this.config.hoveredColor) return;
13351 var s = this.selected;
13352 if(s.id != id || s.name != name) {
13355 s.color = this.config.hoveredColor;
13356 this.st.graph.eachNode(function(n) {
13358 n.setData('border', s);
13360 n.setData('border', false);
13370 Returns an object containing as keys the legend names and as values hex strings with color values.
13375 var legend = barChart.getLegend();
13378 getLegend: function() {
13379 var legend = new Array();
13380 var name = new Array();
13381 var color = new Array();
13383 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13386 var colors = n.getData('colorArray'),
13387 len = colors.length;
13388 $.each(n.getData('stringArray'), function(s, i) {
13389 color[i] = colors[i % len];
13392 legend['name'] = name;
13393 legend['color'] = color;
13398 Method: getMaxValue
13400 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13405 var ans = barChart.getMaxValue();
13408 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13413 //will return 100 for all BarChart instances,
13414 //displaying all of them with the same scale
13415 $jit.BarChart.implement({
13416 'getMaxValue': function() {
13423 getMaxValue: function() {
13424 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13425 this.st.graph.eachNode(function(n) {
13426 var valArray = n.getData('valueArray'),
13428 if(!valArray) return;
13430 $.each(valArray, function(v) {
13434 acum = Math.max.apply(null, valArray);
13436 maxValue = maxValue>acum? maxValue:acum;
13441 setBarType: function(type) {
13442 this.config.type = type;
13443 this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13446 normalizeDims: function() {
13447 //number of elements
13448 var root = this.st.graph.getNode(this.st.root), l=0;
13449 root.eachAdjacency(function() {
13452 var maxValue = this.getMaxValue() || 1,
13453 size = this.st.canvas.getSize(),
13454 config = this.config,
13455 margin = config.Margin,
13456 ticks = config.Ticks,
13457 title = config.Title,
13458 subtitle = config.Subtitle,
13459 grouped = config.type.split(':')[0] == 'grouped',
13460 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13461 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13462 horz = config.orientation == 'horizontal',
13463 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13464 animate = config.animate,
13465 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13467 - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13468 dim1 = horz? 'height':'width',
13469 dim2 = horz? 'width':'height',
13470 basic = config.type.split(':')[0] == 'basic';
13472 // Bug #47147 Correct detection of maxTickValue and step size for asix labels
13473 var iDirection = 10; // We need this var for convert value. 10^2 = 100
13474 var zeroCount = 0; // Pow for iDirection for detection of size of human step.
13475 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.
13476 // Tries to get two first digits from maxValue
13479 // Tries to calculate zeroCount
13480 // if iNumber = 100 we will get zeroCount = 2, iNumber = 0.1
13481 while (iNumber >= 1)
13484 iNumber = iNumber / 10;
13486 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
13490 iDirection = 0.1; // if iNumber is less than 1 we should change iDirection. 0.1^2 = 0.01
13491 // Tries to calculate zeroCount
13492 // if iNumber = 0.01 we will get zeroCount = 2, iNumber = 1
13493 while (iNumber < 1)
13496 iNumber = iNumber * 10;
13498 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
13500 var humanNumber = 0;
13501 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.
13502 // 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.
13503 while (iNumberTemp % 5 != 0)
13507 var isFound = false;
13508 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
13509 // Tries to find humanNumber. Our step is 5. ticks.segments is number of lines = 4 (for example)
13510 // 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
13511 while (isFound == false)
13513 if (iNumberTemp % ticks.segments == 0)
13515 humanNumber = iNumberTemp / ticks.segments;
13519 iNumberTemp = iNumberTemp + 5;
13521 // Getting real values
13522 var maxTickValue = config.Ticks.maxValue = maxTickValue = iNumberTemp * Math.pow(iDirection, zeroCount - 1);
13523 config.Ticks.humanNumber = humanNumber = humanNumber * Math.pow(iDirection, zeroCount - 1);
13524 config.Ticks.segments = Math.floor(maxTickValue / humanNumber);
13526 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13529 this.st.graph.eachNode(function(n) {
13530 var acum = 0, animateValue = [];
13531 $.each(n.getData('valueArray'), function(v) {
13533 animateValue.push(0);
13537 fixedDim = animateValue.length * 40;
13539 n.setData(dim1, fixedDim);
13543 n.setData(dim2, acum * height / maxValue, 'end');
13544 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13545 return n * height / maxValue;
13547 var dimArray = n.getData('dimArray');
13549 n.setData('dimArray', animateValue);
13555 n.setData(dim2, acum * height / maxTickValue);
13556 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13557 return n * height / maxTickValue;
13560 n.setData(dim2, acum * height / maxValue);
13561 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13562 return n * height / maxValue;
13570 //funnel chart options
13573 Options.FunnelChart = {
13577 type: 'stacked', //stacked, grouped, : gradient
13578 labelOffset: 3, //label offset
13579 barsOffset: 0, //distance between bars
13580 hoveredColor: '#9fd4ff',
13581 orientation: 'vertical',
13582 showAggregates: true,
13595 $jit.ST.Plot.NodeTypes.implement({
13596 'funnelchart-basic' : {
13597 'render' : function(node, canvas) {
13598 var pos = node.pos.getc(true),
13599 width = node.getData('width'),
13600 height = node.getData('height'),
13601 algnPos = this.getAlignedPos(pos, width, height),
13602 x = algnPos.x, y = algnPos.y,
13603 dimArray = node.getData('dimArray'),
13604 valueArray = node.getData('valueArray'),
13605 valuelabelArray = node.getData('valuelabelArray'),
13606 linkArray = node.getData('linkArray'),
13607 colorArray = node.getData('colorArray'),
13608 colorLength = colorArray.length,
13609 stringArray = node.getData('stringArray');
13610 var ctx = canvas.getCtx(),
13612 border = node.getData('border'),
13613 gradient = node.getData('gradient'),
13614 config = node.getData('config'),
13615 horz = config.orientation == 'horizontal',
13616 aggregates = config.showAggregates,
13617 showLabels = config.showLabels,
13618 label = config.Label,
13619 size = canvas.getSize(),
13620 labelOffset = config.labelOffset + 10;
13621 minWidth = width * .25;
13624 if (colorArray && dimArray && stringArray) {
13625 var newDimArray = this.positions(dimArray, label.size);
13627 // horizontal lines
13628 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13629 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13631 if(label.type == 'Native') {
13632 if(showLabels(node.name, valAcum, node)) {
13633 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13634 var stringValue = stringArray[i];
13635 var valueLabel = String(valuelabelArray[i]);
13636 var mV = ctx.measureText(stringValue);
13637 var mVL = ctx.measureText(valueLabel);
13642 next_mV = ctx.measureText(stringArray[i + 1]);
13643 next_mVL = ctx.measureText(String(valuelabelArray[i + 1]));
13650 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13651 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13652 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13653 var bottomWidth = minWidth + ((acum) * ratio);
13654 var bottomWidthLabel = minWidth + (newDimArray[i].position * ratio);
13655 var labelOffsetRight = (newDimArray[i].filament) ? (next_mV.width + 25) : 0;
13656 var labelOffsetLeft = (newDimArray[i].filament) ? (next_mVL.width + 25) : 0;
13657 // ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13661 ctx.moveTo(bottomWidth/2,y - acum); //
13662 ctx.lineTo(bottomWidthLabel / 2 + (labelOffset - 10), y - newDimArray[i].position); // top right
13663 ctx.lineTo(bottomWidthLabel / 2 + (labelOffset) + labelOffsetRight + mV.width, y - newDimArray[i].position); // bottom right
13667 ctx.moveTo(-bottomWidth/2,y - acum); //
13668 ctx.lineTo( - bottomWidthLabel / 2 - (labelOffset - 10), y - newDimArray[i].position); // top right
13669 ctx.lineTo( - bottomWidthLabel / 2 - (labelOffset) - labelOffsetLeft - mVL.width, y - newDimArray[i].position); // bottom right
13674 acum += (dimArray[i] || 0);
13675 valAcum += (valueArray[i] || 0);
13682 //funnel segments and labels
13683 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13684 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13685 var colori = colorArray[i % colorLength];
13686 if(label.type == 'Native') {
13687 var stringValue = stringArray[i];
13688 var valueLabel = String(valuelabelArray[i]);
13689 var mV = ctx.measureText(stringValue);
13690 var mVL = ctx.measureText(valueLabel);
13697 next_mV = ctx.measureText(stringArray[i + 1]);
13698 next_mVL = ctx.measureText(String(valuelabelArray[i + 1]));
13705 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13706 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13707 var labelOffsetRight = (newDimArray[i].filament) ? (next_mV.width + 20) : 0;
13708 var labelOffsetLeft = (newDimArray[i].filament) ? (next_mVL.width + 20) : 0;
13710 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13711 var bottomWidth = minWidth + ((acum) * ratio);
13712 var bottomWidthLabel = minWidth + (newDimArray[i].position * ratio);
13717 linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13718 var colorRgb = $.hexToRgb(colori);
13719 var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
13720 function(v) { return (v * .5) >> 0; });
13721 linear.addColorStop(0, 'rgba('+color+',1)');
13722 linear.addColorStop(0.5, 'rgba('+colorRgb+',1)');
13723 linear.addColorStop(1, 'rgba('+color+',1)');
13724 ctx.fillStyle = linear;
13728 ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13729 ctx.lineTo(topWidth/2,y - acum - dimArray[i]); // top right
13730 ctx.lineTo(bottomWidth/2,y - acum); // bottom right
13731 ctx.lineTo(-bottomWidth/2,y - acum); // bottom left
13736 if(border && border.name == stringArray[i]) {
13738 opt.dimValue = dimArray[i];
13745 ctx.strokeStyle = border.color;
13747 //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13751 if(label.type == 'Native') {
13753 ctx.fillStyle = ctx.strokeStyle = label.color;
13754 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13755 ctx.textBaseline = 'middle';
13757 acumValueLabel = valAcum;
13759 if(showLabels(node.name, valAcum, node)) {
13762 ctx.textAlign = 'left';
13763 ctx.fillText(stringArray[i], (bottomWidthLabel / 2) + labelOffset + labelOffsetRight, y - newDimArray[i].position - label.size / 2);
13764 ctx.textAlign = 'right';
13765 ctx.fillText(valuelabelArray[i], (- bottomWidthLabel / 2) - labelOffset - labelOffsetLeft, y - newDimArray[i].position - label.size / 2);
13770 acum += (dimArray[i] || 0);
13771 valAcum += (valueArray[i] || 0);
13777 'contains': function(node, mpos) {
13778 var pos = node.pos.getc(true),
13779 width = node.getData('width'),
13780 height = node.getData('height'),
13781 algnPos = this.getAlignedPos(pos, width, height),
13782 x = algnPos.x, y = algnPos.y,
13783 dimArray = node.getData('dimArray'),
13784 config = node.getData('config'),
13785 st = node.getData('st'),
13787 horz = config.orientation == 'horizontal',
13788 minWidth = width * .25;
13790 canvas = node.getData('canvas'),
13791 size = canvas.getSize(),
13792 offsetY = st.config.offsetY;
13793 //bounding box check
13795 if(mpos.y > y || mpos.y < y - height) {
13799 var newY = Math.abs(mpos.y + offsetY);
13800 var bound = minWidth + (newY * ratio);
13801 var boundLeft = -bound/2;
13802 var boundRight = bound/2;
13803 if(mpos.x < boundLeft || mpos.x > boundRight ) {
13809 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13810 var dimi = dimArray[i];
13814 var url = Url.decode(node.getData('linkArray')[i]);
13816 var intersec = acum;
13817 if(mpos.y >= intersec) {
13819 'name': node.getData('stringArray')[i],
13820 'color': node.getData('colorArray')[i],
13821 'value': node.getData('valueArray')[i],
13822 'percentage': node.getData('percentageArray')[i],
13823 'valuelabel': node.getData('valuelabelArray')[i],
13838 A visualization that displays funnel charts.
13840 Constructor Options:
13842 See <Options.FunnelChart>.
13845 $jit.FunnelChart = new Class({
13847 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13851 initialize: function(opt) {
13852 this.controller = this.config =
13853 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13854 Label: { type: 'Native' }
13856 //set functions for showLabels and showAggregates
13857 var showLabels = this.config.showLabels,
13858 typeLabels = $.type(showLabels),
13859 showAggregates = this.config.showAggregates,
13860 typeAggregates = $.type(showAggregates);
13861 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13862 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13863 Options.Fx.clearCanvas = false;
13864 this.initializeViz();
13867 initializeViz: function() {
13868 var config = this.config, that = this;
13869 var nodeType = config.type.split(":")[0],
13870 horz = config.orientation == 'horizontal',
13872 var st = new $jit.ST({
13873 injectInto: config.injectInto,
13874 orientation: horz? 'left' : 'bottom',
13876 background: config.background,
13877 renderBackground: config.renderBackground,
13878 backgroundColor: config.backgroundColor,
13879 colorStop1: config.colorStop1,
13880 colorStop2: config.colorStop2,
13881 siblingOffset: config.segmentOffset,
13883 withLabels: config.Label.type != 'Native',
13884 useCanvas: config.useCanvas,
13886 type: config.Label.type
13890 type: 'funnelchart-' + nodeType,
13899 enable: config.Tips.enable,
13902 onShow: function(tip, node, contains) {
13903 var elem = contains;
13904 config.Tips.onShow(tip, elem, node);
13905 if(elem.link != 'undefined' && elem.link != '') {
13906 document.body.style.cursor = 'pointer';
13909 onHide: function(call) {
13910 document.body.style.cursor = 'default';
13917 onClick: function(node, eventInfo, evt) {
13918 if(!config.Events.enable) return;
13919 var elem = eventInfo.getContains();
13920 config.Events.onClick(elem, eventInfo, evt);
13922 onMouseMove: function(node, eventInfo, evt) {
13923 if(!config.hoveredColor) return;
13925 var elem = eventInfo.getContains();
13926 that.select(node.id, elem.name, elem.index);
13928 that.select(false, false, false);
13932 onCreateLabel: function(domElement, node) {
13933 var labelConf = config.Label,
13934 valueArray = node.getData('valueArray'),
13935 idArray = node.getData('idArray'),
13936 valuelabelArray = node.getData('valuelabelArray'),
13937 stringArray = node.getData('stringArray');
13938 size = st.canvas.getSize()
13941 for(var i=0, l=valueArray.length; i<l; i++) {
13943 wrapper: document.createElement('div'),
13944 valueLabel: document.createElement('div'),
13945 label: document.createElement('div')
13947 var wrapper = nlbs.wrapper,
13948 label = nlbs.label,
13949 valueLabel = nlbs.valueLabel,
13950 wrapperStyle = wrapper.style,
13951 labelStyle = label.style,
13952 valueLabelStyle = valueLabel.style;
13953 //store node labels
13954 nodeLabels[idArray[i]] = nlbs;
13956 wrapper.appendChild(label);
13957 wrapper.appendChild(valueLabel);
13959 wrapperStyle.position = 'relative';
13960 wrapperStyle.overflow = 'visible';
13961 wrapperStyle.fontSize = labelConf.size + 'px';
13962 wrapperStyle.fontFamily = labelConf.family;
13963 wrapperStyle.color = labelConf.color;
13964 wrapperStyle.textAlign = 'center';
13965 wrapperStyle.width = size.width + 'px';
13966 valueLabelStyle.position = labelStyle.position = 'absolute';
13967 valueLabelStyle.left = labelStyle.left = '0px';
13968 valueLabelStyle.width = (size.width/3) + 'px';
13969 valueLabelStyle.textAlign = 'right';
13970 label.innerHTML = stringArray[i];
13971 valueLabel.innerHTML = valuelabelArray[i];
13972 domElement.id = prefix+'funnel';
13973 domElement.style.width = size.width + 'px';
13975 domElement.appendChild(wrapper);
13979 onPlaceLabel: function(domElement, node) {
13981 var dimArray = node.getData('dimArray'),
13982 idArray = node.getData('idArray'),
13983 valueArray = node.getData('valueArray'),
13984 valuelabelArray = node.getData('valuelabelArray'),
13985 stringArray = node.getData('stringArray');
13986 size = st.canvas.getSize(),
13987 pos = node.pos.getc(true),
13988 domElement.style.left = "0px",
13989 domElement.style.top = "0px",
13990 minWidth = node.getData('width') * .25,
13992 pos = node.pos.getc(true),
13993 labelConf = config.Label;
13996 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13998 var labels = nodeLabels[idArray[i]],
13999 wrapperStyle = labels.wrapper.style,
14000 labelStyle = labels.label.style,
14001 valueLabelStyle = labels.valueLabel.style;
14002 var bottomWidth = minWidth + (acum * ratio);
14004 font = parseInt(wrapperStyle.fontSize, 10),
14005 domStyle = domElement.style;
14009 wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
14010 valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
14011 labelStyle.left = (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
14013 acum += (dimArray[i] || 0);
14021 var size = st.canvas.getSize(),
14022 margin = config.Margin;
14023 title = config.Title;
14024 subtitle = config.Subtitle;
14027 st.config.offsetY = -size.height/2 + margin.bottom
14028 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
14030 st.config.offsetX = (margin.right - margin.left)/2;
14034 this.canvas = this.st.canvas;
14037 renderTitle: function() {
14038 var canvas = this.canvas,
14039 size = canvas.getSize(),
14040 config = this.config,
14041 margin = config.Margin,
14042 label = config.Label,
14043 title = config.Title;
14044 ctx = canvas.getCtx();
14045 ctx.fillStyle = title.color;
14046 ctx.textAlign = 'left';
14047 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
14048 if(label.type == 'Native') {
14049 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
14053 renderSubtitle: function() {
14054 var canvas = this.canvas,
14055 size = canvas.getSize(),
14056 config = this.config,
14057 margin = config.Margin,
14058 label = config.Label,
14059 subtitle = config.Subtitle;
14060 ctx = canvas.getCtx();
14061 ctx.fillStyle = title.color;
14062 ctx.textAlign = 'left';
14063 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
14064 if(label.type == 'Native') {
14065 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
14070 renderDropShadow: function() {
14071 var canvas = this.canvas,
14072 size = canvas.getSize(),
14073 config = this.config,
14074 margin = config.Margin,
14075 horz = config.orientation == 'horizontal',
14076 label = config.Label,
14077 title = config.Title,
14078 shadowThickness = 4,
14079 subtitle = config.Subtitle,
14080 ctx = canvas.getCtx(),
14081 minwidth = (size.width/8) * .25,
14082 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14083 topMargin = (title.text? title.size + title.offset : 0) + margin.top,
14084 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14085 - (config.showLabels && (config.Label.size + config.labelOffset)),
14087 topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
14088 topY = (-size.height/2) + topMargin - shadowThickness;
14089 bottomY = (-size.height/2) + topMargin + height + shadowThickness;
14090 bottomWidth = minwidth + shadowThickness;
14092 ctx.fillStyle = "rgba(0,0,0,.2)";
14093 ctx.moveTo(0,topY);
14094 ctx.lineTo(-topWidth/2,topY); //top left
14095 ctx.lineTo(-bottomWidth/2,bottomY); // bottom left
14096 ctx.lineTo(bottomWidth/2,bottomY); // bottom right
14097 ctx.lineTo(topWidth/2,topY); // top right
14104 renderBackground: function() {
14105 var canvas = this.canvas,
14106 config = this.config,
14107 backgroundColor = config.backgroundColor,
14108 size = canvas.getSize(),
14109 ctx = canvas.getCtx();
14110 //ctx.globalCompositeOperation = "destination-over";
14111 ctx.fillStyle = backgroundColor;
14112 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
14114 clear: function() {
14115 var canvas = this.canvas;
14116 var ctx = canvas.getCtx(),
14117 size = canvas.getSize();
14118 ctx.fillStyle = "rgba(255,255,255,0)";
14119 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
14120 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
14122 resizeGraph: function(json,width) {
14123 var canvas = this.canvas,
14124 size = canvas.getSize(),
14125 config = this.config,
14126 orgHeight = size.height;
14129 canvas.resize(width,orgHeight);
14131 if(typeof FlashCanvas == "undefined") {
14134 this.clear();// hack for flashcanvas bug not properly clearing rectangle
14136 this.loadJSON(json);
14140 loadJSON: function(json) {
14141 if(this.busy) return;
14143 var prefix = $.time(),
14146 name = $.splat(json.label),
14147 color = $.splat(json.color || this.colors),
14148 config = this.config,
14149 canvas = this.canvas,
14150 gradient = !!config.type.split(":")[1],
14151 animate = config.animate,
14152 title = config.Title,
14153 subtitle = config.Subtitle,
14154 renderBackground = config.renderBackground,
14155 horz = config.orientation == 'horizontal',
14157 colorLength = color.length,
14158 nameLength = name.length,
14160 for(var i=0, values=json.values, l=values.length; i<l; i++) {
14161 var val = values[i];
14162 var valArray = $.splat(val.values);
14163 totalValue += parseFloat(valArray.sum());
14167 var nameArray = new Array();
14168 var idArray = new Array();
14169 var valArray = new Array();
14170 var valuelabelArray = new Array();
14171 var linkArray = new Array();
14172 var titleArray = new Array();
14173 var percentageArray = new Array();
14175 for(var i=0, values=json.values, l=values.length; i<l; i++) {
14176 var val = values[i];
14177 nameArray[i] = $.splat(val.label);
14178 idArray[i] = $.splat(prefix + val.label);
14179 valArray[i] = $.splat(val.values);
14180 valuelabelArray[i] = $.splat(val.valuelabels);
14181 linkArray[i] = $.splat(val.links);
14182 titleArray[i] = $.splat(val.titles);
14183 percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
14188 nameArray.reverse();
14189 valArray.reverse();
14190 valuelabelArray.reverse();
14191 linkArray.reverse();
14192 titleArray.reverse();
14193 percentageArray.reverse();
14196 'id': prefix + val.label,
14201 '$idArray': idArray,
14202 '$linkArray': linkArray,
14203 '$titleArray': titleArray,
14204 '$valueArray': valArray,
14205 '$valuelabelArray': valuelabelArray,
14206 '$colorArray': color,
14207 '$colorMono': $.splat(color[i % colorLength]),
14208 '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
14209 '$gradient': gradient,
14211 '$percentageArray' : percentageArray,
14219 'id': prefix + '$root',
14230 this.normalizeDims();
14232 if(renderBackground) {
14233 this.renderBackground();
14235 if(!animate && title.text) {
14236 this.renderTitle();
14238 if(!animate && subtitle.text) {
14239 this.renderSubtitle();
14241 if(typeof FlashCanvas == "undefined") {
14242 this.renderDropShadow();
14245 st.select(st.root);
14249 modes: ['node-property:width:dimArray'],
14251 onComplete: function() {
14257 modes: ['node-property:height:dimArray'],
14259 onComplete: function() {
14272 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.
14276 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
14277 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
14282 barChart.updateJSON(json, {
14283 onComplete: function() {
14284 alert('update complete!');
14289 updateJSON: function(json, onComplete) {
14290 if(this.busy) return;
14294 var graph = st.graph;
14295 var values = json.values;
14296 var animate = this.config.animate;
14298 var horz = this.config.orientation == 'horizontal';
14299 $.each(values, function(v) {
14300 var n = graph.getByName(v.label);
14302 n.setData('valueArray', $.splat(v.values));
14304 n.setData('stringArray', $.splat(json.label));
14308 this.normalizeDims();
14310 st.select(st.root);
14314 modes: ['node-property:width:dimArray'],
14316 onComplete: function() {
14318 onComplete && onComplete.onComplete();
14323 modes: ['node-property:height:dimArray'],
14325 onComplete: function() {
14327 onComplete && onComplete.onComplete();
14334 //adds the little brown bar when hovering the node
14335 select: function(id, name) {
14337 if(!this.config.hoveredColor) return;
14338 var s = this.selected;
14339 if(s.id != id || s.name != name) {
14342 s.color = this.config.hoveredColor;
14343 this.st.graph.eachNode(function(n) {
14345 n.setData('border', s);
14347 n.setData('border', false);
14357 Returns an object containing as keys the legend names and as values hex strings with color values.
14362 var legend = barChart.getLegend();
14365 getLegend: function() {
14366 var legend = new Array();
14367 var name = new Array();
14368 var color = new Array();
14370 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14373 var colors = n.getData('colorArray'),
14374 len = colors.length;
14375 $.each(n.getData('stringArray'), function(s, i) {
14376 color[i] = colors[i % len];
14379 legend['name'] = name;
14380 legend['color'] = color;
14385 Method: getMaxValue
14387 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14392 var ans = barChart.getMaxValue();
14395 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14400 //will return 100 for all BarChart instances,
14401 //displaying all of them with the same scale
14402 $jit.BarChart.implement({
14403 'getMaxValue': function() {
14410 getMaxValue: function() {
14411 var maxValue = 0, stacked = true;
14412 this.st.graph.eachNode(function(n) {
14413 var valArray = n.getData('valueArray'),
14415 if(!valArray) return;
14417 $.each(valArray, function(v) {
14421 acum = Math.max.apply(null, valArray);
14423 maxValue = maxValue>acum? maxValue:acum;
14428 setBarType: function(type) {
14429 this.config.type = type;
14430 this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14433 normalizeDims: function() {
14434 //number of elements
14435 var root = this.st.graph.getNode(this.st.root), l=0;
14436 root.eachAdjacency(function() {
14439 var maxValue = this.getMaxValue() || 1,
14440 size = this.st.canvas.getSize(),
14441 config = this.config,
14442 margin = config.Margin,
14443 title = config.Title,
14444 subtitle = config.Subtitle,
14445 marginWidth = margin.left + margin.right,
14446 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14447 horz = config.orientation == 'horizontal',
14448 animate = config.animate,
14449 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14451 - (config.showLabels && (config.Label.size + config.labelOffset)),
14452 dim1 = horz? 'height':'width',
14453 dim2 = horz? 'width':'height';
14456 minWidth = size.width/8;
14460 this.st.graph.eachNode(function(n) {
14461 var acum = 0, animateValue = [];
14462 $.each(n.getData('valueArray'), function(v) {
14464 animateValue.push(0);
14466 n.setData(dim1, minWidth);
14469 n.setData(dim2, acum * height / maxValue, 'end');
14470 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14471 return n * height / maxValue;
14473 var dimArray = n.getData('dimArray');
14475 n.setData('dimArray', animateValue);
14478 n.setData(dim2, acum * height / maxValue);
14479 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14480 return n * height / maxValue;
14491 * File: Options.PieChart.js
14495 Object: Options.PieChart
14497 <PieChart> options.
14498 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14504 Options.PieChart = {
14510 hoveredColor: '#9fd4ff',
14512 resizeLabels: false,
14513 updateHeights: false
14522 var pie = new $jit.PieChart({
14525 type: 'stacked:gradient'
14532 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14533 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14534 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14535 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14536 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14537 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14538 showLabels - (boolean) Default's *true*. Display the name of the slots.
14539 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.
14540 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.
14543 Options.PieChart = {
14547 offset: 25, // page offset
14549 labelOffset: 3, // label offset
14550 type: 'stacked', // gradient
14552 hoveredColor: '#9fd4ff',
14563 resizeLabels: false,
14565 //only valid for mono-valued datasets
14566 updateHeights: false
14570 * Class: Layouts.Radial
14572 * Implements a Radial Layout.
14576 * <RGraph>, <Hypertree>
14579 Layouts.Radial = new Class({
14584 * Computes nodes' positions.
14588 * property - _optional_ A <Graph.Node> position property to store the new
14589 * positions. Possible values are 'pos', 'end' or 'start'.
14592 compute : function(property) {
14593 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14594 NodeDim.compute(this.graph, prop, this.config);
14595 this.graph.computeLevels(this.root, 0, "ignore");
14596 var lengthFunc = this.createLevelDistanceFunc();
14597 this.computeAngularWidths(prop);
14598 this.computePositions(prop, lengthFunc);
14604 * Performs the main algorithm for computing node positions.
14606 computePositions : function(property, getLength) {
14607 var propArray = property;
14608 var graph = this.graph;
14609 var root = graph.getNode(this.root);
14610 var parent = this.parent;
14611 var config = this.config;
14613 for ( var i=0, l=propArray.length; i < l; i++) {
14614 var pi = propArray[i];
14615 root.setPos($P(0, 0), pi);
14616 root.setData('span', Math.PI * 2, pi);
14624 graph.eachBFS(this.root, function(elem) {
14625 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14626 var angleInit = elem.angleSpan.begin;
14627 var len = getLength(elem);
14628 //Calculate the sum of all angular widths
14629 var totalAngularWidths = 0, subnodes = [], maxDim = {};
14630 elem.eachSubnode(function(sib) {
14631 totalAngularWidths += sib._treeAngularWidth;
14633 for ( var i=0, l=propArray.length; i < l; i++) {
14634 var pi = propArray[i], dim = sib.getData('dim', pi);
14635 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14637 subnodes.push(sib);
14639 //Maintain children order
14640 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14641 if (parent && parent.id == elem.id && subnodes.length > 0
14642 && subnodes[0].dist) {
14643 subnodes.sort(function(a, b) {
14644 return (a.dist >= b.dist) - (a.dist <= b.dist);
14647 //Calculate nodes positions.
14648 for (var k = 0, ls=subnodes.length; k < ls; k++) {
14649 var child = subnodes[k];
14650 if (!child._flag) {
14651 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14652 var theta = angleInit + angleProportion / 2;
14654 for ( var i=0, l=propArray.length; i < l; i++) {
14655 var pi = propArray[i];
14656 child.setPos($P(theta, len), pi);
14657 child.setData('span', angleProportion, pi);
14658 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14661 child.angleSpan = {
14663 end : angleInit + angleProportion
14665 angleInit += angleProportion;
14672 * Method: setAngularWidthForNodes
14674 * Sets nodes angular widths.
14676 setAngularWidthForNodes : function(prop) {
14677 this.graph.eachBFS(this.root, function(elem, i) {
14678 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14679 elem._angularWidth = diamValue / i;
14684 * Method: setSubtreesAngularWidth
14686 * Sets subtrees angular widths.
14688 setSubtreesAngularWidth : function() {
14690 this.graph.eachNode(function(elem) {
14691 that.setSubtreeAngularWidth(elem);
14696 * Method: setSubtreeAngularWidth
14698 * Sets the angular width for a subtree.
14700 setSubtreeAngularWidth : function(elem) {
14701 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14702 elem.eachSubnode(function(child) {
14703 that.setSubtreeAngularWidth(child);
14704 sumAW += child._treeAngularWidth;
14706 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14710 * Method: computeAngularWidths
14712 * Computes nodes and subtrees angular widths.
14714 computeAngularWidths : function(prop) {
14715 this.setAngularWidthForNodes(prop);
14716 this.setSubtreesAngularWidth();
14723 * File: Sunburst.js
14729 A radial space filling tree visualization.
14733 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14737 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.
14741 All <Loader> methods
14743 Constructor Options:
14745 Inherits options from
14748 - <Options.Controller>
14754 - <Options.NodeStyles>
14755 - <Options.Navigation>
14757 Additionally, there are other parameters and some default values changed
14759 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14760 levelDistance - (number) Default's *100*. The distance between levels of the tree.
14761 Node.type - Described in <Options.Node>. Default's to *multipie*.
14762 Node.height - Described in <Options.Node>. Default's *0*.
14763 Edge.type - Described in <Options.Edge>. Default's *none*.
14764 Label.textAlign - Described in <Options.Label>. Default's *start*.
14765 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14767 Instance Properties:
14769 canvas - Access a <Canvas> instance.
14770 graph - Access a <Graph> instance.
14771 op - Access a <Sunburst.Op> instance.
14772 fx - Access a <Sunburst.Plot> instance.
14773 labels - Access a <Sunburst.Label> interface implementation.
14777 $jit.Sunburst = new Class({
14779 Implements: [ Loader, Extras, Layouts.Radial ],
14781 initialize: function(controller) {
14782 var $Sunburst = $jit.Sunburst;
14785 interpolation: 'linear',
14786 levelDistance: 100,
14788 'type': 'multipie',
14795 textAlign: 'start',
14796 textBaseline: 'middle'
14800 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14801 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14803 var canvasConfig = this.config;
14804 if(canvasConfig.useCanvas) {
14805 this.canvas = canvasConfig.useCanvas;
14806 this.config.labelContainer = this.canvas.id + '-label';
14808 if(canvasConfig.background) {
14809 canvasConfig.background = $.merge({
14811 colorStop1: this.config.colorStop1,
14812 colorStop2: this.config.colorStop2
14813 }, canvasConfig.background);
14815 this.canvas = new Canvas(this, canvasConfig);
14816 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14819 this.graphOptions = {
14827 this.graph = new Graph(this.graphOptions, this.config.Node,
14829 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14830 this.fx = new $Sunburst.Plot(this, $Sunburst);
14831 this.op = new $Sunburst.Op(this);
14834 this.rotated = null;
14836 // initialize extras
14837 this.initializeExtras();
14842 createLevelDistanceFunc
14844 Returns the levelDistance function used for calculating a node distance
14845 to its origin. This function returns a function that is computed
14846 per level and not per node, such that all nodes with the same depth will have the
14847 same distance to the origin. The resulting function gets the
14848 parent node as parameter and returns a float.
14851 createLevelDistanceFunc: function() {
14852 var ld = this.config.levelDistance;
14853 return function(elem) {
14854 return (elem._depth + 1) * ld;
14861 Computes positions and plots the tree.
14864 refresh: function() {
14872 An alias for computing new positions to _endPos_
14879 reposition: function() {
14880 this.compute('end');
14886 Rotates the graph so that the selected node is horizontal on the right.
14890 node - (object) A <Graph.Node>.
14891 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14892 opt - (object) Configuration options merged with this visualization configuration options.
14896 <Sunburst.rotateAngle>
14899 rotate: function(node, method, opt) {
14900 var theta = node.getPos(opt.property || 'current').getp(true).theta;
14901 this.rotated = node;
14902 this.rotateAngle(-theta, method, opt);
14906 Method: rotateAngle
14908 Rotates the graph of an angle theta.
14912 node - (object) A <Graph.Node>.
14913 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14914 opt - (object) Configuration options merged with this visualization configuration options.
14921 rotateAngle: function(theta, method, opt) {
14923 var options = $.merge(this.config, opt || {}, {
14926 var prop = opt.property || (method === "animate" ? 'end' : 'current');
14927 if(method === 'animate') {
14928 this.fx.animation.pause();
14930 this.graph.eachNode(function(n) {
14931 var p = n.getPos(prop);
14934 p.theta += Math.PI * 2;
14937 if (method == 'animate') {
14938 this.fx.animate(options);
14939 } else if (method == 'replot') {
14948 Plots the Sunburst. This is a shortcut to *fx.plot*.
14955 $jit.Sunburst.$extend = true;
14957 (function(Sunburst) {
14962 Custom extension of <Graph.Op>.
14966 All <Graph.Op> methods
14973 Sunburst.Op = new Class( {
14975 Implements: Graph.Op
14980 Class: Sunburst.Plot
14982 Custom extension of <Graph.Plot>.
14986 All <Graph.Plot> methods
14993 Sunburst.Plot = new Class( {
14995 Implements: Graph.Plot
15000 Class: Sunburst.Label
15002 Custom extension of <Graph.Label>.
15003 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
15007 All <Graph.Label> methods and subclasses.
15011 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
15014 Sunburst.Label = {};
15017 Sunburst.Label.Native
15019 Custom extension of <Graph.Label.Native>.
15023 All <Graph.Label.Native> methods
15027 <Graph.Label.Native>
15029 Sunburst.Label.Native = new Class( {
15030 Implements: Graph.Label.Native,
15032 initialize: function(viz) {
15034 this.label = viz.config.Label;
15035 this.config = viz.config;
15038 renderLabel: function(canvas, node, controller) {
15039 var span = node.getData('span');
15040 if(span < Math.PI /2 && Math.tan(span) *
15041 this.config.levelDistance * node._depth < 10) {
15044 var ctx = canvas.getCtx();
15045 var measure = ctx.measureText(node.name);
15046 if (node.id == this.viz.root) {
15047 var x = -measure.width / 2, y = 0, thetap = 0;
15051 var ld = controller.levelDistance - indent;
15052 var clone = node.pos.clone();
15053 clone.rho += indent;
15054 var p = clone.getp(true);
15055 var ct = clone.getc(true);
15056 var x = ct.x, y = ct.y;
15057 // get angle in degrees
15059 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
15060 var thetap = cond ? p.theta + pi : p.theta;
15062 x -= Math.abs(Math.cos(p.theta) * measure.width);
15063 y += Math.sin(p.theta) * measure.width;
15064 } else if (node.id == this.viz.root) {
15065 x -= measure.width / 2;
15069 ctx.translate(x, y);
15070 ctx.rotate(thetap);
15071 ctx.fillText(node.name, 0, 0);
15079 Custom extension of <Graph.Label.SVG>.
15083 All <Graph.Label.SVG> methods
15090 Sunburst.Label.SVG = new Class( {
15091 Implements: Graph.Label.SVG,
15093 initialize: function(viz) {
15100 Overrides abstract method placeLabel in <Graph.Plot>.
15104 tag - A DOM label element.
15105 node - A <Graph.Node>.
15106 controller - A configuration/controller object passed to the visualization.
15109 placeLabel: function(tag, node, controller) {
15110 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
15111 var radius = canvas.getSize();
15113 x: Math.round(pos.x + radius.width / 2),
15114 y: Math.round(pos.y + radius.height / 2)
15116 tag.setAttribute('x', labelPos.x);
15117 tag.setAttribute('y', labelPos.y);
15119 var bb = tag.getBBox();
15121 // center the label
15122 var x = tag.getAttribute('x');
15123 var y = tag.getAttribute('y');
15124 // get polar coordinates
15125 var p = node.pos.getp(true);
15126 // get angle in degrees
15128 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
15130 tag.setAttribute('x', x - bb.width);
15131 tag.setAttribute('y', y - bb.height);
15132 } else if (node.id == viz.root) {
15133 tag.setAttribute('x', x - bb.width / 2);
15136 var thetap = cond ? p.theta + pi : p.theta;
15138 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
15142 controller.onPlaceLabel(tag, node);
15147 Sunburst.Label.HTML
15149 Custom extension of <Graph.Label.HTML>.
15153 All <Graph.Label.HTML> methods.
15160 Sunburst.Label.HTML = new Class( {
15161 Implements: Graph.Label.HTML,
15163 initialize: function(viz) {
15169 Overrides abstract method placeLabel in <Graph.Plot>.
15173 tag - A DOM label element.
15174 node - A <Graph.Node>.
15175 controller - A configuration/controller object passed to the visualization.
15178 placeLabel: function(tag, node, controller) {
15179 var pos = node.pos.clone(),
15180 canvas = this.viz.canvas,
15181 height = node.getData('height'),
15182 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
15183 radius = canvas.getSize();
15185 pos = pos.getc(true);
15188 x: Math.round(pos.x + radius.width / 2),
15189 y: Math.round(pos.y + radius.height / 2)
15192 var style = tag.style;
15193 style.left = labelPos.x + 'px';
15194 style.top = labelPos.y + 'px';
15195 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
15197 controller.onPlaceLabel(tag, node);
15202 Class: Sunburst.Plot.NodeTypes
15204 This class contains a list of <Graph.Node> built-in types.
15205 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
15207 You can add your custom node types, customizing your visualization to the extreme.
15212 Sunburst.Plot.NodeTypes.implement({
15214 'render': function(node, canvas) {
15215 //print your custom node to canvas
15218 'contains': function(node, pos) {
15219 //return true if pos is inside the node or false otherwise
15226 Sunburst.Plot.NodeTypes = new Class( {
15229 'contains': $.lambda(false),
15230 'anglecontains': function(node, pos) {
15231 var span = node.getData('span') / 2, theta = node.pos.theta;
15232 var begin = theta - span, end = theta + span;
15234 begin += Math.PI * 2;
15235 var atan = Math.atan2(pos.y, pos.x);
15237 atan += Math.PI * 2;
15239 return (atan > begin && atan <= Math.PI * 2) || atan < end;
15241 return atan > begin && atan < end;
15244 'anglecontainsgauge': function(node, pos) {
15245 var span = node.getData('span') / 2, theta = node.pos.theta;
15246 var config = node.getData('config');
15247 var ld = this.config.levelDistance;
15248 var yOffset = pos.y-(ld/2);
15249 var begin = ((theta - span)/2)+Math.PI,
15250 end = ((theta + span)/2)+Math.PI;
15253 begin += Math.PI * 2;
15254 var atan = Math.atan2(yOffset, pos.x);
15258 atan += Math.PI * 2;
15262 return (atan > begin && atan <= Math.PI * 2) || atan < end;
15264 return atan > begin && atan < end;
15270 'render': function(node, canvas) {
15271 var span = node.getData('span') / 2, theta = node.pos.theta;
15272 var begin = theta - span, end = theta + span;
15273 var polarNode = node.pos.getp(true);
15274 var polar = new Polar(polarNode.rho, begin);
15275 var p1coord = polar.getc(true);
15277 var p2coord = polar.getc(true);
15279 var ctx = canvas.getCtx();
15282 ctx.lineTo(p1coord.x, p1coord.y);
15284 ctx.lineTo(p2coord.x, p2coord.y);
15286 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
15290 'contains': function(node, pos) {
15291 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15292 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15293 var ld = this.config.levelDistance, d = node._depth;
15294 return (rho <= ld * d);
15300 'render': function(node, canvas) {
15301 var height = node.getData('height');
15302 var ldist = height? height : this.config.levelDistance;
15303 var span = node.getData('span') / 2, theta = node.pos.theta;
15304 var begin = theta - span, end = theta + span;
15305 var polarNode = node.pos.getp(true);
15307 var polar = new Polar(polarNode.rho, begin);
15308 var p1coord = polar.getc(true);
15311 var p2coord = polar.getc(true);
15313 polar.rho += ldist;
15314 var p3coord = polar.getc(true);
15316 polar.theta = begin;
15317 var p4coord = polar.getc(true);
15319 var ctx = canvas.getCtx();
15322 ctx.arc(0, 0, polarNode.rho, begin, end, false);
15323 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15324 ctx.moveTo(p1coord.x, p1coord.y);
15325 ctx.lineTo(p4coord.x, p4coord.y);
15326 ctx.moveTo(p2coord.x, p2coord.y);
15327 ctx.lineTo(p3coord.x, p3coord.y);
15330 if (node.collapsed) {
15335 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15341 'contains': function(node, pos) {
15342 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15343 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15344 var height = node.getData('height');
15345 var ldist = height? height : this.config.levelDistance;
15346 var ld = this.config.levelDistance, d = node._depth;
15347 return (rho >= ld * d) && (rho <= (ld * d + ldist));
15353 'gradient-multipie': {
15354 'render': function(node, canvas) {
15355 var ctx = canvas.getCtx();
15356 var height = node.getData('height');
15357 var ldist = height? height : this.config.levelDistance;
15358 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15359 0, 0, node.getPos().rho + ldist);
15361 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15362 $.each(colorArray, function(i) {
15363 ans.push(parseInt(i * 0.5, 10));
15365 var endColor = $.rgbToHex(ans);
15366 radialGradient.addColorStop(0, endColor);
15367 radialGradient.addColorStop(1, node.getData('color'));
15368 ctx.fillStyle = radialGradient;
15369 this.nodeTypes['multipie'].render.call(this, node, canvas);
15371 'contains': function(node, pos) {
15372 return this.nodeTypes['multipie'].contains.call(this, node, pos);
15377 'render': function(node, canvas) {
15378 var ctx = canvas.getCtx();
15379 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15382 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15383 $.each(colorArray, function(i) {
15384 ans.push(parseInt(i * 0.5, 10));
15386 var endColor = $.rgbToHex(ans);
15387 radialGradient.addColorStop(1, endColor);
15388 radialGradient.addColorStop(0, node.getData('color'));
15389 ctx.fillStyle = radialGradient;
15390 this.nodeTypes['pie'].render.call(this, node, canvas);
15392 'contains': function(node, pos) {
15393 return this.nodeTypes['pie'].contains.call(this, node, pos);
15399 Class: Sunburst.Plot.EdgeTypes
15401 This class contains a list of <Graph.Adjacence> built-in types.
15402 Edge types implemented are 'none', 'line' and 'arrow'.
15404 You can add your custom edge types, customizing your visualization to the extreme.
15409 Sunburst.Plot.EdgeTypes.implement({
15411 'render': function(adj, canvas) {
15412 //print your custom edge to canvas
15415 'contains': function(adj, pos) {
15416 //return true if pos is inside the arc or false otherwise
15423 Sunburst.Plot.EdgeTypes = new Class({
15426 'render': function(adj, canvas) {
15427 var from = adj.nodeFrom.pos.getc(true),
15428 to = adj.nodeTo.pos.getc(true);
15429 this.edgeHelper.line.render(from, to, canvas);
15431 'contains': function(adj, pos) {
15432 var from = adj.nodeFrom.pos.getc(true),
15433 to = adj.nodeTo.pos.getc(true);
15434 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15438 'render': function(adj, canvas) {
15439 var from = adj.nodeFrom.pos.getc(true),
15440 to = adj.nodeTo.pos.getc(true),
15441 dim = adj.getData('dim'),
15442 direction = adj.data.$direction,
15443 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15444 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15446 'contains': function(adj, pos) {
15447 var from = adj.nodeFrom.pos.getc(true),
15448 to = adj.nodeTo.pos.getc(true);
15449 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15453 'render': function(adj, canvas) {
15454 var from = adj.nodeFrom.pos.getc(),
15455 to = adj.nodeTo.pos.getc(),
15456 dim = Math.max(from.norm(), to.norm());
15457 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15459 'contains': $.lambda(false) //TODO(nico): Implement this!
15467 * File: PieChart.js
15471 $jit.Sunburst.Plot.NodeTypes.implement({
15472 'piechart-stacked' : {
15473 'render' : function(node, canvas) {
15474 var pos = node.pos.getp(true),
15475 dimArray = node.getData('dimArray'),
15476 valueArray = node.getData('valueArray'),
15477 colorArray = node.getData('colorArray'),
15478 colorLength = colorArray.length,
15479 stringArray = node.getData('stringArray'),
15480 span = node.getData('span') / 2,
15481 theta = node.pos.theta,
15482 begin = theta - span,
15483 end = theta + span,
15486 var ctx = canvas.getCtx(),
15488 gradient = node.getData('gradient'),
15489 border = node.getData('border'),
15490 config = node.getData('config'),
15491 showLabels = config.showLabels,
15492 resizeLabels = config.resizeLabels,
15493 label = config.Label;
15495 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15496 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15498 if (colorArray && dimArray && stringArray) {
15499 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15500 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15501 if(dimi <= 0) continue;
15502 ctx.fillStyle = ctx.strokeStyle = colori;
15503 if(gradient && dimi) {
15504 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15505 xpos, ypos, acum + dimi + config.sliceOffset);
15506 var colorRgb = $.hexToRgb(colori),
15507 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15508 endColor = $.rgbToHex(ans);
15510 radialGradient.addColorStop(0, colori);
15511 radialGradient.addColorStop(0.5, colori);
15512 radialGradient.addColorStop(1, endColor);
15513 ctx.fillStyle = radialGradient;
15516 polar.rho = acum + config.sliceOffset;
15517 polar.theta = begin;
15518 var p1coord = polar.getc(true);
15520 var p2coord = polar.getc(true);
15522 var p3coord = polar.getc(true);
15523 polar.theta = begin;
15524 var p4coord = polar.getc(true);
15527 //fixing FF arc method + fill
15528 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15529 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15531 if(border && border.name == stringArray[i]) {
15533 opt.dimValue = dimArray[i];
15537 acum += (dimi || 0);
15538 valAcum += (valueArray[i] || 0);
15542 ctx.globalCompositeOperation = "source-over";
15544 ctx.strokeStyle = border.color;
15545 var s = begin < end? 1 : -1;
15547 //fixing FF arc method + fill
15548 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15549 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15554 if(showLabels && label.type == 'Native') {
15556 ctx.fillStyle = ctx.strokeStyle = label.color;
15557 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15558 fontSize = (label.size * scale) >> 0;
15559 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15561 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15562 ctx.textBaseline = 'middle';
15563 ctx.textAlign = 'center';
15565 polar.rho = acum + config.labelOffset + config.sliceOffset;
15566 polar.theta = node.pos.theta;
15567 var cart = polar.getc(true);
15569 ctx.fillText(node.name, cart.x, cart.y);
15574 'contains': function(node, pos) {
15575 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15576 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15577 var ld = this.config.levelDistance, d = node._depth;
15578 var config = node.getData('config');
15579 if(rho <=ld * d + config.sliceOffset) {
15580 var dimArray = node.getData('dimArray');
15581 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15582 var dimi = dimArray[i];
15583 if(rho >= acum && rho <= acum + dimi) {
15585 name: node.getData('stringArray')[i],
15586 color: node.getData('colorArray')[i],
15587 value: node.getData('valueArray')[i],
15600 'piechart-basic' : {
15601 'render' : function(node, canvas) {
15602 var pos = node.pos.getp(true),
15603 dimArray = node.getData('dimArray'),
15604 valueArray = node.getData('valueArray'),
15605 colorArray = node.getData('colorMono'),
15606 colorLength = colorArray.length,
15607 stringArray = node.getData('stringArray'),
15608 percentage = node.getData('percentage'),
15609 iteration = node.getData('iteration'),
15610 span = node.getData('span') / 2,
15611 theta = node.pos.theta,
15612 begin = theta - span,
15613 end = theta + span,
15616 var ctx = canvas.getCtx(),
15618 gradient = node.getData('gradient'),
15619 border = node.getData('border'),
15620 config = node.getData('config'),
15621 renderSubtitle = node.getData('renderSubtitle'),
15622 renderBackground = config.renderBackground,
15623 showLabels = config.showLabels,
15624 resizeLabels = config.resizeLabels,
15625 label = config.Label;
15627 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15628 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15629 //background rendering for IE
15630 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15631 backgroundColor = config.backgroundColor,
15632 size = canvas.getSize();
15634 ctx.fillStyle = backgroundColor;
15635 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15639 var margin = config.Margin,
15640 title = config.Title,
15641 subtitle = config.Subtitle;
15642 ctx.fillStyle = title.color;
15643 ctx.textAlign = 'left';
15645 if(title.text != "") {
15646 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15648 if(label.type == 'Native') {
15649 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15653 if(subtitle.text != "") {
15654 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15655 if(label.type == 'Native') {
15656 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15661 if (colorArray && dimArray && stringArray) {
15662 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15663 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15664 if(dimi <= 0) continue;
15665 ctx.fillStyle = ctx.strokeStyle = colori;
15667 polar.rho = acum + config.sliceOffset;
15668 polar.theta = begin;
15669 var p1coord = polar.getc(true);
15671 var p2coord = polar.getc(true);
15673 var p3coord = polar.getc(true);
15674 polar.theta = begin;
15675 var p4coord = polar.getc(true);
15677 if(typeof FlashCanvas == "undefined") {
15680 ctx.fillStyle = "rgba(0,0,0,.2)";
15681 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15682 ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);
15685 if(gradient && dimi) {
15686 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15687 xpos, ypos, acum + dimi + config.sliceOffset);
15688 var colorRgb = $.hexToRgb(colori),
15689 endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15690 endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15692 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15693 radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15694 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15695 radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15696 ctx.fillStyle = radialGradient;
15701 //fixing FF arc method + fill
15703 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15704 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15706 if(border && border.name == stringArray[i]) {
15708 opt.dimValue = dimArray[i];
15711 opt.sliceValue = valueArray[i];
15713 acum += (dimi || 0);
15714 valAcum += (valueArray[i] || 0);
15718 ctx.globalCompositeOperation = "source-over";
15720 ctx.strokeStyle = border.color;
15721 var s = begin < end? 1 : -1;
15723 //fixing FF arc method + fill
15724 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15725 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15730 if(showLabels && label.type == 'Native') {
15732 ctx.fillStyle = ctx.strokeStyle = label.color;
15733 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15734 fontSize = (label.size * scale) >> 0;
15735 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15737 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15738 ctx.textBaseline = 'middle';
15739 ctx.textAlign = 'center';
15741 angle = theta * 360 / (2 * pi);
15742 polar.rho = acum + config.labelOffset + config.sliceOffset;
15743 polar.theta = node.pos.theta;
15744 var cart = polar.getc(true);
15745 if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15748 if(config.labelType == 'name') {
15749 ctx.fillText(node.name, cart.x, cart.y);
15751 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15758 'contains': function(node, pos) {
15759 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15760 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15761 var ld = this.config.levelDistance, d = node._depth;
15762 var config = node.getData('config');
15764 if(rho <=ld * d + config.sliceOffset) {
15765 var dimArray = node.getData('dimArray');
15766 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15767 var dimi = dimArray[i];
15768 if(rho >= acum && rho <= acum + dimi) {
15769 var url = Url.decode(node.getData('linkArray')[i]);
15771 name: node.getData('stringArray')[i],
15773 color: node.getData('colorArray')[i],
15774 value: node.getData('valueArray')[i],
15775 percentage: node.getData('percentage'),
15776 valuelabel: node.getData('valuelabelsArray')[i],
15794 A visualization that displays stacked bar charts.
15796 Constructor Options:
15798 See <Options.PieChart>.
15801 $jit.PieChart = new Class({
15803 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15807 initialize: function(opt) {
15808 this.controller = this.config =
15809 $.merge(Options("Canvas", "PieChart", "Label"), {
15810 Label: { type: 'Native' }
15812 this.initializeViz();
15815 initializeViz: function() {
15816 var config = this.config, that = this;
15817 var nodeType = config.type.split(":")[0];
15818 var sb = new $jit.Sunburst({
15819 injectInto: config.injectInto,
15820 useCanvas: config.useCanvas,
15821 withLabels: config.Label.type != 'Native',
15822 background: config.background,
15823 renderBackground: config.renderBackground,
15824 backgroundColor: config.backgroundColor,
15825 colorStop1: config.colorStop1,
15826 colorStop2: config.colorStop2,
15828 type: config.Label.type
15832 type: 'piechart-' + nodeType,
15840 enable: config.Tips.enable,
15843 onShow: function(tip, node, contains) {
15844 var elem = contains;
15845 config.Tips.onShow(tip, elem, node);
15846 if(elem.link != 'undefined' && elem.link != '') {
15847 document.body.style.cursor = 'pointer';
15850 onHide: function() {
15851 document.body.style.cursor = 'default';
15857 onClick: function(node, eventInfo, evt) {
15858 if(!config.Events.enable) return;
15859 var elem = eventInfo.getContains();
15860 config.Events.onClick(elem, eventInfo, evt);
15862 onMouseMove: function(node, eventInfo, evt) {
15863 if(!config.hoveredColor) return;
15865 var elem = eventInfo.getContains();
15866 that.select(node.id, elem.name, elem.index);
15868 that.select(false, false, false);
15872 onCreateLabel: function(domElement, node) {
15873 var labelConf = config.Label;
15874 if(config.showLabels) {
15875 var style = domElement.style;
15876 style.fontSize = labelConf.size + 'px';
15877 style.fontFamily = labelConf.family;
15878 style.color = labelConf.color;
15879 style.textAlign = 'center';
15880 if(config.labelType == 'name') {
15881 domElement.innerHTML = node.name;
15883 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15885 domElement.style.width = '400px';
15888 onPlaceLabel: function(domElement, node) {
15889 if(!config.showLabels) return;
15890 var pos = node.pos.getp(true),
15891 dimArray = node.getData('dimArray'),
15892 span = node.getData('span') / 2,
15893 theta = node.pos.theta,
15894 begin = theta - span,
15895 end = theta + span,
15898 var showLabels = config.showLabels,
15899 resizeLabels = config.resizeLabels,
15900 label = config.Label;
15903 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15904 acum += dimArray[i];
15906 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15907 fontSize = (label.size * scale) >> 0;
15908 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15909 domElement.style.fontSize = fontSize + 'px';
15910 polar.rho = acum + config.labelOffset + config.sliceOffset;
15911 polar.theta = (begin + end) / 2;
15912 var pos = polar.getc(true);
15913 var radius = that.canvas.getSize();
15915 x: Math.round(pos.x + radius.width / 2),
15916 y: Math.round(pos.y + radius.height / 2)
15918 domElement.style.left = (labelPos.x - 200) + 'px';
15919 domElement.style.top = labelPos.y + 'px';
15924 var size = sb.canvas.getSize(),
15926 sb.config.levelDistance = min(size.width, size.height)/2
15927 - config.offset - config.sliceOffset;
15929 this.canvas = this.sb.canvas;
15930 this.canvas.getCtx().globalCompositeOperation = 'lighter';
15932 renderBackground: function() {
15933 var canvas = this.canvas,
15934 config = this.config,
15935 backgroundColor = config.backgroundColor,
15936 size = canvas.getSize(),
15937 ctx = canvas.getCtx();
15938 ctx.globalCompositeOperation = "destination-over";
15939 ctx.fillStyle = backgroundColor;
15940 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15942 renderTitle: function() {
15943 var canvas = this.canvas,
15944 size = canvas.getSize(),
15945 config = this.config,
15946 margin = config.Margin,
15947 radius = this.sb.config.levelDistance,
15948 title = config.Title,
15949 label = config.Label,
15950 subtitle = config.Subtitle;
15951 ctx = canvas.getCtx();
15952 ctx.fillStyle = title.color;
15953 ctx.textAlign = 'left';
15954 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15956 if(label.type == 'Native') {
15957 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15960 renderSubtitle: function() {
15961 var canvas = this.canvas,
15962 size = canvas.getSize(),
15963 config = this.config,
15964 margin = config.Margin,
15965 radius = this.sb.config.levelDistance,
15966 title = config.Title,
15967 label = config.Label,
15968 subtitle = config.Subtitle;
15969 ctx = canvas.getCtx();
15970 ctx.fillStyle = title.color;
15971 ctx.textAlign = 'left';
15972 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15974 if(label.type == 'Native') {
15975 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15978 clear: function() {
15979 var canvas = this.canvas;
15980 var ctx = canvas.getCtx(),
15981 size = canvas.getSize();
15982 ctx.fillStyle = "rgba(255,255,255,0)";
15983 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15984 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15986 resizeGraph: function(json,width) {
15987 var canvas = this.canvas,
15988 size = canvas.getSize(),
15989 config = this.config,
15990 orgHeight = size.height;
15992 canvas.resize(width,orgHeight);
15993 if(typeof FlashCanvas == "undefined") {
15996 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15998 this.loadJSON(json);
16004 Loads JSON data into the visualization.
16008 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>.
16012 var pieChart = new $jit.PieChart(options);
16013 pieChart.loadJSON(json);
16016 loadJSON: function(json) {
16017 var prefix = $.time(),
16020 name = $.splat(json.label),
16021 nameLength = name.length,
16022 color = $.splat(json.color || this.colors),
16023 colorLength = color.length,
16024 config = this.config,
16025 renderBackground = config.renderBackground,
16026 title = config.Title,
16027 subtitle = config.Subtitle,
16028 gradient = !!config.type.split(":")[1],
16029 animate = config.animate,
16030 mono = nameLength == 1;
16032 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16033 var val = values[i];
16034 var valArray = $.splat(val.values);
16035 totalValue += parseFloat(valArray.sum());
16038 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16039 var val = values[i];
16040 var valArray = $.splat(val.values);
16041 var percentage = (valArray.sum()/totalValue) * 100;
16043 var linkArray = $.splat(val.links);
16044 var valuelabelsArray = $.splat(val.valuelabels);
16048 'id': prefix + val.label,
16052 'valuelabel': valuelabelsArray,
16053 '$linkArray': linkArray,
16054 '$valuelabelsArray': valuelabelsArray,
16055 '$valueArray': valArray,
16056 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16057 '$colorMono': $.splat(color[i % colorLength]),
16058 '$stringArray': name,
16059 '$gradient': gradient,
16062 '$percentage': percentage.toFixed(1),
16063 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16069 'id': prefix + '$root',
16081 this.normalizeDims();
16085 if(title.text != "") {
16086 this.renderTitle();
16089 if(subtitle.text != "") {
16090 this.renderSubtitle();
16092 if(renderBackground && typeof FlashCanvas == "undefined") {
16093 this.renderBackground();
16098 modes: ['node-property:dimArray'],
16107 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.
16111 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
16112 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
16117 pieChart.updateJSON(json, {
16118 onComplete: function() {
16119 alert('update complete!');
16124 updateJSON: function(json, onComplete) {
16125 if(this.busy) return;
16129 var graph = sb.graph;
16130 var values = json.values;
16131 var animate = this.config.animate;
16133 $.each(values, function(v) {
16134 var n = graph.getByName(v.label),
16135 vals = $.splat(v.values);
16137 n.setData('valueArray', vals);
16138 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16140 n.setData('stringArray', $.splat(json.label));
16144 this.normalizeDims();
16148 modes: ['node-property:dimArray:span', 'linear'],
16150 onComplete: function() {
16152 onComplete && onComplete.onComplete();
16160 //adds the little brown bar when hovering the node
16161 select: function(id, name) {
16162 if(!this.config.hoveredColor) return;
16163 var s = this.selected;
16164 if(s.id != id || s.name != name) {
16167 s.color = this.config.hoveredColor;
16168 this.sb.graph.eachNode(function(n) {
16170 n.setData('border', s);
16172 n.setData('border', false);
16182 Returns an object containing as keys the legend names and as values hex strings with color values.
16187 var legend = pieChart.getLegend();
16190 getLegend: function() {
16191 var legend = new Array();
16192 var name = new Array();
16193 var color = new Array();
16195 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16198 var colors = n.getData('colorArray'),
16199 len = colors.length;
16200 $.each(n.getData('stringArray'), function(s, i) {
16201 color[i] = colors[i % len];
16204 legend['name'] = name;
16205 legend['color'] = color;
16210 Method: getMaxValue
16212 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16217 var ans = pieChart.getMaxValue();
16220 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16225 //will return 100 for all PieChart instances,
16226 //displaying all of them with the same scale
16227 $jit.PieChart.implement({
16228 'getMaxValue': function() {
16235 getMaxValue: function() {
16237 this.sb.graph.eachNode(function(n) {
16238 var valArray = n.getData('valueArray'),
16240 $.each(valArray, function(v) {
16243 maxValue = maxValue>acum? maxValue:acum;
16248 normalizeDims: function() {
16249 //number of elements
16250 var root = this.sb.graph.getNode(this.sb.root), l=0;
16251 root.eachAdjacency(function() {
16254 var maxValue = this.getMaxValue() || 1,
16255 config = this.config,
16256 animate = config.animate,
16257 rho = this.sb.config.levelDistance;
16258 this.sb.graph.eachNode(function(n) {
16259 var acum = 0, animateValue = [];
16260 $.each(n.getData('valueArray'), function(v) {
16262 animateValue.push(1);
16264 var stat = (animateValue.length == 1) && !config.updateHeights;
16266 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16267 return stat? rho: (n * rho / maxValue);
16269 var dimArray = n.getData('dimArray');
16271 n.setData('dimArray', animateValue);
16274 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16275 return stat? rho : (n * rho / maxValue);
16278 n.setData('normalizedDim', acum / maxValue);
16286 Options.GaugeChart = {
16290 offset: 25, // page offset
16292 labelOffset: 3, // label offset
16293 type: 'stacked', // gradient
16295 hoveredColor: '#9fd4ff',
16306 resizeLabels: false,
16308 //only valid for mono-valued datasets
16309 updateHeights: false
16314 $jit.Sunburst.Plot.NodeTypes.implement({
16315 'gaugechart-basic' : {
16316 'render' : function(node, canvas) {
16317 var pos = node.pos.getp(true),
16318 dimArray = node.getData('dimArray'),
16319 valueArray = node.getData('valueArray'),
16320 valuelabelsArray = node.getData('valuelabelsArray'),
16321 gaugeTarget = node.getData('gaugeTarget'),
16322 nodeIteration = node.getData('nodeIteration'),
16323 nodeLength = node.getData('nodeLength'),
16324 colorArray = node.getData('colorMono'),
16325 colorLength = colorArray.length,
16326 stringArray = node.getData('stringArray'),
16327 span = node.getData('span') / 2,
16328 theta = node.pos.theta,
16329 begin = ((theta - span)/2)+Math.PI,
16330 end = ((theta + span)/2)+Math.PI,
16334 var ctx = canvas.getCtx(),
16336 gradient = node.getData('gradient'),
16337 border = node.getData('border'),
16338 config = node.getData('config'),
16339 showLabels = config.showLabels,
16340 resizeLabels = config.resizeLabels,
16341 label = config.Label;
16343 var xpos = Math.cos((begin + end) /2);
16344 var ypos = Math.sin((begin + end) /2);
16346 if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16347 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16348 var dimi = dimArray[i], colori = colorArray[i % colorLength];
16349 if(dimi <= 0) continue;
16350 ctx.fillStyle = ctx.strokeStyle = colori;
16351 if(gradient && dimi) {
16352 var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16353 xpos, (ypos + dimi/2), acum + dimi);
16354 var colorRgb = $.hexToRgb(colori),
16355 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16356 endColor = $.rgbToHex(ans);
16358 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16359 radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16360 radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16361 radialGradient.addColorStop(1, 'rgba('+ans+',1)');
16362 ctx.fillStyle = radialGradient;
16366 polar.theta = begin;
16367 var p1coord = polar.getc(true);
16369 var p2coord = polar.getc(true);
16371 var p3coord = polar.getc(true);
16372 polar.theta = begin;
16373 var p4coord = polar.getc(true);
16377 //fixing FF arc method + fill
16378 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16379 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16383 acum += (dimi || 0);
16384 valAcum += (valueArray[i] || 0);
16387 if(showLabels && label.type == 'Native') {
16389 ctx.fillStyle = ctx.strokeStyle = label.color;
16392 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16393 ctx.textBaseline = 'bottom';
16394 ctx.textAlign = 'center';
16396 polar.rho = acum * .65;
16397 polar.theta = begin;
16398 var cart = polar.getc(true);
16400 //changes y pos of first label
16401 if(nodeIteration == 1) {
16402 textY = cart.y - (label.size/2) + acum /2;
16404 textY = cart.y + acum/2;
16407 if(config.labelType == 'name') {
16408 ctx.fillText(node.name, cart.x, textY);
16410 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16414 if(nodeIteration == nodeLength) {
16416 var cart = polar.getc(true);
16417 if(config.labelType == 'name') {
16418 ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16420 ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16429 'contains': function(node, pos) {
16433 if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16434 var config = node.getData('config');
16435 var ld = this.config.levelDistance , d = node._depth;
16436 var yOffset = pos.y - (ld/2);
16437 var xOffset = pos.x;
16438 var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16439 if(rho <=parseInt(ld * d)) {
16440 var dimArray = node.getData('dimArray');
16441 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16442 var dimi = dimArray[i];
16443 if(rho >= ld * .8 && rho <= acum + dimi) {
16445 var url = Url.decode(node.getData('linkArray')[i]);
16447 name: node.getData('stringArray')[i],
16449 color: node.getData('colorArray')[i],
16450 value: node.getData('valueArray')[i],
16451 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16471 A visualization that displays gauge charts
16473 Constructor Options:
16475 See <Options.Gauge>.
16478 $jit.GaugeChart = new Class({
16480 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16484 initialize: function(opt) {
16485 this.controller = this.config =
16486 $.merge(Options("Canvas", "GaugeChart", "Label"), {
16487 Label: { type: 'Native' }
16489 this.initializeViz();
16492 initializeViz: function() {
16493 var config = this.config, that = this;
16494 var nodeType = config.type.split(":")[0];
16495 var sb = new $jit.Sunburst({
16496 injectInto: config.injectInto,
16497 useCanvas: config.useCanvas,
16498 withLabels: config.Label.type != 'Native',
16499 background: config.background,
16500 renderBackground: config.renderBackground,
16501 backgroundColor: config.backgroundColor,
16502 colorStop1: config.colorStop1,
16503 colorStop2: config.colorStop2,
16505 type: config.Label.type
16509 type: 'gaugechart-' + nodeType,
16517 enable: config.Tips.enable,
16520 onShow: function(tip, node, contains) {
16521 var elem = contains;
16522 config.Tips.onShow(tip, elem, node);
16523 if(elem.link != 'undefined' && elem.link != '') {
16524 document.body.style.cursor = 'pointer';
16527 onHide: function() {
16528 document.body.style.cursor = 'default';
16534 onClick: function(node, eventInfo, evt) {
16535 if(!config.Events.enable) return;
16536 var elem = eventInfo.getContains();
16537 config.Events.onClick(elem, eventInfo, evt);
16540 onCreateLabel: function(domElement, node) {
16541 var labelConf = config.Label;
16542 if(config.showLabels) {
16543 var style = domElement.style;
16544 style.fontSize = labelConf.size + 'px';
16545 style.fontFamily = labelConf.family;
16546 style.color = labelConf.color;
16547 style.textAlign = 'center';
16548 valuelabelsArray = node.getData('valuelabelsArray'),
16549 nodeIteration = node.getData('nodeIteration'),
16550 nodeLength = node.getData('nodeLength'),
16551 canvas = sb.canvas,
16554 if(config.labelType == 'name') {
16555 domElement.innerHTML = node.name;
16557 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16560 domElement.style.width = '400px';
16563 if(nodeIteration == nodeLength && nodeLength != 0) {
16564 idLabel = canvas.id + "-label";
16565 container = document.getElementById(idLabel);
16566 finalLabel = document.createElement('div');
16567 finalLabelStyle = finalLabel.style;
16568 finalLabel.id = prefix + "finalLabel";
16569 finalLabelStyle.position = "absolute";
16570 finalLabelStyle.width = "400px";
16571 finalLabelStyle.left = "0px";
16572 container.appendChild(finalLabel);
16573 if(config.labelType == 'name') {
16574 finalLabel.innerHTML = node.name;
16576 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16582 onPlaceLabel: function(domElement, node) {
16583 if(!config.showLabels) return;
16584 var pos = node.pos.getp(true),
16585 dimArray = node.getData('dimArray'),
16586 nodeIteration = node.getData('nodeIteration'),
16587 nodeLength = node.getData('nodeLength'),
16588 span = node.getData('span') / 2,
16589 theta = node.pos.theta,
16590 begin = ((theta - span)/2)+Math.PI,
16591 end = ((theta + span)/2)+Math.PI,
16594 var showLabels = config.showLabels,
16595 resizeLabels = config.resizeLabels,
16596 label = config.Label,
16597 radiusOffset = sb.config.levelDistance;
16600 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16601 acum += dimArray[i];
16603 var scale = resizeLabels? node.getData('normalizedDim') : 1,
16604 fontSize = (label.size * scale) >> 0;
16605 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16606 domElement.style.fontSize = fontSize + 'px';
16607 polar.rho = acum * .65;
16608 polar.theta = begin;
16609 var pos = polar.getc(true);
16610 var radius = that.canvas.getSize();
16612 x: Math.round(pos.x + radius.width / 2),
16613 y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16618 domElement.style.left = (labelPos.x - 200) + 'px';
16619 domElement.style.top = labelPos.y + 'px';
16621 //reposition first label
16622 if(nodeIteration == 1) {
16623 domElement.style.top = labelPos.y - label.size + 'px';
16627 //position final label
16628 if(nodeIteration == nodeLength && nodeLength != 0) {
16630 var final = polar.getc(true);
16632 x: Math.round(final.x + radius.width / 2),
16633 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16635 finalLabel.style.left = (finalPos.x - 200) + "px";
16636 finalLabel.style.top = finalPos.y - label.size + "px";
16644 this.canvas = this.sb.canvas;
16645 var size = sb.canvas.getSize(),
16647 sb.config.levelDistance = min(size.width, size.height)/2
16648 - config.offset - config.sliceOffset;
16653 renderBackground: function() {
16654 var canvas = this.sb.canvas,
16655 config = this.config,
16656 style = config.gaugeStyle,
16657 ctx = canvas.getCtx(),
16658 size = canvas.getSize(),
16659 radius = this.sb.config.levelDistance,
16660 startAngle = (Math.PI/180)*1,
16661 endAngle = (Math.PI/180)*179;
16664 //background border
16665 ctx.fillStyle = style.borderColor;
16667 ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true);
16671 var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16672 radialGradient.addColorStop(0, '#ffffff');
16673 radialGradient.addColorStop(0.3, style.backgroundColor);
16674 radialGradient.addColorStop(0.6, style.backgroundColor);
16675 radialGradient.addColorStop(1, '#FFFFFF');
16676 ctx.fillStyle = radialGradient;
16679 startAngle = (Math.PI/180)*0;
16680 endAngle = (Math.PI/180)*180;
16682 ctx.arc(0,radius/2,radius,startAngle,endAngle, true);
16690 renderNeedle: function(gaugePosition,target) {
16691 var canvas = this.sb.canvas,
16692 config = this.config,
16693 style = config.gaugeStyle,
16694 ctx = canvas.getCtx(),
16695 size = canvas.getSize(),
16696 radius = this.sb.config.levelDistance;
16697 gaugeCenter = (radius/2);
16699 endAngle = (Math.PI/180)*180;
16703 ctx.fillStyle = style.needleColor;
16704 var segments = 180/target;
16705 needleAngle = gaugePosition * segments;
16706 ctx.translate(0, gaugeCenter);
16708 ctx.rotate(needleAngle * Math.PI / 180);
16712 ctx.lineTo(-radius*.9,-1);
16713 ctx.lineTo(-radius*.9,1);
16723 ctx.strokeStyle = '#aa0000';
16725 ctx.rotate(needleAngle * Math.PI / 180);
16729 ctx.lineTo(-radius*.8,-1);
16730 ctx.lineTo(-radius*.8,1);
16738 ctx.fillStyle = "#000000";
16739 ctx.lineWidth = style.borderSize;
16740 ctx.strokeStyle = style.borderColor;
16741 var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16742 radialGradient.addColorStop(0, '#666666');
16743 radialGradient.addColorStop(0.8, '#444444');
16744 radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
16745 ctx.fillStyle = radialGradient;
16746 ctx.translate(0,5);
16749 ctx.arc(0,0,radius*.2,startAngle,endAngle, true);
16756 renderTicks: function(values) {
16757 var canvas = this.sb.canvas,
16758 config = this.config,
16759 style = config.gaugeStyle,
16760 ctx = canvas.getCtx(),
16761 size = canvas.getSize(),
16762 radius = this.sb.config.levelDistance,
16763 gaugeCenter = (radius/2);
16766 ctx.strokeStyle = style.borderColor;
16768 ctx.lineCap = "round";
16769 for(var i=0, total = 0, l=values.length; i<l; i++) {
16770 var val = values[i];
16771 if(val.label != 'GaugePosition') {
16772 total += (parseInt(val.values) || 0);
16776 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16777 var val = values[i];
16778 if(val.label != 'GaugePosition') {
16779 acum += (parseInt(val.values) || 0);
16781 var segments = 180/total;
16782 angle = acum * segments;
16786 ctx.translate(0, gaugeCenter);
16788 ctx.rotate(angle * (Math.PI/180));
16789 ctx.moveTo(-radius,0);
16790 ctx.lineTo(-radius*.75,0);
16798 renderPositionLabel: function(position) {
16799 var canvas = this.sb.canvas,
16800 config = this.config,
16801 label = config.Label,
16802 style = config.gaugeStyle,
16803 ctx = canvas.getCtx(),
16804 size = canvas.getSize(),
16805 radius = this.sb.config.levelDistance,
16806 gaugeCenter = (radius/2);
16807 ctx.textBaseline = 'middle';
16808 ctx.textAlign = 'center';
16809 ctx.font = style.positionFontSize + 'px ' + label.family;
16810 ctx.fillStyle = "#ffffff";
16812 height = style.positionFontSize + 10,
16814 idLabel = canvas.id + "-label";
16815 container = document.getElementById(idLabel);
16816 if(label.type == 'Native') {
16817 var m = ctx.measureText(position),
16818 width = m.width + 40;
16822 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16823 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16824 if(label.type == 'Native') {
16825 ctx.fillStyle = label.color;
16826 ctx.fillText(position, 0, (height/2) + style.positionOffset);
16828 var labelDiv = document.createElement('div');
16829 labelDivStyle = labelDiv.style;
16830 labelDivStyle.color = label.color;
16831 labelDivStyle.fontSize = style.positionFontSize + "px";
16832 labelDivStyle.position = "absolute";
16833 labelDivStyle.width = width + "px";
16834 labelDivStyle.left = (size.width/2) - (width/2) + "px";
16835 labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16836 labelDiv.innerHTML = position;
16837 container.appendChild(labelDiv);
16842 renderSubtitle: function() {
16843 var canvas = this.canvas,
16844 size = canvas.getSize(),
16845 config = this.config,
16846 margin = config.Margin,
16847 radius = this.sb.config.levelDistance,
16848 title = config.Title,
16849 label = config.Label,
16850 subtitle = config.Subtitle;
16851 ctx = canvas.getCtx();
16852 ctx.fillStyle = title.color;
16853 ctx.textAlign = 'left';
16854 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16856 if(label.type == 'Native') {
16857 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2));
16861 renderChartBackground: function() {
16862 var canvas = this.canvas,
16863 config = this.config,
16864 backgroundColor = config.backgroundColor,
16865 size = canvas.getSize(),
16866 ctx = canvas.getCtx();
16867 //ctx.globalCompositeOperation = "destination-over";
16868 ctx.fillStyle = backgroundColor;
16869 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16871 clear: function() {
16872 var canvas = this.canvas;
16873 var ctx = canvas.getCtx(),
16874 size = canvas.getSize();
16875 ctx.fillStyle = "rgba(255,255,255,0)";
16876 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16877 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16879 resizeGraph: function(json,width) {
16880 var canvas = this.canvas,
16881 size = canvas.getSize(),
16882 orgHeight = size.height;
16884 canvas.resize(width,orgHeight);
16885 if(typeof FlashCanvas == "undefined") {
16888 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16890 this.loadJSON(json);
16893 loadJSON: function(json) {
16895 var prefix = $.time(),
16898 name = $.splat(json.label),
16899 nameLength = name.length,
16900 color = $.splat(json.color || this.colors),
16901 colorLength = color.length,
16902 config = this.config,
16903 renderBackground = config.renderBackground,
16904 gradient = !!config.type.split(":")[1],
16905 animate = config.animate,
16906 mono = nameLength == 1;
16907 var props = $.splat(json.properties)[0];
16909 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16911 var val = values[i];
16912 if(val.label != 'GaugePosition') {
16913 var valArray = $.splat(val.values);
16914 var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16915 var valuelabelsArray = $.splat(val.valuelabels);
16918 'id': prefix + val.label,
16922 'valuelabel': valuelabelsArray,
16923 '$linkArray': linkArray,
16924 '$valuelabelsArray': valuelabelsArray,
16925 '$valueArray': valArray,
16926 '$nodeIteration': i,
16927 '$nodeLength': l-1,
16928 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16929 '$colorMono': $.splat(color[i % colorLength]),
16930 '$stringArray': name,
16931 '$gradient': gradient,
16933 '$gaugeTarget': props['gaugeTarget'],
16934 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16939 var gaugePosition = val.gvalue;
16940 var gaugePositionLabel = val.gvaluelabel;
16944 'id': prefix + '$root',
16957 if(renderBackground) {
16958 this.renderChartBackground();
16961 this.renderBackground();
16962 this.renderSubtitle();
16964 this.normalizeDims();
16969 modes: ['node-property:dimArray'],
16975 this.renderPositionLabel(gaugePositionLabel);
16976 if (props['gaugeTarget'] != 0) {
16977 this.renderTicks(json.values);
16978 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16985 updateJSON: function(json, onComplete) {
16986 if(this.busy) return;
16990 var graph = sb.graph;
16991 var values = json.values;
16992 var animate = this.config.animate;
16994 $.each(values, function(v) {
16995 var n = graph.getByName(v.label),
16996 vals = $.splat(v.values);
16998 n.setData('valueArray', vals);
16999 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
17001 n.setData('stringArray', $.splat(json.label));
17005 this.normalizeDims();
17009 modes: ['node-property:dimArray:span', 'linear'],
17011 onComplete: function() {
17013 onComplete && onComplete.onComplete();
17021 //adds the little brown bar when hovering the node
17022 select: function(id, name) {
17023 if(!this.config.hoveredColor) return;
17024 var s = this.selected;
17025 if(s.id != id || s.name != name) {
17028 s.color = this.config.hoveredColor;
17029 this.sb.graph.eachNode(function(n) {
17031 n.setData('border', s);
17033 n.setData('border', false);
17043 Returns an object containing as keys the legend names and as values hex strings with color values.
17048 var legend = pieChart.getLegend();
17051 getLegend: function() {
17052 var legend = new Array();
17053 var name = new Array();
17054 var color = new Array();
17056 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
17059 var colors = n.getData('colorArray'),
17060 len = colors.length;
17061 $.each(n.getData('stringArray'), function(s, i) {
17062 color[i] = colors[i % len];
17065 legend['name'] = name;
17066 legend['color'] = color;
17071 Method: getMaxValue
17073 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
17078 var ans = pieChart.getMaxValue();
17081 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
17086 //will return 100 for all PieChart instances,
17087 //displaying all of them with the same scale
17088 $jit.PieChart.implement({
17089 'getMaxValue': function() {
17096 getMaxValue: function() {
17098 this.sb.graph.eachNode(function(n) {
17099 var valArray = n.getData('valueArray'),
17101 $.each(valArray, function(v) {
17104 maxValue = maxValue>acum? maxValue:acum;
17109 normalizeDims: function() {
17110 //number of elements
17111 var root = this.sb.graph.getNode(this.sb.root), l=0;
17112 root.eachAdjacency(function() {
17115 var maxValue = this.getMaxValue() || 1,
17116 config = this.config,
17117 animate = config.animate,
17118 rho = this.sb.config.levelDistance;
17119 this.sb.graph.eachNode(function(n) {
17120 var acum = 0, animateValue = [];
17121 $.each(n.getData('valueArray'), function(v) {
17123 animateValue.push(1);
17125 var stat = (animateValue.length == 1) && !config.updateHeights;
17127 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
17128 return stat? rho: (n * rho / maxValue);
17130 var dimArray = n.getData('dimArray');
17132 n.setData('dimArray', animateValue);
17135 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
17136 return stat? rho : (n * rho / maxValue);
17139 n.setData('normalizedDim', acum / maxValue);
17146 * Class: Layouts.TM
17148 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
17157 Layouts.TM.SliceAndDice = new Class({
17158 compute: function(prop) {
17159 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17160 this.controller.onBeforeCompute(root);
17161 var size = this.canvas.getSize(),
17162 config = this.config,
17163 width = size.width,
17164 height = size.height;
17165 this.graph.computeLevels(this.root, 0, "ignore");
17166 //set root position and dimensions
17167 root.getPos(prop).setc(-width/2, -height/2);
17168 root.setData('width', width, prop);
17169 root.setData('height', height + config.titleHeight, prop);
17170 this.computePositions(root, root, this.layout.orientation, prop);
17171 this.controller.onAfterCompute(root);
17174 computePositions: function(par, ch, orn, prop) {
17175 //compute children areas
17177 par.eachSubnode(function(n) {
17178 totalArea += n.getData('area', prop);
17181 var config = this.config,
17182 offst = config.offset,
17183 width = par.getData('width', prop),
17184 height = par.getData('height', prop) - config.titleHeight,
17185 fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
17187 var otherSize, size, dim, pos, pos2, posth, pos2th;
17188 var horizontal = (orn == "h");
17191 otherSize = height;
17192 size = width * fact;
17196 posth = config.titleHeight;
17200 otherSize = height * fact;
17206 pos2th = config.titleHeight;
17208 var cpos = ch.getPos(prop);
17209 ch.setData('width', size, prop);
17210 ch.setData('height', otherSize, prop);
17211 var offsetSize = 0, tm = this;
17212 ch.eachSubnode(function(n) {
17213 var p = n.getPos(prop);
17214 p[pos] = offsetSize + cpos[pos] + posth;
17215 p[pos2] = cpos[pos2] + pos2th;
17216 tm.computePositions(ch, n, orn, prop);
17217 offsetSize += n.getData(dim, prop);
17223 Layouts.TM.Area = {
17227 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17231 json - A JSON tree. See also <Loader.loadJSON>.
17232 coord - A coordinates object specifying width, height, left and top style properties.
17234 compute: function(prop) {
17235 prop = prop || "current";
17236 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17237 this.controller.onBeforeCompute(root);
17238 var config = this.config,
17239 size = this.canvas.getSize(),
17240 width = size.width,
17241 height = size.height,
17242 offst = config.offset,
17243 offwdth = width - offst,
17244 offhght = height - offst;
17245 this.graph.computeLevels(this.root, 0, "ignore");
17246 //set root position and dimensions
17247 root.getPos(prop).setc(-width/2, -height/2);
17248 root.setData('width', width, prop);
17249 root.setData('height', height, prop);
17250 //create a coordinates object
17252 'top': -height/2 + config.titleHeight,
17255 'height': offhght - config.titleHeight
17257 this.computePositions(root, coord, prop);
17258 this.controller.onAfterCompute(root);
17264 Computes dimensions and positions of a group of nodes
17265 according to a custom layout row condition.
17269 tail - An array of nodes.
17270 initElem - An array of nodes (containing the initial node to be laid).
17271 w - A fixed dimension where nodes will be layed out.
17272 coord - A coordinates object specifying width, height, left and top style properties.
17273 comp - A custom comparison function
17275 computeDim: function(tail, initElem, w, coord, comp, prop) {
17276 if(tail.length + initElem.length == 1) {
17277 var l = (tail.length == 1)? tail : initElem;
17278 this.layoutLast(l, w, coord, prop);
17281 if(tail.length >= 2 && initElem.length == 0) {
17282 initElem = [tail.shift()];
17284 if(tail.length == 0) {
17285 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17289 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17290 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17292 var newCoords = this.layoutRow(initElem, w, coord, prop);
17293 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17299 Method: worstAspectRatio
17301 Calculates the worst aspect ratio of a group of rectangles.
17305 <http://en.wikipedia.org/wiki/Aspect_ratio>
17309 ch - An array of nodes.
17310 w - The fixed dimension where rectangles are being laid out.
17314 The worst aspect ratio.
17318 worstAspectRatio: function(ch, w) {
17319 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17320 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17321 for(var i=0, l=ch.length; i<l; i++) {
17322 var area = ch[i]._area;
17324 minArea = minArea < area? minArea : area;
17325 maxArea = maxArea > area? maxArea : area;
17327 var sqw = w * w, sqAreaSum = areaSum * areaSum;
17328 return Math.max(sqw * maxArea / sqAreaSum,
17329 sqAreaSum / (sqw * minArea));
17333 Method: avgAspectRatio
17335 Calculates the average aspect ratio of a group of rectangles.
17339 <http://en.wikipedia.org/wiki/Aspect_ratio>
17343 ch - An array of nodes.
17344 w - The fixed dimension where rectangles are being laid out.
17348 The average aspect ratio.
17352 avgAspectRatio: function(ch, w) {
17353 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17355 for(var i=0, l=ch.length; i<l; i++) {
17356 var area = ch[i]._area;
17358 arSum += w > h? w / h : h / w;
17366 Performs the layout of the last computed sibling.
17370 ch - An array of nodes.
17371 w - A fixed dimension where nodes will be layed out.
17372 coord - A coordinates object specifying width, height, left and top style properties.
17374 layoutLast: function(ch, w, coord, prop) {
17376 child.getPos(prop).setc(coord.left, coord.top);
17377 child.setData('width', coord.width, prop);
17378 child.setData('height', coord.height, prop);
17383 Layouts.TM.Squarified = new Class({
17384 Implements: Layouts.TM.Area,
17386 computePositions: function(node, coord, prop) {
17387 var config = this.config;
17389 if (coord.width >= coord.height)
17390 this.layout.orientation = 'h';
17392 this.layout.orientation = 'v';
17394 var ch = node.getSubnodes([1, 1], "ignore");
17395 if(ch.length > 0) {
17396 this.processChildrenLayout(node, ch, coord, prop);
17397 for(var i=0, l=ch.length; i<l; i++) {
17399 var offst = config.offset,
17400 height = chi.getData('height', prop) - offst - config.titleHeight,
17401 width = chi.getData('width', prop) - offst;
17402 var chipos = chi.getPos(prop);
17406 'top': chipos.y + config.titleHeight,
17409 this.computePositions(chi, coord, prop);
17415 Method: processChildrenLayout
17417 Computes children real areas and other useful parameters for performing the Squarified algorithm.
17421 par - The parent node of the json subtree.
17422 ch - An Array of nodes
17423 coord - A coordinates object specifying width, height, left and top style properties.
17425 processChildrenLayout: function(par, ch, coord, prop) {
17426 //compute children real areas
17427 var parentArea = coord.width * coord.height;
17428 var i, l=ch.length, totalChArea=0, chArea = [];
17429 for(i=0; i<l; i++) {
17430 chArea[i] = parseFloat(ch[i].getData('area', prop));
17431 totalChArea += chArea[i];
17433 for(i=0; i<l; i++) {
17434 ch[i]._area = parentArea * chArea[i] / totalChArea;
17436 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17437 ch.sort(function(a, b) {
17438 var diff = b._area - a._area;
17439 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
17441 var initElem = [ch[0]];
17442 var tail = ch.slice(1);
17443 this.squarify(tail, initElem, minimumSideValue, coord, prop);
17449 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17453 tail - An array of nodes.
17454 initElem - An array of nodes, containing the initial node to be laid out.
17455 w - A fixed dimension where nodes will be laid out.
17456 coord - A coordinates object specifying width, height, left and top style properties.
17458 squarify: function(tail, initElem, w, coord, prop) {
17459 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17465 Performs the layout of an array of nodes.
17469 ch - An array of nodes.
17470 w - A fixed dimension where nodes will be laid out.
17471 coord - A coordinates object specifying width, height, left and top style properties.
17473 layoutRow: function(ch, w, coord, prop) {
17474 if(this.layout.horizontal()) {
17475 return this.layoutV(ch, w, coord, prop);
17477 return this.layoutH(ch, w, coord, prop);
17481 layoutV: function(ch, w, coord, prop) {
17482 var totalArea = 0, rnd = function(x) { return x; };
17483 $.each(ch, function(elem) { totalArea += elem._area; });
17484 var width = rnd(totalArea / w), top = 0;
17485 for(var i=0, l=ch.length; i<l; i++) {
17486 var h = rnd(ch[i]._area / width);
17488 chi.getPos(prop).setc(coord.left, coord.top + top);
17489 chi.setData('width', width, prop);
17490 chi.setData('height', h, prop);
17494 'height': coord.height,
17495 'width': coord.width - width,
17497 'left': coord.left + width
17499 //take minimum side value.
17500 ans.dim = Math.min(ans.width, ans.height);
17501 if(ans.dim != ans.height) this.layout.change();
17505 layoutH: function(ch, w, coord, prop) {
17507 $.each(ch, function(elem) { totalArea += elem._area; });
17508 var height = totalArea / w,
17512 for(var i=0, l=ch.length; i<l; i++) {
17514 var w = chi._area / height;
17515 chi.getPos(prop).setc(coord.left + left, top);
17516 chi.setData('width', w, prop);
17517 chi.setData('height', height, prop);
17521 'height': coord.height - height,
17522 'width': coord.width,
17523 'top': coord.top + height,
17526 ans.dim = Math.min(ans.width, ans.height);
17527 if(ans.dim != ans.width) this.layout.change();
17532 Layouts.TM.Strip = new Class({
17533 Implements: Layouts.TM.Area,
17538 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17542 json - A JSON subtree. See also <Loader.loadJSON>.
17543 coord - A coordinates object specifying width, height, left and top style properties.
17545 computePositions: function(node, coord, prop) {
17546 var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17547 if(ch.length > 0) {
17548 this.processChildrenLayout(node, ch, coord, prop);
17549 for(var i=0, l=ch.length; i<l; i++) {
17551 var offst = config.offset,
17552 height = chi.getData('height', prop) - offst - config.titleHeight,
17553 width = chi.getData('width', prop) - offst;
17554 var chipos = chi.getPos(prop);
17558 'top': chipos.y + config.titleHeight,
17561 this.computePositions(chi, coord, prop);
17567 Method: processChildrenLayout
17569 Computes children real areas and other useful parameters for performing the Strip algorithm.
17573 par - The parent node of the json subtree.
17574 ch - An Array of nodes
17575 coord - A coordinates object specifying width, height, left and top style properties.
17577 processChildrenLayout: function(par, ch, coord, prop) {
17578 //compute children real areas
17579 var parentArea = coord.width * coord.height;
17580 var i, l=ch.length, totalChArea=0, chArea = [];
17581 for(i=0; i<l; i++) {
17582 chArea[i] = +ch[i].getData('area', prop);
17583 totalChArea += chArea[i];
17585 for(i=0; i<l; i++) {
17586 ch[i]._area = parentArea * chArea[i] / totalChArea;
17588 var side = this.layout.horizontal()? coord.width : coord.height;
17589 var initElem = [ch[0]];
17590 var tail = ch.slice(1);
17591 this.stripify(tail, initElem, side, coord, prop);
17597 Performs an heuristic method to calculate div elements sizes in order to have
17598 a good compromise between aspect ratio and order.
17602 tail - An array of nodes.
17603 initElem - An array of nodes.
17604 w - A fixed dimension where nodes will be layed out.
17605 coord - A coordinates object specifying width, height, left and top style properties.
17607 stripify: function(tail, initElem, w, coord, prop) {
17608 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17614 Performs the layout of an array of nodes.
17618 ch - An array of nodes.
17619 w - A fixed dimension where nodes will be laid out.
17620 coord - A coordinates object specifying width, height, left and top style properties.
17622 layoutRow: function(ch, w, coord, prop) {
17623 if(this.layout.horizontal()) {
17624 return this.layoutH(ch, w, coord, prop);
17626 return this.layoutV(ch, w, coord, prop);
17630 layoutV: function(ch, w, coord, prop) {
17632 $.each(ch, function(elem) { totalArea += elem._area; });
17633 var width = totalArea / w, top = 0;
17634 for(var i=0, l=ch.length; i<l; i++) {
17636 var h = chi._area / width;
17637 chi.getPos(prop).setc(coord.left,
17638 coord.top + (w - h - top));
17639 chi.setData('width', width, prop);
17640 chi.setData('height', h, prop);
17645 'height': coord.height,
17646 'width': coord.width - width,
17648 'left': coord.left + width,
17653 layoutH: function(ch, w, coord, prop) {
17655 $.each(ch, function(elem) { totalArea += elem._area; });
17656 var height = totalArea / w,
17657 top = coord.height - height,
17660 for(var i=0, l=ch.length; i<l; i++) {
17662 var s = chi._area / height;
17663 chi.getPos(prop).setc(coord.left + left, coord.top + top);
17664 chi.setData('width', s, prop);
17665 chi.setData('height', height, prop);
17669 'height': coord.height - height,
17670 'width': coord.width,
17672 'left': coord.left,
17679 * Class: Layouts.Icicle
17681 * Implements the icicle tree layout.
17689 Layouts.Icicle = new Class({
17693 * Called by loadJSON to calculate all node positions.
17697 * posType - The nodes' position to compute. Either "start", "end" or
17698 * "current". Defaults to "current".
17700 compute: function(posType) {
17701 posType = posType || "current";
17702 var root = this.graph.getNode(this.root),
17703 config = this.config,
17704 size = this.canvas.getSize(),
17705 width = size.width,
17706 height = size.height,
17707 offset = config.offset,
17708 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17710 this.controller.onBeforeCompute(root);
17712 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17716 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17718 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17719 var maxDepth = Math.min(treeDepth, levelsToShow-1);
17720 var initialDepth = startNode._depth;
17721 if(this.layout.horizontal()) {
17722 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17724 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17728 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17729 root.getPos(posType).setc(x, y);
17730 root.setData('width', width, posType);
17731 root.setData('height', height, posType);
17733 var nodeLength, prevNodeLength = 0, totalDim = 0;
17734 var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17736 if(!children.length)
17739 $.each(children, function(e) { totalDim += e.getData('dim'); });
17741 for(var i=0, l=children.length; i < l; i++) {
17742 if(this.layout.horizontal()) {
17743 nodeLength = height * children[i].getData('dim') / totalDim;
17744 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17747 nodeLength = width * children[i].getData('dim') / totalDim;
17748 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17765 Icicle space filling visualization.
17769 All <Loader> methods
17771 Constructor Options:
17773 Inherits options from
17776 - <Options.Controller>
17782 - <Options.NodeStyles>
17783 - <Options.Navigation>
17785 Additionally, there are other parameters and some default values changed
17787 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17788 offset - (number) Default's *2*. Boxes offset.
17789 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17790 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17791 animate - (boolean) Default's *false*. Whether to animate transitions.
17792 Node.type - Described in <Options.Node>. Default's *rectangle*.
17793 Label.type - Described in <Options.Label>. Default's *Native*.
17794 duration - Described in <Options.Fx>. Default's *700*.
17795 fps - Described in <Options.Fx>. Default's *45*.
17797 Instance Properties:
17799 canvas - Access a <Canvas> instance.
17800 graph - Access a <Graph> instance.
17801 op - Access a <Icicle.Op> instance.
17802 fx - Access a <Icicle.Plot> instance.
17803 labels - Access a <Icicle.Label> interface implementation.
17807 $jit.Icicle = new Class({
17808 Implements: [ Loader, Extras, Layouts.Icicle ],
17812 vertical: function(){
17813 return this.orientation == "v";
17815 horizontal: function(){
17816 return this.orientation == "h";
17818 change: function(){
17819 this.orientation = this.vertical()? "h" : "v";
17823 initialize: function(controller) {
17828 levelsToShow: Number.MAX_VALUE,
17829 constrained: false,
17844 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17845 "Events", "Navigation", "Controller", "Label");
17846 this.controller = this.config = $.merge(opts, config, controller);
17847 this.layout.orientation = this.config.orientation;
17849 var canvasConfig = this.config;
17850 if (canvasConfig.useCanvas) {
17851 this.canvas = canvasConfig.useCanvas;
17852 this.config.labelContainer = this.canvas.id + '-label';
17854 this.canvas = new Canvas(this, canvasConfig);
17855 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17858 this.graphOptions = {
17867 this.graph = new Graph(
17868 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17870 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17871 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17872 this.op = new $jit.Icicle.Op(this);
17873 this.group = new $jit.Icicle.Group(this);
17874 this.clickedNode = null;
17876 this.initializeExtras();
17882 Computes positions and plots the tree.
17884 refresh: function(){
17885 var labelType = this.config.Label.type;
17886 if(labelType != 'Native') {
17888 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17897 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
17901 this.fx.plot(this.config);
17907 Sets the node as root.
17911 node - (object) A <Graph.Node>.
17914 enter: function (node) {
17920 config = this.config;
17923 onComplete: function() {
17924 //compute positions of newly inserted nodes
17928 if(config.animate) {
17929 that.graph.nodeList.setDataset(['current', 'end'], {
17930 'alpha': [1, 0] //fade nodes
17933 Graph.Util.eachSubgraph(node, function(n) {
17934 n.setData('alpha', 1, 'end');
17939 modes:['node-property:alpha'],
17940 onComplete: function() {
17941 that.clickedNode = node;
17942 that.compute('end');
17945 modes:['linear', 'node-property:width:height'],
17947 onComplete: function() {
17949 that.clickedNode = node;
17955 that.clickedNode = node;
17962 if(config.request) {
17963 this.requestNodes(clickedNode, callback);
17965 callback.onComplete();
17972 Sets the parent node of the current selected node as root.
17980 GUtil = Graph.Util,
17981 config = this.config,
17982 graph = this.graph,
17983 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17984 parent = parents[0],
17985 clickedNode = parent,
17986 previousClickedNode = this.clickedNode;
17989 this.events.hoveredNode = false;
17996 //final plot callback
17998 onComplete: function() {
17999 that.clickedNode = parent;
18000 if(config.request) {
18001 that.requestNodes(parent, {
18002 onComplete: function() {
18016 //animate node positions
18017 if(config.animate) {
18018 this.clickedNode = clickedNode;
18019 this.compute('end');
18020 //animate the visible subtree only
18021 this.clickedNode = previousClickedNode;
18023 modes:['linear', 'node-property:width:height'],
18025 onComplete: function() {
18026 //animate the parent subtree
18027 that.clickedNode = clickedNode;
18028 //change nodes alpha
18029 graph.nodeList.setDataset(['current', 'end'], {
18032 GUtil.eachSubgraph(previousClickedNode, function(node) {
18033 node.setData('alpha', 1);
18037 modes:['node-property:alpha'],
18038 onComplete: function() {
18039 callback.onComplete();
18045 callback.onComplete();
18048 requestNodes: function(node, onComplete){
18049 var handler = $.merge(this.controller, onComplete),
18050 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
18052 if (handler.request) {
18053 var leaves = [], d = node._depth;
18054 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
18055 if (n.drawn && !Graph.Util.anySubnode(n)) {
18057 n._level = n._depth - d;
18058 if (this.config.constrained)
18059 n._level = levelsToShow - n._level;
18063 this.group.requestNodes(leaves, handler);
18065 handler.onComplete();
18073 Custom extension of <Graph.Op>.
18077 All <Graph.Op> methods
18084 $jit.Icicle.Op = new Class({
18086 Implements: Graph.Op
18091 * Performs operations on group of nodes.
18093 $jit.Icicle.Group = new Class({
18095 initialize: function(viz){
18097 this.canvas = viz.canvas;
18098 this.config = viz.config;
18102 * Calls the request method on the controller to request a subtree for each node.
18104 requestNodes: function(nodes, controller){
18105 var counter = 0, len = nodes.length, nodeSelected = {};
18106 var complete = function(){
18107 controller.onComplete();
18109 var viz = this.viz;
18112 for(var i = 0; i < len; i++) {
18113 nodeSelected[nodes[i].id] = nodes[i];
18114 controller.request(nodes[i].id, nodes[i]._level, {
18115 onComplete: function(nodeId, data){
18116 if (data && data.children) {
18122 if (++counter == len) {
18123 Graph.Util.computeLevels(viz.graph, viz.root, 0);
18135 Custom extension of <Graph.Plot>.
18139 All <Graph.Plot> methods
18146 $jit.Icicle.Plot = new Class({
18147 Implements: Graph.Plot,
18149 plot: function(opt, animating){
18150 opt = opt || this.viz.controller;
18151 var viz = this.viz,
18153 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
18154 initialDepth = root._depth;
18156 viz.canvas.clear();
18157 this.plotTree(root, $.merge(opt, {
18158 'withLabels': true,
18159 'hideLabels': false,
18160 'plotSubtree': function(root, node) {
18161 return !viz.config.constrained ||
18162 (node._depth - initialDepth < viz.config.levelsToShow);
18169 Class: Icicle.Label
18171 Custom extension of <Graph.Label>.
18172 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18176 All <Graph.Label> methods and subclasses.
18180 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18183 $jit.Icicle.Label = {};
18186 Icicle.Label.Native
18188 Custom extension of <Graph.Label.Native>.
18192 All <Graph.Label.Native> methods
18196 <Graph.Label.Native>
18199 $jit.Icicle.Label.Native = new Class({
18200 Implements: Graph.Label.Native,
18202 renderLabel: function(canvas, node, controller) {
18203 var ctx = canvas.getCtx(),
18204 width = node.getData('width'),
18205 height = node.getData('height'),
18206 size = node.getLabelData('size'),
18207 m = ctx.measureText(node.name);
18209 // Guess as much as possible if the label will fit in the node
18210 if(height < (size * 1.5) || width < m.width)
18213 var pos = node.pos.getc(true);
18214 ctx.fillText(node.name,
18216 pos.y + height / 2);
18223 Custom extension of <Graph.Label.SVG>.
18227 All <Graph.Label.SVG> methods
18233 $jit.Icicle.Label.SVG = new Class( {
18234 Implements: Graph.Label.SVG,
18236 initialize: function(viz){
18243 Overrides abstract method placeLabel in <Graph.Plot>.
18247 tag - A DOM label element.
18248 node - A <Graph.Node>.
18249 controller - A configuration/controller object passed to the visualization.
18251 placeLabel: function(tag, node, controller){
18252 var pos = node.pos.getc(true), canvas = this.viz.canvas;
18253 var radius = canvas.getSize();
18255 x: Math.round(pos.x + radius.width / 2),
18256 y: Math.round(pos.y + radius.height / 2)
18258 tag.setAttribute('x', labelPos.x);
18259 tag.setAttribute('y', labelPos.y);
18261 controller.onPlaceLabel(tag, node);
18268 Custom extension of <Graph.Label.HTML>.
18272 All <Graph.Label.HTML> methods.
18279 $jit.Icicle.Label.HTML = new Class( {
18280 Implements: Graph.Label.HTML,
18282 initialize: function(viz){
18289 Overrides abstract method placeLabel in <Graph.Plot>.
18293 tag - A DOM label element.
18294 node - A <Graph.Node>.
18295 controller - A configuration/controller object passed to the visualization.
18297 placeLabel: function(tag, node, controller){
18298 var pos = node.pos.getc(true), canvas = this.viz.canvas;
18299 var radius = canvas.getSize();
18301 x: Math.round(pos.x + radius.width / 2),
18302 y: Math.round(pos.y + radius.height / 2)
18305 var style = tag.style;
18306 style.left = labelPos.x + 'px';
18307 style.top = labelPos.y + 'px';
18308 style.display = '';
18310 controller.onPlaceLabel(tag, node);
18315 Class: Icicle.Plot.NodeTypes
18317 This class contains a list of <Graph.Node> built-in types.
18318 Node types implemented are 'none', 'rectangle'.
18320 You can add your custom node types, customizing your visualization to the extreme.
18325 Icicle.Plot.NodeTypes.implement({
18327 'render': function(node, canvas) {
18328 //print your custom node to canvas
18331 'contains': function(node, pos) {
18332 //return true if pos is inside the node or false otherwise
18339 $jit.Icicle.Plot.NodeTypes = new Class( {
18345 'render': function(node, canvas, animating) {
18346 var config = this.viz.config;
18347 var offset = config.offset;
18348 var width = node.getData('width');
18349 var height = node.getData('height');
18350 var border = node.getData('border');
18351 var pos = node.pos.getc(true);
18352 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18353 var ctx = canvas.getCtx();
18355 if(width - offset < 2 || height - offset < 2) return;
18357 if(config.cushion) {
18358 var color = node.getData('color');
18359 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
18360 posy + (height - offset)/2, 1,
18361 posx + (width-offset)/2, posy + (height-offset)/2,
18362 width < height? height : width);
18363 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
18364 function(r) { return r * 0.3 >> 0; }));
18365 lg.addColorStop(0, color);
18366 lg.addColorStop(1, colorGrad);
18367 ctx.fillStyle = lg;
18371 ctx.strokeStyle = border;
18375 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18376 border && ctx.strokeRect(pos.x, pos.y, width, height);
18379 'contains': function(node, pos) {
18380 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18381 var npos = node.pos.getc(true),
18382 width = node.getData('width'),
18383 height = node.getData('height');
18384 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18389 $jit.Icicle.Plot.EdgeTypes = new Class( {
18396 * File: Layouts.ForceDirected.js
18401 * Class: Layouts.ForceDirected
18403 * Implements a Force Directed Layout.
18411 * Marcus Cobden <http://marcuscobden.co.uk>
18414 Layouts.ForceDirected = new Class({
18416 getOptions: function(random) {
18417 var s = this.canvas.getSize();
18418 var w = s.width, h = s.height;
18421 this.graph.eachNode(function(n) {
18424 var k2 = w * h / count, k = Math.sqrt(k2);
18425 var l = this.config.levelDistance;
18431 nodef: function(x) { return k2 / (x || 1); },
18432 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18436 compute: function(property, incremental) {
18437 var prop = $.splat(property || ['current', 'start', 'end']);
18438 var opt = this.getOptions();
18439 NodeDim.compute(this.graph, prop, this.config);
18440 this.graph.computeLevels(this.root, 0, "ignore");
18441 this.graph.eachNode(function(n) {
18442 $.each(prop, function(p) {
18443 var pos = n.getPos(p);
18444 if(pos.equals(Complex.KER)) {
18445 pos.x = opt.width/5 * (Math.random() - 0.5);
18446 pos.y = opt.height/5 * (Math.random() - 0.5);
18448 //initialize disp vector
18450 $.each(prop, function(p) {
18451 n.disp[p] = $C(0, 0);
18455 this.computePositions(prop, opt, incremental);
18458 computePositions: function(property, opt, incremental) {
18459 var times = this.config.iterations, i = 0, that = this;
18462 for(var total=incremental.iter, j=0; j<total; j++) {
18463 opt.t = opt.tstart * (1 - i++/(times -1));
18464 that.computePositionStep(property, opt);
18466 incremental.onComplete();
18470 incremental.onStep(Math.round(i / (times -1) * 100));
18471 setTimeout(iter, 1);
18474 for(; i < times; i++) {
18475 opt.t = opt.tstart * (1 - i/(times -1));
18476 this.computePositionStep(property, opt);
18481 computePositionStep: function(property, opt) {
18482 var graph = this.graph;
18483 var min = Math.min, max = Math.max;
18484 var dpos = $C(0, 0);
18485 //calculate repulsive forces
18486 graph.eachNode(function(v) {
18488 $.each(property, function(p) {
18489 v.disp[p].x = 0; v.disp[p].y = 0;
18491 graph.eachNode(function(u) {
18493 $.each(property, function(p) {
18494 var vp = v.getPos(p), up = u.getPos(p);
18495 dpos.x = vp.x - up.x;
18496 dpos.y = vp.y - up.y;
18497 var norm = dpos.norm() || 1;
18498 v.disp[p].$add(dpos
18499 .$scale(opt.nodef(norm) / norm));
18504 //calculate attractive forces
18505 var T = !!graph.getNode(this.root).visited;
18506 graph.eachNode(function(node) {
18507 node.eachAdjacency(function(adj) {
18508 var nodeTo = adj.nodeTo;
18509 if(!!nodeTo.visited === T) {
18510 $.each(property, function(p) {
18511 var vp = node.getPos(p), up = nodeTo.getPos(p);
18512 dpos.x = vp.x - up.x;
18513 dpos.y = vp.y - up.y;
18514 var norm = dpos.norm() || 1;
18515 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18516 nodeTo.disp[p].$add(dpos.$scale(-1));
18522 //arrange positions to fit the canvas
18523 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18524 graph.eachNode(function(u) {
18525 $.each(property, function(p) {
18526 var disp = u.disp[p];
18527 var norm = disp.norm() || 1;
18528 var p = u.getPos(p);
18529 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
18530 disp.y * min(Math.abs(disp.y), t) / norm));
18531 p.x = min(w2, max(-w2, p.x));
18532 p.y = min(h2, max(-h2, p.y));
18539 * File: ForceDirected.js
18543 Class: ForceDirected
18545 A visualization that lays graphs using a Force-Directed layout algorithm.
18549 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18553 All <Loader> methods
18555 Constructor Options:
18557 Inherits options from
18560 - <Options.Controller>
18566 - <Options.NodeStyles>
18567 - <Options.Navigation>
18569 Additionally, there are two parameters
18571 levelDistance - (number) Default's *50*. The natural length desired for the edges.
18572 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*.
18574 Instance Properties:
18576 canvas - Access a <Canvas> instance.
18577 graph - Access a <Graph> instance.
18578 op - Access a <ForceDirected.Op> instance.
18579 fx - Access a <ForceDirected.Plot> instance.
18580 labels - Access a <ForceDirected.Label> interface implementation.
18584 $jit.ForceDirected = new Class( {
18586 Implements: [ Loader, Extras, Layouts.ForceDirected ],
18588 initialize: function(controller) {
18589 var $ForceDirected = $jit.ForceDirected;
18596 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18597 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18599 var canvasConfig = this.config;
18600 if(canvasConfig.useCanvas) {
18601 this.canvas = canvasConfig.useCanvas;
18602 this.config.labelContainer = this.canvas.id + '-label';
18604 if(canvasConfig.background) {
18605 canvasConfig.background = $.merge({
18607 }, canvasConfig.background);
18609 this.canvas = new Canvas(this, canvasConfig);
18610 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18613 this.graphOptions = {
18621 this.graph = new Graph(this.graphOptions, this.config.Node,
18623 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18624 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18625 this.op = new $ForceDirected.Op(this);
18628 // initialize extras
18629 this.initializeExtras();
18635 Computes positions and plots the tree.
18637 refresh: function() {
18642 reposition: function() {
18643 this.compute('end');
18647 Method: computeIncremental
18649 Performs the Force Directed algorithm incrementally.
18653 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
18654 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
18655 avoiding browser messages such as "This script is taking too long to complete".
18659 opt - (object) The object properties are described below
18661 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
18662 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18664 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
18665 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
18666 computations for final animation positions then you can just choose 'end'.
18668 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
18669 parameter a percentage value.
18671 onComplete - A callback function called when the algorithm completed.
18675 In this example I calculate the end positions and then animate the graph to those positions
18678 var fd = new $jit.ForceDirected(...);
18679 fd.computeIncremental({
18682 onStep: function(perc) {
18683 Log.write("loading " + perc + "%");
18685 onComplete: function() {
18692 In this example I calculate all positions and (re)plot the graph
18695 var fd = new ForceDirected(...);
18696 fd.computeIncremental({
18698 property: ['end', 'start', 'current'],
18699 onStep: function(perc) {
18700 Log.write("loading " + perc + "%");
18702 onComplete: function() {
18710 computeIncremental: function(opt) {
18715 onComplete: $.empty
18718 this.config.onBeforeCompute(this.graph.getNode(this.root));
18719 this.compute(opt.property, opt);
18725 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18734 Animates the graph from the current positions to the 'end' node positions.
18736 animate: function(opt) {
18737 this.fx.animate($.merge( {
18738 modes: [ 'linear' ]
18743 $jit.ForceDirected.$extend = true;
18745 (function(ForceDirected) {
18748 Class: ForceDirected.Op
18750 Custom extension of <Graph.Op>.
18754 All <Graph.Op> methods
18761 ForceDirected.Op = new Class( {
18763 Implements: Graph.Op
18768 Class: ForceDirected.Plot
18770 Custom extension of <Graph.Plot>.
18774 All <Graph.Plot> methods
18781 ForceDirected.Plot = new Class( {
18783 Implements: Graph.Plot
18788 Class: ForceDirected.Label
18790 Custom extension of <Graph.Label>.
18791 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18795 All <Graph.Label> methods and subclasses.
18799 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18802 ForceDirected.Label = {};
18805 ForceDirected.Label.Native
18807 Custom extension of <Graph.Label.Native>.
18811 All <Graph.Label.Native> methods
18815 <Graph.Label.Native>
18818 ForceDirected.Label.Native = new Class( {
18819 Implements: Graph.Label.Native
18823 ForceDirected.Label.SVG
18825 Custom extension of <Graph.Label.SVG>.
18829 All <Graph.Label.SVG> methods
18836 ForceDirected.Label.SVG = new Class( {
18837 Implements: Graph.Label.SVG,
18839 initialize: function(viz) {
18846 Overrides abstract method placeLabel in <Graph.Label>.
18850 tag - A DOM label element.
18851 node - A <Graph.Node>.
18852 controller - A configuration/controller object passed to the visualization.
18855 placeLabel: function(tag, node, controller) {
18856 var pos = node.pos.getc(true),
18857 canvas = this.viz.canvas,
18858 ox = canvas.translateOffsetX,
18859 oy = canvas.translateOffsetY,
18860 sx = canvas.scaleOffsetX,
18861 sy = canvas.scaleOffsetY,
18862 radius = canvas.getSize();
18864 x: Math.round(pos.x * sx + ox + radius.width / 2),
18865 y: Math.round(pos.y * sy + oy + radius.height / 2)
18867 tag.setAttribute('x', labelPos.x);
18868 tag.setAttribute('y', labelPos.y);
18870 controller.onPlaceLabel(tag, node);
18875 ForceDirected.Label.HTML
18877 Custom extension of <Graph.Label.HTML>.
18881 All <Graph.Label.HTML> methods.
18888 ForceDirected.Label.HTML = new Class( {
18889 Implements: Graph.Label.HTML,
18891 initialize: function(viz) {
18897 Overrides abstract method placeLabel in <Graph.Plot>.
18901 tag - A DOM label element.
18902 node - A <Graph.Node>.
18903 controller - A configuration/controller object passed to the visualization.
18906 placeLabel: function(tag, node, controller) {
18907 var pos = node.pos.getc(true),
18908 canvas = this.viz.canvas,
18909 ox = canvas.translateOffsetX,
18910 oy = canvas.translateOffsetY,
18911 sx = canvas.scaleOffsetX,
18912 sy = canvas.scaleOffsetY,
18913 radius = canvas.getSize();
18915 x: Math.round(pos.x * sx + ox + radius.width / 2),
18916 y: Math.round(pos.y * sy + oy + radius.height / 2)
18918 var style = tag.style;
18919 style.left = labelPos.x + 'px';
18920 style.top = labelPos.y + 'px';
18921 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18923 controller.onPlaceLabel(tag, node);
18928 Class: ForceDirected.Plot.NodeTypes
18930 This class contains a list of <Graph.Node> built-in types.
18931 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18933 You can add your custom node types, customizing your visualization to the extreme.
18938 ForceDirected.Plot.NodeTypes.implement({
18940 'render': function(node, canvas) {
18941 //print your custom node to canvas
18944 'contains': function(node, pos) {
18945 //return true if pos is inside the node or false otherwise
18952 ForceDirected.Plot.NodeTypes = new Class({
18955 'contains': $.lambda(false)
18958 'render': function(node, canvas){
18959 var pos = node.pos.getc(true),
18960 dim = node.getData('dim');
18961 this.nodeHelper.circle.render('fill', pos, dim, canvas);
18963 'contains': function(node, pos){
18964 var npos = node.pos.getc(true),
18965 dim = node.getData('dim');
18966 return this.nodeHelper.circle.contains(npos, pos, dim);
18970 'render': function(node, canvas){
18971 var pos = node.pos.getc(true),
18972 width = node.getData('width'),
18973 height = node.getData('height');
18974 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18976 // TODO(nico): be more precise...
18977 'contains': function(node, pos){
18978 var npos = node.pos.getc(true),
18979 width = node.getData('width'),
18980 height = node.getData('height');
18981 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18985 'render': function(node, canvas){
18986 var pos = node.pos.getc(true),
18987 dim = node.getData('dim');
18988 this.nodeHelper.square.render('fill', pos, dim, canvas);
18990 'contains': function(node, pos){
18991 var npos = node.pos.getc(true),
18992 dim = node.getData('dim');
18993 return this.nodeHelper.square.contains(npos, pos, dim);
18997 'render': function(node, canvas){
18998 var pos = node.pos.getc(true),
18999 width = node.getData('width'),
19000 height = node.getData('height');
19001 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
19003 'contains': function(node, pos){
19004 var npos = node.pos.getc(true),
19005 width = node.getData('width'),
19006 height = node.getData('height');
19007 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
19011 'render': function(node, canvas){
19012 var pos = node.pos.getc(true),
19013 dim = node.getData('dim');
19014 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
19016 'contains': function(node, pos) {
19017 var npos = node.pos.getc(true),
19018 dim = node.getData('dim');
19019 return this.nodeHelper.triangle.contains(npos, pos, dim);
19023 'render': function(node, canvas){
19024 var pos = node.pos.getc(true),
19025 dim = node.getData('dim');
19026 this.nodeHelper.star.render('fill', pos, dim, canvas);
19028 'contains': function(node, pos) {
19029 var npos = node.pos.getc(true),
19030 dim = node.getData('dim');
19031 return this.nodeHelper.star.contains(npos, pos, dim);
19037 Class: ForceDirected.Plot.EdgeTypes
19039 This class contains a list of <Graph.Adjacence> built-in types.
19040 Edge types implemented are 'none', 'line' and 'arrow'.
19042 You can add your custom edge types, customizing your visualization to the extreme.
19047 ForceDirected.Plot.EdgeTypes.implement({
19049 'render': function(adj, canvas) {
19050 //print your custom edge to canvas
19053 'contains': function(adj, pos) {
19054 //return true if pos is inside the arc or false otherwise
19061 ForceDirected.Plot.EdgeTypes = new Class({
19064 'render': function(adj, canvas) {
19065 var from = adj.nodeFrom.pos.getc(true),
19066 to = adj.nodeTo.pos.getc(true);
19067 this.edgeHelper.line.render(from, to, canvas);
19069 'contains': function(adj, pos) {
19070 var from = adj.nodeFrom.pos.getc(true),
19071 to = adj.nodeTo.pos.getc(true);
19072 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
19076 'render': function(adj, canvas) {
19077 var from = adj.nodeFrom.pos.getc(true),
19078 to = adj.nodeTo.pos.getc(true),
19079 dim = adj.getData('dim'),
19080 direction = adj.data.$direction,
19081 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
19082 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
19084 'contains': function(adj, pos) {
19085 var from = adj.nodeFrom.pos.getc(true),
19086 to = adj.nodeTo.pos.getc(true);
19087 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
19092 })($jit.ForceDirected);
19104 $jit.TM.$extend = true;
19109 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
19113 All <Loader> methods
19115 Constructor Options:
19117 Inherits options from
19120 - <Options.Controller>
19126 - <Options.NodeStyles>
19127 - <Options.Navigation>
19129 Additionally, there are other parameters and some default values changed
19131 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
19132 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
19133 offset - (number) Default's *2*. Boxes offset.
19134 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
19135 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
19136 animate - (boolean) Default's *false*. Whether to animate transitions.
19137 Node.type - Described in <Options.Node>. Default's *rectangle*.
19138 duration - Described in <Options.Fx>. Default's *700*.
19139 fps - Described in <Options.Fx>. Default's *45*.
19141 Instance Properties:
19143 canvas - Access a <Canvas> instance.
19144 graph - Access a <Graph> instance.
19145 op - Access a <TM.Op> instance.
19146 fx - Access a <TM.Plot> instance.
19147 labels - Access a <TM.Label> interface implementation.
19151 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
19153 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
19157 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.
19163 vertical: function(){
19164 return this.orientation == "v";
19166 horizontal: function(){
19167 return this.orientation == "h";
19169 change: function(){
19170 this.orientation = this.vertical()? "h" : "v";
19174 initialize: function(controller){
19180 constrained: false,
19185 //we all know why this is not zero,
19192 textAlign: 'center',
19193 textBaseline: 'top'
19202 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19203 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19204 this.layout.orientation = this.config.orientation;
19206 var canvasConfig = this.config;
19207 if (canvasConfig.useCanvas) {
19208 this.canvas = canvasConfig.useCanvas;
19209 this.config.labelContainer = this.canvas.id + '-label';
19211 if(canvasConfig.background) {
19212 canvasConfig.background = $.merge({
19214 }, canvasConfig.background);
19216 this.canvas = new Canvas(this, canvasConfig);
19217 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19220 this.graphOptions = {
19228 this.graph = new Graph(this.graphOptions, this.config.Node,
19230 this.labels = new TM.Label[canvasConfig.Label.type](this);
19231 this.fx = new TM.Plot(this);
19232 this.op = new TM.Op(this);
19233 this.group = new TM.Group(this);
19234 this.geom = new TM.Geom(this);
19235 this.clickedNode = null;
19237 // initialize extras
19238 this.initializeExtras();
19244 Computes positions and plots the tree.
19246 refresh: function(){
19247 if(this.busy) return;
19250 if(this.config.animate) {
19251 this.compute('end');
19252 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
19253 && this.clickedNode.id || this.root));
19254 this.fx.animate($.merge(this.config, {
19255 modes: ['linear', 'node-property:width:height'],
19256 onComplete: function() {
19261 var labelType = this.config.Label.type;
19262 if(labelType != 'Native') {
19264 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
19268 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
19269 && this.clickedNode.id || this.root));
19277 Plots the TreeMap. This is a shortcut to *fx.plot*.
19287 Returns whether the node is a leaf.
19291 n - (object) A <Graph.Node>.
19295 return n.getSubnodes([
19297 ], "ignore").length == 0;
19303 Sets the node as root.
19307 n - (object) A <Graph.Node>.
19310 enter: function(n){
19311 if(this.busy) return;
19315 config = this.config,
19316 graph = this.graph,
19318 previousClickedNode = this.clickedNode;
19321 onComplete: function() {
19322 //ensure that nodes are shown for that level
19323 if(config.levelsToShow > 0) {
19324 that.geom.setRightLevelToShow(n);
19326 //compute positions of newly inserted nodes
19327 if(config.levelsToShow > 0 || config.request) that.compute();
19328 if(config.animate) {
19330 graph.nodeList.setData('alpha', 0, 'end');
19331 n.eachSubgraph(function(n) {
19332 n.setData('alpha', 1, 'end');
19336 modes:['node-property:alpha'],
19337 onComplete: function() {
19338 //compute end positions
19339 that.clickedNode = clickedNode;
19340 that.compute('end');
19341 //animate positions
19342 //TODO(nico) commenting this line didn't seem to throw errors...
19343 that.clickedNode = previousClickedNode;
19345 modes:['linear', 'node-property:width:height'],
19347 onComplete: function() {
19349 //TODO(nico) check comment above
19350 that.clickedNode = clickedNode;
19357 that.clickedNode = n;
19362 if(config.request) {
19363 this.requestNodes(clickedNode, callback);
19365 callback.onComplete();
19372 Sets the parent node of the current selected node as root.
19376 if(this.busy) return;
19378 this.events.hoveredNode = false;
19380 config = this.config,
19381 graph = this.graph,
19382 parents = graph.getNode(this.clickedNode
19383 && this.clickedNode.id || this.root).getParents(),
19384 parent = parents[0],
19385 clickedNode = parent,
19386 previousClickedNode = this.clickedNode;
19388 //if no parents return
19393 //final plot callback
19395 onComplete: function() {
19396 that.clickedNode = parent;
19397 if(config.request) {
19398 that.requestNodes(parent, {
19399 onComplete: function() {
19413 if (config.levelsToShow > 0)
19414 this.geom.setRightLevelToShow(parent);
19415 //animate node positions
19416 if(config.animate) {
19417 this.clickedNode = clickedNode;
19418 this.compute('end');
19419 //animate the visible subtree only
19420 this.clickedNode = previousClickedNode;
19422 modes:['linear', 'node-property:width:height'],
19424 onComplete: function() {
19425 //animate the parent subtree
19426 that.clickedNode = clickedNode;
19427 //change nodes alpha
19428 graph.eachNode(function(n) {
19429 n.setDataset(['current', 'end'], {
19433 previousClickedNode.eachSubgraph(function(node) {
19434 node.setData('alpha', 1);
19438 modes:['node-property:alpha'],
19439 onComplete: function() {
19440 callback.onComplete();
19446 callback.onComplete();
19450 requestNodes: function(node, onComplete){
19451 var handler = $.merge(this.controller, onComplete),
19452 lev = this.config.levelsToShow;
19453 if (handler.request) {
19454 var leaves = [], d = node._depth;
19455 node.eachLevel(0, lev, function(n){
19456 var nodeLevel = lev - (n._depth - d);
19457 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19459 n._level = nodeLevel;
19462 this.group.requestNodes(leaves, handler);
19464 handler.onComplete();
19472 Custom extension of <Graph.Op>.
19476 All <Graph.Op> methods
19483 TM.Op = new Class({
19484 Implements: Graph.Op,
19486 initialize: function(viz){
19491 //extend level methods of Graph.Geom
19492 TM.Geom = new Class({
19493 Implements: Graph.Geom,
19495 getRightLevelToShow: function() {
19496 return this.viz.config.levelsToShow;
19499 setRightLevelToShow: function(node) {
19500 var level = this.getRightLevelToShow(),
19501 fx = this.viz.labels;
19502 node.eachLevel(0, level+1, function(n) {
19503 var d = n._depth - node._depth;
19508 fx.hideLabel(n, false);
19516 delete node.ignore;
19522 Performs operations on group of nodes.
19525 TM.Group = new Class( {
19527 initialize: function(viz){
19529 this.canvas = viz.canvas;
19530 this.config = viz.config;
19535 Calls the request method on the controller to request a subtree for each node.
19537 requestNodes: function(nodes, controller){
19538 var counter = 0, len = nodes.length, nodeSelected = {};
19539 var complete = function(){
19540 controller.onComplete();
19542 var viz = this.viz;
19545 for ( var i = 0; i < len; i++) {
19546 nodeSelected[nodes[i].id] = nodes[i];
19547 controller.request(nodes[i].id, nodes[i]._level, {
19548 onComplete: function(nodeId, data){
19549 if (data && data.children) {
19555 if (++counter == len) {
19556 viz.graph.computeLevels(viz.root, 0);
19568 Custom extension of <Graph.Plot>.
19572 All <Graph.Plot> methods
19579 TM.Plot = new Class({
19581 Implements: Graph.Plot,
19583 initialize: function(viz){
19585 this.config = viz.config;
19586 this.node = this.config.Node;
19587 this.edge = this.config.Edge;
19588 this.animation = new Animation;
19589 this.nodeTypes = new TM.Plot.NodeTypes;
19590 this.edgeTypes = new TM.Plot.EdgeTypes;
19591 this.labels = viz.labels;
19594 plot: function(opt, animating){
19595 var viz = this.viz,
19597 viz.canvas.clear();
19598 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19599 'withLabels': true,
19600 'hideLabels': false,
19601 'plotSubtree': function(n, ch){
19602 return n.anySubnode("exist");
19611 Custom extension of <Graph.Label>.
19612 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19616 All <Graph.Label> methods and subclasses.
19620 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19628 Custom extension of <Graph.Label.Native>.
19632 All <Graph.Label.Native> methods
19636 <Graph.Label.Native>
19638 TM.Label.Native = new Class({
19639 Implements: Graph.Label.Native,
19641 initialize: function(viz) {
19642 this.config = viz.config;
19643 this.leaf = viz.leaf;
19646 renderLabel: function(canvas, node, controller){
19647 if(!this.leaf(node) && !this.config.titleHeight) return;
19648 var pos = node.pos.getc(true),
19649 ctx = canvas.getCtx(),
19650 width = node.getData('width'),
19651 height = node.getData('height'),
19652 x = pos.x + width/2,
19655 ctx.fillText(node.name, x, y, width);
19662 Custom extension of <Graph.Label.SVG>.
19666 All <Graph.Label.SVG> methods
19672 TM.Label.SVG = new Class( {
19673 Implements: Graph.Label.SVG,
19675 initialize: function(viz){
19677 this.leaf = viz.leaf;
19678 this.config = viz.config;
19684 Overrides abstract method placeLabel in <Graph.Plot>.
19688 tag - A DOM label element.
19689 node - A <Graph.Node>.
19690 controller - A configuration/controller object passed to the visualization.
19693 placeLabel: function(tag, node, controller){
19694 var pos = node.pos.getc(true),
19695 canvas = this.viz.canvas,
19696 ox = canvas.translateOffsetX,
19697 oy = canvas.translateOffsetY,
19698 sx = canvas.scaleOffsetX,
19699 sy = canvas.scaleOffsetY,
19700 radius = canvas.getSize();
19702 x: Math.round(pos.x * sx + ox + radius.width / 2),
19703 y: Math.round(pos.y * sy + oy + radius.height / 2)
19705 tag.setAttribute('x', labelPos.x);
19706 tag.setAttribute('y', labelPos.y);
19708 if(!this.leaf(node) && !this.config.titleHeight) {
19709 tag.style.display = 'none';
19711 controller.onPlaceLabel(tag, node);
19718 Custom extension of <Graph.Label.HTML>.
19722 All <Graph.Label.HTML> methods.
19729 TM.Label.HTML = new Class( {
19730 Implements: Graph.Label.HTML,
19732 initialize: function(viz){
19734 this.leaf = viz.leaf;
19735 this.config = viz.config;
19741 Overrides abstract method placeLabel in <Graph.Plot>.
19745 tag - A DOM label element.
19746 node - A <Graph.Node>.
19747 controller - A configuration/controller object passed to the visualization.
19750 placeLabel: function(tag, node, controller){
19751 var pos = node.pos.getc(true),
19752 canvas = this.viz.canvas,
19753 ox = canvas.translateOffsetX,
19754 oy = canvas.translateOffsetY,
19755 sx = canvas.scaleOffsetX,
19756 sy = canvas.scaleOffsetY,
19757 radius = canvas.getSize();
19759 x: Math.round(pos.x * sx + ox + radius.width / 2),
19760 y: Math.round(pos.y * sy + oy + radius.height / 2)
19763 var style = tag.style;
19764 style.left = labelPos.x + 'px';
19765 style.top = labelPos.y + 'px';
19766 style.width = node.getData('width') * sx + 'px';
19767 style.height = node.getData('height') * sy + 'px';
19768 style.zIndex = node._depth * 100;
19769 style.display = '';
19771 if(!this.leaf(node) && !this.config.titleHeight) {
19772 tag.style.display = 'none';
19774 controller.onPlaceLabel(tag, node);
19779 Class: TM.Plot.NodeTypes
19781 This class contains a list of <Graph.Node> built-in types.
19782 Node types implemented are 'none', 'rectangle'.
19784 You can add your custom node types, customizing your visualization to the extreme.
19789 TM.Plot.NodeTypes.implement({
19791 'render': function(node, canvas) {
19792 //print your custom node to canvas
19795 'contains': function(node, pos) {
19796 //return true if pos is inside the node or false otherwise
19803 TM.Plot.NodeTypes = new Class( {
19809 'render': function(node, canvas, animating){
19810 var leaf = this.viz.leaf(node),
19811 config = this.config,
19812 offst = config.offset,
19813 titleHeight = config.titleHeight,
19814 pos = node.pos.getc(true),
19815 width = node.getData('width'),
19816 height = node.getData('height'),
19817 border = node.getData('border'),
19818 ctx = canvas.getCtx(),
19819 posx = pos.x + offst / 2,
19820 posy = pos.y + offst / 2;
19821 if(width <= offst || height <= offst) return;
19823 if(config.cushion) {
19824 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
19825 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19826 var color = node.getData('color');
19827 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
19828 function(r) { return r * 0.2 >> 0; }));
19829 lg.addColorStop(0, color);
19830 lg.addColorStop(1, colorGrad);
19831 ctx.fillStyle = lg;
19833 ctx.fillRect(posx, posy, width - offst, height - offst);
19836 ctx.strokeStyle = border;
19837 ctx.strokeRect(posx, posy, width - offst, height - offst);
19840 } else if(titleHeight > 0){
19841 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19842 titleHeight - offst);
19845 ctx.strokeStyle = border;
19846 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19852 'contains': function(node, pos) {
19853 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19854 var npos = node.pos.getc(true),
19855 width = node.getData('width'),
19856 leaf = this.viz.leaf(node),
19857 height = leaf? node.getData('height') : this.config.titleHeight;
19858 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19863 TM.Plot.EdgeTypes = new Class( {
19868 Class: TM.SliceAndDice
19870 A slice and dice TreeMap visualization.
19874 All <TM.Base> methods and properties.
19876 TM.SliceAndDice = new Class( {
19878 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19883 Class: TM.Squarified
19885 A squarified TreeMap visualization.
19889 All <TM.Base> methods and properties.
19891 TM.Squarified = new Class( {
19893 Loader, Extras, TM.Base, Layouts.TM.Squarified
19900 A strip TreeMap visualization.
19904 All <TM.Base> methods and properties.
19906 TM.Strip = new Class( {
19908 Loader, Extras, TM.Base, Layouts.TM.Strip
19921 A radial graph visualization with advanced animations.
19925 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>
19929 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.
19933 All <Loader> methods
19935 Constructor Options:
19937 Inherits options from
19940 - <Options.Controller>
19946 - <Options.NodeStyles>
19947 - <Options.Navigation>
19949 Additionally, there are other parameters and some default values changed
19951 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19952 levelDistance - (number) Default's *100*. The distance between levels of the tree.
19954 Instance Properties:
19956 canvas - Access a <Canvas> instance.
19957 graph - Access a <Graph> instance.
19958 op - Access a <RGraph.Op> instance.
19959 fx - Access a <RGraph.Plot> instance.
19960 labels - Access a <RGraph.Label> interface implementation.
19963 $jit.RGraph = new Class( {
19966 Loader, Extras, Layouts.Radial
19969 initialize: function(controller){
19970 var $RGraph = $jit.RGraph;
19973 interpolation: 'linear',
19977 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19978 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19980 var canvasConfig = this.config;
19981 if(canvasConfig.useCanvas) {
19982 this.canvas = canvasConfig.useCanvas;
19983 this.config.labelContainer = this.canvas.id + '-label';
19985 if(canvasConfig.background) {
19986 canvasConfig.background = $.merge({
19988 }, canvasConfig.background);
19990 this.canvas = new Canvas(this, canvasConfig);
19991 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19994 this.graphOptions = {
20002 this.graph = new Graph(this.graphOptions, this.config.Node,
20004 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
20005 this.fx = new $RGraph.Plot(this, $RGraph);
20006 this.op = new $RGraph.Op(this);
20010 this.parent = false;
20011 // initialize extras
20012 this.initializeExtras();
20017 createLevelDistanceFunc
20019 Returns the levelDistance function used for calculating a node distance
20020 to its origin. This function returns a function that is computed
20021 per level and not per node, such that all nodes with the same depth will have the
20022 same distance to the origin. The resulting function gets the
20023 parent node as parameter and returns a float.
20026 createLevelDistanceFunc: function(){
20027 var ld = this.config.levelDistance;
20028 return function(elem){
20029 return (elem._depth + 1) * ld;
20036 Computes positions and plots the tree.
20039 refresh: function(){
20044 reposition: function(){
20045 this.compute('end');
20051 Plots the RGraph. This is a shortcut to *fx.plot*.
20057 getNodeAndParentAngle
20059 Returns the _parent_ of the given node, also calculating its angle span.
20061 getNodeAndParentAngle: function(id){
20063 var n = this.graph.getNode(id);
20064 var ps = n.getParents();
20065 var p = (ps.length > 0)? ps[0] : false;
20067 var posParent = p.pos.getc(), posChild = n.pos.getc();
20068 var newPos = posParent.add(posChild.scale(-1));
20069 theta = Math.atan2(newPos.y, newPos.x);
20071 theta += 2 * Math.PI;
20081 Enumerates the children in order to maintain child ordering (second constraint of the paper).
20083 tagChildren: function(par, id){
20084 if (par.angleSpan) {
20086 par.eachAdjacency(function(elem){
20087 adjs.push(elem.nodeTo);
20089 var len = adjs.length;
20090 for ( var i = 0; i < len && id != adjs[i].id; i++)
20092 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
20093 adjs[j].dist = k++;
20100 Animates the <RGraph> to center the node specified by *id*.
20104 id - A <Graph.Node> id.
20105 opt - (optional|object) An object containing some extra properties described below
20106 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20111 rgraph.onClick('someid');
20113 rgraph.onClick('someid', {
20119 onClick: function(id, opt){
20120 if (this.root != id && !this.busy) {
20124 this.controller.onBeforeCompute(this.graph.getNode(id));
20125 var obj = this.getNodeAndParentAngle(id);
20127 // second constraint
20128 this.tagChildren(obj.parent, id);
20129 this.parent = obj.parent;
20130 this.compute('end');
20132 // first constraint
20133 var thetaDiff = obj.theta - obj.parent.endPos.theta;
20134 this.graph.eachNode(function(elem){
20135 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
20138 var mode = this.config.interpolation;
20140 onComplete: $.empty
20143 this.fx.animate($.merge( {
20149 onComplete: function(){
20158 $jit.RGraph.$extend = true;
20165 Custom extension of <Graph.Op>.
20169 All <Graph.Op> methods
20176 RGraph.Op = new Class( {
20178 Implements: Graph.Op
20185 Custom extension of <Graph.Plot>.
20189 All <Graph.Plot> methods
20196 RGraph.Plot = new Class( {
20198 Implements: Graph.Plot
20203 Object: RGraph.Label
20205 Custom extension of <Graph.Label>.
20206 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20210 All <Graph.Label> methods and subclasses.
20214 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20220 RGraph.Label.Native
20222 Custom extension of <Graph.Label.Native>.
20226 All <Graph.Label.Native> methods
20230 <Graph.Label.Native>
20233 RGraph.Label.Native = new Class( {
20234 Implements: Graph.Label.Native
20240 Custom extension of <Graph.Label.SVG>.
20244 All <Graph.Label.SVG> methods
20251 RGraph.Label.SVG = new Class( {
20252 Implements: Graph.Label.SVG,
20254 initialize: function(viz){
20261 Overrides abstract method placeLabel in <Graph.Plot>.
20265 tag - A DOM label element.
20266 node - A <Graph.Node>.
20267 controller - A configuration/controller object passed to the visualization.
20270 placeLabel: function(tag, node, controller){
20271 var pos = node.pos.getc(true),
20272 canvas = this.viz.canvas,
20273 ox = canvas.translateOffsetX,
20274 oy = canvas.translateOffsetY,
20275 sx = canvas.scaleOffsetX,
20276 sy = canvas.scaleOffsetY,
20277 radius = canvas.getSize();
20279 x: Math.round(pos.x * sx + ox + radius.width / 2),
20280 y: Math.round(pos.y * sy + oy + radius.height / 2)
20282 tag.setAttribute('x', labelPos.x);
20283 tag.setAttribute('y', labelPos.y);
20285 controller.onPlaceLabel(tag, node);
20292 Custom extension of <Graph.Label.HTML>.
20296 All <Graph.Label.HTML> methods.
20303 RGraph.Label.HTML = new Class( {
20304 Implements: Graph.Label.HTML,
20306 initialize: function(viz){
20312 Overrides abstract method placeLabel in <Graph.Plot>.
20316 tag - A DOM label element.
20317 node - A <Graph.Node>.
20318 controller - A configuration/controller object passed to the visualization.
20321 placeLabel: function(tag, node, controller){
20322 var pos = node.pos.getc(true),
20323 canvas = this.viz.canvas,
20324 ox = canvas.translateOffsetX,
20325 oy = canvas.translateOffsetY,
20326 sx = canvas.scaleOffsetX,
20327 sy = canvas.scaleOffsetY,
20328 radius = canvas.getSize();
20330 x: Math.round(pos.x * sx + ox + radius.width / 2),
20331 y: Math.round(pos.y * sy + oy + radius.height / 2)
20334 var style = tag.style;
20335 style.left = labelPos.x + 'px';
20336 style.top = labelPos.y + 'px';
20337 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20339 controller.onPlaceLabel(tag, node);
20344 Class: RGraph.Plot.NodeTypes
20346 This class contains a list of <Graph.Node> built-in types.
20347 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20349 You can add your custom node types, customizing your visualization to the extreme.
20354 RGraph.Plot.NodeTypes.implement({
20356 'render': function(node, canvas) {
20357 //print your custom node to canvas
20360 'contains': function(node, pos) {
20361 //return true if pos is inside the node or false otherwise
20368 RGraph.Plot.NodeTypes = new Class({
20371 'contains': $.lambda(false)
20374 'render': function(node, canvas){
20375 var pos = node.pos.getc(true),
20376 dim = node.getData('dim');
20377 this.nodeHelper.circle.render('fill', pos, dim, canvas);
20379 'contains': function(node, pos){
20380 var npos = node.pos.getc(true),
20381 dim = node.getData('dim');
20382 return this.nodeHelper.circle.contains(npos, pos, dim);
20386 'render': function(node, canvas){
20387 var pos = node.pos.getc(true),
20388 width = node.getData('width'),
20389 height = node.getData('height');
20390 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20392 // TODO(nico): be more precise...
20393 'contains': function(node, pos){
20394 var npos = node.pos.getc(true),
20395 width = node.getData('width'),
20396 height = node.getData('height');
20397 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20401 'render': function(node, canvas){
20402 var pos = node.pos.getc(true),
20403 dim = node.getData('dim');
20404 this.nodeHelper.square.render('fill', pos, dim, canvas);
20406 'contains': function(node, pos){
20407 var npos = node.pos.getc(true),
20408 dim = node.getData('dim');
20409 return this.nodeHelper.square.contains(npos, pos, dim);
20413 'render': function(node, canvas){
20414 var pos = node.pos.getc(true),
20415 width = node.getData('width'),
20416 height = node.getData('height');
20417 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20419 'contains': function(node, pos){
20420 var npos = node.pos.getc(true),
20421 width = node.getData('width'),
20422 height = node.getData('height');
20423 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20427 'render': function(node, canvas){
20428 var pos = node.pos.getc(true),
20429 dim = node.getData('dim');
20430 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20432 'contains': function(node, pos) {
20433 var npos = node.pos.getc(true),
20434 dim = node.getData('dim');
20435 return this.nodeHelper.triangle.contains(npos, pos, dim);
20439 'render': function(node, canvas){
20440 var pos = node.pos.getc(true),
20441 dim = node.getData('dim');
20442 this.nodeHelper.star.render('fill', pos, dim, canvas);
20444 'contains': function(node, pos) {
20445 var npos = node.pos.getc(true),
20446 dim = node.getData('dim');
20447 return this.nodeHelper.star.contains(npos, pos, dim);
20453 Class: RGraph.Plot.EdgeTypes
20455 This class contains a list of <Graph.Adjacence> built-in types.
20456 Edge types implemented are 'none', 'line' and 'arrow'.
20458 You can add your custom edge types, customizing your visualization to the extreme.
20463 RGraph.Plot.EdgeTypes.implement({
20465 'render': function(adj, canvas) {
20466 //print your custom edge to canvas
20469 'contains': function(adj, pos) {
20470 //return true if pos is inside the arc or false otherwise
20477 RGraph.Plot.EdgeTypes = new Class({
20480 'render': function(adj, canvas) {
20481 var from = adj.nodeFrom.pos.getc(true),
20482 to = adj.nodeTo.pos.getc(true);
20483 this.edgeHelper.line.render(from, to, canvas);
20485 'contains': function(adj, pos) {
20486 var from = adj.nodeFrom.pos.getc(true),
20487 to = adj.nodeTo.pos.getc(true);
20488 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20492 'render': function(adj, canvas) {
20493 var from = adj.nodeFrom.pos.getc(true),
20494 to = adj.nodeTo.pos.getc(true),
20495 dim = adj.getData('dim'),
20496 direction = adj.data.$direction,
20497 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20498 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20500 'contains': function(adj, pos) {
20501 var from = adj.nodeFrom.pos.getc(true),
20502 to = adj.nodeTo.pos.getc(true);
20503 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20512 * File: Hypertree.js
20519 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
20523 moebiusTransformation
20525 Calculates a moebius transformation for this point / complex.
20526 For more information go to:
20527 http://en.wikipedia.org/wiki/Moebius_transformation.
20531 c - An initialized Complex instance representing a translation Vector.
20534 Complex.prototype.moebiusTransformation = function(c) {
20535 var num = this.add(c);
20536 var den = c.$conjugate().$prod(this);
20538 return num.$div(den);
20542 moebiusTransformation
20544 Calculates a moebius transformation for the hyperbolic tree.
20546 <http://en.wikipedia.org/wiki/Moebius_transformation>
20550 graph - A <Graph> instance.
20552 prop - A property array.
20553 theta - Rotation angle.
20554 startPos - _optional_ start position.
20556 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20557 this.eachNode(graph, function(elem) {
20558 for ( var i = 0; i < prop.length; i++) {
20559 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20560 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20568 A Hyperbolic Tree/Graph visualization.
20572 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
20573 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20577 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.
20581 All <Loader> methods
20583 Constructor Options:
20585 Inherits options from
20588 - <Options.Controller>
20594 - <Options.NodeStyles>
20595 - <Options.Navigation>
20597 Additionally, there are other parameters and some default values changed
20599 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*.
20600 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.
20601 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20602 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20603 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
20605 Instance Properties:
20607 canvas - Access a <Canvas> instance.
20608 graph - Access a <Graph> instance.
20609 op - Access a <Hypertree.Op> instance.
20610 fx - Access a <Hypertree.Plot> instance.
20611 labels - Access a <Hypertree.Label> interface implementation.
20615 $jit.Hypertree = new Class( {
20617 Implements: [ Loader, Extras, Layouts.Radial ],
20619 initialize: function(controller) {
20620 var $Hypertree = $jit.Hypertree;
20631 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20632 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20634 var canvasConfig = this.config;
20635 if(canvasConfig.useCanvas) {
20636 this.canvas = canvasConfig.useCanvas;
20637 this.config.labelContainer = this.canvas.id + '-label';
20639 if(canvasConfig.background) {
20640 canvasConfig.background = $.merge({
20642 }, canvasConfig.background);
20644 this.canvas = new Canvas(this, canvasConfig);
20645 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20648 this.graphOptions = {
20656 this.graph = new Graph(this.graphOptions, this.config.Node,
20658 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20659 this.fx = new $Hypertree.Plot(this, $Hypertree);
20660 this.op = new $Hypertree.Op(this);
20664 // initialize extras
20665 this.initializeExtras();
20670 createLevelDistanceFunc
20672 Returns the levelDistance function used for calculating a node distance
20673 to its origin. This function returns a function that is computed
20674 per level and not per node, such that all nodes with the same depth will have the
20675 same distance to the origin. The resulting function gets the
20676 parent node as parameter and returns a float.
20679 createLevelDistanceFunc: function() {
20680 // get max viz. length.
20681 var r = this.getRadius();
20683 var depth = 0, max = Math.max, config = this.config;
20684 this.graph.eachNode(function(node) {
20685 depth = max(node._depth, depth);
20688 // node distance generator
20689 var genDistFunc = function(a) {
20690 return function(node) {
20692 var d = node._depth + 1;
20693 var acum = 0, pow = Math.pow;
20695 acum += pow(a, d--);
20697 return acum - config.offset;
20700 // estimate better edge length.
20701 for ( var i = 0.51; i <= 1; i += 0.01) {
20702 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20703 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20705 return genDistFunc(0.75);
20711 Returns the current radius of the visualization. If *config.radius* is *auto* then it
20712 calculates the radius by taking the smaller size of the <Canvas> widget.
20719 getRadius: function() {
20720 var rad = this.config.radius;
20721 if (rad !== "auto") { return rad; }
20722 var s = this.canvas.getSize();
20723 return Math.min(s.width, s.height) / 2;
20729 Computes positions and plots the tree.
20733 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20736 refresh: function(reposition) {
20739 this.graph.eachNode(function(node) {
20740 node.startPos.rho = node.pos.rho = node.endPos.rho;
20741 node.startPos.theta = node.pos.theta = node.endPos.theta;
20752 Computes nodes' positions and restores the tree to its previous position.
20754 For calculating nodes' positions the root must be placed on its origin. This method does this
20755 and then attemps to restore the hypertree to its previous position.
20758 reposition: function() {
20759 this.compute('end');
20760 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20761 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20763 this.graph.eachNode(function(node) {
20765 node.endPos.rho = node.pos.rho;
20766 node.endPos.theta = node.pos.theta;
20774 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
20784 Animates the <Hypertree> to center the node specified by *id*.
20788 id - A <Graph.Node> id.
20789 opt - (optional|object) An object containing some extra properties described below
20790 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20795 ht.onClick('someid');
20797 ht.onClick('someid', {
20803 onClick: function(id, opt) {
20804 var pos = this.graph.getNode(id).pos.getc(true);
20805 this.move(pos, opt);
20811 Translates the tree to the given position.
20815 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20816 opt - This object has been defined in <Hypertree.onClick>
20821 ht.move({ x: 0, y: 0.7 }, {
20827 move: function(pos, opt) {
20828 var versor = $C(pos.x, pos.y);
20829 if (this.busy === false && versor.norm() < 1) {
20831 var root = this.graph.getClosestNodeToPos(versor), that = this;
20832 this.graph.computeLevels(root.id, 0);
20833 this.controller.onBeforeCompute(root);
20835 onComplete: $.empty
20837 this.fx.animate($.merge( {
20838 modes: [ 'moebius' ],
20841 onComplete: function() {
20850 $jit.Hypertree.$extend = true;
20852 (function(Hypertree) {
20855 Class: Hypertree.Op
20857 Custom extension of <Graph.Op>.
20861 All <Graph.Op> methods
20868 Hypertree.Op = new Class( {
20870 Implements: Graph.Op
20875 Class: Hypertree.Plot
20877 Custom extension of <Graph.Plot>.
20881 All <Graph.Plot> methods
20888 Hypertree.Plot = new Class( {
20890 Implements: Graph.Plot
20895 Object: Hypertree.Label
20897 Custom extension of <Graph.Label>.
20898 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20902 All <Graph.Label> methods and subclasses.
20906 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20909 Hypertree.Label = {};
20912 Hypertree.Label.Native
20914 Custom extension of <Graph.Label.Native>.
20918 All <Graph.Label.Native> methods
20922 <Graph.Label.Native>
20925 Hypertree.Label.Native = new Class( {
20926 Implements: Graph.Label.Native,
20928 initialize: function(viz) {
20932 renderLabel: function(canvas, node, controller) {
20933 var ctx = canvas.getCtx();
20934 var coord = node.pos.getc(true);
20935 var s = this.viz.getRadius();
20936 ctx.fillText(node.name, coord.x * s, coord.y * s);
20941 Hypertree.Label.SVG
20943 Custom extension of <Graph.Label.SVG>.
20947 All <Graph.Label.SVG> methods
20954 Hypertree.Label.SVG = new Class( {
20955 Implements: Graph.Label.SVG,
20957 initialize: function(viz) {
20964 Overrides abstract method placeLabel in <Graph.Plot>.
20968 tag - A DOM label element.
20969 node - A <Graph.Node>.
20970 controller - A configuration/controller object passed to the visualization.
20973 placeLabel: function(tag, node, controller) {
20974 var pos = node.pos.getc(true),
20975 canvas = this.viz.canvas,
20976 ox = canvas.translateOffsetX,
20977 oy = canvas.translateOffsetY,
20978 sx = canvas.scaleOffsetX,
20979 sy = canvas.scaleOffsetY,
20980 radius = canvas.getSize(),
20981 r = this.viz.getRadius();
20983 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20984 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20986 tag.setAttribute('x', labelPos.x);
20987 tag.setAttribute('y', labelPos.y);
20988 controller.onPlaceLabel(tag, node);
20993 Hypertree.Label.HTML
20995 Custom extension of <Graph.Label.HTML>.
20999 All <Graph.Label.HTML> methods.
21006 Hypertree.Label.HTML = new Class( {
21007 Implements: Graph.Label.HTML,
21009 initialize: function(viz) {
21015 Overrides abstract method placeLabel in <Graph.Plot>.
21019 tag - A DOM label element.
21020 node - A <Graph.Node>.
21021 controller - A configuration/controller object passed to the visualization.
21024 placeLabel: function(tag, node, controller) {
21025 var pos = node.pos.getc(true),
21026 canvas = this.viz.canvas,
21027 ox = canvas.translateOffsetX,
21028 oy = canvas.translateOffsetY,
21029 sx = canvas.scaleOffsetX,
21030 sy = canvas.scaleOffsetY,
21031 radius = canvas.getSize(),
21032 r = this.viz.getRadius();
21034 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
21035 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
21037 var style = tag.style;
21038 style.left = labelPos.x + 'px';
21039 style.top = labelPos.y + 'px';
21040 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
21042 controller.onPlaceLabel(tag, node);
21047 Class: Hypertree.Plot.NodeTypes
21049 This class contains a list of <Graph.Node> built-in types.
21050 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
21052 You can add your custom node types, customizing your visualization to the extreme.
21057 Hypertree.Plot.NodeTypes.implement({
21059 'render': function(node, canvas) {
21060 //print your custom node to canvas
21063 'contains': function(node, pos) {
21064 //return true if pos is inside the node or false otherwise
21071 Hypertree.Plot.NodeTypes = new Class({
21074 'contains': $.lambda(false)
21077 'render': function(node, canvas) {
21078 var nconfig = this.node,
21079 dim = node.getData('dim'),
21080 p = node.pos.getc();
21081 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21082 p.$scale(node.scale);
21084 this.nodeHelper.circle.render('fill', p, dim, canvas);
21087 'contains': function(node, pos) {
21088 var dim = node.getData('dim'),
21089 npos = node.pos.getc().$scale(node.scale);
21090 return this.nodeHelper.circle.contains(npos, pos, dim);
21094 'render': function(node, canvas) {
21095 var pos = node.pos.getc().$scale(node.scale),
21096 width = node.getData('width'),
21097 height = node.getData('height');
21098 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
21100 'contains': function(node, pos) {
21101 var width = node.getData('width'),
21102 height = node.getData('height'),
21103 npos = node.pos.getc().$scale(node.scale);
21104 return this.nodeHelper.circle.contains(npos, pos, width, height);
21108 'render': function(node, canvas) {
21109 var nconfig = this.node,
21110 dim = node.getData('dim'),
21111 p = node.pos.getc();
21112 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21113 p.$scale(node.scale);
21115 this.nodeHelper.square.render('fill', p, dim, canvas);
21118 'contains': function(node, pos) {
21119 var dim = node.getData('dim'),
21120 npos = node.pos.getc().$scale(node.scale);
21121 return this.nodeHelper.square.contains(npos, pos, dim);
21125 'render': function(node, canvas) {
21126 var nconfig = this.node,
21127 width = node.getData('width'),
21128 height = node.getData('height'),
21129 pos = node.pos.getc();
21130 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
21131 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
21132 pos.$scale(node.scale);
21133 if (width > 0.2 && height > 0.2) {
21134 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
21137 'contains': function(node, pos) {
21138 var width = node.getData('width'),
21139 height = node.getData('height'),
21140 npos = node.pos.getc().$scale(node.scale);
21141 return this.nodeHelper.square.contains(npos, pos, width, height);
21145 'render': function(node, canvas) {
21146 var nconfig = this.node,
21147 dim = node.getData('dim'),
21148 p = node.pos.getc();
21149 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21150 p.$scale(node.scale);
21152 this.nodeHelper.triangle.render('fill', p, dim, canvas);
21155 'contains': function(node, pos) {
21156 var dim = node.getData('dim'),
21157 npos = node.pos.getc().$scale(node.scale);
21158 return this.nodeHelper.triangle.contains(npos, pos, dim);
21162 'render': function(node, canvas) {
21163 var nconfig = this.node,
21164 dim = node.getData('dim'),
21165 p = node.pos.getc();
21166 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21167 p.$scale(node.scale);
21169 this.nodeHelper.star.render('fill', p, dim, canvas);
21172 'contains': function(node, pos) {
21173 var dim = node.getData('dim'),
21174 npos = node.pos.getc().$scale(node.scale);
21175 return this.nodeHelper.star.contains(npos, pos, dim);
21181 Class: Hypertree.Plot.EdgeTypes
21183 This class contains a list of <Graph.Adjacence> built-in types.
21184 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
21186 You can add your custom edge types, customizing your visualization to the extreme.
21191 Hypertree.Plot.EdgeTypes.implement({
21193 'render': function(adj, canvas) {
21194 //print your custom edge to canvas
21197 'contains': function(adj, pos) {
21198 //return true if pos is inside the arc or false otherwise
21205 Hypertree.Plot.EdgeTypes = new Class({
21208 'render': function(adj, canvas) {
21209 var from = adj.nodeFrom.pos.getc(true),
21210 to = adj.nodeTo.pos.getc(true),
21211 r = adj.nodeFrom.scale;
21212 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
21214 'contains': function(adj, pos) {
21215 var from = adj.nodeFrom.pos.getc(true),
21216 to = adj.nodeTo.pos.getc(true),
21217 r = adj.nodeFrom.scale;
21218 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21222 'render': function(adj, canvas) {
21223 var from = adj.nodeFrom.pos.getc(true),
21224 to = adj.nodeTo.pos.getc(true),
21225 r = adj.nodeFrom.scale,
21226 dim = adj.getData('dim'),
21227 direction = adj.data.$direction,
21228 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
21229 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
21231 'contains': function(adj, pos) {
21232 var from = adj.nodeFrom.pos.getc(true),
21233 to = adj.nodeTo.pos.getc(true),
21234 r = adj.nodeFrom.scale;
21235 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21239 'render': function(adj, canvas) {
21240 var from = adj.nodeFrom.pos.getc(),
21241 to = adj.nodeTo.pos.getc(),
21242 dim = this.viz.getRadius();
21243 this.edgeHelper.hyperline.render(from, to, dim, canvas);
21245 'contains': $.lambda(false)
21249 })($jit.Hypertree);