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();
13203 if (typeof val.id == 'undefined') {
13204 val.id = val.label;
13207 'id': prefix + val.id,
13212 '$linkArray': linkArray,
13213 '$gvl': val.gvaluelabel,
13214 '$titleArray': titleArray,
13215 '$valueArray': valArray,
13216 '$valuelabelArray': valuelabelArray,
13217 '$colorArray': color,
13218 '$colorMono': $.splat(color[i % colorLength]),
13219 '$stringArray': name,
13220 '$barTotalValue': barTotalValue,
13221 '$groupTotalValue': groupTotalValue,
13222 '$nodeCount': values.length,
13223 '$gradient': gradient,
13230 'id': prefix + '$root',
13241 this.normalizeDims();
13243 if(renderBackground) {
13244 this.renderBackground();
13247 if(!animate && ticks.enable) {
13248 this.renderTicks();
13250 if(!animate && note.text) {
13251 this.renderScrollNote();
13253 if(!animate && title.text) {
13254 this.renderTitle();
13256 if(!animate && subtitle.text) {
13257 this.renderSubtitle();
13261 st.select(st.root);
13265 modes: ['node-property:width:dimArray'],
13267 onComplete: function() {
13273 modes: ['node-property:height:dimArray'],
13275 onComplete: function() {
13288 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.
13292 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13293 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13298 barChart.updateJSON(json, {
13299 onComplete: function() {
13300 alert('update complete!');
13305 updateJSON: function(json, onComplete) {
13306 if(this.busy) return;
13310 var graph = st.graph;
13311 var values = json.values;
13312 var animate = this.config.animate;
13314 var horz = this.config.orientation == 'horizontal';
13315 $.each(values, function(v) {
13316 var n = graph.getByName(v.label);
13318 n.setData('valueArray', $.splat(v.values));
13320 n.setData('stringArray', $.splat(json.label));
13324 this.normalizeDims();
13326 st.select(st.root);
13330 modes: ['node-property:width:dimArray'],
13332 onComplete: function() {
13334 onComplete && onComplete.onComplete();
13339 modes: ['node-property:height:dimArray'],
13341 onComplete: function() {
13343 onComplete && onComplete.onComplete();
13350 //adds the little brown bar when hovering the node
13351 select: function(id, name) {
13353 if(!this.config.hoveredColor) return;
13354 var s = this.selected;
13355 if(s.id != id || s.name != name) {
13358 s.color = this.config.hoveredColor;
13359 this.st.graph.eachNode(function(n) {
13361 n.setData('border', s);
13363 n.setData('border', false);
13373 Returns an object containing as keys the legend names and as values hex strings with color values.
13378 var legend = barChart.getLegend();
13381 getLegend: function() {
13382 var legend = new Array();
13383 var name = new Array();
13384 var color = new Array();
13386 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13389 var colors = n.getData('colorArray'),
13390 len = colors.length;
13391 $.each(n.getData('stringArray'), function(s, i) {
13392 color[i] = colors[i % len];
13395 legend['name'] = name;
13396 legend['color'] = color;
13401 Method: getMaxValue
13403 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13408 var ans = barChart.getMaxValue();
13411 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13416 //will return 100 for all BarChart instances,
13417 //displaying all of them with the same scale
13418 $jit.BarChart.implement({
13419 'getMaxValue': function() {
13426 getMaxValue: function() {
13427 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13428 this.st.graph.eachNode(function(n) {
13429 var valArray = n.getData('valueArray'),
13431 if(!valArray) return;
13433 $.each(valArray, function(v) {
13437 acum = Math.max.apply(null, valArray);
13439 maxValue = maxValue>acum? maxValue:acum;
13444 setBarType: function(type) {
13445 this.config.type = type;
13446 this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13449 normalizeDims: function() {
13450 //number of elements
13451 var root = this.st.graph.getNode(this.st.root), l=0;
13452 root.eachAdjacency(function() {
13455 var maxValue = this.getMaxValue() || 1,
13456 size = this.st.canvas.getSize(),
13457 config = this.config,
13458 margin = config.Margin,
13459 ticks = config.Ticks,
13460 title = config.Title,
13461 subtitle = config.Subtitle,
13462 grouped = config.type.split(':')[0] == 'grouped',
13463 marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13464 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13465 horz = config.orientation == 'horizontal',
13466 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13467 animate = config.animate,
13468 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
13470 - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13471 dim1 = horz? 'height':'width',
13472 dim2 = horz? 'width':'height',
13473 basic = config.type.split(':')[0] == 'basic';
13475 // Bug #47147 Correct detection of maxTickValue and step size for asix labels
13476 var iDirection = 10; // We need this var for convert value. 10^2 = 100
13477 var zeroCount = 0; // Pow for iDirection for detection of size of human step.
13478 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.
13479 // Tries to get two first digits from maxValue
13482 // Tries to calculate zeroCount
13483 // if iNumber = 100 we will get zeroCount = 2, iNumber = 0.1
13484 while (iNumber >= 1)
13487 iNumber = iNumber / 10;
13489 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
13493 iDirection = 0.1; // if iNumber is less than 1 we should change iDirection. 0.1^2 = 0.01
13494 // Tries to calculate zeroCount
13495 // if iNumber = 0.01 we will get zeroCount = 2, iNumber = 1
13496 while (iNumber < 1)
13499 iNumber = iNumber * 10;
13501 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
13503 var humanNumber = 0;
13504 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.
13505 // 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.
13506 while (iNumberTemp % 5 != 0)
13510 var isFound = false;
13511 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
13512 // Tries to find humanNumber. Our step is 5. ticks.segments is number of lines = 4 (for example)
13513 // 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
13514 while (isFound == false)
13516 if (iNumberTemp % ticks.segments == 0)
13518 humanNumber = iNumberTemp / ticks.segments;
13522 iNumberTemp = iNumberTemp + 5;
13524 // Getting real values
13525 var maxTickValue = config.Ticks.maxValue = maxTickValue = iNumberTemp * Math.pow(iDirection, zeroCount - 1);
13526 config.Ticks.humanNumber = humanNumber = humanNumber * Math.pow(iDirection, zeroCount - 1);
13527 config.Ticks.segments = Math.floor(maxTickValue / humanNumber);
13529 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13532 this.st.graph.eachNode(function(n) {
13533 var acum = 0, animateValue = [];
13534 $.each(n.getData('valueArray'), function(v) {
13536 animateValue.push(0);
13540 fixedDim = animateValue.length * 40;
13542 n.setData(dim1, fixedDim);
13546 n.setData(dim2, acum * height / maxValue, 'end');
13547 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13548 return n * height / maxValue;
13550 var dimArray = n.getData('dimArray');
13552 n.setData('dimArray', animateValue);
13558 n.setData(dim2, acum * height / maxTickValue);
13559 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13560 return n * height / maxTickValue;
13563 n.setData(dim2, acum * height / maxValue);
13564 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13565 return n * height / maxValue;
13573 //funnel chart options
13576 Options.FunnelChart = {
13580 type: 'stacked', //stacked, grouped, : gradient
13581 labelOffset: 3, //label offset
13582 barsOffset: 0, //distance between bars
13583 hoveredColor: '#9fd4ff',
13584 orientation: 'vertical',
13585 showAggregates: true,
13598 $jit.ST.Plot.NodeTypes.implement({
13599 'funnelchart-basic' : {
13600 'render' : function(node, canvas) {
13601 var pos = node.pos.getc(true),
13602 width = node.getData('width'),
13603 height = node.getData('height'),
13604 algnPos = this.getAlignedPos(pos, width, height),
13605 x = algnPos.x, y = algnPos.y,
13606 dimArray = node.getData('dimArray'),
13607 valueArray = node.getData('valueArray'),
13608 valuelabelArray = node.getData('valuelabelArray'),
13609 linkArray = node.getData('linkArray'),
13610 colorArray = node.getData('colorArray'),
13611 colorLength = colorArray.length,
13612 stringArray = node.getData('stringArray');
13613 var ctx = canvas.getCtx(),
13615 border = node.getData('border'),
13616 gradient = node.getData('gradient'),
13617 config = node.getData('config'),
13618 horz = config.orientation == 'horizontal',
13619 aggregates = config.showAggregates,
13620 showLabels = config.showLabels,
13621 label = config.Label,
13622 size = canvas.getSize(),
13623 labelOffset = config.labelOffset + 10;
13624 minWidth = width * .25;
13627 if (colorArray && dimArray && stringArray) {
13628 var newDimArray = this.positions(dimArray, label.size);
13630 // horizontal lines
13631 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13632 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13634 if(label.type == 'Native') {
13635 if(showLabels(node.name, valAcum, node)) {
13636 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13637 var stringValue = stringArray[i];
13638 var valueLabel = String(valuelabelArray[i]);
13639 var mV = ctx.measureText(stringValue);
13640 var mVL = ctx.measureText(valueLabel);
13645 next_mV = ctx.measureText(stringArray[i + 1]);
13646 next_mVL = ctx.measureText(String(valuelabelArray[i + 1]));
13653 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13654 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13655 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13656 var bottomWidth = minWidth + ((acum) * ratio);
13657 var bottomWidthLabel = minWidth + (newDimArray[i].position * ratio);
13658 var labelOffsetRight = (newDimArray[i].filament) ? (next_mV.width + 25) : 0;
13659 var labelOffsetLeft = (newDimArray[i].filament) ? (next_mVL.width + 25) : 0;
13660 // ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13664 ctx.moveTo(bottomWidth/2,y - acum); //
13665 ctx.lineTo(bottomWidthLabel / 2 + (labelOffset - 10), y - newDimArray[i].position); // top right
13666 ctx.lineTo(bottomWidthLabel / 2 + (labelOffset) + labelOffsetRight + mV.width, y - newDimArray[i].position); // bottom right
13670 ctx.moveTo(-bottomWidth/2,y - acum); //
13671 ctx.lineTo( - bottomWidthLabel / 2 - (labelOffset - 10), y - newDimArray[i].position); // top right
13672 ctx.lineTo( - bottomWidthLabel / 2 - (labelOffset) - labelOffsetLeft - mVL.width, y - newDimArray[i].position); // bottom right
13677 acum += (dimArray[i] || 0);
13678 valAcum += (valueArray[i] || 0);
13685 //funnel segments and labels
13686 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13687 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13688 var colori = colorArray[i % colorLength];
13689 if(label.type == 'Native') {
13690 var stringValue = stringArray[i];
13691 var valueLabel = String(valuelabelArray[i]);
13692 var mV = ctx.measureText(stringValue);
13693 var mVL = ctx.measureText(valueLabel);
13700 next_mV = ctx.measureText(stringArray[i + 1]);
13701 next_mVL = ctx.measureText(String(valuelabelArray[i + 1]));
13708 var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13709 var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13710 var labelOffsetRight = (newDimArray[i].filament) ? (next_mV.width + 20) : 0;
13711 var labelOffsetLeft = (newDimArray[i].filament) ? (next_mVL.width + 20) : 0;
13713 var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13714 var bottomWidth = minWidth + ((acum) * ratio);
13715 var bottomWidthLabel = minWidth + (newDimArray[i].position * ratio);
13720 linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13721 var colorRgb = $.hexToRgb(colori);
13722 var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
13723 function(v) { return (v * .5) >> 0; });
13724 linear.addColorStop(0, 'rgba('+color+',1)');
13725 linear.addColorStop(0.5, 'rgba('+colorRgb+',1)');
13726 linear.addColorStop(1, 'rgba('+color+',1)');
13727 ctx.fillStyle = linear;
13731 ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13732 ctx.lineTo(topWidth/2,y - acum - dimArray[i]); // top right
13733 ctx.lineTo(bottomWidth/2,y - acum); // bottom right
13734 ctx.lineTo(-bottomWidth/2,y - acum); // bottom left
13739 if(border && border.name == stringArray[i]) {
13741 opt.dimValue = dimArray[i];
13748 ctx.strokeStyle = border.color;
13750 //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13754 if(label.type == 'Native') {
13756 ctx.fillStyle = ctx.strokeStyle = label.color;
13757 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13758 ctx.textBaseline = 'middle';
13760 acumValueLabel = valAcum;
13762 if(showLabels(node.name, valAcum, node)) {
13765 ctx.textAlign = 'left';
13766 ctx.fillText(stringArray[i], (bottomWidthLabel / 2) + labelOffset + labelOffsetRight, y - newDimArray[i].position - label.size / 2);
13767 ctx.textAlign = 'right';
13768 ctx.fillText(valuelabelArray[i], (- bottomWidthLabel / 2) - labelOffset - labelOffsetLeft, y - newDimArray[i].position - label.size / 2);
13773 acum += (dimArray[i] || 0);
13774 valAcum += (valueArray[i] || 0);
13780 'contains': function(node, mpos) {
13781 var pos = node.pos.getc(true),
13782 width = node.getData('width'),
13783 height = node.getData('height'),
13784 algnPos = this.getAlignedPos(pos, width, height),
13785 x = algnPos.x, y = algnPos.y,
13786 dimArray = node.getData('dimArray'),
13787 config = node.getData('config'),
13788 st = node.getData('st'),
13790 horz = config.orientation == 'horizontal',
13791 minWidth = width * .25;
13793 canvas = node.getData('canvas'),
13794 size = canvas.getSize(),
13795 offsetY = st.config.offsetY;
13796 //bounding box check
13798 if(mpos.y > y || mpos.y < y - height) {
13802 var newY = Math.abs(mpos.y + offsetY);
13803 var bound = minWidth + (newY * ratio);
13804 var boundLeft = -bound/2;
13805 var boundRight = bound/2;
13806 if(mpos.x < boundLeft || mpos.x > boundRight ) {
13812 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13813 var dimi = dimArray[i];
13817 var url = Url.decode(node.getData('linkArray')[i]);
13819 var intersec = acum;
13820 if(mpos.y >= intersec) {
13822 'name': node.getData('stringArray')[i],
13823 'color': node.getData('colorArray')[i],
13824 'value': node.getData('valueArray')[i],
13825 'percentage': node.getData('percentageArray')[i],
13826 'valuelabel': node.getData('valuelabelArray')[i],
13841 A visualization that displays funnel charts.
13843 Constructor Options:
13845 See <Options.FunnelChart>.
13848 $jit.FunnelChart = new Class({
13850 colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13854 initialize: function(opt) {
13855 this.controller = this.config =
13856 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13857 Label: { type: 'Native' }
13859 //set functions for showLabels and showAggregates
13860 var showLabels = this.config.showLabels,
13861 typeLabels = $.type(showLabels),
13862 showAggregates = this.config.showAggregates,
13863 typeAggregates = $.type(showAggregates);
13864 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13865 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13866 Options.Fx.clearCanvas = false;
13867 this.initializeViz();
13870 initializeViz: function() {
13871 var config = this.config, that = this;
13872 var nodeType = config.type.split(":")[0],
13873 horz = config.orientation == 'horizontal',
13875 var st = new $jit.ST({
13876 injectInto: config.injectInto,
13877 orientation: horz? 'left' : 'bottom',
13879 background: config.background,
13880 renderBackground: config.renderBackground,
13881 backgroundColor: config.backgroundColor,
13882 colorStop1: config.colorStop1,
13883 colorStop2: config.colorStop2,
13884 siblingOffset: config.segmentOffset,
13886 withLabels: config.Label.type != 'Native',
13887 useCanvas: config.useCanvas,
13889 type: config.Label.type
13893 type: 'funnelchart-' + nodeType,
13902 enable: config.Tips.enable,
13905 onShow: function(tip, node, contains) {
13906 var elem = contains;
13907 config.Tips.onShow(tip, elem, node);
13908 if(elem.link != 'undefined' && elem.link != '') {
13909 document.body.style.cursor = 'pointer';
13912 onHide: function(call) {
13913 document.body.style.cursor = 'default';
13920 onClick: function(node, eventInfo, evt) {
13921 if(!config.Events.enable) return;
13922 var elem = eventInfo.getContains();
13923 config.Events.onClick(elem, eventInfo, evt);
13925 onMouseMove: function(node, eventInfo, evt) {
13926 if(!config.hoveredColor) return;
13928 var elem = eventInfo.getContains();
13929 that.select(node.id, elem.name, elem.index);
13931 that.select(false, false, false);
13935 onCreateLabel: function(domElement, node) {
13936 var labelConf = config.Label,
13937 valueArray = node.getData('valueArray'),
13938 idArray = node.getData('idArray'),
13939 valuelabelArray = node.getData('valuelabelArray'),
13940 stringArray = node.getData('stringArray');
13941 size = st.canvas.getSize()
13944 for(var i=0, l=valueArray.length; i<l; i++) {
13946 wrapper: document.createElement('div'),
13947 valueLabel: document.createElement('div'),
13948 label: document.createElement('div')
13950 var wrapper = nlbs.wrapper,
13951 label = nlbs.label,
13952 valueLabel = nlbs.valueLabel,
13953 wrapperStyle = wrapper.style,
13954 labelStyle = label.style,
13955 valueLabelStyle = valueLabel.style;
13956 //store node labels
13957 nodeLabels[idArray[i]] = nlbs;
13959 wrapper.appendChild(label);
13960 wrapper.appendChild(valueLabel);
13962 wrapperStyle.position = 'relative';
13963 wrapperStyle.overflow = 'visible';
13964 wrapperStyle.fontSize = labelConf.size + 'px';
13965 wrapperStyle.fontFamily = labelConf.family;
13966 wrapperStyle.color = labelConf.color;
13967 wrapperStyle.textAlign = 'center';
13968 wrapperStyle.width = size.width + 'px';
13969 valueLabelStyle.position = labelStyle.position = 'absolute';
13970 valueLabelStyle.left = labelStyle.left = '0px';
13971 valueLabelStyle.width = (size.width/3) + 'px';
13972 valueLabelStyle.textAlign = 'right';
13973 label.innerHTML = stringArray[i];
13974 valueLabel.innerHTML = valuelabelArray[i];
13975 domElement.id = prefix+'funnel';
13976 domElement.style.width = size.width + 'px';
13978 domElement.appendChild(wrapper);
13982 onPlaceLabel: function(domElement, node) {
13984 var dimArray = node.getData('dimArray'),
13985 idArray = node.getData('idArray'),
13986 valueArray = node.getData('valueArray'),
13987 valuelabelArray = node.getData('valuelabelArray'),
13988 stringArray = node.getData('stringArray');
13989 size = st.canvas.getSize(),
13990 pos = node.pos.getc(true),
13991 domElement.style.left = "0px",
13992 domElement.style.top = "0px",
13993 minWidth = node.getData('width') * .25,
13995 pos = node.pos.getc(true),
13996 labelConf = config.Label;
13999 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
14001 var labels = nodeLabels[idArray[i]],
14002 wrapperStyle = labels.wrapper.style,
14003 labelStyle = labels.label.style,
14004 valueLabelStyle = labels.valueLabel.style;
14005 var bottomWidth = minWidth + (acum * ratio);
14007 font = parseInt(wrapperStyle.fontSize, 10),
14008 domStyle = domElement.style;
14012 wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
14013 valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
14014 labelStyle.left = (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
14016 acum += (dimArray[i] || 0);
14024 var size = st.canvas.getSize(),
14025 margin = config.Margin;
14026 title = config.Title;
14027 subtitle = config.Subtitle;
14030 st.config.offsetY = -size.height/2 + margin.bottom
14031 + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
14033 st.config.offsetX = (margin.right - margin.left)/2;
14037 this.canvas = this.st.canvas;
14040 renderTitle: function() {
14041 var canvas = this.canvas,
14042 size = canvas.getSize(),
14043 config = this.config,
14044 margin = config.Margin,
14045 label = config.Label,
14046 title = config.Title;
14047 ctx = canvas.getCtx();
14048 ctx.fillStyle = title.color;
14049 ctx.textAlign = 'left';
14050 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
14051 if(label.type == 'Native') {
14052 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
14056 renderSubtitle: function() {
14057 var canvas = this.canvas,
14058 size = canvas.getSize(),
14059 config = this.config,
14060 margin = config.Margin,
14061 label = config.Label,
14062 subtitle = config.Subtitle;
14063 ctx = canvas.getCtx();
14064 ctx.fillStyle = title.color;
14065 ctx.textAlign = 'left';
14066 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
14067 if(label.type == 'Native') {
14068 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
14073 renderDropShadow: function() {
14074 var canvas = this.canvas,
14075 size = canvas.getSize(),
14076 config = this.config,
14077 margin = config.Margin,
14078 horz = config.orientation == 'horizontal',
14079 label = config.Label,
14080 title = config.Title,
14081 shadowThickness = 4,
14082 subtitle = config.Subtitle,
14083 ctx = canvas.getCtx(),
14084 minwidth = (size.width/8) * .25,
14085 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14086 topMargin = (title.text? title.size + title.offset : 0) + margin.top,
14087 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14088 - (config.showLabels && (config.Label.size + config.labelOffset)),
14090 topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
14091 topY = (-size.height/2) + topMargin - shadowThickness;
14092 bottomY = (-size.height/2) + topMargin + height + shadowThickness;
14093 bottomWidth = minwidth + shadowThickness;
14095 ctx.fillStyle = "rgba(0,0,0,.2)";
14096 ctx.moveTo(0,topY);
14097 ctx.lineTo(-topWidth/2,topY); //top left
14098 ctx.lineTo(-bottomWidth/2,bottomY); // bottom left
14099 ctx.lineTo(bottomWidth/2,bottomY); // bottom right
14100 ctx.lineTo(topWidth/2,topY); // top right
14107 renderBackground: function() {
14108 var canvas = this.canvas,
14109 config = this.config,
14110 backgroundColor = config.backgroundColor,
14111 size = canvas.getSize(),
14112 ctx = canvas.getCtx();
14113 //ctx.globalCompositeOperation = "destination-over";
14114 ctx.fillStyle = backgroundColor;
14115 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
14117 clear: function() {
14118 var canvas = this.canvas;
14119 var ctx = canvas.getCtx(),
14120 size = canvas.getSize();
14121 ctx.fillStyle = "rgba(255,255,255,0)";
14122 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
14123 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
14125 resizeGraph: function(json,width) {
14126 var canvas = this.canvas,
14127 size = canvas.getSize(),
14128 config = this.config,
14129 orgHeight = size.height;
14132 canvas.resize(width,orgHeight);
14134 if(typeof FlashCanvas == "undefined") {
14137 this.clear();// hack for flashcanvas bug not properly clearing rectangle
14139 this.loadJSON(json);
14143 loadJSON: function(json) {
14144 if(this.busy) return;
14146 var prefix = $.time(),
14149 name = $.splat(json.label),
14150 color = $.splat(json.color || this.colors),
14151 config = this.config,
14152 canvas = this.canvas,
14153 gradient = !!config.type.split(":")[1],
14154 animate = config.animate,
14155 title = config.Title,
14156 subtitle = config.Subtitle,
14157 renderBackground = config.renderBackground,
14158 horz = config.orientation == 'horizontal',
14160 colorLength = color.length,
14161 nameLength = name.length,
14163 for(var i=0, values=json.values, l=values.length; i<l; i++) {
14164 var val = values[i];
14165 var valArray = $.splat(val.values);
14166 totalValue += parseFloat(valArray.sum());
14170 var nameArray = new Array();
14171 var idArray = new Array();
14172 var valArray = new Array();
14173 var valuelabelArray = new Array();
14174 var linkArray = new Array();
14175 var titleArray = new Array();
14176 var percentageArray = new Array();
14178 for(var i=0, values=json.values, l=values.length; i<l; i++) {
14179 var val = values[i];
14180 nameArray[i] = $.splat(val.label);
14181 idArray[i] = $.splat(prefix + val.label);
14182 valArray[i] = $.splat(val.values);
14183 valuelabelArray[i] = $.splat(val.valuelabels);
14184 linkArray[i] = $.splat(val.links);
14185 titleArray[i] = $.splat(val.titles);
14186 percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
14191 nameArray.reverse();
14192 valArray.reverse();
14193 valuelabelArray.reverse();
14194 linkArray.reverse();
14195 titleArray.reverse();
14196 percentageArray.reverse();
14199 'id': prefix + val.label,
14204 '$idArray': idArray,
14205 '$linkArray': linkArray,
14206 '$titleArray': titleArray,
14207 '$valueArray': valArray,
14208 '$valuelabelArray': valuelabelArray,
14209 '$colorArray': color,
14210 '$colorMono': $.splat(color[i % colorLength]),
14211 '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
14212 '$gradient': gradient,
14214 '$percentageArray' : percentageArray,
14222 'id': prefix + '$root',
14233 this.normalizeDims();
14235 if(renderBackground) {
14236 this.renderBackground();
14238 if(!animate && title.text) {
14239 this.renderTitle();
14241 if(!animate && subtitle.text) {
14242 this.renderSubtitle();
14244 if(typeof FlashCanvas == "undefined") {
14245 this.renderDropShadow();
14248 st.select(st.root);
14252 modes: ['node-property:width:dimArray'],
14254 onComplete: function() {
14260 modes: ['node-property:height:dimArray'],
14262 onComplete: function() {
14275 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.
14279 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
14280 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
14285 barChart.updateJSON(json, {
14286 onComplete: function() {
14287 alert('update complete!');
14292 updateJSON: function(json, onComplete) {
14293 if(this.busy) return;
14297 var graph = st.graph;
14298 var values = json.values;
14299 var animate = this.config.animate;
14301 var horz = this.config.orientation == 'horizontal';
14302 $.each(values, function(v) {
14303 var n = graph.getByName(v.label);
14305 n.setData('valueArray', $.splat(v.values));
14307 n.setData('stringArray', $.splat(json.label));
14311 this.normalizeDims();
14313 st.select(st.root);
14317 modes: ['node-property:width:dimArray'],
14319 onComplete: function() {
14321 onComplete && onComplete.onComplete();
14326 modes: ['node-property:height:dimArray'],
14328 onComplete: function() {
14330 onComplete && onComplete.onComplete();
14337 //adds the little brown bar when hovering the node
14338 select: function(id, name) {
14340 if(!this.config.hoveredColor) return;
14341 var s = this.selected;
14342 if(s.id != id || s.name != name) {
14345 s.color = this.config.hoveredColor;
14346 this.st.graph.eachNode(function(n) {
14348 n.setData('border', s);
14350 n.setData('border', false);
14360 Returns an object containing as keys the legend names and as values hex strings with color values.
14365 var legend = barChart.getLegend();
14368 getLegend: function() {
14369 var legend = new Array();
14370 var name = new Array();
14371 var color = new Array();
14373 this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14376 var colors = n.getData('colorArray'),
14377 len = colors.length;
14378 $.each(n.getData('stringArray'), function(s, i) {
14379 color[i] = colors[i % len];
14382 legend['name'] = name;
14383 legend['color'] = color;
14388 Method: getMaxValue
14390 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14395 var ans = barChart.getMaxValue();
14398 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14403 //will return 100 for all BarChart instances,
14404 //displaying all of them with the same scale
14405 $jit.BarChart.implement({
14406 'getMaxValue': function() {
14413 getMaxValue: function() {
14414 var maxValue = 0, stacked = true;
14415 this.st.graph.eachNode(function(n) {
14416 var valArray = n.getData('valueArray'),
14418 if(!valArray) return;
14420 $.each(valArray, function(v) {
14424 acum = Math.max.apply(null, valArray);
14426 maxValue = maxValue>acum? maxValue:acum;
14431 setBarType: function(type) {
14432 this.config.type = type;
14433 this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14436 normalizeDims: function() {
14437 //number of elements
14438 var root = this.st.graph.getNode(this.st.root), l=0;
14439 root.eachAdjacency(function() {
14442 var maxValue = this.getMaxValue() || 1,
14443 size = this.st.canvas.getSize(),
14444 config = this.config,
14445 margin = config.Margin,
14446 title = config.Title,
14447 subtitle = config.Subtitle,
14448 marginWidth = margin.left + margin.right,
14449 marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14450 horz = config.orientation == 'horizontal',
14451 animate = config.animate,
14452 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
14454 - (config.showLabels && (config.Label.size + config.labelOffset)),
14455 dim1 = horz? 'height':'width',
14456 dim2 = horz? 'width':'height';
14459 minWidth = size.width/8;
14463 this.st.graph.eachNode(function(n) {
14464 var acum = 0, animateValue = [];
14465 $.each(n.getData('valueArray'), function(v) {
14467 animateValue.push(0);
14469 n.setData(dim1, minWidth);
14472 n.setData(dim2, acum * height / maxValue, 'end');
14473 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14474 return n * height / maxValue;
14476 var dimArray = n.getData('dimArray');
14478 n.setData('dimArray', animateValue);
14481 n.setData(dim2, acum * height / maxValue);
14482 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
14483 return n * height / maxValue;
14494 * File: Options.PieChart.js
14498 Object: Options.PieChart
14500 <PieChart> options.
14501 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14507 Options.PieChart = {
14513 hoveredColor: '#9fd4ff',
14515 resizeLabels: false,
14516 updateHeights: false
14525 var pie = new $jit.PieChart({
14528 type: 'stacked:gradient'
14535 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14536 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14537 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14538 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14539 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14540 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14541 showLabels - (boolean) Default's *true*. Display the name of the slots.
14542 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.
14543 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.
14546 Options.PieChart = {
14550 offset: 25, // page offset
14552 labelOffset: 3, // label offset
14553 type: 'stacked', // gradient
14555 hoveredColor: '#9fd4ff',
14566 resizeLabels: false,
14568 //only valid for mono-valued datasets
14569 updateHeights: false
14573 * Class: Layouts.Radial
14575 * Implements a Radial Layout.
14579 * <RGraph>, <Hypertree>
14582 Layouts.Radial = new Class({
14587 * Computes nodes' positions.
14591 * property - _optional_ A <Graph.Node> position property to store the new
14592 * positions. Possible values are 'pos', 'end' or 'start'.
14595 compute : function(property) {
14596 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14597 NodeDim.compute(this.graph, prop, this.config);
14598 this.graph.computeLevels(this.root, 0, "ignore");
14599 var lengthFunc = this.createLevelDistanceFunc();
14600 this.computeAngularWidths(prop);
14601 this.computePositions(prop, lengthFunc);
14607 * Performs the main algorithm for computing node positions.
14609 computePositions : function(property, getLength) {
14610 var propArray = property;
14611 var graph = this.graph;
14612 var root = graph.getNode(this.root);
14613 var parent = this.parent;
14614 var config = this.config;
14616 for ( var i=0, l=propArray.length; i < l; i++) {
14617 var pi = propArray[i];
14618 root.setPos($P(0, 0), pi);
14619 root.setData('span', Math.PI * 2, pi);
14627 graph.eachBFS(this.root, function(elem) {
14628 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14629 var angleInit = elem.angleSpan.begin;
14630 var len = getLength(elem);
14631 //Calculate the sum of all angular widths
14632 var totalAngularWidths = 0, subnodes = [], maxDim = {};
14633 elem.eachSubnode(function(sib) {
14634 totalAngularWidths += sib._treeAngularWidth;
14636 for ( var i=0, l=propArray.length; i < l; i++) {
14637 var pi = propArray[i], dim = sib.getData('dim', pi);
14638 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14640 subnodes.push(sib);
14642 //Maintain children order
14643 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14644 if (parent && parent.id == elem.id && subnodes.length > 0
14645 && subnodes[0].dist) {
14646 subnodes.sort(function(a, b) {
14647 return (a.dist >= b.dist) - (a.dist <= b.dist);
14650 //Calculate nodes positions.
14651 for (var k = 0, ls=subnodes.length; k < ls; k++) {
14652 var child = subnodes[k];
14653 if (!child._flag) {
14654 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14655 var theta = angleInit + angleProportion / 2;
14657 for ( var i=0, l=propArray.length; i < l; i++) {
14658 var pi = propArray[i];
14659 child.setPos($P(theta, len), pi);
14660 child.setData('span', angleProportion, pi);
14661 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14664 child.angleSpan = {
14666 end : angleInit + angleProportion
14668 angleInit += angleProportion;
14675 * Method: setAngularWidthForNodes
14677 * Sets nodes angular widths.
14679 setAngularWidthForNodes : function(prop) {
14680 this.graph.eachBFS(this.root, function(elem, i) {
14681 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14682 elem._angularWidth = diamValue / i;
14687 * Method: setSubtreesAngularWidth
14689 * Sets subtrees angular widths.
14691 setSubtreesAngularWidth : function() {
14693 this.graph.eachNode(function(elem) {
14694 that.setSubtreeAngularWidth(elem);
14699 * Method: setSubtreeAngularWidth
14701 * Sets the angular width for a subtree.
14703 setSubtreeAngularWidth : function(elem) {
14704 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14705 elem.eachSubnode(function(child) {
14706 that.setSubtreeAngularWidth(child);
14707 sumAW += child._treeAngularWidth;
14709 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14713 * Method: computeAngularWidths
14715 * Computes nodes and subtrees angular widths.
14717 computeAngularWidths : function(prop) {
14718 this.setAngularWidthForNodes(prop);
14719 this.setSubtreesAngularWidth();
14726 * File: Sunburst.js
14732 A radial space filling tree visualization.
14736 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14740 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.
14744 All <Loader> methods
14746 Constructor Options:
14748 Inherits options from
14751 - <Options.Controller>
14757 - <Options.NodeStyles>
14758 - <Options.Navigation>
14760 Additionally, there are other parameters and some default values changed
14762 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14763 levelDistance - (number) Default's *100*. The distance between levels of the tree.
14764 Node.type - Described in <Options.Node>. Default's to *multipie*.
14765 Node.height - Described in <Options.Node>. Default's *0*.
14766 Edge.type - Described in <Options.Edge>. Default's *none*.
14767 Label.textAlign - Described in <Options.Label>. Default's *start*.
14768 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14770 Instance Properties:
14772 canvas - Access a <Canvas> instance.
14773 graph - Access a <Graph> instance.
14774 op - Access a <Sunburst.Op> instance.
14775 fx - Access a <Sunburst.Plot> instance.
14776 labels - Access a <Sunburst.Label> interface implementation.
14780 $jit.Sunburst = new Class({
14782 Implements: [ Loader, Extras, Layouts.Radial ],
14784 initialize: function(controller) {
14785 var $Sunburst = $jit.Sunburst;
14788 interpolation: 'linear',
14789 levelDistance: 100,
14791 'type': 'multipie',
14798 textAlign: 'start',
14799 textBaseline: 'middle'
14803 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14804 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14806 var canvasConfig = this.config;
14807 if(canvasConfig.useCanvas) {
14808 this.canvas = canvasConfig.useCanvas;
14809 this.config.labelContainer = this.canvas.id + '-label';
14811 if(canvasConfig.background) {
14812 canvasConfig.background = $.merge({
14814 colorStop1: this.config.colorStop1,
14815 colorStop2: this.config.colorStop2
14816 }, canvasConfig.background);
14818 this.canvas = new Canvas(this, canvasConfig);
14819 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14822 this.graphOptions = {
14830 this.graph = new Graph(this.graphOptions, this.config.Node,
14832 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14833 this.fx = new $Sunburst.Plot(this, $Sunburst);
14834 this.op = new $Sunburst.Op(this);
14837 this.rotated = null;
14839 // initialize extras
14840 this.initializeExtras();
14845 createLevelDistanceFunc
14847 Returns the levelDistance function used for calculating a node distance
14848 to its origin. This function returns a function that is computed
14849 per level and not per node, such that all nodes with the same depth will have the
14850 same distance to the origin. The resulting function gets the
14851 parent node as parameter and returns a float.
14854 createLevelDistanceFunc: function() {
14855 var ld = this.config.levelDistance;
14856 return function(elem) {
14857 return (elem._depth + 1) * ld;
14864 Computes positions and plots the tree.
14867 refresh: function() {
14875 An alias for computing new positions to _endPos_
14882 reposition: function() {
14883 this.compute('end');
14889 Rotates the graph so that the selected node is horizontal on the right.
14893 node - (object) A <Graph.Node>.
14894 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14895 opt - (object) Configuration options merged with this visualization configuration options.
14899 <Sunburst.rotateAngle>
14902 rotate: function(node, method, opt) {
14903 var theta = node.getPos(opt.property || 'current').getp(true).theta;
14904 this.rotated = node;
14905 this.rotateAngle(-theta, method, opt);
14909 Method: rotateAngle
14911 Rotates the graph of an angle theta.
14915 node - (object) A <Graph.Node>.
14916 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14917 opt - (object) Configuration options merged with this visualization configuration options.
14924 rotateAngle: function(theta, method, opt) {
14926 var options = $.merge(this.config, opt || {}, {
14929 var prop = opt.property || (method === "animate" ? 'end' : 'current');
14930 if(method === 'animate') {
14931 this.fx.animation.pause();
14933 this.graph.eachNode(function(n) {
14934 var p = n.getPos(prop);
14937 p.theta += Math.PI * 2;
14940 if (method == 'animate') {
14941 this.fx.animate(options);
14942 } else if (method == 'replot') {
14951 Plots the Sunburst. This is a shortcut to *fx.plot*.
14958 $jit.Sunburst.$extend = true;
14960 (function(Sunburst) {
14965 Custom extension of <Graph.Op>.
14969 All <Graph.Op> methods
14976 Sunburst.Op = new Class( {
14978 Implements: Graph.Op
14983 Class: Sunburst.Plot
14985 Custom extension of <Graph.Plot>.
14989 All <Graph.Plot> methods
14996 Sunburst.Plot = new Class( {
14998 Implements: Graph.Plot
15003 Class: Sunburst.Label
15005 Custom extension of <Graph.Label>.
15006 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
15010 All <Graph.Label> methods and subclasses.
15014 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
15017 Sunburst.Label = {};
15020 Sunburst.Label.Native
15022 Custom extension of <Graph.Label.Native>.
15026 All <Graph.Label.Native> methods
15030 <Graph.Label.Native>
15032 Sunburst.Label.Native = new Class( {
15033 Implements: Graph.Label.Native,
15035 initialize: function(viz) {
15037 this.label = viz.config.Label;
15038 this.config = viz.config;
15041 renderLabel: function(canvas, node, controller) {
15042 var span = node.getData('span');
15043 if(span < Math.PI /2 && Math.tan(span) *
15044 this.config.levelDistance * node._depth < 10) {
15047 var ctx = canvas.getCtx();
15048 var measure = ctx.measureText(node.name);
15049 if (node.id == this.viz.root) {
15050 var x = -measure.width / 2, y = 0, thetap = 0;
15054 var ld = controller.levelDistance - indent;
15055 var clone = node.pos.clone();
15056 clone.rho += indent;
15057 var p = clone.getp(true);
15058 var ct = clone.getc(true);
15059 var x = ct.x, y = ct.y;
15060 // get angle in degrees
15062 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
15063 var thetap = cond ? p.theta + pi : p.theta;
15065 x -= Math.abs(Math.cos(p.theta) * measure.width);
15066 y += Math.sin(p.theta) * measure.width;
15067 } else if (node.id == this.viz.root) {
15068 x -= measure.width / 2;
15072 ctx.translate(x, y);
15073 ctx.rotate(thetap);
15074 ctx.fillText(node.name, 0, 0);
15082 Custom extension of <Graph.Label.SVG>.
15086 All <Graph.Label.SVG> methods
15093 Sunburst.Label.SVG = new Class( {
15094 Implements: Graph.Label.SVG,
15096 initialize: function(viz) {
15103 Overrides abstract method placeLabel in <Graph.Plot>.
15107 tag - A DOM label element.
15108 node - A <Graph.Node>.
15109 controller - A configuration/controller object passed to the visualization.
15112 placeLabel: function(tag, node, controller) {
15113 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
15114 var radius = canvas.getSize();
15116 x: Math.round(pos.x + radius.width / 2),
15117 y: Math.round(pos.y + radius.height / 2)
15119 tag.setAttribute('x', labelPos.x);
15120 tag.setAttribute('y', labelPos.y);
15122 var bb = tag.getBBox();
15124 // center the label
15125 var x = tag.getAttribute('x');
15126 var y = tag.getAttribute('y');
15127 // get polar coordinates
15128 var p = node.pos.getp(true);
15129 // get angle in degrees
15131 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
15133 tag.setAttribute('x', x - bb.width);
15134 tag.setAttribute('y', y - bb.height);
15135 } else if (node.id == viz.root) {
15136 tag.setAttribute('x', x - bb.width / 2);
15139 var thetap = cond ? p.theta + pi : p.theta;
15141 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
15145 controller.onPlaceLabel(tag, node);
15150 Sunburst.Label.HTML
15152 Custom extension of <Graph.Label.HTML>.
15156 All <Graph.Label.HTML> methods.
15163 Sunburst.Label.HTML = new Class( {
15164 Implements: Graph.Label.HTML,
15166 initialize: function(viz) {
15172 Overrides abstract method placeLabel in <Graph.Plot>.
15176 tag - A DOM label element.
15177 node - A <Graph.Node>.
15178 controller - A configuration/controller object passed to the visualization.
15181 placeLabel: function(tag, node, controller) {
15182 var pos = node.pos.clone(),
15183 canvas = this.viz.canvas,
15184 height = node.getData('height'),
15185 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
15186 radius = canvas.getSize();
15188 pos = pos.getc(true);
15191 x: Math.round(pos.x + radius.width / 2),
15192 y: Math.round(pos.y + radius.height / 2)
15195 var style = tag.style;
15196 style.left = labelPos.x + 'px';
15197 style.top = labelPos.y + 'px';
15198 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
15200 controller.onPlaceLabel(tag, node);
15205 Class: Sunburst.Plot.NodeTypes
15207 This class contains a list of <Graph.Node> built-in types.
15208 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
15210 You can add your custom node types, customizing your visualization to the extreme.
15215 Sunburst.Plot.NodeTypes.implement({
15217 'render': function(node, canvas) {
15218 //print your custom node to canvas
15221 'contains': function(node, pos) {
15222 //return true if pos is inside the node or false otherwise
15229 Sunburst.Plot.NodeTypes = new Class( {
15232 'contains': $.lambda(false),
15233 'anglecontains': function(node, pos) {
15234 var span = node.getData('span') / 2, theta = node.pos.theta;
15235 var begin = theta - span, end = theta + span;
15237 begin += Math.PI * 2;
15238 var atan = Math.atan2(pos.y, pos.x);
15240 atan += Math.PI * 2;
15242 return (atan > begin && atan <= Math.PI * 2) || atan < end;
15244 return atan > begin && atan < end;
15247 'anglecontainsgauge': function(node, pos) {
15248 var span = node.getData('span') / 2, theta = node.pos.theta;
15249 var config = node.getData('config');
15250 var ld = this.config.levelDistance;
15251 var yOffset = pos.y-(ld/2);
15252 var begin = ((theta - span)/2)+Math.PI,
15253 end = ((theta + span)/2)+Math.PI;
15256 begin += Math.PI * 2;
15257 var atan = Math.atan2(yOffset, pos.x);
15261 atan += Math.PI * 2;
15265 return (atan > begin && atan <= Math.PI * 2) || atan < end;
15267 return atan > begin && atan < end;
15273 'render': function(node, canvas) {
15274 var span = node.getData('span') / 2, theta = node.pos.theta;
15275 var begin = theta - span, end = theta + span;
15276 var polarNode = node.pos.getp(true);
15277 var polar = new Polar(polarNode.rho, begin);
15278 var p1coord = polar.getc(true);
15280 var p2coord = polar.getc(true);
15282 var ctx = canvas.getCtx();
15285 ctx.lineTo(p1coord.x, p1coord.y);
15287 ctx.lineTo(p2coord.x, p2coord.y);
15289 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
15293 'contains': function(node, pos) {
15294 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15295 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15296 var ld = this.config.levelDistance, d = node._depth;
15297 return (rho <= ld * d);
15303 'render': function(node, canvas) {
15304 var height = node.getData('height');
15305 var ldist = height? height : this.config.levelDistance;
15306 var span = node.getData('span') / 2, theta = node.pos.theta;
15307 var begin = theta - span, end = theta + span;
15308 var polarNode = node.pos.getp(true);
15310 var polar = new Polar(polarNode.rho, begin);
15311 var p1coord = polar.getc(true);
15314 var p2coord = polar.getc(true);
15316 polar.rho += ldist;
15317 var p3coord = polar.getc(true);
15319 polar.theta = begin;
15320 var p4coord = polar.getc(true);
15322 var ctx = canvas.getCtx();
15325 ctx.arc(0, 0, polarNode.rho, begin, end, false);
15326 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15327 ctx.moveTo(p1coord.x, p1coord.y);
15328 ctx.lineTo(p4coord.x, p4coord.y);
15329 ctx.moveTo(p2coord.x, p2coord.y);
15330 ctx.lineTo(p3coord.x, p3coord.y);
15333 if (node.collapsed) {
15338 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15344 'contains': function(node, pos) {
15345 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15346 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15347 var height = node.getData('height');
15348 var ldist = height? height : this.config.levelDistance;
15349 var ld = this.config.levelDistance, d = node._depth;
15350 return (rho >= ld * d) && (rho <= (ld * d + ldist));
15356 'gradient-multipie': {
15357 'render': function(node, canvas) {
15358 var ctx = canvas.getCtx();
15359 var height = node.getData('height');
15360 var ldist = height? height : this.config.levelDistance;
15361 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15362 0, 0, node.getPos().rho + ldist);
15364 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15365 $.each(colorArray, function(i) {
15366 ans.push(parseInt(i * 0.5, 10));
15368 var endColor = $.rgbToHex(ans);
15369 radialGradient.addColorStop(0, endColor);
15370 radialGradient.addColorStop(1, node.getData('color'));
15371 ctx.fillStyle = radialGradient;
15372 this.nodeTypes['multipie'].render.call(this, node, canvas);
15374 'contains': function(node, pos) {
15375 return this.nodeTypes['multipie'].contains.call(this, node, pos);
15380 'render': function(node, canvas) {
15381 var ctx = canvas.getCtx();
15382 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15385 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15386 $.each(colorArray, function(i) {
15387 ans.push(parseInt(i * 0.5, 10));
15389 var endColor = $.rgbToHex(ans);
15390 radialGradient.addColorStop(1, endColor);
15391 radialGradient.addColorStop(0, node.getData('color'));
15392 ctx.fillStyle = radialGradient;
15393 this.nodeTypes['pie'].render.call(this, node, canvas);
15395 'contains': function(node, pos) {
15396 return this.nodeTypes['pie'].contains.call(this, node, pos);
15402 Class: Sunburst.Plot.EdgeTypes
15404 This class contains a list of <Graph.Adjacence> built-in types.
15405 Edge types implemented are 'none', 'line' and 'arrow'.
15407 You can add your custom edge types, customizing your visualization to the extreme.
15412 Sunburst.Plot.EdgeTypes.implement({
15414 'render': function(adj, canvas) {
15415 //print your custom edge to canvas
15418 'contains': function(adj, pos) {
15419 //return true if pos is inside the arc or false otherwise
15426 Sunburst.Plot.EdgeTypes = new Class({
15429 'render': function(adj, canvas) {
15430 var from = adj.nodeFrom.pos.getc(true),
15431 to = adj.nodeTo.pos.getc(true);
15432 this.edgeHelper.line.render(from, to, canvas);
15434 'contains': function(adj, pos) {
15435 var from = adj.nodeFrom.pos.getc(true),
15436 to = adj.nodeTo.pos.getc(true);
15437 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15441 'render': function(adj, canvas) {
15442 var from = adj.nodeFrom.pos.getc(true),
15443 to = adj.nodeTo.pos.getc(true),
15444 dim = adj.getData('dim'),
15445 direction = adj.data.$direction,
15446 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15447 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15449 'contains': function(adj, pos) {
15450 var from = adj.nodeFrom.pos.getc(true),
15451 to = adj.nodeTo.pos.getc(true);
15452 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15456 'render': function(adj, canvas) {
15457 var from = adj.nodeFrom.pos.getc(),
15458 to = adj.nodeTo.pos.getc(),
15459 dim = Math.max(from.norm(), to.norm());
15460 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15462 'contains': $.lambda(false) //TODO(nico): Implement this!
15470 * File: PieChart.js
15474 $jit.Sunburst.Plot.NodeTypes.implement({
15475 'piechart-stacked' : {
15476 'render' : function(node, canvas) {
15477 var pos = node.pos.getp(true),
15478 dimArray = node.getData('dimArray'),
15479 valueArray = node.getData('valueArray'),
15480 colorArray = node.getData('colorArray'),
15481 colorLength = colorArray.length,
15482 stringArray = node.getData('stringArray'),
15483 span = node.getData('span') / 2,
15484 theta = node.pos.theta,
15485 begin = theta - span,
15486 end = theta + span,
15489 var ctx = canvas.getCtx(),
15491 gradient = node.getData('gradient'),
15492 border = node.getData('border'),
15493 config = node.getData('config'),
15494 showLabels = config.showLabels,
15495 resizeLabels = config.resizeLabels,
15496 label = config.Label;
15498 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15499 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15501 if (colorArray && dimArray && stringArray) {
15502 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15503 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15504 if(dimi <= 0) continue;
15505 ctx.fillStyle = ctx.strokeStyle = colori;
15506 if(gradient && dimi) {
15507 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15508 xpos, ypos, acum + dimi + config.sliceOffset);
15509 var colorRgb = $.hexToRgb(colori),
15510 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15511 endColor = $.rgbToHex(ans);
15513 radialGradient.addColorStop(0, colori);
15514 radialGradient.addColorStop(0.5, colori);
15515 radialGradient.addColorStop(1, endColor);
15516 ctx.fillStyle = radialGradient;
15519 polar.rho = acum + config.sliceOffset;
15520 polar.theta = begin;
15521 var p1coord = polar.getc(true);
15523 var p2coord = polar.getc(true);
15525 var p3coord = polar.getc(true);
15526 polar.theta = begin;
15527 var p4coord = polar.getc(true);
15530 //fixing FF arc method + fill
15531 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15532 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15534 if(border && border.name == stringArray[i]) {
15536 opt.dimValue = dimArray[i];
15540 acum += (dimi || 0);
15541 valAcum += (valueArray[i] || 0);
15545 ctx.globalCompositeOperation = "source-over";
15547 ctx.strokeStyle = border.color;
15548 var s = begin < end? 1 : -1;
15550 //fixing FF arc method + fill
15551 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15552 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15557 if(showLabels && label.type == 'Native') {
15559 ctx.fillStyle = ctx.strokeStyle = label.color;
15560 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15561 fontSize = (label.size * scale) >> 0;
15562 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15564 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15565 ctx.textBaseline = 'middle';
15566 ctx.textAlign = 'center';
15568 polar.rho = acum + config.labelOffset + config.sliceOffset;
15569 polar.theta = node.pos.theta;
15570 var cart = polar.getc(true);
15572 ctx.fillText(node.name, cart.x, cart.y);
15577 'contains': function(node, pos) {
15578 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15579 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15580 var ld = this.config.levelDistance, d = node._depth;
15581 var config = node.getData('config');
15582 if(rho <=ld * d + config.sliceOffset) {
15583 var dimArray = node.getData('dimArray');
15584 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15585 var dimi = dimArray[i];
15586 if(rho >= acum && rho <= acum + dimi) {
15588 name: node.getData('stringArray')[i],
15589 color: node.getData('colorArray')[i],
15590 value: node.getData('valueArray')[i],
15603 'piechart-basic' : {
15604 'render' : function(node, canvas) {
15605 var pos = node.pos.getp(true),
15606 dimArray = node.getData('dimArray'),
15607 valueArray = node.getData('valueArray'),
15608 colorArray = node.getData('colorMono'),
15609 colorLength = colorArray.length,
15610 stringArray = node.getData('stringArray'),
15611 percentage = node.getData('percentage'),
15612 iteration = node.getData('iteration'),
15613 span = node.getData('span') / 2,
15614 theta = node.pos.theta,
15615 begin = theta - span,
15616 end = theta + span,
15619 var ctx = canvas.getCtx(),
15621 gradient = node.getData('gradient'),
15622 border = node.getData('border'),
15623 config = node.getData('config'),
15624 renderSubtitle = node.getData('renderSubtitle'),
15625 renderBackground = config.renderBackground,
15626 showLabels = config.showLabels,
15627 resizeLabels = config.resizeLabels,
15628 label = config.Label;
15630 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15631 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15632 //background rendering for IE
15633 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15634 backgroundColor = config.backgroundColor,
15635 size = canvas.getSize();
15637 ctx.fillStyle = backgroundColor;
15638 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15642 var margin = config.Margin,
15643 title = config.Title,
15644 subtitle = config.Subtitle;
15645 ctx.fillStyle = title.color;
15646 ctx.textAlign = 'left';
15648 if(title.text != "") {
15649 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15651 if(label.type == 'Native') {
15652 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15656 if(subtitle.text != "") {
15657 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15658 if(label.type == 'Native') {
15659 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15664 if (colorArray && dimArray && stringArray) {
15665 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15666 var dimi = dimArray[i], colori = colorArray[i % colorLength];
15667 if(dimi <= 0) continue;
15668 ctx.fillStyle = ctx.strokeStyle = colori;
15670 polar.rho = acum + config.sliceOffset;
15671 polar.theta = begin;
15672 var p1coord = polar.getc(true);
15674 var p2coord = polar.getc(true);
15676 var p3coord = polar.getc(true);
15677 polar.theta = begin;
15678 var p4coord = polar.getc(true);
15680 if(typeof FlashCanvas == "undefined") {
15683 ctx.fillStyle = "rgba(0,0,0,.2)";
15684 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15685 ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);
15688 if(gradient && dimi) {
15689 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15690 xpos, ypos, acum + dimi + config.sliceOffset);
15691 var colorRgb = $.hexToRgb(colori),
15692 endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15693 endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15695 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15696 radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15697 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15698 radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15699 ctx.fillStyle = radialGradient;
15704 //fixing FF arc method + fill
15706 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15707 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15709 if(border && border.name == stringArray[i]) {
15711 opt.dimValue = dimArray[i];
15714 opt.sliceValue = valueArray[i];
15716 acum += (dimi || 0);
15717 valAcum += (valueArray[i] || 0);
15721 ctx.globalCompositeOperation = "source-over";
15723 ctx.strokeStyle = border.color;
15724 var s = begin < end? 1 : -1;
15726 //fixing FF arc method + fill
15727 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15728 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15733 if(showLabels && label.type == 'Native') {
15735 ctx.fillStyle = ctx.strokeStyle = label.color;
15736 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15737 fontSize = (label.size * scale) >> 0;
15738 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15740 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15741 ctx.textBaseline = 'middle';
15742 ctx.textAlign = 'center';
15744 angle = theta * 360 / (2 * pi);
15745 polar.rho = acum + config.labelOffset + config.sliceOffset;
15746 polar.theta = node.pos.theta;
15747 var cart = polar.getc(true);
15748 if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15751 if(config.labelType == 'name') {
15752 ctx.fillText(node.name, cart.x, cart.y);
15754 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15761 'contains': function(node, pos) {
15762 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15763 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15764 var ld = this.config.levelDistance, d = node._depth;
15765 var config = node.getData('config');
15767 if(rho <=ld * d + config.sliceOffset) {
15768 var dimArray = node.getData('dimArray');
15769 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15770 var dimi = dimArray[i];
15771 if(rho >= acum && rho <= acum + dimi) {
15772 var url = Url.decode(node.getData('linkArray')[i]);
15774 name: node.getData('stringArray')[i],
15776 color: node.getData('colorArray')[i],
15777 value: node.getData('valueArray')[i],
15778 percentage: node.getData('percentage'),
15779 valuelabel: node.getData('valuelabelsArray')[i],
15797 A visualization that displays stacked bar charts.
15799 Constructor Options:
15801 See <Options.PieChart>.
15804 $jit.PieChart = new Class({
15806 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15810 initialize: function(opt) {
15811 this.controller = this.config =
15812 $.merge(Options("Canvas", "PieChart", "Label"), {
15813 Label: { type: 'Native' }
15815 this.initializeViz();
15818 initializeViz: function() {
15819 var config = this.config, that = this;
15820 var nodeType = config.type.split(":")[0];
15821 var sb = new $jit.Sunburst({
15822 injectInto: config.injectInto,
15823 useCanvas: config.useCanvas,
15824 withLabels: config.Label.type != 'Native',
15825 background: config.background,
15826 renderBackground: config.renderBackground,
15827 backgroundColor: config.backgroundColor,
15828 colorStop1: config.colorStop1,
15829 colorStop2: config.colorStop2,
15831 type: config.Label.type
15835 type: 'piechart-' + nodeType,
15843 enable: config.Tips.enable,
15846 onShow: function(tip, node, contains) {
15847 var elem = contains;
15848 config.Tips.onShow(tip, elem, node);
15849 if(elem.link != 'undefined' && elem.link != '') {
15850 document.body.style.cursor = 'pointer';
15853 onHide: function() {
15854 document.body.style.cursor = 'default';
15860 onClick: function(node, eventInfo, evt) {
15861 if(!config.Events.enable) return;
15862 var elem = eventInfo.getContains();
15863 config.Events.onClick(elem, eventInfo, evt);
15865 onMouseMove: function(node, eventInfo, evt) {
15866 if(!config.hoveredColor) return;
15868 var elem = eventInfo.getContains();
15869 that.select(node.id, elem.name, elem.index);
15871 that.select(false, false, false);
15875 onCreateLabel: function(domElement, node) {
15876 var labelConf = config.Label;
15877 if(config.showLabels) {
15878 var style = domElement.style;
15879 style.fontSize = labelConf.size + 'px';
15880 style.fontFamily = labelConf.family;
15881 style.color = labelConf.color;
15882 style.textAlign = 'center';
15883 if(config.labelType == 'name') {
15884 domElement.innerHTML = node.name;
15886 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15888 domElement.style.width = '400px';
15891 onPlaceLabel: function(domElement, node) {
15892 if(!config.showLabels) return;
15893 var pos = node.pos.getp(true),
15894 dimArray = node.getData('dimArray'),
15895 span = node.getData('span') / 2,
15896 theta = node.pos.theta,
15897 begin = theta - span,
15898 end = theta + span,
15901 var showLabels = config.showLabels,
15902 resizeLabels = config.resizeLabels,
15903 label = config.Label;
15906 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15907 acum += dimArray[i];
15909 var scale = resizeLabels? node.getData('normalizedDim') : 1,
15910 fontSize = (label.size * scale) >> 0;
15911 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15912 domElement.style.fontSize = fontSize + 'px';
15913 polar.rho = acum + config.labelOffset + config.sliceOffset;
15914 polar.theta = (begin + end) / 2;
15915 var pos = polar.getc(true);
15916 var radius = that.canvas.getSize();
15918 x: Math.round(pos.x + radius.width / 2),
15919 y: Math.round(pos.y + radius.height / 2)
15921 domElement.style.left = (labelPos.x - 200) + 'px';
15922 domElement.style.top = labelPos.y + 'px';
15927 var size = sb.canvas.getSize(),
15929 sb.config.levelDistance = min(size.width, size.height)/2
15930 - config.offset - config.sliceOffset;
15932 this.canvas = this.sb.canvas;
15933 this.canvas.getCtx().globalCompositeOperation = 'lighter';
15935 renderBackground: function() {
15936 var canvas = this.canvas,
15937 config = this.config,
15938 backgroundColor = config.backgroundColor,
15939 size = canvas.getSize(),
15940 ctx = canvas.getCtx();
15941 ctx.globalCompositeOperation = "destination-over";
15942 ctx.fillStyle = backgroundColor;
15943 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15945 renderTitle: function() {
15946 var canvas = this.canvas,
15947 size = canvas.getSize(),
15948 config = this.config,
15949 margin = config.Margin,
15950 radius = this.sb.config.levelDistance,
15951 title = config.Title,
15952 label = config.Label,
15953 subtitle = config.Subtitle;
15954 ctx = canvas.getCtx();
15955 ctx.fillStyle = title.color;
15956 ctx.textAlign = 'left';
15957 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15959 if(label.type == 'Native') {
15960 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top);
15963 renderSubtitle: function() {
15964 var canvas = this.canvas,
15965 size = canvas.getSize(),
15966 config = this.config,
15967 margin = config.Margin,
15968 radius = this.sb.config.levelDistance,
15969 title = config.Title,
15970 label = config.Label,
15971 subtitle = config.Subtitle;
15972 ctx = canvas.getCtx();
15973 ctx.fillStyle = title.color;
15974 ctx.textAlign = 'left';
15975 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15977 if(label.type == 'Native') {
15978 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom);
15981 clear: function() {
15982 var canvas = this.canvas;
15983 var ctx = canvas.getCtx(),
15984 size = canvas.getSize();
15985 ctx.fillStyle = "rgba(255,255,255,0)";
15986 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15987 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15989 resizeGraph: function(json,width) {
15990 var canvas = this.canvas,
15991 size = canvas.getSize(),
15992 config = this.config,
15993 orgHeight = size.height;
15995 canvas.resize(width,orgHeight);
15996 if(typeof FlashCanvas == "undefined") {
15999 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16001 this.loadJSON(json);
16007 Loads JSON data into the visualization.
16011 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>.
16015 var pieChart = new $jit.PieChart(options);
16016 pieChart.loadJSON(json);
16019 loadJSON: function(json) {
16020 var prefix = $.time(),
16023 name = $.splat(json.label),
16024 nameLength = name.length,
16025 color = $.splat(json.color || this.colors),
16026 colorLength = color.length,
16027 config = this.config,
16028 renderBackground = config.renderBackground,
16029 title = config.Title,
16030 subtitle = config.Subtitle,
16031 gradient = !!config.type.split(":")[1],
16032 animate = config.animate,
16033 mono = nameLength == 1;
16035 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16036 var val = values[i];
16037 var valArray = $.splat(val.values);
16038 totalValue += parseFloat(valArray.sum());
16041 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16042 var val = values[i];
16043 var valArray = $.splat(val.values);
16044 var percentage = (valArray.sum()/totalValue) * 100;
16046 var linkArray = $.splat(val.links);
16047 var valuelabelsArray = $.splat(val.valuelabels);
16051 'id': prefix + val.label,
16055 'valuelabel': valuelabelsArray,
16056 '$linkArray': linkArray,
16057 '$valuelabelsArray': valuelabelsArray,
16058 '$valueArray': valArray,
16059 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16060 '$colorMono': $.splat(color[i % colorLength]),
16061 '$stringArray': name,
16062 '$gradient': gradient,
16065 '$percentage': percentage.toFixed(1),
16066 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16072 'id': prefix + '$root',
16084 this.normalizeDims();
16088 if(title.text != "") {
16089 this.renderTitle();
16092 if(subtitle.text != "") {
16093 this.renderSubtitle();
16095 if(renderBackground && typeof FlashCanvas == "undefined") {
16096 this.renderBackground();
16101 modes: ['node-property:dimArray'],
16110 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.
16114 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
16115 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
16120 pieChart.updateJSON(json, {
16121 onComplete: function() {
16122 alert('update complete!');
16127 updateJSON: function(json, onComplete) {
16128 if(this.busy) return;
16132 var graph = sb.graph;
16133 var values = json.values;
16134 var animate = this.config.animate;
16136 $.each(values, function(v) {
16137 var n = graph.getByName(v.label),
16138 vals = $.splat(v.values);
16140 n.setData('valueArray', vals);
16141 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16143 n.setData('stringArray', $.splat(json.label));
16147 this.normalizeDims();
16151 modes: ['node-property:dimArray:span', 'linear'],
16153 onComplete: function() {
16155 onComplete && onComplete.onComplete();
16163 //adds the little brown bar when hovering the node
16164 select: function(id, name) {
16165 if(!this.config.hoveredColor) return;
16166 var s = this.selected;
16167 if(s.id != id || s.name != name) {
16170 s.color = this.config.hoveredColor;
16171 this.sb.graph.eachNode(function(n) {
16173 n.setData('border', s);
16175 n.setData('border', false);
16185 Returns an object containing as keys the legend names and as values hex strings with color values.
16190 var legend = pieChart.getLegend();
16193 getLegend: function() {
16194 var legend = new Array();
16195 var name = new Array();
16196 var color = new Array();
16198 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16201 var colors = n.getData('colorArray'),
16202 len = colors.length;
16203 $.each(n.getData('stringArray'), function(s, i) {
16204 color[i] = colors[i % len];
16207 legend['name'] = name;
16208 legend['color'] = color;
16213 Method: getMaxValue
16215 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16220 var ans = pieChart.getMaxValue();
16223 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16228 //will return 100 for all PieChart instances,
16229 //displaying all of them with the same scale
16230 $jit.PieChart.implement({
16231 'getMaxValue': function() {
16238 getMaxValue: function() {
16240 this.sb.graph.eachNode(function(n) {
16241 var valArray = n.getData('valueArray'),
16243 $.each(valArray, function(v) {
16246 maxValue = maxValue>acum? maxValue:acum;
16251 normalizeDims: function() {
16252 //number of elements
16253 var root = this.sb.graph.getNode(this.sb.root), l=0;
16254 root.eachAdjacency(function() {
16257 var maxValue = this.getMaxValue() || 1,
16258 config = this.config,
16259 animate = config.animate,
16260 rho = this.sb.config.levelDistance;
16261 this.sb.graph.eachNode(function(n) {
16262 var acum = 0, animateValue = [];
16263 $.each(n.getData('valueArray'), function(v) {
16265 animateValue.push(1);
16267 var stat = (animateValue.length == 1) && !config.updateHeights;
16269 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16270 return stat? rho: (n * rho / maxValue);
16272 var dimArray = n.getData('dimArray');
16274 n.setData('dimArray', animateValue);
16277 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
16278 return stat? rho : (n * rho / maxValue);
16281 n.setData('normalizedDim', acum / maxValue);
16289 Options.GaugeChart = {
16293 offset: 25, // page offset
16295 labelOffset: 3, // label offset
16296 type: 'stacked', // gradient
16298 hoveredColor: '#9fd4ff',
16309 resizeLabels: false,
16311 //only valid for mono-valued datasets
16312 updateHeights: false
16317 $jit.Sunburst.Plot.NodeTypes.implement({
16318 'gaugechart-basic' : {
16319 'render' : function(node, canvas) {
16320 var pos = node.pos.getp(true),
16321 dimArray = node.getData('dimArray'),
16322 valueArray = node.getData('valueArray'),
16323 valuelabelsArray = node.getData('valuelabelsArray'),
16324 gaugeTarget = node.getData('gaugeTarget'),
16325 nodeIteration = node.getData('nodeIteration'),
16326 nodeLength = node.getData('nodeLength'),
16327 colorArray = node.getData('colorMono'),
16328 colorLength = colorArray.length,
16329 stringArray = node.getData('stringArray'),
16330 span = node.getData('span') / 2,
16331 theta = node.pos.theta,
16332 begin = ((theta - span)/2)+Math.PI,
16333 end = ((theta + span)/2)+Math.PI,
16337 var ctx = canvas.getCtx(),
16339 gradient = node.getData('gradient'),
16340 border = node.getData('border'),
16341 config = node.getData('config'),
16342 showLabels = config.showLabels,
16343 resizeLabels = config.resizeLabels,
16344 label = config.Label;
16346 var xpos = Math.cos((begin + end) /2);
16347 var ypos = Math.sin((begin + end) /2);
16349 if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16350 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16351 var dimi = dimArray[i], colori = colorArray[i % colorLength];
16352 if(dimi <= 0) continue;
16353 ctx.fillStyle = ctx.strokeStyle = colori;
16354 if(gradient && dimi) {
16355 var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16356 xpos, (ypos + dimi/2), acum + dimi);
16357 var colorRgb = $.hexToRgb(colori),
16358 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16359 endColor = $.rgbToHex(ans);
16361 radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16362 radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16363 radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16364 radialGradient.addColorStop(1, 'rgba('+ans+',1)');
16365 ctx.fillStyle = radialGradient;
16369 polar.theta = begin;
16370 var p1coord = polar.getc(true);
16372 var p2coord = polar.getc(true);
16374 var p3coord = polar.getc(true);
16375 polar.theta = begin;
16376 var p4coord = polar.getc(true);
16380 //fixing FF arc method + fill
16381 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16382 ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16386 acum += (dimi || 0);
16387 valAcum += (valueArray[i] || 0);
16390 if(showLabels && label.type == 'Native') {
16392 ctx.fillStyle = ctx.strokeStyle = label.color;
16395 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16396 ctx.textBaseline = 'bottom';
16397 ctx.textAlign = 'center';
16399 polar.rho = acum * .65;
16400 polar.theta = begin;
16401 var cart = polar.getc(true);
16403 //changes y pos of first label
16404 if(nodeIteration == 1) {
16405 textY = cart.y - (label.size/2) + acum /2;
16407 textY = cart.y + acum/2;
16410 if(config.labelType == 'name') {
16411 ctx.fillText(node.name, cart.x, textY);
16413 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16417 if(nodeIteration == nodeLength) {
16419 var cart = polar.getc(true);
16420 if(config.labelType == 'name') {
16421 ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16423 ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16432 'contains': function(node, pos) {
16436 if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16437 var config = node.getData('config');
16438 var ld = this.config.levelDistance , d = node._depth;
16439 var yOffset = pos.y - (ld/2);
16440 var xOffset = pos.x;
16441 var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16442 if(rho <=parseInt(ld * d)) {
16443 var dimArray = node.getData('dimArray');
16444 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16445 var dimi = dimArray[i];
16446 if(rho >= ld * .8 && rho <= acum + dimi) {
16448 var url = Url.decode(node.getData('linkArray')[i]);
16450 name: node.getData('stringArray')[i],
16452 color: node.getData('colorArray')[i],
16453 value: node.getData('valueArray')[i],
16454 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16474 A visualization that displays gauge charts
16476 Constructor Options:
16478 See <Options.Gauge>.
16481 $jit.GaugeChart = new Class({
16483 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16487 initialize: function(opt) {
16488 this.controller = this.config =
16489 $.merge(Options("Canvas", "GaugeChart", "Label"), {
16490 Label: { type: 'Native' }
16492 this.initializeViz();
16495 initializeViz: function() {
16496 var config = this.config, that = this;
16497 var nodeType = config.type.split(":")[0];
16498 var sb = new $jit.Sunburst({
16499 injectInto: config.injectInto,
16500 useCanvas: config.useCanvas,
16501 withLabels: config.Label.type != 'Native',
16502 background: config.background,
16503 renderBackground: config.renderBackground,
16504 backgroundColor: config.backgroundColor,
16505 colorStop1: config.colorStop1,
16506 colorStop2: config.colorStop2,
16508 type: config.Label.type
16512 type: 'gaugechart-' + nodeType,
16520 enable: config.Tips.enable,
16523 onShow: function(tip, node, contains) {
16524 var elem = contains;
16525 config.Tips.onShow(tip, elem, node);
16526 if(elem.link != 'undefined' && elem.link != '') {
16527 document.body.style.cursor = 'pointer';
16530 onHide: function() {
16531 document.body.style.cursor = 'default';
16537 onClick: function(node, eventInfo, evt) {
16538 if(!config.Events.enable) return;
16539 var elem = eventInfo.getContains();
16540 config.Events.onClick(elem, eventInfo, evt);
16543 onCreateLabel: function(domElement, node) {
16544 var labelConf = config.Label;
16545 if(config.showLabels) {
16546 var style = domElement.style;
16547 style.fontSize = labelConf.size + 'px';
16548 style.fontFamily = labelConf.family;
16549 style.color = labelConf.color;
16550 style.textAlign = 'center';
16551 valuelabelsArray = node.getData('valuelabelsArray'),
16552 nodeIteration = node.getData('nodeIteration'),
16553 nodeLength = node.getData('nodeLength'),
16554 canvas = sb.canvas,
16557 if(config.labelType == 'name') {
16558 domElement.innerHTML = node.name;
16560 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16563 domElement.style.width = '400px';
16566 if(nodeIteration == nodeLength && nodeLength != 0) {
16567 idLabel = canvas.id + "-label";
16568 container = document.getElementById(idLabel);
16569 finalLabel = document.createElement('div');
16570 finalLabelStyle = finalLabel.style;
16571 finalLabel.id = prefix + "finalLabel";
16572 finalLabelStyle.position = "absolute";
16573 finalLabelStyle.width = "400px";
16574 finalLabelStyle.left = "0px";
16575 container.appendChild(finalLabel);
16576 if(config.labelType == 'name') {
16577 finalLabel.innerHTML = node.name;
16579 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16585 onPlaceLabel: function(domElement, node) {
16586 if(!config.showLabels) return;
16587 var pos = node.pos.getp(true),
16588 dimArray = node.getData('dimArray'),
16589 nodeIteration = node.getData('nodeIteration'),
16590 nodeLength = node.getData('nodeLength'),
16591 span = node.getData('span') / 2,
16592 theta = node.pos.theta,
16593 begin = ((theta - span)/2)+Math.PI,
16594 end = ((theta + span)/2)+Math.PI,
16597 var showLabels = config.showLabels,
16598 resizeLabels = config.resizeLabels,
16599 label = config.Label,
16600 radiusOffset = sb.config.levelDistance;
16603 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16604 acum += dimArray[i];
16606 var scale = resizeLabels? node.getData('normalizedDim') : 1,
16607 fontSize = (label.size * scale) >> 0;
16608 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16609 domElement.style.fontSize = fontSize + 'px';
16610 polar.rho = acum * .65;
16611 polar.theta = begin;
16612 var pos = polar.getc(true);
16613 var radius = that.canvas.getSize();
16615 x: Math.round(pos.x + radius.width / 2),
16616 y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16621 domElement.style.left = (labelPos.x - 200) + 'px';
16622 domElement.style.top = labelPos.y + 'px';
16624 //reposition first label
16625 if(nodeIteration == 1) {
16626 domElement.style.top = labelPos.y - label.size + 'px';
16630 //position final label
16631 if(nodeIteration == nodeLength && nodeLength != 0) {
16633 var final = polar.getc(true);
16635 x: Math.round(final.x + radius.width / 2),
16636 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16638 finalLabel.style.left = (finalPos.x - 200) + "px";
16639 finalLabel.style.top = finalPos.y - label.size + "px";
16647 this.canvas = this.sb.canvas;
16648 var size = sb.canvas.getSize(),
16650 sb.config.levelDistance = min(size.width, size.height)/2
16651 - config.offset - config.sliceOffset;
16656 renderBackground: function() {
16657 var canvas = this.sb.canvas,
16658 config = this.config,
16659 style = config.gaugeStyle,
16660 ctx = canvas.getCtx(),
16661 size = canvas.getSize(),
16662 radius = this.sb.config.levelDistance,
16663 startAngle = (Math.PI/180)*1,
16664 endAngle = (Math.PI/180)*179;
16667 //background border
16668 ctx.fillStyle = style.borderColor;
16670 ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true);
16674 var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16675 radialGradient.addColorStop(0, '#ffffff');
16676 radialGradient.addColorStop(0.3, style.backgroundColor);
16677 radialGradient.addColorStop(0.6, style.backgroundColor);
16678 radialGradient.addColorStop(1, '#FFFFFF');
16679 ctx.fillStyle = radialGradient;
16682 startAngle = (Math.PI/180)*0;
16683 endAngle = (Math.PI/180)*180;
16685 ctx.arc(0,radius/2,radius,startAngle,endAngle, true);
16693 renderNeedle: function(gaugePosition,target) {
16694 var canvas = this.sb.canvas,
16695 config = this.config,
16696 style = config.gaugeStyle,
16697 ctx = canvas.getCtx(),
16698 size = canvas.getSize(),
16699 radius = this.sb.config.levelDistance;
16700 gaugeCenter = (radius/2);
16702 endAngle = (Math.PI/180)*180;
16706 ctx.fillStyle = style.needleColor;
16707 var segments = 180/target;
16708 needleAngle = gaugePosition * segments;
16709 ctx.translate(0, gaugeCenter);
16711 ctx.rotate(needleAngle * Math.PI / 180);
16715 ctx.lineTo(-radius*.9,-1);
16716 ctx.lineTo(-radius*.9,1);
16726 ctx.strokeStyle = '#aa0000';
16728 ctx.rotate(needleAngle * Math.PI / 180);
16732 ctx.lineTo(-radius*.8,-1);
16733 ctx.lineTo(-radius*.8,1);
16741 ctx.fillStyle = "#000000";
16742 ctx.lineWidth = style.borderSize;
16743 ctx.strokeStyle = style.borderColor;
16744 var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16745 radialGradient.addColorStop(0, '#666666');
16746 radialGradient.addColorStop(0.8, '#444444');
16747 radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
16748 ctx.fillStyle = radialGradient;
16749 ctx.translate(0,5);
16752 ctx.arc(0,0,radius*.2,startAngle,endAngle, true);
16759 renderTicks: function(values) {
16760 var canvas = this.sb.canvas,
16761 config = this.config,
16762 style = config.gaugeStyle,
16763 ctx = canvas.getCtx(),
16764 size = canvas.getSize(),
16765 radius = this.sb.config.levelDistance,
16766 gaugeCenter = (radius/2);
16769 ctx.strokeStyle = style.borderColor;
16771 ctx.lineCap = "round";
16772 for(var i=0, total = 0, l=values.length; i<l; i++) {
16773 var val = values[i];
16774 if(val.label != 'GaugePosition') {
16775 total += (parseInt(val.values) || 0);
16779 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16780 var val = values[i];
16781 if(val.label != 'GaugePosition') {
16782 acum += (parseInt(val.values) || 0);
16784 var segments = 180/total;
16785 angle = acum * segments;
16789 ctx.translate(0, gaugeCenter);
16791 ctx.rotate(angle * (Math.PI/180));
16792 ctx.moveTo(-radius,0);
16793 ctx.lineTo(-radius*.75,0);
16801 renderPositionLabel: function(position) {
16802 var canvas = this.sb.canvas,
16803 config = this.config,
16804 label = config.Label,
16805 style = config.gaugeStyle,
16806 ctx = canvas.getCtx(),
16807 size = canvas.getSize(),
16808 radius = this.sb.config.levelDistance,
16809 gaugeCenter = (radius/2);
16810 ctx.textBaseline = 'middle';
16811 ctx.textAlign = 'center';
16812 ctx.font = style.positionFontSize + 'px ' + label.family;
16813 ctx.fillStyle = "#ffffff";
16815 height = style.positionFontSize + 10,
16817 idLabel = canvas.id + "-label";
16818 container = document.getElementById(idLabel);
16819 if(label.type == 'Native') {
16820 var m = ctx.measureText(position),
16821 width = m.width + 40;
16825 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16826 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16827 if(label.type == 'Native') {
16828 ctx.fillStyle = label.color;
16829 ctx.fillText(position, 0, (height/2) + style.positionOffset);
16831 var labelDiv = document.createElement('div');
16832 labelDivStyle = labelDiv.style;
16833 labelDivStyle.color = label.color;
16834 labelDivStyle.fontSize = style.positionFontSize + "px";
16835 labelDivStyle.position = "absolute";
16836 labelDivStyle.width = width + "px";
16837 labelDivStyle.left = (size.width/2) - (width/2) + "px";
16838 labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16839 labelDiv.innerHTML = position;
16840 container.appendChild(labelDiv);
16845 renderSubtitle: function() {
16846 var canvas = this.canvas,
16847 size = canvas.getSize(),
16848 config = this.config,
16849 margin = config.Margin,
16850 radius = this.sb.config.levelDistance,
16851 title = config.Title,
16852 label = config.Label,
16853 subtitle = config.Subtitle;
16854 ctx = canvas.getCtx();
16855 ctx.fillStyle = title.color;
16856 ctx.textAlign = 'left';
16857 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16859 if(label.type == 'Native') {
16860 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2));
16864 renderChartBackground: function() {
16865 var canvas = this.canvas,
16866 config = this.config,
16867 backgroundColor = config.backgroundColor,
16868 size = canvas.getSize(),
16869 ctx = canvas.getCtx();
16870 //ctx.globalCompositeOperation = "destination-over";
16871 ctx.fillStyle = backgroundColor;
16872 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16874 clear: function() {
16875 var canvas = this.canvas;
16876 var ctx = canvas.getCtx(),
16877 size = canvas.getSize();
16878 ctx.fillStyle = "rgba(255,255,255,0)";
16879 ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16880 ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16882 resizeGraph: function(json,width) {
16883 var canvas = this.canvas,
16884 size = canvas.getSize(),
16885 orgHeight = size.height;
16887 canvas.resize(width,orgHeight);
16888 if(typeof FlashCanvas == "undefined") {
16891 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16893 this.loadJSON(json);
16896 loadJSON: function(json) {
16898 var prefix = $.time(),
16901 name = $.splat(json.label),
16902 nameLength = name.length,
16903 color = $.splat(json.color || this.colors),
16904 colorLength = color.length,
16905 config = this.config,
16906 renderBackground = config.renderBackground,
16907 gradient = !!config.type.split(":")[1],
16908 animate = config.animate,
16909 mono = nameLength == 1;
16910 var props = $.splat(json.properties)[0];
16912 for(var i=0, values=json.values, l=values.length; i<l; i++) {
16914 var val = values[i];
16915 if(val.label != 'GaugePosition') {
16916 var valArray = $.splat(val.values);
16917 var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16918 var valuelabelsArray = $.splat(val.valuelabels);
16921 'id': prefix + val.label,
16925 'valuelabel': valuelabelsArray,
16926 '$linkArray': linkArray,
16927 '$valuelabelsArray': valuelabelsArray,
16928 '$valueArray': valArray,
16929 '$nodeIteration': i,
16930 '$nodeLength': l-1,
16931 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16932 '$colorMono': $.splat(color[i % colorLength]),
16933 '$stringArray': name,
16934 '$gradient': gradient,
16936 '$gaugeTarget': props['gaugeTarget'],
16937 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16942 var gaugePosition = val.gvalue;
16943 var gaugePositionLabel = val.gvaluelabel;
16947 'id': prefix + '$root',
16960 if(renderBackground) {
16961 this.renderChartBackground();
16964 this.renderBackground();
16965 this.renderSubtitle();
16967 this.normalizeDims();
16972 modes: ['node-property:dimArray'],
16978 this.renderPositionLabel(gaugePositionLabel);
16979 if (props['gaugeTarget'] != 0) {
16980 this.renderTicks(json.values);
16981 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16988 updateJSON: function(json, onComplete) {
16989 if(this.busy) return;
16993 var graph = sb.graph;
16994 var values = json.values;
16995 var animate = this.config.animate;
16997 $.each(values, function(v) {
16998 var n = graph.getByName(v.label),
16999 vals = $.splat(v.values);
17001 n.setData('valueArray', vals);
17002 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
17004 n.setData('stringArray', $.splat(json.label));
17008 this.normalizeDims();
17012 modes: ['node-property:dimArray:span', 'linear'],
17014 onComplete: function() {
17016 onComplete && onComplete.onComplete();
17024 //adds the little brown bar when hovering the node
17025 select: function(id, name) {
17026 if(!this.config.hoveredColor) return;
17027 var s = this.selected;
17028 if(s.id != id || s.name != name) {
17031 s.color = this.config.hoveredColor;
17032 this.sb.graph.eachNode(function(n) {
17034 n.setData('border', s);
17036 n.setData('border', false);
17046 Returns an object containing as keys the legend names and as values hex strings with color values.
17051 var legend = pieChart.getLegend();
17054 getLegend: function() {
17055 var legend = new Array();
17056 var name = new Array();
17057 var color = new Array();
17059 this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
17062 var colors = n.getData('colorArray'),
17063 len = colors.length;
17064 $.each(n.getData('stringArray'), function(s, i) {
17065 color[i] = colors[i % len];
17068 legend['name'] = name;
17069 legend['color'] = color;
17074 Method: getMaxValue
17076 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
17081 var ans = pieChart.getMaxValue();
17084 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
17089 //will return 100 for all PieChart instances,
17090 //displaying all of them with the same scale
17091 $jit.PieChart.implement({
17092 'getMaxValue': function() {
17099 getMaxValue: function() {
17101 this.sb.graph.eachNode(function(n) {
17102 var valArray = n.getData('valueArray'),
17104 $.each(valArray, function(v) {
17107 maxValue = maxValue>acum? maxValue:acum;
17112 normalizeDims: function() {
17113 //number of elements
17114 var root = this.sb.graph.getNode(this.sb.root), l=0;
17115 root.eachAdjacency(function() {
17118 var maxValue = this.getMaxValue() || 1,
17119 config = this.config,
17120 animate = config.animate,
17121 rho = this.sb.config.levelDistance;
17122 this.sb.graph.eachNode(function(n) {
17123 var acum = 0, animateValue = [];
17124 $.each(n.getData('valueArray'), function(v) {
17126 animateValue.push(1);
17128 var stat = (animateValue.length == 1) && !config.updateHeights;
17130 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
17131 return stat? rho: (n * rho / maxValue);
17133 var dimArray = n.getData('dimArray');
17135 n.setData('dimArray', animateValue);
17138 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
17139 return stat? rho : (n * rho / maxValue);
17142 n.setData('normalizedDim', acum / maxValue);
17149 * Class: Layouts.TM
17151 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
17160 Layouts.TM.SliceAndDice = new Class({
17161 compute: function(prop) {
17162 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17163 this.controller.onBeforeCompute(root);
17164 var size = this.canvas.getSize(),
17165 config = this.config,
17166 width = size.width,
17167 height = size.height;
17168 this.graph.computeLevels(this.root, 0, "ignore");
17169 //set root position and dimensions
17170 root.getPos(prop).setc(-width/2, -height/2);
17171 root.setData('width', width, prop);
17172 root.setData('height', height + config.titleHeight, prop);
17173 this.computePositions(root, root, this.layout.orientation, prop);
17174 this.controller.onAfterCompute(root);
17177 computePositions: function(par, ch, orn, prop) {
17178 //compute children areas
17180 par.eachSubnode(function(n) {
17181 totalArea += n.getData('area', prop);
17184 var config = this.config,
17185 offst = config.offset,
17186 width = par.getData('width', prop),
17187 height = par.getData('height', prop) - config.titleHeight,
17188 fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
17190 var otherSize, size, dim, pos, pos2, posth, pos2th;
17191 var horizontal = (orn == "h");
17194 otherSize = height;
17195 size = width * fact;
17199 posth = config.titleHeight;
17203 otherSize = height * fact;
17209 pos2th = config.titleHeight;
17211 var cpos = ch.getPos(prop);
17212 ch.setData('width', size, prop);
17213 ch.setData('height', otherSize, prop);
17214 var offsetSize = 0, tm = this;
17215 ch.eachSubnode(function(n) {
17216 var p = n.getPos(prop);
17217 p[pos] = offsetSize + cpos[pos] + posth;
17218 p[pos2] = cpos[pos2] + pos2th;
17219 tm.computePositions(ch, n, orn, prop);
17220 offsetSize += n.getData(dim, prop);
17226 Layouts.TM.Area = {
17230 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17234 json - A JSON tree. See also <Loader.loadJSON>.
17235 coord - A coordinates object specifying width, height, left and top style properties.
17237 compute: function(prop) {
17238 prop = prop || "current";
17239 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17240 this.controller.onBeforeCompute(root);
17241 var config = this.config,
17242 size = this.canvas.getSize(),
17243 width = size.width,
17244 height = size.height,
17245 offst = config.offset,
17246 offwdth = width - offst,
17247 offhght = height - offst;
17248 this.graph.computeLevels(this.root, 0, "ignore");
17249 //set root position and dimensions
17250 root.getPos(prop).setc(-width/2, -height/2);
17251 root.setData('width', width, prop);
17252 root.setData('height', height, prop);
17253 //create a coordinates object
17255 'top': -height/2 + config.titleHeight,
17258 'height': offhght - config.titleHeight
17260 this.computePositions(root, coord, prop);
17261 this.controller.onAfterCompute(root);
17267 Computes dimensions and positions of a group of nodes
17268 according to a custom layout row condition.
17272 tail - An array of nodes.
17273 initElem - An array of nodes (containing the initial node to be laid).
17274 w - A fixed dimension where nodes will be layed out.
17275 coord - A coordinates object specifying width, height, left and top style properties.
17276 comp - A custom comparison function
17278 computeDim: function(tail, initElem, w, coord, comp, prop) {
17279 if(tail.length + initElem.length == 1) {
17280 var l = (tail.length == 1)? tail : initElem;
17281 this.layoutLast(l, w, coord, prop);
17284 if(tail.length >= 2 && initElem.length == 0) {
17285 initElem = [tail.shift()];
17287 if(tail.length == 0) {
17288 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17292 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17293 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17295 var newCoords = this.layoutRow(initElem, w, coord, prop);
17296 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17302 Method: worstAspectRatio
17304 Calculates the worst aspect ratio of a group of rectangles.
17308 <http://en.wikipedia.org/wiki/Aspect_ratio>
17312 ch - An array of nodes.
17313 w - The fixed dimension where rectangles are being laid out.
17317 The worst aspect ratio.
17321 worstAspectRatio: function(ch, w) {
17322 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17323 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17324 for(var i=0, l=ch.length; i<l; i++) {
17325 var area = ch[i]._area;
17327 minArea = minArea < area? minArea : area;
17328 maxArea = maxArea > area? maxArea : area;
17330 var sqw = w * w, sqAreaSum = areaSum * areaSum;
17331 return Math.max(sqw * maxArea / sqAreaSum,
17332 sqAreaSum / (sqw * minArea));
17336 Method: avgAspectRatio
17338 Calculates the average aspect ratio of a group of rectangles.
17342 <http://en.wikipedia.org/wiki/Aspect_ratio>
17346 ch - An array of nodes.
17347 w - The fixed dimension where rectangles are being laid out.
17351 The average aspect ratio.
17355 avgAspectRatio: function(ch, w) {
17356 if(!ch || ch.length == 0) return Number.MAX_VALUE;
17358 for(var i=0, l=ch.length; i<l; i++) {
17359 var area = ch[i]._area;
17361 arSum += w > h? w / h : h / w;
17369 Performs the layout of the last computed sibling.
17373 ch - An array of nodes.
17374 w - A fixed dimension where nodes will be layed out.
17375 coord - A coordinates object specifying width, height, left and top style properties.
17377 layoutLast: function(ch, w, coord, prop) {
17379 child.getPos(prop).setc(coord.left, coord.top);
17380 child.setData('width', coord.width, prop);
17381 child.setData('height', coord.height, prop);
17386 Layouts.TM.Squarified = new Class({
17387 Implements: Layouts.TM.Area,
17389 computePositions: function(node, coord, prop) {
17390 var config = this.config;
17392 if (coord.width >= coord.height)
17393 this.layout.orientation = 'h';
17395 this.layout.orientation = 'v';
17397 var ch = node.getSubnodes([1, 1], "ignore");
17398 if(ch.length > 0) {
17399 this.processChildrenLayout(node, ch, coord, prop);
17400 for(var i=0, l=ch.length; i<l; i++) {
17402 var offst = config.offset,
17403 height = chi.getData('height', prop) - offst - config.titleHeight,
17404 width = chi.getData('width', prop) - offst;
17405 var chipos = chi.getPos(prop);
17409 'top': chipos.y + config.titleHeight,
17412 this.computePositions(chi, coord, prop);
17418 Method: processChildrenLayout
17420 Computes children real areas and other useful parameters for performing the Squarified algorithm.
17424 par - The parent node of the json subtree.
17425 ch - An Array of nodes
17426 coord - A coordinates object specifying width, height, left and top style properties.
17428 processChildrenLayout: function(par, ch, coord, prop) {
17429 //compute children real areas
17430 var parentArea = coord.width * coord.height;
17431 var i, l=ch.length, totalChArea=0, chArea = [];
17432 for(i=0; i<l; i++) {
17433 chArea[i] = parseFloat(ch[i].getData('area', prop));
17434 totalChArea += chArea[i];
17436 for(i=0; i<l; i++) {
17437 ch[i]._area = parentArea * chArea[i] / totalChArea;
17439 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17440 ch.sort(function(a, b) {
17441 var diff = b._area - a._area;
17442 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
17444 var initElem = [ch[0]];
17445 var tail = ch.slice(1);
17446 this.squarify(tail, initElem, minimumSideValue, coord, prop);
17452 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17456 tail - An array of nodes.
17457 initElem - An array of nodes, containing the initial node to be laid out.
17458 w - A fixed dimension where nodes will be laid out.
17459 coord - A coordinates object specifying width, height, left and top style properties.
17461 squarify: function(tail, initElem, w, coord, prop) {
17462 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17468 Performs the layout of an array of nodes.
17472 ch - An array of nodes.
17473 w - A fixed dimension where nodes will be laid out.
17474 coord - A coordinates object specifying width, height, left and top style properties.
17476 layoutRow: function(ch, w, coord, prop) {
17477 if(this.layout.horizontal()) {
17478 return this.layoutV(ch, w, coord, prop);
17480 return this.layoutH(ch, w, coord, prop);
17484 layoutV: function(ch, w, coord, prop) {
17485 var totalArea = 0, rnd = function(x) { return x; };
17486 $.each(ch, function(elem) { totalArea += elem._area; });
17487 var width = rnd(totalArea / w), top = 0;
17488 for(var i=0, l=ch.length; i<l; i++) {
17489 var h = rnd(ch[i]._area / width);
17491 chi.getPos(prop).setc(coord.left, coord.top + top);
17492 chi.setData('width', width, prop);
17493 chi.setData('height', h, prop);
17497 'height': coord.height,
17498 'width': coord.width - width,
17500 'left': coord.left + width
17502 //take minimum side value.
17503 ans.dim = Math.min(ans.width, ans.height);
17504 if(ans.dim != ans.height) this.layout.change();
17508 layoutH: function(ch, w, coord, prop) {
17510 $.each(ch, function(elem) { totalArea += elem._area; });
17511 var height = totalArea / w,
17515 for(var i=0, l=ch.length; i<l; i++) {
17517 var w = chi._area / height;
17518 chi.getPos(prop).setc(coord.left + left, top);
17519 chi.setData('width', w, prop);
17520 chi.setData('height', height, prop);
17524 'height': coord.height - height,
17525 'width': coord.width,
17526 'top': coord.top + height,
17529 ans.dim = Math.min(ans.width, ans.height);
17530 if(ans.dim != ans.width) this.layout.change();
17535 Layouts.TM.Strip = new Class({
17536 Implements: Layouts.TM.Area,
17541 Called by loadJSON to calculate recursively all node positions and lay out the tree.
17545 json - A JSON subtree. See also <Loader.loadJSON>.
17546 coord - A coordinates object specifying width, height, left and top style properties.
17548 computePositions: function(node, coord, prop) {
17549 var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17550 if(ch.length > 0) {
17551 this.processChildrenLayout(node, ch, coord, prop);
17552 for(var i=0, l=ch.length; i<l; i++) {
17554 var offst = config.offset,
17555 height = chi.getData('height', prop) - offst - config.titleHeight,
17556 width = chi.getData('width', prop) - offst;
17557 var chipos = chi.getPos(prop);
17561 'top': chipos.y + config.titleHeight,
17564 this.computePositions(chi, coord, prop);
17570 Method: processChildrenLayout
17572 Computes children real areas and other useful parameters for performing the Strip algorithm.
17576 par - The parent node of the json subtree.
17577 ch - An Array of nodes
17578 coord - A coordinates object specifying width, height, left and top style properties.
17580 processChildrenLayout: function(par, ch, coord, prop) {
17581 //compute children real areas
17582 var parentArea = coord.width * coord.height;
17583 var i, l=ch.length, totalChArea=0, chArea = [];
17584 for(i=0; i<l; i++) {
17585 chArea[i] = +ch[i].getData('area', prop);
17586 totalChArea += chArea[i];
17588 for(i=0; i<l; i++) {
17589 ch[i]._area = parentArea * chArea[i] / totalChArea;
17591 var side = this.layout.horizontal()? coord.width : coord.height;
17592 var initElem = [ch[0]];
17593 var tail = ch.slice(1);
17594 this.stripify(tail, initElem, side, coord, prop);
17600 Performs an heuristic method to calculate div elements sizes in order to have
17601 a good compromise between aspect ratio and order.
17605 tail - An array of nodes.
17606 initElem - An array of nodes.
17607 w - A fixed dimension where nodes will be layed out.
17608 coord - A coordinates object specifying width, height, left and top style properties.
17610 stripify: function(tail, initElem, w, coord, prop) {
17611 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17617 Performs the layout of an array of nodes.
17621 ch - An array of nodes.
17622 w - A fixed dimension where nodes will be laid out.
17623 coord - A coordinates object specifying width, height, left and top style properties.
17625 layoutRow: function(ch, w, coord, prop) {
17626 if(this.layout.horizontal()) {
17627 return this.layoutH(ch, w, coord, prop);
17629 return this.layoutV(ch, w, coord, prop);
17633 layoutV: function(ch, w, coord, prop) {
17635 $.each(ch, function(elem) { totalArea += elem._area; });
17636 var width = totalArea / w, top = 0;
17637 for(var i=0, l=ch.length; i<l; i++) {
17639 var h = chi._area / width;
17640 chi.getPos(prop).setc(coord.left,
17641 coord.top + (w - h - top));
17642 chi.setData('width', width, prop);
17643 chi.setData('height', h, prop);
17648 'height': coord.height,
17649 'width': coord.width - width,
17651 'left': coord.left + width,
17656 layoutH: function(ch, w, coord, prop) {
17658 $.each(ch, function(elem) { totalArea += elem._area; });
17659 var height = totalArea / w,
17660 top = coord.height - height,
17663 for(var i=0, l=ch.length; i<l; i++) {
17665 var s = chi._area / height;
17666 chi.getPos(prop).setc(coord.left + left, coord.top + top);
17667 chi.setData('width', s, prop);
17668 chi.setData('height', height, prop);
17672 'height': coord.height - height,
17673 'width': coord.width,
17675 'left': coord.left,
17682 * Class: Layouts.Icicle
17684 * Implements the icicle tree layout.
17692 Layouts.Icicle = new Class({
17696 * Called by loadJSON to calculate all node positions.
17700 * posType - The nodes' position to compute. Either "start", "end" or
17701 * "current". Defaults to "current".
17703 compute: function(posType) {
17704 posType = posType || "current";
17705 var root = this.graph.getNode(this.root),
17706 config = this.config,
17707 size = this.canvas.getSize(),
17708 width = size.width,
17709 height = size.height,
17710 offset = config.offset,
17711 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17713 this.controller.onBeforeCompute(root);
17715 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17719 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17721 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17722 var maxDepth = Math.min(treeDepth, levelsToShow-1);
17723 var initialDepth = startNode._depth;
17724 if(this.layout.horizontal()) {
17725 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17727 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17731 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17732 root.getPos(posType).setc(x, y);
17733 root.setData('width', width, posType);
17734 root.setData('height', height, posType);
17736 var nodeLength, prevNodeLength = 0, totalDim = 0;
17737 var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17739 if(!children.length)
17742 $.each(children, function(e) { totalDim += e.getData('dim'); });
17744 for(var i=0, l=children.length; i < l; i++) {
17745 if(this.layout.horizontal()) {
17746 nodeLength = height * children[i].getData('dim') / totalDim;
17747 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17750 nodeLength = width * children[i].getData('dim') / totalDim;
17751 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17768 Icicle space filling visualization.
17772 All <Loader> methods
17774 Constructor Options:
17776 Inherits options from
17779 - <Options.Controller>
17785 - <Options.NodeStyles>
17786 - <Options.Navigation>
17788 Additionally, there are other parameters and some default values changed
17790 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17791 offset - (number) Default's *2*. Boxes offset.
17792 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17793 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17794 animate - (boolean) Default's *false*. Whether to animate transitions.
17795 Node.type - Described in <Options.Node>. Default's *rectangle*.
17796 Label.type - Described in <Options.Label>. Default's *Native*.
17797 duration - Described in <Options.Fx>. Default's *700*.
17798 fps - Described in <Options.Fx>. Default's *45*.
17800 Instance Properties:
17802 canvas - Access a <Canvas> instance.
17803 graph - Access a <Graph> instance.
17804 op - Access a <Icicle.Op> instance.
17805 fx - Access a <Icicle.Plot> instance.
17806 labels - Access a <Icicle.Label> interface implementation.
17810 $jit.Icicle = new Class({
17811 Implements: [ Loader, Extras, Layouts.Icicle ],
17815 vertical: function(){
17816 return this.orientation == "v";
17818 horizontal: function(){
17819 return this.orientation == "h";
17821 change: function(){
17822 this.orientation = this.vertical()? "h" : "v";
17826 initialize: function(controller) {
17831 levelsToShow: Number.MAX_VALUE,
17832 constrained: false,
17847 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17848 "Events", "Navigation", "Controller", "Label");
17849 this.controller = this.config = $.merge(opts, config, controller);
17850 this.layout.orientation = this.config.orientation;
17852 var canvasConfig = this.config;
17853 if (canvasConfig.useCanvas) {
17854 this.canvas = canvasConfig.useCanvas;
17855 this.config.labelContainer = this.canvas.id + '-label';
17857 this.canvas = new Canvas(this, canvasConfig);
17858 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17861 this.graphOptions = {
17870 this.graph = new Graph(
17871 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17873 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17874 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17875 this.op = new $jit.Icicle.Op(this);
17876 this.group = new $jit.Icicle.Group(this);
17877 this.clickedNode = null;
17879 this.initializeExtras();
17885 Computes positions and plots the tree.
17887 refresh: function(){
17888 var labelType = this.config.Label.type;
17889 if(labelType != 'Native') {
17891 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17900 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
17904 this.fx.plot(this.config);
17910 Sets the node as root.
17914 node - (object) A <Graph.Node>.
17917 enter: function (node) {
17923 config = this.config;
17926 onComplete: function() {
17927 //compute positions of newly inserted nodes
17931 if(config.animate) {
17932 that.graph.nodeList.setDataset(['current', 'end'], {
17933 'alpha': [1, 0] //fade nodes
17936 Graph.Util.eachSubgraph(node, function(n) {
17937 n.setData('alpha', 1, 'end');
17942 modes:['node-property:alpha'],
17943 onComplete: function() {
17944 that.clickedNode = node;
17945 that.compute('end');
17948 modes:['linear', 'node-property:width:height'],
17950 onComplete: function() {
17952 that.clickedNode = node;
17958 that.clickedNode = node;
17965 if(config.request) {
17966 this.requestNodes(clickedNode, callback);
17968 callback.onComplete();
17975 Sets the parent node of the current selected node as root.
17983 GUtil = Graph.Util,
17984 config = this.config,
17985 graph = this.graph,
17986 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17987 parent = parents[0],
17988 clickedNode = parent,
17989 previousClickedNode = this.clickedNode;
17992 this.events.hoveredNode = false;
17999 //final plot callback
18001 onComplete: function() {
18002 that.clickedNode = parent;
18003 if(config.request) {
18004 that.requestNodes(parent, {
18005 onComplete: function() {
18019 //animate node positions
18020 if(config.animate) {
18021 this.clickedNode = clickedNode;
18022 this.compute('end');
18023 //animate the visible subtree only
18024 this.clickedNode = previousClickedNode;
18026 modes:['linear', 'node-property:width:height'],
18028 onComplete: function() {
18029 //animate the parent subtree
18030 that.clickedNode = clickedNode;
18031 //change nodes alpha
18032 graph.nodeList.setDataset(['current', 'end'], {
18035 GUtil.eachSubgraph(previousClickedNode, function(node) {
18036 node.setData('alpha', 1);
18040 modes:['node-property:alpha'],
18041 onComplete: function() {
18042 callback.onComplete();
18048 callback.onComplete();
18051 requestNodes: function(node, onComplete){
18052 var handler = $.merge(this.controller, onComplete),
18053 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
18055 if (handler.request) {
18056 var leaves = [], d = node._depth;
18057 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
18058 if (n.drawn && !Graph.Util.anySubnode(n)) {
18060 n._level = n._depth - d;
18061 if (this.config.constrained)
18062 n._level = levelsToShow - n._level;
18066 this.group.requestNodes(leaves, handler);
18068 handler.onComplete();
18076 Custom extension of <Graph.Op>.
18080 All <Graph.Op> methods
18087 $jit.Icicle.Op = new Class({
18089 Implements: Graph.Op
18094 * Performs operations on group of nodes.
18096 $jit.Icicle.Group = new Class({
18098 initialize: function(viz){
18100 this.canvas = viz.canvas;
18101 this.config = viz.config;
18105 * Calls the request method on the controller to request a subtree for each node.
18107 requestNodes: function(nodes, controller){
18108 var counter = 0, len = nodes.length, nodeSelected = {};
18109 var complete = function(){
18110 controller.onComplete();
18112 var viz = this.viz;
18115 for(var i = 0; i < len; i++) {
18116 nodeSelected[nodes[i].id] = nodes[i];
18117 controller.request(nodes[i].id, nodes[i]._level, {
18118 onComplete: function(nodeId, data){
18119 if (data && data.children) {
18125 if (++counter == len) {
18126 Graph.Util.computeLevels(viz.graph, viz.root, 0);
18138 Custom extension of <Graph.Plot>.
18142 All <Graph.Plot> methods
18149 $jit.Icicle.Plot = new Class({
18150 Implements: Graph.Plot,
18152 plot: function(opt, animating){
18153 opt = opt || this.viz.controller;
18154 var viz = this.viz,
18156 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
18157 initialDepth = root._depth;
18159 viz.canvas.clear();
18160 this.plotTree(root, $.merge(opt, {
18161 'withLabels': true,
18162 'hideLabels': false,
18163 'plotSubtree': function(root, node) {
18164 return !viz.config.constrained ||
18165 (node._depth - initialDepth < viz.config.levelsToShow);
18172 Class: Icicle.Label
18174 Custom extension of <Graph.Label>.
18175 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18179 All <Graph.Label> methods and subclasses.
18183 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18186 $jit.Icicle.Label = {};
18189 Icicle.Label.Native
18191 Custom extension of <Graph.Label.Native>.
18195 All <Graph.Label.Native> methods
18199 <Graph.Label.Native>
18202 $jit.Icicle.Label.Native = new Class({
18203 Implements: Graph.Label.Native,
18205 renderLabel: function(canvas, node, controller) {
18206 var ctx = canvas.getCtx(),
18207 width = node.getData('width'),
18208 height = node.getData('height'),
18209 size = node.getLabelData('size'),
18210 m = ctx.measureText(node.name);
18212 // Guess as much as possible if the label will fit in the node
18213 if(height < (size * 1.5) || width < m.width)
18216 var pos = node.pos.getc(true);
18217 ctx.fillText(node.name,
18219 pos.y + height / 2);
18226 Custom extension of <Graph.Label.SVG>.
18230 All <Graph.Label.SVG> methods
18236 $jit.Icicle.Label.SVG = new Class( {
18237 Implements: Graph.Label.SVG,
18239 initialize: function(viz){
18246 Overrides abstract method placeLabel in <Graph.Plot>.
18250 tag - A DOM label element.
18251 node - A <Graph.Node>.
18252 controller - A configuration/controller object passed to the visualization.
18254 placeLabel: function(tag, node, controller){
18255 var pos = node.pos.getc(true), canvas = this.viz.canvas;
18256 var radius = canvas.getSize();
18258 x: Math.round(pos.x + radius.width / 2),
18259 y: Math.round(pos.y + radius.height / 2)
18261 tag.setAttribute('x', labelPos.x);
18262 tag.setAttribute('y', labelPos.y);
18264 controller.onPlaceLabel(tag, node);
18271 Custom extension of <Graph.Label.HTML>.
18275 All <Graph.Label.HTML> methods.
18282 $jit.Icicle.Label.HTML = new Class( {
18283 Implements: Graph.Label.HTML,
18285 initialize: function(viz){
18292 Overrides abstract method placeLabel in <Graph.Plot>.
18296 tag - A DOM label element.
18297 node - A <Graph.Node>.
18298 controller - A configuration/controller object passed to the visualization.
18300 placeLabel: function(tag, node, controller){
18301 var pos = node.pos.getc(true), canvas = this.viz.canvas;
18302 var radius = canvas.getSize();
18304 x: Math.round(pos.x + radius.width / 2),
18305 y: Math.round(pos.y + radius.height / 2)
18308 var style = tag.style;
18309 style.left = labelPos.x + 'px';
18310 style.top = labelPos.y + 'px';
18311 style.display = '';
18313 controller.onPlaceLabel(tag, node);
18318 Class: Icicle.Plot.NodeTypes
18320 This class contains a list of <Graph.Node> built-in types.
18321 Node types implemented are 'none', 'rectangle'.
18323 You can add your custom node types, customizing your visualization to the extreme.
18328 Icicle.Plot.NodeTypes.implement({
18330 'render': function(node, canvas) {
18331 //print your custom node to canvas
18334 'contains': function(node, pos) {
18335 //return true if pos is inside the node or false otherwise
18342 $jit.Icicle.Plot.NodeTypes = new Class( {
18348 'render': function(node, canvas, animating) {
18349 var config = this.viz.config;
18350 var offset = config.offset;
18351 var width = node.getData('width');
18352 var height = node.getData('height');
18353 var border = node.getData('border');
18354 var pos = node.pos.getc(true);
18355 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18356 var ctx = canvas.getCtx();
18358 if(width - offset < 2 || height - offset < 2) return;
18360 if(config.cushion) {
18361 var color = node.getData('color');
18362 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
18363 posy + (height - offset)/2, 1,
18364 posx + (width-offset)/2, posy + (height-offset)/2,
18365 width < height? height : width);
18366 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
18367 function(r) { return r * 0.3 >> 0; }));
18368 lg.addColorStop(0, color);
18369 lg.addColorStop(1, colorGrad);
18370 ctx.fillStyle = lg;
18374 ctx.strokeStyle = border;
18378 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18379 border && ctx.strokeRect(pos.x, pos.y, width, height);
18382 'contains': function(node, pos) {
18383 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18384 var npos = node.pos.getc(true),
18385 width = node.getData('width'),
18386 height = node.getData('height');
18387 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18392 $jit.Icicle.Plot.EdgeTypes = new Class( {
18399 * File: Layouts.ForceDirected.js
18404 * Class: Layouts.ForceDirected
18406 * Implements a Force Directed Layout.
18414 * Marcus Cobden <http://marcuscobden.co.uk>
18417 Layouts.ForceDirected = new Class({
18419 getOptions: function(random) {
18420 var s = this.canvas.getSize();
18421 var w = s.width, h = s.height;
18424 this.graph.eachNode(function(n) {
18427 var k2 = w * h / count, k = Math.sqrt(k2);
18428 var l = this.config.levelDistance;
18434 nodef: function(x) { return k2 / (x || 1); },
18435 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18439 compute: function(property, incremental) {
18440 var prop = $.splat(property || ['current', 'start', 'end']);
18441 var opt = this.getOptions();
18442 NodeDim.compute(this.graph, prop, this.config);
18443 this.graph.computeLevels(this.root, 0, "ignore");
18444 this.graph.eachNode(function(n) {
18445 $.each(prop, function(p) {
18446 var pos = n.getPos(p);
18447 if(pos.equals(Complex.KER)) {
18448 pos.x = opt.width/5 * (Math.random() - 0.5);
18449 pos.y = opt.height/5 * (Math.random() - 0.5);
18451 //initialize disp vector
18453 $.each(prop, function(p) {
18454 n.disp[p] = $C(0, 0);
18458 this.computePositions(prop, opt, incremental);
18461 computePositions: function(property, opt, incremental) {
18462 var times = this.config.iterations, i = 0, that = this;
18465 for(var total=incremental.iter, j=0; j<total; j++) {
18466 opt.t = opt.tstart * (1 - i++/(times -1));
18467 that.computePositionStep(property, opt);
18469 incremental.onComplete();
18473 incremental.onStep(Math.round(i / (times -1) * 100));
18474 setTimeout(iter, 1);
18477 for(; i < times; i++) {
18478 opt.t = opt.tstart * (1 - i/(times -1));
18479 this.computePositionStep(property, opt);
18484 computePositionStep: function(property, opt) {
18485 var graph = this.graph;
18486 var min = Math.min, max = Math.max;
18487 var dpos = $C(0, 0);
18488 //calculate repulsive forces
18489 graph.eachNode(function(v) {
18491 $.each(property, function(p) {
18492 v.disp[p].x = 0; v.disp[p].y = 0;
18494 graph.eachNode(function(u) {
18496 $.each(property, function(p) {
18497 var vp = v.getPos(p), up = u.getPos(p);
18498 dpos.x = vp.x - up.x;
18499 dpos.y = vp.y - up.y;
18500 var norm = dpos.norm() || 1;
18501 v.disp[p].$add(dpos
18502 .$scale(opt.nodef(norm) / norm));
18507 //calculate attractive forces
18508 var T = !!graph.getNode(this.root).visited;
18509 graph.eachNode(function(node) {
18510 node.eachAdjacency(function(adj) {
18511 var nodeTo = adj.nodeTo;
18512 if(!!nodeTo.visited === T) {
18513 $.each(property, function(p) {
18514 var vp = node.getPos(p), up = nodeTo.getPos(p);
18515 dpos.x = vp.x - up.x;
18516 dpos.y = vp.y - up.y;
18517 var norm = dpos.norm() || 1;
18518 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18519 nodeTo.disp[p].$add(dpos.$scale(-1));
18525 //arrange positions to fit the canvas
18526 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18527 graph.eachNode(function(u) {
18528 $.each(property, function(p) {
18529 var disp = u.disp[p];
18530 var norm = disp.norm() || 1;
18531 var p = u.getPos(p);
18532 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
18533 disp.y * min(Math.abs(disp.y), t) / norm));
18534 p.x = min(w2, max(-w2, p.x));
18535 p.y = min(h2, max(-h2, p.y));
18542 * File: ForceDirected.js
18546 Class: ForceDirected
18548 A visualization that lays graphs using a Force-Directed layout algorithm.
18552 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18556 All <Loader> methods
18558 Constructor Options:
18560 Inherits options from
18563 - <Options.Controller>
18569 - <Options.NodeStyles>
18570 - <Options.Navigation>
18572 Additionally, there are two parameters
18574 levelDistance - (number) Default's *50*. The natural length desired for the edges.
18575 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*.
18577 Instance Properties:
18579 canvas - Access a <Canvas> instance.
18580 graph - Access a <Graph> instance.
18581 op - Access a <ForceDirected.Op> instance.
18582 fx - Access a <ForceDirected.Plot> instance.
18583 labels - Access a <ForceDirected.Label> interface implementation.
18587 $jit.ForceDirected = new Class( {
18589 Implements: [ Loader, Extras, Layouts.ForceDirected ],
18591 initialize: function(controller) {
18592 var $ForceDirected = $jit.ForceDirected;
18599 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18600 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18602 var canvasConfig = this.config;
18603 if(canvasConfig.useCanvas) {
18604 this.canvas = canvasConfig.useCanvas;
18605 this.config.labelContainer = this.canvas.id + '-label';
18607 if(canvasConfig.background) {
18608 canvasConfig.background = $.merge({
18610 }, canvasConfig.background);
18612 this.canvas = new Canvas(this, canvasConfig);
18613 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18616 this.graphOptions = {
18624 this.graph = new Graph(this.graphOptions, this.config.Node,
18626 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18627 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18628 this.op = new $ForceDirected.Op(this);
18631 // initialize extras
18632 this.initializeExtras();
18638 Computes positions and plots the tree.
18640 refresh: function() {
18645 reposition: function() {
18646 this.compute('end');
18650 Method: computeIncremental
18652 Performs the Force Directed algorithm incrementally.
18656 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
18657 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
18658 avoiding browser messages such as "This script is taking too long to complete".
18662 opt - (object) The object properties are described below
18664 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
18665 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18667 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
18668 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
18669 computations for final animation positions then you can just choose 'end'.
18671 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
18672 parameter a percentage value.
18674 onComplete - A callback function called when the algorithm completed.
18678 In this example I calculate the end positions and then animate the graph to those positions
18681 var fd = new $jit.ForceDirected(...);
18682 fd.computeIncremental({
18685 onStep: function(perc) {
18686 Log.write("loading " + perc + "%");
18688 onComplete: function() {
18695 In this example I calculate all positions and (re)plot the graph
18698 var fd = new ForceDirected(...);
18699 fd.computeIncremental({
18701 property: ['end', 'start', 'current'],
18702 onStep: function(perc) {
18703 Log.write("loading " + perc + "%");
18705 onComplete: function() {
18713 computeIncremental: function(opt) {
18718 onComplete: $.empty
18721 this.config.onBeforeCompute(this.graph.getNode(this.root));
18722 this.compute(opt.property, opt);
18728 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18737 Animates the graph from the current positions to the 'end' node positions.
18739 animate: function(opt) {
18740 this.fx.animate($.merge( {
18741 modes: [ 'linear' ]
18746 $jit.ForceDirected.$extend = true;
18748 (function(ForceDirected) {
18751 Class: ForceDirected.Op
18753 Custom extension of <Graph.Op>.
18757 All <Graph.Op> methods
18764 ForceDirected.Op = new Class( {
18766 Implements: Graph.Op
18771 Class: ForceDirected.Plot
18773 Custom extension of <Graph.Plot>.
18777 All <Graph.Plot> methods
18784 ForceDirected.Plot = new Class( {
18786 Implements: Graph.Plot
18791 Class: ForceDirected.Label
18793 Custom extension of <Graph.Label>.
18794 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18798 All <Graph.Label> methods and subclasses.
18802 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18805 ForceDirected.Label = {};
18808 ForceDirected.Label.Native
18810 Custom extension of <Graph.Label.Native>.
18814 All <Graph.Label.Native> methods
18818 <Graph.Label.Native>
18821 ForceDirected.Label.Native = new Class( {
18822 Implements: Graph.Label.Native
18826 ForceDirected.Label.SVG
18828 Custom extension of <Graph.Label.SVG>.
18832 All <Graph.Label.SVG> methods
18839 ForceDirected.Label.SVG = new Class( {
18840 Implements: Graph.Label.SVG,
18842 initialize: function(viz) {
18849 Overrides abstract method placeLabel in <Graph.Label>.
18853 tag - A DOM label element.
18854 node - A <Graph.Node>.
18855 controller - A configuration/controller object passed to the visualization.
18858 placeLabel: function(tag, node, controller) {
18859 var pos = node.pos.getc(true),
18860 canvas = this.viz.canvas,
18861 ox = canvas.translateOffsetX,
18862 oy = canvas.translateOffsetY,
18863 sx = canvas.scaleOffsetX,
18864 sy = canvas.scaleOffsetY,
18865 radius = canvas.getSize();
18867 x: Math.round(pos.x * sx + ox + radius.width / 2),
18868 y: Math.round(pos.y * sy + oy + radius.height / 2)
18870 tag.setAttribute('x', labelPos.x);
18871 tag.setAttribute('y', labelPos.y);
18873 controller.onPlaceLabel(tag, node);
18878 ForceDirected.Label.HTML
18880 Custom extension of <Graph.Label.HTML>.
18884 All <Graph.Label.HTML> methods.
18891 ForceDirected.Label.HTML = new Class( {
18892 Implements: Graph.Label.HTML,
18894 initialize: function(viz) {
18900 Overrides abstract method placeLabel in <Graph.Plot>.
18904 tag - A DOM label element.
18905 node - A <Graph.Node>.
18906 controller - A configuration/controller object passed to the visualization.
18909 placeLabel: function(tag, node, controller) {
18910 var pos = node.pos.getc(true),
18911 canvas = this.viz.canvas,
18912 ox = canvas.translateOffsetX,
18913 oy = canvas.translateOffsetY,
18914 sx = canvas.scaleOffsetX,
18915 sy = canvas.scaleOffsetY,
18916 radius = canvas.getSize();
18918 x: Math.round(pos.x * sx + ox + radius.width / 2),
18919 y: Math.round(pos.y * sy + oy + radius.height / 2)
18921 var style = tag.style;
18922 style.left = labelPos.x + 'px';
18923 style.top = labelPos.y + 'px';
18924 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18926 controller.onPlaceLabel(tag, node);
18931 Class: ForceDirected.Plot.NodeTypes
18933 This class contains a list of <Graph.Node> built-in types.
18934 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18936 You can add your custom node types, customizing your visualization to the extreme.
18941 ForceDirected.Plot.NodeTypes.implement({
18943 'render': function(node, canvas) {
18944 //print your custom node to canvas
18947 'contains': function(node, pos) {
18948 //return true if pos is inside the node or false otherwise
18955 ForceDirected.Plot.NodeTypes = new Class({
18958 'contains': $.lambda(false)
18961 'render': function(node, canvas){
18962 var pos = node.pos.getc(true),
18963 dim = node.getData('dim');
18964 this.nodeHelper.circle.render('fill', pos, dim, canvas);
18966 'contains': function(node, pos){
18967 var npos = node.pos.getc(true),
18968 dim = node.getData('dim');
18969 return this.nodeHelper.circle.contains(npos, pos, dim);
18973 'render': function(node, canvas){
18974 var pos = node.pos.getc(true),
18975 width = node.getData('width'),
18976 height = node.getData('height');
18977 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18979 // TODO(nico): be more precise...
18980 'contains': function(node, pos){
18981 var npos = node.pos.getc(true),
18982 width = node.getData('width'),
18983 height = node.getData('height');
18984 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18988 'render': function(node, canvas){
18989 var pos = node.pos.getc(true),
18990 dim = node.getData('dim');
18991 this.nodeHelper.square.render('fill', pos, dim, canvas);
18993 'contains': function(node, pos){
18994 var npos = node.pos.getc(true),
18995 dim = node.getData('dim');
18996 return this.nodeHelper.square.contains(npos, pos, dim);
19000 'render': function(node, canvas){
19001 var pos = node.pos.getc(true),
19002 width = node.getData('width'),
19003 height = node.getData('height');
19004 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
19006 'contains': function(node, pos){
19007 var npos = node.pos.getc(true),
19008 width = node.getData('width'),
19009 height = node.getData('height');
19010 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
19014 'render': function(node, canvas){
19015 var pos = node.pos.getc(true),
19016 dim = node.getData('dim');
19017 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
19019 'contains': function(node, pos) {
19020 var npos = node.pos.getc(true),
19021 dim = node.getData('dim');
19022 return this.nodeHelper.triangle.contains(npos, pos, dim);
19026 'render': function(node, canvas){
19027 var pos = node.pos.getc(true),
19028 dim = node.getData('dim');
19029 this.nodeHelper.star.render('fill', pos, dim, canvas);
19031 'contains': function(node, pos) {
19032 var npos = node.pos.getc(true),
19033 dim = node.getData('dim');
19034 return this.nodeHelper.star.contains(npos, pos, dim);
19040 Class: ForceDirected.Plot.EdgeTypes
19042 This class contains a list of <Graph.Adjacence> built-in types.
19043 Edge types implemented are 'none', 'line' and 'arrow'.
19045 You can add your custom edge types, customizing your visualization to the extreme.
19050 ForceDirected.Plot.EdgeTypes.implement({
19052 'render': function(adj, canvas) {
19053 //print your custom edge to canvas
19056 'contains': function(adj, pos) {
19057 //return true if pos is inside the arc or false otherwise
19064 ForceDirected.Plot.EdgeTypes = new Class({
19067 'render': function(adj, canvas) {
19068 var from = adj.nodeFrom.pos.getc(true),
19069 to = adj.nodeTo.pos.getc(true);
19070 this.edgeHelper.line.render(from, to, canvas);
19072 'contains': function(adj, pos) {
19073 var from = adj.nodeFrom.pos.getc(true),
19074 to = adj.nodeTo.pos.getc(true);
19075 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
19079 'render': function(adj, canvas) {
19080 var from = adj.nodeFrom.pos.getc(true),
19081 to = adj.nodeTo.pos.getc(true),
19082 dim = adj.getData('dim'),
19083 direction = adj.data.$direction,
19084 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
19085 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
19087 'contains': function(adj, pos) {
19088 var from = adj.nodeFrom.pos.getc(true),
19089 to = adj.nodeTo.pos.getc(true);
19090 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
19095 })($jit.ForceDirected);
19107 $jit.TM.$extend = true;
19112 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
19116 All <Loader> methods
19118 Constructor Options:
19120 Inherits options from
19123 - <Options.Controller>
19129 - <Options.NodeStyles>
19130 - <Options.Navigation>
19132 Additionally, there are other parameters and some default values changed
19134 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
19135 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
19136 offset - (number) Default's *2*. Boxes offset.
19137 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
19138 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
19139 animate - (boolean) Default's *false*. Whether to animate transitions.
19140 Node.type - Described in <Options.Node>. Default's *rectangle*.
19141 duration - Described in <Options.Fx>. Default's *700*.
19142 fps - Described in <Options.Fx>. Default's *45*.
19144 Instance Properties:
19146 canvas - Access a <Canvas> instance.
19147 graph - Access a <Graph> instance.
19148 op - Access a <TM.Op> instance.
19149 fx - Access a <TM.Plot> instance.
19150 labels - Access a <TM.Label> interface implementation.
19154 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
19156 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
19160 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.
19166 vertical: function(){
19167 return this.orientation == "v";
19169 horizontal: function(){
19170 return this.orientation == "h";
19172 change: function(){
19173 this.orientation = this.vertical()? "h" : "v";
19177 initialize: function(controller){
19183 constrained: false,
19188 //we all know why this is not zero,
19195 textAlign: 'center',
19196 textBaseline: 'top'
19205 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19206 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19207 this.layout.orientation = this.config.orientation;
19209 var canvasConfig = this.config;
19210 if (canvasConfig.useCanvas) {
19211 this.canvas = canvasConfig.useCanvas;
19212 this.config.labelContainer = this.canvas.id + '-label';
19214 if(canvasConfig.background) {
19215 canvasConfig.background = $.merge({
19217 }, canvasConfig.background);
19219 this.canvas = new Canvas(this, canvasConfig);
19220 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19223 this.graphOptions = {
19231 this.graph = new Graph(this.graphOptions, this.config.Node,
19233 this.labels = new TM.Label[canvasConfig.Label.type](this);
19234 this.fx = new TM.Plot(this);
19235 this.op = new TM.Op(this);
19236 this.group = new TM.Group(this);
19237 this.geom = new TM.Geom(this);
19238 this.clickedNode = null;
19240 // initialize extras
19241 this.initializeExtras();
19247 Computes positions and plots the tree.
19249 refresh: function(){
19250 if(this.busy) return;
19253 if(this.config.animate) {
19254 this.compute('end');
19255 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
19256 && this.clickedNode.id || this.root));
19257 this.fx.animate($.merge(this.config, {
19258 modes: ['linear', 'node-property:width:height'],
19259 onComplete: function() {
19264 var labelType = this.config.Label.type;
19265 if(labelType != 'Native') {
19267 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
19271 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
19272 && this.clickedNode.id || this.root));
19280 Plots the TreeMap. This is a shortcut to *fx.plot*.
19290 Returns whether the node is a leaf.
19294 n - (object) A <Graph.Node>.
19298 return n.getSubnodes([
19300 ], "ignore").length == 0;
19306 Sets the node as root.
19310 n - (object) A <Graph.Node>.
19313 enter: function(n){
19314 if(this.busy) return;
19318 config = this.config,
19319 graph = this.graph,
19321 previousClickedNode = this.clickedNode;
19324 onComplete: function() {
19325 //ensure that nodes are shown for that level
19326 if(config.levelsToShow > 0) {
19327 that.geom.setRightLevelToShow(n);
19329 //compute positions of newly inserted nodes
19330 if(config.levelsToShow > 0 || config.request) that.compute();
19331 if(config.animate) {
19333 graph.nodeList.setData('alpha', 0, 'end');
19334 n.eachSubgraph(function(n) {
19335 n.setData('alpha', 1, 'end');
19339 modes:['node-property:alpha'],
19340 onComplete: function() {
19341 //compute end positions
19342 that.clickedNode = clickedNode;
19343 that.compute('end');
19344 //animate positions
19345 //TODO(nico) commenting this line didn't seem to throw errors...
19346 that.clickedNode = previousClickedNode;
19348 modes:['linear', 'node-property:width:height'],
19350 onComplete: function() {
19352 //TODO(nico) check comment above
19353 that.clickedNode = clickedNode;
19360 that.clickedNode = n;
19365 if(config.request) {
19366 this.requestNodes(clickedNode, callback);
19368 callback.onComplete();
19375 Sets the parent node of the current selected node as root.
19379 if(this.busy) return;
19381 this.events.hoveredNode = false;
19383 config = this.config,
19384 graph = this.graph,
19385 parents = graph.getNode(this.clickedNode
19386 && this.clickedNode.id || this.root).getParents(),
19387 parent = parents[0],
19388 clickedNode = parent,
19389 previousClickedNode = this.clickedNode;
19391 //if no parents return
19396 //final plot callback
19398 onComplete: function() {
19399 that.clickedNode = parent;
19400 if(config.request) {
19401 that.requestNodes(parent, {
19402 onComplete: function() {
19416 if (config.levelsToShow > 0)
19417 this.geom.setRightLevelToShow(parent);
19418 //animate node positions
19419 if(config.animate) {
19420 this.clickedNode = clickedNode;
19421 this.compute('end');
19422 //animate the visible subtree only
19423 this.clickedNode = previousClickedNode;
19425 modes:['linear', 'node-property:width:height'],
19427 onComplete: function() {
19428 //animate the parent subtree
19429 that.clickedNode = clickedNode;
19430 //change nodes alpha
19431 graph.eachNode(function(n) {
19432 n.setDataset(['current', 'end'], {
19436 previousClickedNode.eachSubgraph(function(node) {
19437 node.setData('alpha', 1);
19441 modes:['node-property:alpha'],
19442 onComplete: function() {
19443 callback.onComplete();
19449 callback.onComplete();
19453 requestNodes: function(node, onComplete){
19454 var handler = $.merge(this.controller, onComplete),
19455 lev = this.config.levelsToShow;
19456 if (handler.request) {
19457 var leaves = [], d = node._depth;
19458 node.eachLevel(0, lev, function(n){
19459 var nodeLevel = lev - (n._depth - d);
19460 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19462 n._level = nodeLevel;
19465 this.group.requestNodes(leaves, handler);
19467 handler.onComplete();
19475 Custom extension of <Graph.Op>.
19479 All <Graph.Op> methods
19486 TM.Op = new Class({
19487 Implements: Graph.Op,
19489 initialize: function(viz){
19494 //extend level methods of Graph.Geom
19495 TM.Geom = new Class({
19496 Implements: Graph.Geom,
19498 getRightLevelToShow: function() {
19499 return this.viz.config.levelsToShow;
19502 setRightLevelToShow: function(node) {
19503 var level = this.getRightLevelToShow(),
19504 fx = this.viz.labels;
19505 node.eachLevel(0, level+1, function(n) {
19506 var d = n._depth - node._depth;
19511 fx.hideLabel(n, false);
19519 delete node.ignore;
19525 Performs operations on group of nodes.
19528 TM.Group = new Class( {
19530 initialize: function(viz){
19532 this.canvas = viz.canvas;
19533 this.config = viz.config;
19538 Calls the request method on the controller to request a subtree for each node.
19540 requestNodes: function(nodes, controller){
19541 var counter = 0, len = nodes.length, nodeSelected = {};
19542 var complete = function(){
19543 controller.onComplete();
19545 var viz = this.viz;
19548 for ( var i = 0; i < len; i++) {
19549 nodeSelected[nodes[i].id] = nodes[i];
19550 controller.request(nodes[i].id, nodes[i]._level, {
19551 onComplete: function(nodeId, data){
19552 if (data && data.children) {
19558 if (++counter == len) {
19559 viz.graph.computeLevels(viz.root, 0);
19571 Custom extension of <Graph.Plot>.
19575 All <Graph.Plot> methods
19582 TM.Plot = new Class({
19584 Implements: Graph.Plot,
19586 initialize: function(viz){
19588 this.config = viz.config;
19589 this.node = this.config.Node;
19590 this.edge = this.config.Edge;
19591 this.animation = new Animation;
19592 this.nodeTypes = new TM.Plot.NodeTypes;
19593 this.edgeTypes = new TM.Plot.EdgeTypes;
19594 this.labels = viz.labels;
19597 plot: function(opt, animating){
19598 var viz = this.viz,
19600 viz.canvas.clear();
19601 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19602 'withLabels': true,
19603 'hideLabels': false,
19604 'plotSubtree': function(n, ch){
19605 return n.anySubnode("exist");
19614 Custom extension of <Graph.Label>.
19615 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19619 All <Graph.Label> methods and subclasses.
19623 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19631 Custom extension of <Graph.Label.Native>.
19635 All <Graph.Label.Native> methods
19639 <Graph.Label.Native>
19641 TM.Label.Native = new Class({
19642 Implements: Graph.Label.Native,
19644 initialize: function(viz) {
19645 this.config = viz.config;
19646 this.leaf = viz.leaf;
19649 renderLabel: function(canvas, node, controller){
19650 if(!this.leaf(node) && !this.config.titleHeight) return;
19651 var pos = node.pos.getc(true),
19652 ctx = canvas.getCtx(),
19653 width = node.getData('width'),
19654 height = node.getData('height'),
19655 x = pos.x + width/2,
19658 ctx.fillText(node.name, x, y, width);
19665 Custom extension of <Graph.Label.SVG>.
19669 All <Graph.Label.SVG> methods
19675 TM.Label.SVG = new Class( {
19676 Implements: Graph.Label.SVG,
19678 initialize: function(viz){
19680 this.leaf = viz.leaf;
19681 this.config = viz.config;
19687 Overrides abstract method placeLabel in <Graph.Plot>.
19691 tag - A DOM label element.
19692 node - A <Graph.Node>.
19693 controller - A configuration/controller object passed to the visualization.
19696 placeLabel: function(tag, node, controller){
19697 var pos = node.pos.getc(true),
19698 canvas = this.viz.canvas,
19699 ox = canvas.translateOffsetX,
19700 oy = canvas.translateOffsetY,
19701 sx = canvas.scaleOffsetX,
19702 sy = canvas.scaleOffsetY,
19703 radius = canvas.getSize();
19705 x: Math.round(pos.x * sx + ox + radius.width / 2),
19706 y: Math.round(pos.y * sy + oy + radius.height / 2)
19708 tag.setAttribute('x', labelPos.x);
19709 tag.setAttribute('y', labelPos.y);
19711 if(!this.leaf(node) && !this.config.titleHeight) {
19712 tag.style.display = 'none';
19714 controller.onPlaceLabel(tag, node);
19721 Custom extension of <Graph.Label.HTML>.
19725 All <Graph.Label.HTML> methods.
19732 TM.Label.HTML = new Class( {
19733 Implements: Graph.Label.HTML,
19735 initialize: function(viz){
19737 this.leaf = viz.leaf;
19738 this.config = viz.config;
19744 Overrides abstract method placeLabel in <Graph.Plot>.
19748 tag - A DOM label element.
19749 node - A <Graph.Node>.
19750 controller - A configuration/controller object passed to the visualization.
19753 placeLabel: function(tag, node, controller){
19754 var pos = node.pos.getc(true),
19755 canvas = this.viz.canvas,
19756 ox = canvas.translateOffsetX,
19757 oy = canvas.translateOffsetY,
19758 sx = canvas.scaleOffsetX,
19759 sy = canvas.scaleOffsetY,
19760 radius = canvas.getSize();
19762 x: Math.round(pos.x * sx + ox + radius.width / 2),
19763 y: Math.round(pos.y * sy + oy + radius.height / 2)
19766 var style = tag.style;
19767 style.left = labelPos.x + 'px';
19768 style.top = labelPos.y + 'px';
19769 style.width = node.getData('width') * sx + 'px';
19770 style.height = node.getData('height') * sy + 'px';
19771 style.zIndex = node._depth * 100;
19772 style.display = '';
19774 if(!this.leaf(node) && !this.config.titleHeight) {
19775 tag.style.display = 'none';
19777 controller.onPlaceLabel(tag, node);
19782 Class: TM.Plot.NodeTypes
19784 This class contains a list of <Graph.Node> built-in types.
19785 Node types implemented are 'none', 'rectangle'.
19787 You can add your custom node types, customizing your visualization to the extreme.
19792 TM.Plot.NodeTypes.implement({
19794 'render': function(node, canvas) {
19795 //print your custom node to canvas
19798 'contains': function(node, pos) {
19799 //return true if pos is inside the node or false otherwise
19806 TM.Plot.NodeTypes = new Class( {
19812 'render': function(node, canvas, animating){
19813 var leaf = this.viz.leaf(node),
19814 config = this.config,
19815 offst = config.offset,
19816 titleHeight = config.titleHeight,
19817 pos = node.pos.getc(true),
19818 width = node.getData('width'),
19819 height = node.getData('height'),
19820 border = node.getData('border'),
19821 ctx = canvas.getCtx(),
19822 posx = pos.x + offst / 2,
19823 posy = pos.y + offst / 2;
19824 if(width <= offst || height <= offst) return;
19826 if(config.cushion) {
19827 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
19828 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19829 var color = node.getData('color');
19830 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
19831 function(r) { return r * 0.2 >> 0; }));
19832 lg.addColorStop(0, color);
19833 lg.addColorStop(1, colorGrad);
19834 ctx.fillStyle = lg;
19836 ctx.fillRect(posx, posy, width - offst, height - offst);
19839 ctx.strokeStyle = border;
19840 ctx.strokeRect(posx, posy, width - offst, height - offst);
19843 } else if(titleHeight > 0){
19844 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19845 titleHeight - offst);
19848 ctx.strokeStyle = border;
19849 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19855 'contains': function(node, pos) {
19856 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19857 var npos = node.pos.getc(true),
19858 width = node.getData('width'),
19859 leaf = this.viz.leaf(node),
19860 height = leaf? node.getData('height') : this.config.titleHeight;
19861 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19866 TM.Plot.EdgeTypes = new Class( {
19871 Class: TM.SliceAndDice
19873 A slice and dice TreeMap visualization.
19877 All <TM.Base> methods and properties.
19879 TM.SliceAndDice = new Class( {
19881 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19886 Class: TM.Squarified
19888 A squarified TreeMap visualization.
19892 All <TM.Base> methods and properties.
19894 TM.Squarified = new Class( {
19896 Loader, Extras, TM.Base, Layouts.TM.Squarified
19903 A strip TreeMap visualization.
19907 All <TM.Base> methods and properties.
19909 TM.Strip = new Class( {
19911 Loader, Extras, TM.Base, Layouts.TM.Strip
19924 A radial graph visualization with advanced animations.
19928 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>
19932 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.
19936 All <Loader> methods
19938 Constructor Options:
19940 Inherits options from
19943 - <Options.Controller>
19949 - <Options.NodeStyles>
19950 - <Options.Navigation>
19952 Additionally, there are other parameters and some default values changed
19954 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19955 levelDistance - (number) Default's *100*. The distance between levels of the tree.
19957 Instance Properties:
19959 canvas - Access a <Canvas> instance.
19960 graph - Access a <Graph> instance.
19961 op - Access a <RGraph.Op> instance.
19962 fx - Access a <RGraph.Plot> instance.
19963 labels - Access a <RGraph.Label> interface implementation.
19966 $jit.RGraph = new Class( {
19969 Loader, Extras, Layouts.Radial
19972 initialize: function(controller){
19973 var $RGraph = $jit.RGraph;
19976 interpolation: 'linear',
19980 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19981 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19983 var canvasConfig = this.config;
19984 if(canvasConfig.useCanvas) {
19985 this.canvas = canvasConfig.useCanvas;
19986 this.config.labelContainer = this.canvas.id + '-label';
19988 if(canvasConfig.background) {
19989 canvasConfig.background = $.merge({
19991 }, canvasConfig.background);
19993 this.canvas = new Canvas(this, canvasConfig);
19994 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19997 this.graphOptions = {
20005 this.graph = new Graph(this.graphOptions, this.config.Node,
20007 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
20008 this.fx = new $RGraph.Plot(this, $RGraph);
20009 this.op = new $RGraph.Op(this);
20013 this.parent = false;
20014 // initialize extras
20015 this.initializeExtras();
20020 createLevelDistanceFunc
20022 Returns the levelDistance function used for calculating a node distance
20023 to its origin. This function returns a function that is computed
20024 per level and not per node, such that all nodes with the same depth will have the
20025 same distance to the origin. The resulting function gets the
20026 parent node as parameter and returns a float.
20029 createLevelDistanceFunc: function(){
20030 var ld = this.config.levelDistance;
20031 return function(elem){
20032 return (elem._depth + 1) * ld;
20039 Computes positions and plots the tree.
20042 refresh: function(){
20047 reposition: function(){
20048 this.compute('end');
20054 Plots the RGraph. This is a shortcut to *fx.plot*.
20060 getNodeAndParentAngle
20062 Returns the _parent_ of the given node, also calculating its angle span.
20064 getNodeAndParentAngle: function(id){
20066 var n = this.graph.getNode(id);
20067 var ps = n.getParents();
20068 var p = (ps.length > 0)? ps[0] : false;
20070 var posParent = p.pos.getc(), posChild = n.pos.getc();
20071 var newPos = posParent.add(posChild.scale(-1));
20072 theta = Math.atan2(newPos.y, newPos.x);
20074 theta += 2 * Math.PI;
20084 Enumerates the children in order to maintain child ordering (second constraint of the paper).
20086 tagChildren: function(par, id){
20087 if (par.angleSpan) {
20089 par.eachAdjacency(function(elem){
20090 adjs.push(elem.nodeTo);
20092 var len = adjs.length;
20093 for ( var i = 0; i < len && id != adjs[i].id; i++)
20095 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
20096 adjs[j].dist = k++;
20103 Animates the <RGraph> to center the node specified by *id*.
20107 id - A <Graph.Node> id.
20108 opt - (optional|object) An object containing some extra properties described below
20109 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20114 rgraph.onClick('someid');
20116 rgraph.onClick('someid', {
20122 onClick: function(id, opt){
20123 if (this.root != id && !this.busy) {
20127 this.controller.onBeforeCompute(this.graph.getNode(id));
20128 var obj = this.getNodeAndParentAngle(id);
20130 // second constraint
20131 this.tagChildren(obj.parent, id);
20132 this.parent = obj.parent;
20133 this.compute('end');
20135 // first constraint
20136 var thetaDiff = obj.theta - obj.parent.endPos.theta;
20137 this.graph.eachNode(function(elem){
20138 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
20141 var mode = this.config.interpolation;
20143 onComplete: $.empty
20146 this.fx.animate($.merge( {
20152 onComplete: function(){
20161 $jit.RGraph.$extend = true;
20168 Custom extension of <Graph.Op>.
20172 All <Graph.Op> methods
20179 RGraph.Op = new Class( {
20181 Implements: Graph.Op
20188 Custom extension of <Graph.Plot>.
20192 All <Graph.Plot> methods
20199 RGraph.Plot = new Class( {
20201 Implements: Graph.Plot
20206 Object: RGraph.Label
20208 Custom extension of <Graph.Label>.
20209 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20213 All <Graph.Label> methods and subclasses.
20217 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20223 RGraph.Label.Native
20225 Custom extension of <Graph.Label.Native>.
20229 All <Graph.Label.Native> methods
20233 <Graph.Label.Native>
20236 RGraph.Label.Native = new Class( {
20237 Implements: Graph.Label.Native
20243 Custom extension of <Graph.Label.SVG>.
20247 All <Graph.Label.SVG> methods
20254 RGraph.Label.SVG = new Class( {
20255 Implements: Graph.Label.SVG,
20257 initialize: function(viz){
20264 Overrides abstract method placeLabel in <Graph.Plot>.
20268 tag - A DOM label element.
20269 node - A <Graph.Node>.
20270 controller - A configuration/controller object passed to the visualization.
20273 placeLabel: function(tag, node, controller){
20274 var pos = node.pos.getc(true),
20275 canvas = this.viz.canvas,
20276 ox = canvas.translateOffsetX,
20277 oy = canvas.translateOffsetY,
20278 sx = canvas.scaleOffsetX,
20279 sy = canvas.scaleOffsetY,
20280 radius = canvas.getSize();
20282 x: Math.round(pos.x * sx + ox + radius.width / 2),
20283 y: Math.round(pos.y * sy + oy + radius.height / 2)
20285 tag.setAttribute('x', labelPos.x);
20286 tag.setAttribute('y', labelPos.y);
20288 controller.onPlaceLabel(tag, node);
20295 Custom extension of <Graph.Label.HTML>.
20299 All <Graph.Label.HTML> methods.
20306 RGraph.Label.HTML = new Class( {
20307 Implements: Graph.Label.HTML,
20309 initialize: function(viz){
20315 Overrides abstract method placeLabel in <Graph.Plot>.
20319 tag - A DOM label element.
20320 node - A <Graph.Node>.
20321 controller - A configuration/controller object passed to the visualization.
20324 placeLabel: function(tag, node, controller){
20325 var pos = node.pos.getc(true),
20326 canvas = this.viz.canvas,
20327 ox = canvas.translateOffsetX,
20328 oy = canvas.translateOffsetY,
20329 sx = canvas.scaleOffsetX,
20330 sy = canvas.scaleOffsetY,
20331 radius = canvas.getSize();
20333 x: Math.round(pos.x * sx + ox + radius.width / 2),
20334 y: Math.round(pos.y * sy + oy + radius.height / 2)
20337 var style = tag.style;
20338 style.left = labelPos.x + 'px';
20339 style.top = labelPos.y + 'px';
20340 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20342 controller.onPlaceLabel(tag, node);
20347 Class: RGraph.Plot.NodeTypes
20349 This class contains a list of <Graph.Node> built-in types.
20350 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20352 You can add your custom node types, customizing your visualization to the extreme.
20357 RGraph.Plot.NodeTypes.implement({
20359 'render': function(node, canvas) {
20360 //print your custom node to canvas
20363 'contains': function(node, pos) {
20364 //return true if pos is inside the node or false otherwise
20371 RGraph.Plot.NodeTypes = new Class({
20374 'contains': $.lambda(false)
20377 'render': function(node, canvas){
20378 var pos = node.pos.getc(true),
20379 dim = node.getData('dim');
20380 this.nodeHelper.circle.render('fill', pos, dim, canvas);
20382 'contains': function(node, pos){
20383 var npos = node.pos.getc(true),
20384 dim = node.getData('dim');
20385 return this.nodeHelper.circle.contains(npos, pos, dim);
20389 'render': function(node, canvas){
20390 var pos = node.pos.getc(true),
20391 width = node.getData('width'),
20392 height = node.getData('height');
20393 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20395 // TODO(nico): be more precise...
20396 'contains': function(node, pos){
20397 var npos = node.pos.getc(true),
20398 width = node.getData('width'),
20399 height = node.getData('height');
20400 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20404 'render': function(node, canvas){
20405 var pos = node.pos.getc(true),
20406 dim = node.getData('dim');
20407 this.nodeHelper.square.render('fill', pos, dim, canvas);
20409 'contains': function(node, pos){
20410 var npos = node.pos.getc(true),
20411 dim = node.getData('dim');
20412 return this.nodeHelper.square.contains(npos, pos, dim);
20416 'render': function(node, canvas){
20417 var pos = node.pos.getc(true),
20418 width = node.getData('width'),
20419 height = node.getData('height');
20420 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20422 'contains': function(node, pos){
20423 var npos = node.pos.getc(true),
20424 width = node.getData('width'),
20425 height = node.getData('height');
20426 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20430 'render': function(node, canvas){
20431 var pos = node.pos.getc(true),
20432 dim = node.getData('dim');
20433 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20435 'contains': function(node, pos) {
20436 var npos = node.pos.getc(true),
20437 dim = node.getData('dim');
20438 return this.nodeHelper.triangle.contains(npos, pos, dim);
20442 'render': function(node, canvas){
20443 var pos = node.pos.getc(true),
20444 dim = node.getData('dim');
20445 this.nodeHelper.star.render('fill', pos, dim, canvas);
20447 'contains': function(node, pos) {
20448 var npos = node.pos.getc(true),
20449 dim = node.getData('dim');
20450 return this.nodeHelper.star.contains(npos, pos, dim);
20456 Class: RGraph.Plot.EdgeTypes
20458 This class contains a list of <Graph.Adjacence> built-in types.
20459 Edge types implemented are 'none', 'line' and 'arrow'.
20461 You can add your custom edge types, customizing your visualization to the extreme.
20466 RGraph.Plot.EdgeTypes.implement({
20468 'render': function(adj, canvas) {
20469 //print your custom edge to canvas
20472 'contains': function(adj, pos) {
20473 //return true if pos is inside the arc or false otherwise
20480 RGraph.Plot.EdgeTypes = new Class({
20483 'render': function(adj, canvas) {
20484 var from = adj.nodeFrom.pos.getc(true),
20485 to = adj.nodeTo.pos.getc(true);
20486 this.edgeHelper.line.render(from, to, canvas);
20488 'contains': function(adj, pos) {
20489 var from = adj.nodeFrom.pos.getc(true),
20490 to = adj.nodeTo.pos.getc(true);
20491 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20495 'render': function(adj, canvas) {
20496 var from = adj.nodeFrom.pos.getc(true),
20497 to = adj.nodeTo.pos.getc(true),
20498 dim = adj.getData('dim'),
20499 direction = adj.data.$direction,
20500 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20501 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20503 'contains': function(adj, pos) {
20504 var from = adj.nodeFrom.pos.getc(true),
20505 to = adj.nodeTo.pos.getc(true);
20506 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20515 * File: Hypertree.js
20522 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
20526 moebiusTransformation
20528 Calculates a moebius transformation for this point / complex.
20529 For more information go to:
20530 http://en.wikipedia.org/wiki/Moebius_transformation.
20534 c - An initialized Complex instance representing a translation Vector.
20537 Complex.prototype.moebiusTransformation = function(c) {
20538 var num = this.add(c);
20539 var den = c.$conjugate().$prod(this);
20541 return num.$div(den);
20545 moebiusTransformation
20547 Calculates a moebius transformation for the hyperbolic tree.
20549 <http://en.wikipedia.org/wiki/Moebius_transformation>
20553 graph - A <Graph> instance.
20555 prop - A property array.
20556 theta - Rotation angle.
20557 startPos - _optional_ start position.
20559 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20560 this.eachNode(graph, function(elem) {
20561 for ( var i = 0; i < prop.length; i++) {
20562 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20563 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20571 A Hyperbolic Tree/Graph visualization.
20575 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
20576 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20580 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.
20584 All <Loader> methods
20586 Constructor Options:
20588 Inherits options from
20591 - <Options.Controller>
20597 - <Options.NodeStyles>
20598 - <Options.Navigation>
20600 Additionally, there are other parameters and some default values changed
20602 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*.
20603 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.
20604 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20605 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20606 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
20608 Instance Properties:
20610 canvas - Access a <Canvas> instance.
20611 graph - Access a <Graph> instance.
20612 op - Access a <Hypertree.Op> instance.
20613 fx - Access a <Hypertree.Plot> instance.
20614 labels - Access a <Hypertree.Label> interface implementation.
20618 $jit.Hypertree = new Class( {
20620 Implements: [ Loader, Extras, Layouts.Radial ],
20622 initialize: function(controller) {
20623 var $Hypertree = $jit.Hypertree;
20634 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20635 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20637 var canvasConfig = this.config;
20638 if(canvasConfig.useCanvas) {
20639 this.canvas = canvasConfig.useCanvas;
20640 this.config.labelContainer = this.canvas.id + '-label';
20642 if(canvasConfig.background) {
20643 canvasConfig.background = $.merge({
20645 }, canvasConfig.background);
20647 this.canvas = new Canvas(this, canvasConfig);
20648 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20651 this.graphOptions = {
20659 this.graph = new Graph(this.graphOptions, this.config.Node,
20661 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20662 this.fx = new $Hypertree.Plot(this, $Hypertree);
20663 this.op = new $Hypertree.Op(this);
20667 // initialize extras
20668 this.initializeExtras();
20673 createLevelDistanceFunc
20675 Returns the levelDistance function used for calculating a node distance
20676 to its origin. This function returns a function that is computed
20677 per level and not per node, such that all nodes with the same depth will have the
20678 same distance to the origin. The resulting function gets the
20679 parent node as parameter and returns a float.
20682 createLevelDistanceFunc: function() {
20683 // get max viz. length.
20684 var r = this.getRadius();
20686 var depth = 0, max = Math.max, config = this.config;
20687 this.graph.eachNode(function(node) {
20688 depth = max(node._depth, depth);
20691 // node distance generator
20692 var genDistFunc = function(a) {
20693 return function(node) {
20695 var d = node._depth + 1;
20696 var acum = 0, pow = Math.pow;
20698 acum += pow(a, d--);
20700 return acum - config.offset;
20703 // estimate better edge length.
20704 for ( var i = 0.51; i <= 1; i += 0.01) {
20705 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20706 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20708 return genDistFunc(0.75);
20714 Returns the current radius of the visualization. If *config.radius* is *auto* then it
20715 calculates the radius by taking the smaller size of the <Canvas> widget.
20722 getRadius: function() {
20723 var rad = this.config.radius;
20724 if (rad !== "auto") { return rad; }
20725 var s = this.canvas.getSize();
20726 return Math.min(s.width, s.height) / 2;
20732 Computes positions and plots the tree.
20736 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20739 refresh: function(reposition) {
20742 this.graph.eachNode(function(node) {
20743 node.startPos.rho = node.pos.rho = node.endPos.rho;
20744 node.startPos.theta = node.pos.theta = node.endPos.theta;
20755 Computes nodes' positions and restores the tree to its previous position.
20757 For calculating nodes' positions the root must be placed on its origin. This method does this
20758 and then attemps to restore the hypertree to its previous position.
20761 reposition: function() {
20762 this.compute('end');
20763 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20764 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20766 this.graph.eachNode(function(node) {
20768 node.endPos.rho = node.pos.rho;
20769 node.endPos.theta = node.pos.theta;
20777 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
20787 Animates the <Hypertree> to center the node specified by *id*.
20791 id - A <Graph.Node> id.
20792 opt - (optional|object) An object containing some extra properties described below
20793 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20798 ht.onClick('someid');
20800 ht.onClick('someid', {
20806 onClick: function(id, opt) {
20807 var pos = this.graph.getNode(id).pos.getc(true);
20808 this.move(pos, opt);
20814 Translates the tree to the given position.
20818 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20819 opt - This object has been defined in <Hypertree.onClick>
20824 ht.move({ x: 0, y: 0.7 }, {
20830 move: function(pos, opt) {
20831 var versor = $C(pos.x, pos.y);
20832 if (this.busy === false && versor.norm() < 1) {
20834 var root = this.graph.getClosestNodeToPos(versor), that = this;
20835 this.graph.computeLevels(root.id, 0);
20836 this.controller.onBeforeCompute(root);
20838 onComplete: $.empty
20840 this.fx.animate($.merge( {
20841 modes: [ 'moebius' ],
20844 onComplete: function() {
20853 $jit.Hypertree.$extend = true;
20855 (function(Hypertree) {
20858 Class: Hypertree.Op
20860 Custom extension of <Graph.Op>.
20864 All <Graph.Op> methods
20871 Hypertree.Op = new Class( {
20873 Implements: Graph.Op
20878 Class: Hypertree.Plot
20880 Custom extension of <Graph.Plot>.
20884 All <Graph.Plot> methods
20891 Hypertree.Plot = new Class( {
20893 Implements: Graph.Plot
20898 Object: Hypertree.Label
20900 Custom extension of <Graph.Label>.
20901 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20905 All <Graph.Label> methods and subclasses.
20909 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20912 Hypertree.Label = {};
20915 Hypertree.Label.Native
20917 Custom extension of <Graph.Label.Native>.
20921 All <Graph.Label.Native> methods
20925 <Graph.Label.Native>
20928 Hypertree.Label.Native = new Class( {
20929 Implements: Graph.Label.Native,
20931 initialize: function(viz) {
20935 renderLabel: function(canvas, node, controller) {
20936 var ctx = canvas.getCtx();
20937 var coord = node.pos.getc(true);
20938 var s = this.viz.getRadius();
20939 ctx.fillText(node.name, coord.x * s, coord.y * s);
20944 Hypertree.Label.SVG
20946 Custom extension of <Graph.Label.SVG>.
20950 All <Graph.Label.SVG> methods
20957 Hypertree.Label.SVG = new Class( {
20958 Implements: Graph.Label.SVG,
20960 initialize: function(viz) {
20967 Overrides abstract method placeLabel in <Graph.Plot>.
20971 tag - A DOM label element.
20972 node - A <Graph.Node>.
20973 controller - A configuration/controller object passed to the visualization.
20976 placeLabel: function(tag, node, controller) {
20977 var pos = node.pos.getc(true),
20978 canvas = this.viz.canvas,
20979 ox = canvas.translateOffsetX,
20980 oy = canvas.translateOffsetY,
20981 sx = canvas.scaleOffsetX,
20982 sy = canvas.scaleOffsetY,
20983 radius = canvas.getSize(),
20984 r = this.viz.getRadius();
20986 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20987 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20989 tag.setAttribute('x', labelPos.x);
20990 tag.setAttribute('y', labelPos.y);
20991 controller.onPlaceLabel(tag, node);
20996 Hypertree.Label.HTML
20998 Custom extension of <Graph.Label.HTML>.
21002 All <Graph.Label.HTML> methods.
21009 Hypertree.Label.HTML = new Class( {
21010 Implements: Graph.Label.HTML,
21012 initialize: function(viz) {
21018 Overrides abstract method placeLabel in <Graph.Plot>.
21022 tag - A DOM label element.
21023 node - A <Graph.Node>.
21024 controller - A configuration/controller object passed to the visualization.
21027 placeLabel: function(tag, node, controller) {
21028 var pos = node.pos.getc(true),
21029 canvas = this.viz.canvas,
21030 ox = canvas.translateOffsetX,
21031 oy = canvas.translateOffsetY,
21032 sx = canvas.scaleOffsetX,
21033 sy = canvas.scaleOffsetY,
21034 radius = canvas.getSize(),
21035 r = this.viz.getRadius();
21037 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
21038 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
21040 var style = tag.style;
21041 style.left = labelPos.x + 'px';
21042 style.top = labelPos.y + 'px';
21043 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
21045 controller.onPlaceLabel(tag, node);
21050 Class: Hypertree.Plot.NodeTypes
21052 This class contains a list of <Graph.Node> built-in types.
21053 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
21055 You can add your custom node types, customizing your visualization to the extreme.
21060 Hypertree.Plot.NodeTypes.implement({
21062 'render': function(node, canvas) {
21063 //print your custom node to canvas
21066 'contains': function(node, pos) {
21067 //return true if pos is inside the node or false otherwise
21074 Hypertree.Plot.NodeTypes = new Class({
21077 'contains': $.lambda(false)
21080 'render': function(node, canvas) {
21081 var nconfig = this.node,
21082 dim = node.getData('dim'),
21083 p = node.pos.getc();
21084 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21085 p.$scale(node.scale);
21087 this.nodeHelper.circle.render('fill', p, dim, canvas);
21090 'contains': function(node, pos) {
21091 var dim = node.getData('dim'),
21092 npos = node.pos.getc().$scale(node.scale);
21093 return this.nodeHelper.circle.contains(npos, pos, dim);
21097 'render': function(node, canvas) {
21098 var pos = node.pos.getc().$scale(node.scale),
21099 width = node.getData('width'),
21100 height = node.getData('height');
21101 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
21103 'contains': function(node, pos) {
21104 var width = node.getData('width'),
21105 height = node.getData('height'),
21106 npos = node.pos.getc().$scale(node.scale);
21107 return this.nodeHelper.circle.contains(npos, pos, width, height);
21111 'render': function(node, canvas) {
21112 var nconfig = this.node,
21113 dim = node.getData('dim'),
21114 p = node.pos.getc();
21115 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21116 p.$scale(node.scale);
21118 this.nodeHelper.square.render('fill', p, dim, canvas);
21121 'contains': function(node, pos) {
21122 var dim = node.getData('dim'),
21123 npos = node.pos.getc().$scale(node.scale);
21124 return this.nodeHelper.square.contains(npos, pos, dim);
21128 'render': function(node, canvas) {
21129 var nconfig = this.node,
21130 width = node.getData('width'),
21131 height = node.getData('height'),
21132 pos = node.pos.getc();
21133 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
21134 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
21135 pos.$scale(node.scale);
21136 if (width > 0.2 && height > 0.2) {
21137 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
21140 'contains': function(node, pos) {
21141 var width = node.getData('width'),
21142 height = node.getData('height'),
21143 npos = node.pos.getc().$scale(node.scale);
21144 return this.nodeHelper.square.contains(npos, pos, width, height);
21148 'render': function(node, canvas) {
21149 var nconfig = this.node,
21150 dim = node.getData('dim'),
21151 p = node.pos.getc();
21152 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21153 p.$scale(node.scale);
21155 this.nodeHelper.triangle.render('fill', p, dim, canvas);
21158 'contains': function(node, pos) {
21159 var dim = node.getData('dim'),
21160 npos = node.pos.getc().$scale(node.scale);
21161 return this.nodeHelper.triangle.contains(npos, pos, dim);
21165 'render': function(node, canvas) {
21166 var nconfig = this.node,
21167 dim = node.getData('dim'),
21168 p = node.pos.getc();
21169 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21170 p.$scale(node.scale);
21172 this.nodeHelper.star.render('fill', p, dim, canvas);
21175 'contains': function(node, pos) {
21176 var dim = node.getData('dim'),
21177 npos = node.pos.getc().$scale(node.scale);
21178 return this.nodeHelper.star.contains(npos, pos, dim);
21184 Class: Hypertree.Plot.EdgeTypes
21186 This class contains a list of <Graph.Adjacence> built-in types.
21187 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
21189 You can add your custom edge types, customizing your visualization to the extreme.
21194 Hypertree.Plot.EdgeTypes.implement({
21196 'render': function(adj, canvas) {
21197 //print your custom edge to canvas
21200 'contains': function(adj, pos) {
21201 //return true if pos is inside the arc or false otherwise
21208 Hypertree.Plot.EdgeTypes = new Class({
21211 'render': function(adj, canvas) {
21212 var from = adj.nodeFrom.pos.getc(true),
21213 to = adj.nodeTo.pos.getc(true),
21214 r = adj.nodeFrom.scale;
21215 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
21217 'contains': function(adj, pos) {
21218 var from = adj.nodeFrom.pos.getc(true),
21219 to = adj.nodeTo.pos.getc(true),
21220 r = adj.nodeFrom.scale;
21221 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21225 'render': function(adj, canvas) {
21226 var from = adj.nodeFrom.pos.getc(true),
21227 to = adj.nodeTo.pos.getc(true),
21228 r = adj.nodeFrom.scale,
21229 dim = adj.getData('dim'),
21230 direction = adj.data.$direction,
21231 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
21232 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
21234 'contains': function(adj, pos) {
21235 var from = adj.nodeFrom.pos.getc(true),
21236 to = adj.nodeTo.pos.getc(true),
21237 r = adj.nodeFrom.scale;
21238 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21242 'render': function(adj, canvas) {
21243 var from = adj.nodeFrom.pos.getc(),
21244 to = adj.nodeTo.pos.getc(),
21245 dim = this.viz.getRadius();
21246 this.edgeHelper.hyperline.render(from, to, dim, canvas);
21248 'contains': $.lambda(false)
21252 })($jit.Hypertree);