2 Copyright (c) 2011 Sencha Inc. - Author: Nicolas Garcia Belmonte (http://philogb.github.com/)
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33 Defines the namespace for all library Classes and Objects.
34 This variable is the *only* global variable defined in the Toolkit.
35 There are also other interesting properties attached to this variable described below.
37 window.$jit = function(w) {
46 $jit.version = '2.0.1';
50 Works just like *document.getElementById*
54 var element = $jit.id('elementId');
62 Contains utility functions.
64 Some of the utility functions and the Class system were based in the MooTools Framework
65 <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>.
66 MIT license <http://mootools.net/license.txt>.
68 These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
69 I'd suggest you to use the functions from those libraries instead of using these, since their functions
70 are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
74 return document.getElementById(d);
77 $.empty = function() {
83 Augment an object by appending another object's properties.
87 original - (object) The object to be extended.
88 extended - (object) An object which properties are going to be appended to the original object.
92 $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
95 $.extend = function(original, extended) {
96 for ( var key in (extended || {}))
97 original[key] = extended[key];
101 $.lambda = function(value) {
102 return (typeof value == 'function') ? value : function() {
107 $.time = Date.now || function() {
114 Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
118 obj - (mixed) The object to be wrapped in an array.
122 $jit.util.splat(3); //[3]
123 $jit.util.splat([3]); //[3]
126 $.splat = function(obj) {
127 var type = $.type(obj);
128 return type ? ((type != 'array') ? [ obj ] : obj) : [];
131 $.type = function(elem) {
132 var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
133 if(type != 'object') return type;
134 if(elem && elem.$$family) return elem.$$family;
135 return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
137 $.type.s = Object.prototype.toString;
142 Iterates through an iterable applying *f*.
146 iterable - (array) The original array.
147 fn - (function) The function to apply to the array elements.
151 $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
154 $.each = function(iterable, fn) {
155 var type = $.type(iterable);
156 if (type == 'object') {
157 for ( var key in iterable)
158 fn(iterable[key], key);
160 for ( var i = 0, l = iterable.length; i < l; i++)
165 $.indexOf = function(array, item) {
166 if(Array.indexOf) return array.indexOf(item);
167 for(var i=0,l=array.length; i<l; i++) {
168 if(array[i] === item) return i;
176 Maps or collects an array by applying *f*.
180 array - (array) The original array.
181 f - (function) The function to apply to the array elements.
185 $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
188 $.map = function(array, f) {
190 $.each(array, function(elem, i) {
191 ans.push(f(elem, i));
199 Iteratively applies the binary function *f* storing the result in an accumulator.
203 array - (array) The original array.
204 f - (function) The function to apply to the array elements.
205 opt - (optional|mixed) The starting value for the acumulator.
209 $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
212 $.reduce = function(array, f, opt) {
213 var l = array.length;
215 var acum = arguments.length == 3? opt : array[--l];
217 acum = f(acum, array[l]);
225 Merges n-objects and their sub-objects creating a new, fresh object.
229 An arbitrary number of objects.
233 $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
236 $.merge = function() {
238 for ( var i = 0, l = arguments.length; i < l; i++) {
239 var object = arguments[i];
240 if ($.type(object) != 'object')
242 for ( var key in object) {
243 var op = object[key], mp = mix[key];
244 mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
245 .merge(mp, op) : $.unlink(op);
251 $.unlink = function(object) {
253 switch ($.type(object)) {
256 for ( var p in object)
257 unlinked[p] = $.unlink(object[p]);
261 for ( var i = 0, l = object.length; i < l; i++)
262 unlinked[i] = $.unlink(object[i]);
271 if(arguments.length === 0) return [];
272 for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
273 for(var i=0, row=[]; i<l; i++) {
274 row.push(arguments[i][j]);
284 Converts an RGB array into a Hex string.
288 srcArray - (array) An array with R, G and B values
292 $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
295 $.rgbToHex = function(srcArray, array) {
296 if (srcArray.length < 3)
298 if (srcArray.length == 4 && srcArray[3] == 0 && !array)
299 return 'transparent';
301 for ( var i = 0; i < 3; i++) {
302 var bit = (srcArray[i] - 0).toString(16);
303 hex.push(bit.length == 1 ? '0' + bit : bit);
305 return array ? hex : '#' + hex.join('');
311 Converts an Hex color string into an RGB array.
315 hex - (string) A color hex string.
319 $jit.util.hexToRgb('#fff'); //[255, 255, 255]
322 $.hexToRgb = function(hex) {
323 if (hex.length != 7) {
324 hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
329 for ( var i = 0; i < 3; i++) {
331 if (value.length == 1)
333 rgb.push(parseInt(value, 16));
337 hex = parseInt(hex.slice(1), 16);
338 return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
342 $.destroy = function(elem) {
345 elem.parentNode.removeChild(elem);
346 if (elem.clearAttributes)
347 elem.clearAttributes();
350 $.clean = function(elem) {
351 for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
359 Cross-browser add event listener.
363 obj - (obj) The Element to attach the listener to.
364 type - (string) The listener type. For example 'click', or 'mousemove'.
365 fn - (function) The callback function to be used when the event is fired.
369 $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
372 $.addEvent = function(obj, type, fn) {
373 if (obj.addEventListener)
374 obj.addEventListener(type, fn, false);
376 obj.attachEvent('on' + type, fn);
379 $.addEvents = function(obj, typeObj) {
380 for(var type in typeObj) {
381 $.addEvent(obj, type, typeObj[type]);
385 $.hasClass = function(obj, klass) {
386 return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
389 $.addClass = function(obj, klass) {
390 if (!$.hasClass(obj, klass))
391 obj.className = (obj.className + " " + klass);
394 $.removeClass = function(obj, klass) {
395 obj.className = obj.className.replace(new RegExp(
396 '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
399 $.getPos = function(elem) {
400 var offset = getOffsets(elem);
401 var scroll = getScrolls(elem);
403 x: offset.x - scroll.x,
404 y: offset.y - scroll.y
407 function getOffsets(elem) {
412 while (elem && !isBody(elem)) {
413 position.x += elem.offsetLeft;
414 position.y += elem.offsetTop;
415 elem = elem.offsetParent;
420 function getScrolls(elem) {
425 while (elem && !isBody(elem)) {
426 position.x += elem.scrollLeft;
427 position.y += elem.scrollTop;
428 elem = elem.parentNode;
433 function isBody(element) {
434 return (/^(?:body|html)$/i).test(element.tagName);
439 get: function(e, win) {
441 return e || win.event;
443 getWheel: function(e) {
444 return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
446 isRightClick: function(e) {
447 return (e.which == 3 || e.button == 2);
449 getPos: function(e, win) {
450 // get mouse position
453 var doc = win.document;
454 doc = doc.documentElement || doc.body;
455 //TODO(nico): make touch event handling better
456 if(e.touches && e.touches.length) {
460 x: e.pageX || (e.clientX + doc.scrollLeft),
461 y: e.pageY || (e.clientY + doc.scrollTop)
466 if (e.stopPropagation) e.stopPropagation();
467 e.cancelBubble = true;
468 if (e.preventDefault) e.preventDefault();
469 else e.returnValue = false;
473 $jit.util = $jit.id = $;
475 var Class = function(properties) {
476 properties = properties || {};
477 var klass = function() {
478 for ( var key in this) {
479 if (typeof this[key] != 'function')
480 this[key] = $.unlink(this[key]);
482 this.constructor = klass;
483 if (Class.prototyping)
485 var instance = this.initialize ? this.initialize.apply(this, arguments)
488 this.$$family = 'class';
492 for ( var mutator in Class.Mutators) {
493 if (!properties[mutator])
495 properties = Class.Mutators[mutator](properties, properties[mutator]);
496 delete properties[mutator];
499 $.extend(klass, this);
500 klass.constructor = Class;
501 klass.prototype = properties;
507 Implements: function(self, klasses) {
508 $.each($.splat(klasses), function(klass) {
509 Class.prototyping = klass;
510 var instance = (typeof klass == 'function') ? new klass : klass;
511 for ( var prop in instance) {
512 if (!(prop in self)) {
513 self[prop] = instance[prop];
516 delete Class.prototyping;
525 inherit: function(object, properties) {
526 for ( var key in properties) {
527 var override = properties[key];
528 var previous = object[key];
529 var type = $.type(override);
530 if (previous && type == 'function') {
531 if (override != previous) {
532 Class.override(object, key, override);
534 } else if (type == 'object') {
535 object[key] = $.merge(previous, override);
537 object[key] = override;
543 override: function(object, name, method) {
544 var parent = Class.prototyping;
545 if (parent && object[name] != parent[name])
547 var override = function() {
548 var previous = this.parent;
549 this.parent = parent ? parent[name] : object[name];
550 var value = method.apply(this, arguments);
551 this.parent = previous;
554 object[name] = override;
559 Class.prototype.implement = function() {
560 var proto = this.prototype;
561 $.each(Array.prototype.slice.call(arguments || []), function(properties) {
562 Class.inherit(proto, properties);
572 Provides JSON utility functions.
574 Most of these functions are JSON-tree traversal and manipulation functions.
580 Clears all tree nodes having depth greater than maxLevel.
584 tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
585 maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
588 prune: function(tree, maxLevel) {
589 this.each(tree, function(elem, i) {
590 if (i == maxLevel && elem.children) {
591 delete elem.children;
599 Returns the parent node of the node having _id_ as id.
603 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
604 id - (string) The _id_ of the child node whose parent will be returned.
608 A tree JSON node if any, or false otherwise.
611 getParent: function(tree, id) {
614 var ch = tree.children;
615 if (ch && ch.length > 0) {
616 for ( var i = 0; i < ch.length; i++) {
620 var ans = this.getParent(ch[i], id);
631 Returns the subtree that matches the given id.
635 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
636 id - (string) A node *unique* identifier.
640 A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
643 getSubtree: function(tree, id) {
646 for ( var i = 0, ch = tree.children; ch && i < ch.length; i++) {
647 var t = this.getSubtree(ch[i], id);
656 Iterates on tree nodes with relative depth less or equal than a specified level.
660 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
661 initLevel - (number) An integer specifying the initial relative level. Usually zero.
662 toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
663 action - (function) A function that receives a node and an integer specifying the actual level of the node.
667 $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
668 alert(node.name + ' ' + depth);
672 eachLevel: function(tree, initLevel, toLevel, action) {
673 if (initLevel <= toLevel) {
674 action(tree, initLevel);
675 if(!tree.children) return;
676 for ( var i = 0, ch = tree.children; i < ch.length; i++) {
677 this.eachLevel(ch[i], initLevel + 1, toLevel, action);
684 A JSON tree iterator.
688 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
689 action - (function) A function that receives a node.
693 $jit.json.each(tree, function(node) {
699 each: function(tree, action) {
700 this.eachLevel(tree, 0, Number.MAX_VALUE, action);
706 An object containing multiple type of transformations.
717 var Trans = $jit.Trans;
721 var makeTrans = function(transition, params){
722 params = $.splat(params);
723 return $.extend(transition, {
724 easeIn: function(pos){
725 return transition(pos, params);
727 easeOut: function(pos){
728 return 1 - transition(1 - pos, params);
730 easeInOut: function(pos){
731 return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
732 2 * (1 - pos), params)) / 2;
740 return Math.pow(p, x[0] || 6);
744 return Math.pow(2, 8 * (p - 1));
748 return 1 - Math.sin(Math.acos(p));
752 return 1 - Math.sin((1 - p) * Math.PI / 2);
755 Back: function(p, x){
757 return Math.pow(p, 2) * ((x + 1) * p - x);
762 for ( var a = 0, b = 1; 1; a += b, b /= 2) {
763 if (p >= (7 - 4 * a) / 11) {
764 value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
771 Elastic: function(p, x){
772 return Math.pow(2, 10 * --p)
773 * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
778 $.each(transitions, function(val, key){
779 Trans[key] = makeTrans(val);
783 'Quad', 'Cubic', 'Quart', 'Quint'
784 ], function(elem, i){
785 Trans[elem] = makeTrans(function(p){
795 A Class that can perform animations for generic objects.
797 If you are looking for animation transitions please take a look at the <Trans> object.
805 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>.
809 var Animation = new Class( {
811 initialize: function(options){
812 this.setOptions(options);
815 setOptions: function(options){
819 transition: Trans.Quart.easeInOut,
824 this.opt = $.merge(opt, options || {});
829 var time = $.time(), opt = this.opt;
830 if (time < this.time + opt.duration) {
831 var delta = opt.transition((time - this.time) / opt.duration);
834 this.timer = clearInterval(this.timer);
848 startTimer: function(){
849 var that = this, fps = this.opt.fps;
852 this.time = $.time() - this.time;
853 this.timer = setInterval((function(){
855 }), Math.round(1000 / fps));
869 stopTimer: function(){
872 this.time = $.time() - this.time;
873 this.timer = clearInterval(this.timer);
880 if (this.opt.link == 'cancel') {
889 var Options = function() {
890 var args = arguments;
891 for(var i=0, l=args.length, ans={}; i<l; i++) {
892 var opt = Options[args[i]];
903 * File: Options.AreaChart.js
908 Object: Options.AreaChart
911 Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
917 Options.AreaChart = {
922 showAggregates: true,
924 filterOnClick: false,
925 restoreOnRightClick: false
934 var areaChart = new $jit.AreaChart({
936 type: 'stacked:gradient',
939 restoreOnRightClick: true
946 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
947 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
948 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
949 selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
950 showAggregates - (boolean, function) Default's *true*. Display the values of the stacks. Can also be a function that returns *true* or *false* to display or filter some values. That same function can also return a string with the formatted value.
951 showLabels - (boolean, function) Default's *true*. Display the name of the slots. Can also be a function that returns *true* or *false* to display or not each label.
952 filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
953 restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
957 Options.AreaChart = {
961 labelOffset: 3, // label offset
962 type: 'stacked', // gradient
973 showAggregates: true,
975 filterOnClick: false,
976 restoreOnRightClick: false
980 * File: Options.Margin.js
985 Object: Options.Margin
987 Canvas drawing margins.
1006 var viz = new $jit.Viz({
1017 top - (number) Default's *0*. Top margin.
1018 left - (number) Default's *0*. Left margin.
1019 right - (number) Default's *0*. Right margin.
1020 bottom - (number) Default's *0*. Bottom margin.
1034 * File: Options.Canvas.js
1039 Object: Options.Canvas
1041 These are Canvas general options, like where to append it in the DOM, its dimensions, background,
1042 and other more advanced options.
1062 var viz = new $jit.Viz({
1063 injectInto: 'someContainerId',
1071 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.
1072 type - (string) Context type. Default's 2D but can be 3D for webGL enabled browsers.
1073 width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1074 height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1075 useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1076 withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1077 background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1096 direction: { x: -100, y: -100, z: -100 },
1097 color: [0.5, 0.3, 0.1]
1104 * File: Options.Tree.js
1109 Object: Options.Tree
1111 Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1117 orientation: "left",
1129 var st = new $jit.ST({
1130 orientation: 'left',
1139 subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1140 siblingOffset - (number) Default's 5. Separation offset between siblings.
1141 orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1142 align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1143 indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1144 multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1150 orientation: "left",
1160 * File: Options.Node.js
1165 Object: Options.Node
1167 Provides Node rendering options for Tree and Graph based visualizations.
1194 var viz = new $jit.Viz({
1206 overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1207 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.
1208 color - (string) Default's *#ccb*. Node color.
1209 alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1210 dim - (number) Default's *3*. An extra parameter used by 'circle', 'square', 'triangle' and 'star' node types. Depending on each shape, this parameter can set the radius of a circle, half the length of the side of a square, half the base and half the height of a triangle or the length of a side of a star (concave decagon).
1211 height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1212 width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1213 autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1214 autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1215 lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1216 transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1217 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.
1218 angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1219 span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1220 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.
1240 //Raw canvas styles to be
1241 //applied to the context instance
1242 //before plotting a node
1248 * File: Options.Edge.js
1253 Object: Options.Edge
1255 Provides Edge rendering options for Tree and Graph based visualizations.
1274 var viz = new $jit.Viz({
1280 shadowColor: '#ccc',
1289 overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1290 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.
1291 color - (string) Default's '#ccb'. Edge color.
1292 lineWidth - (number) Default's *1*. Line/Edge width.
1293 alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1294 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.
1295 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*.
1296 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.
1300 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.
1313 //Raw canvas styles to be
1314 //applied to the context instance
1315 //before plotting an edge
1321 * File: Options.Fx.js
1328 Provides animation options like duration of the animations, frames per second and animation transitions.
1336 transition: $jit.Trans.Quart.easeInOut,
1344 var viz = new $jit.Viz({
1347 transition: $jit.Trans.linear
1353 clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1354 duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1355 fps - (number) Default's *40*. Frames per second.
1356 transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1360 This object is used for specifying different animation transitions in all visualizations.
1362 There are many different type of animation transitions.
1366 Displays a linear transition
1374 Displays a Quadratic transition.
1378 >Trans.Quad.easeInOut
1384 Displays a Cubic transition.
1387 >Trans.Cubic.easeOut
1388 >Trans.Cubic.easeInOut
1394 Displays a Quartetic transition.
1397 >Trans.Quart.easeOut
1398 >Trans.Quart.easeInOut
1404 Displays a Quintic transition.
1407 >Trans.Quint.easeOut
1408 >Trans.Quint.easeInOut
1414 Displays an Exponential transition.
1418 >Trans.Expo.easeInOut
1424 Displays a Circular transition.
1428 >Trans.Circ.easeInOut
1434 Displays a Sineousidal transition.
1438 >Trans.Sine.easeInOut
1446 >Trans.Back.easeInOut
1454 >Trans.Bounce.easeIn
1455 >Trans.Bounce.easeOut
1456 >Trans.Bounce.easeInOut
1464 >Trans.Elastic.easeIn
1465 >Trans.Elastic.easeOut
1466 >Trans.Elastic.easeInOut
1472 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>.
1481 transition: $jit.Trans.Quart.easeInOut,
1486 * File: Options.Label.js
1490 Object: Options.Label
1492 Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.
1499 type: 'HTML', //'SVG', 'Native'
1502 family: 'sans-serif',
1503 textAlign: 'center',
1504 textBaseline: 'alphabetic',
1512 var viz = new $jit.Viz({
1523 overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1524 type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1525 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.
1526 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.
1527 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.
1528 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.
1534 type: 'HTML', //'SVG', 'Native'
1537 family: 'sans-serif',
1538 textAlign: 'center',
1539 textBaseline: 'alphabetic',
1545 * File: Options.Tips.js
1550 Object: Options.Tips
1570 var viz = new $jit.Viz({
1576 onShow: function(tip, node) {
1577 tip.innerHTML = node.name;
1585 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.
1586 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.
1587 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.
1588 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.
1589 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.
1590 onHide() - This callack is used when hiding a tooltip.
1607 * File: Options.NodeStyles.js
1612 Object: Options.NodeStyles
1614 Apply different styles when a node is hovered or selected.
1619 Options.NodeStyles = {
1630 var viz = new $jit.Viz({
1645 enable - (boolean) Default's *false*. Whether to enable this option.
1646 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>.
1647 stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1648 stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1651 Options.NodeStyles = {
1662 * File: Options.Events.js
1667 Object: Options.Events
1669 Configuration for adding mouse/touch event handlers to Nodes.
1676 enableForEdges: false,
1679 onRightClick: $.empty,
1680 onMouseMove: $.empty,
1681 onMouseEnter: $.empty,
1682 onMouseLeave: $.empty,
1683 onDragStart: $.empty,
1684 onDragMove: $.empty,
1685 onDragCancel: $.empty,
1687 onTouchStart: $.empty,
1688 onTouchMove: $.empty,
1689 onTouchEnd: $.empty,
1690 onTouchCancel: $.empty,
1691 onMouseWheel: $.empty
1698 var viz = new $jit.Viz({
1701 onClick: function(node, eventInfo, e) {
1704 onMouseEnter: function(node, eventInfo, e) {
1705 viz.canvas.getElement().style.cursor = 'pointer';
1707 onMouseLeave: function(node, eventInfo, e) {
1708 viz.canvas.getElement().style.cursor = '';
1716 enable - (boolean) Default's *false*. Whether to enable the Event system.
1717 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*.
1718 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.
1719 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.
1720 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.
1721 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.
1722 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.
1723 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.
1724 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.
1725 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.
1726 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.
1727 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.
1728 onTouchStart(node, eventInfo, e) - Behaves just like onDragStart.
1729 onTouchMove(node, eventInfo, e) - Behaves just like onDragMove.
1730 onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd.
1731 onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1732 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.
1739 enableForEdges: false,
1742 onRightClick: $.empty,
1743 onMouseMove: $.empty,
1744 onMouseEnter: $.empty,
1745 onMouseLeave: $.empty,
1746 onDragStart: $.empty,
1747 onDragMove: $.empty,
1748 onDragCancel: $.empty,
1750 onTouchStart: $.empty,
1751 onTouchMove: $.empty,
1752 onTouchEnd: $.empty,
1753 onMouseWheel: $.empty
1757 * File: Options.Navigation.js
1762 Object: Options.Navigation
1764 Panning and zooming options for Graph/Tree based visualizations. These options are implemented
1765 by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1771 Options.Navigation = {
1774 panning: false, //true, 'avoid nodes'
1783 var viz = new $jit.Viz({
1786 panning: 'avoid nodes',
1794 enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1795 type - (string) Default's 'auto'. Whether to attach the navigation events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. When 'auto' set when you let the <Options.Label> *type* parameter decide this.
1796 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>.
1797 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.
1801 Options.Navigation = {
1806 panning: false, //true | 'avoid nodes'
1811 * File: Options.Controller.js
1816 Object: Options.Controller
1818 Provides controller methods. Controller methods are callback functions that get called at different stages
1819 of the animation, computing or plotting of the visualization.
1823 All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1829 Options.Controller = {
1830 onBeforeCompute: $.empty,
1831 onAfterCompute: $.empty,
1832 onCreateLabel: $.empty,
1833 onPlaceLabel: $.empty,
1834 onComplete: $.empty,
1835 onBeforePlotLine:$.empty,
1836 onAfterPlotLine: $.empty,
1837 onBeforePlotNode:$.empty,
1838 onAfterPlotNode: $.empty,
1847 var viz = new $jit.Viz({
1848 onBeforePlotNode: function(node) {
1850 node.setData('color', '#ffc');
1852 node.removeData('color');
1855 onBeforePlotLine: function(adj) {
1856 if(adj.nodeFrom.selected && adj.nodeTo.selected) {
1857 adj.setData('color', '#ffc');
1859 adj.removeData('color');
1862 onAfterCompute: function() {
1870 onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
1871 onAfterCompute() - This method is triggered after all animations or computations ended.
1872 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.
1873 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.
1874 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.
1875 onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
1876 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.
1877 onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
1879 *Used in <ST>, <TM.Base> and <Icicle> visualizations*
1881 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.
1884 Options.Controller = {
1887 onBeforeCompute: $.empty,
1888 onAfterCompute: $.empty,
1889 onCreateLabel: $.empty,
1890 onPlaceLabel: $.empty,
1891 onComplete: $.empty,
1892 onBeforePlotLine:$.empty,
1893 onAfterPlotLine: $.empty,
1894 onBeforePlotNode:$.empty,
1895 onAfterPlotNode: $.empty,
1903 * Provides Extras such as Tips and Style Effects.
1907 * Provides the <Tips> and <NodeStyles> classes and functions.
1912 * Manager for mouse events (clicking and mouse moving).
1914 * This class is used for registering objects implementing onClick
1915 * and onMousemove methods. These methods are called when clicking or
1916 * moving the mouse around the Canvas.
1917 * For now, <Tips> and <NodeStyles> are classes implementing these methods.
1920 var ExtrasInitializer = {
1921 initialize: function(className, viz) {
1923 this.canvas = viz.canvas;
1924 this.config = viz.config[className];
1925 this.nodeTypes = viz.fx.nodeTypes;
1926 var type = this.config.type;
1927 this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
1928 this.labelContainer = this.dom && viz.labels.getLabelContainer();
1929 this.isEnabled() && this.initializePost();
1931 initializePost: $.empty,
1932 setAsProperty: $.lambda(false),
1933 isEnabled: function() {
1934 return this.config.enable;
1936 isLabel: function(e, win, group) {
1937 e = $.event.get(e, win);
1938 var labelContainer = this.labelContainer,
1939 target = e.target || e.srcElement,
1940 related = e.relatedTarget;
1942 return related && related == this.viz.canvas.getCtx().canvas
1943 && !!target && this.isDescendantOf(target, labelContainer);
1945 return this.isDescendantOf(target, labelContainer);
1948 isDescendantOf: function(elem, par) {
1949 while(elem && elem.parentNode) {
1950 if(elem.parentNode == par)
1952 elem = elem.parentNode;
1958 var EventsInterface = {
1960 onMouseDown: $.empty,
1961 onMouseMove: $.empty,
1962 onMouseOver: $.empty,
1963 onMouseOut: $.empty,
1964 onMouseWheel: $.empty,
1965 onTouchStart: $.empty,
1966 onTouchMove: $.empty,
1967 onTouchEnd: $.empty,
1968 onTouchCancel: $.empty
1971 var MouseEventsManager = new Class({
1972 initialize: function(viz) {
1974 this.canvas = viz.canvas;
1977 this.registeredObjects = [];
1978 this.attachEvents();
1981 attachEvents: function() {
1982 var htmlCanvas = this.canvas.getElement(),
1984 htmlCanvas.oncontextmenu = $.lambda(false);
1985 $.addEvents(htmlCanvas, {
1986 'mouseup': function(e, win) {
1987 var event = $.event.get(e, win);
1988 that.handleEvent('MouseUp', e, win,
1989 that.makeEventObject(e, win),
1990 $.event.isRightClick(event));
1992 'mousedown': function(e, win) {
1993 var event = $.event.get(e, win);
1994 that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win),
1995 $.event.isRightClick(event));
1997 'mousemove': function(e, win) {
1998 that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2000 'mouseover': function(e, win) {
2001 that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2003 'mouseout': function(e, win) {
2004 that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2006 'touchstart': function(e, win) {
2007 that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2009 'touchmove': function(e, win) {
2010 that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2012 'touchend': function(e, win) {
2013 that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2016 //attach mousewheel event
2017 var handleMouseWheel = function(e, win) {
2018 var event = $.event.get(e, win);
2019 var wheel = $.event.getWheel(event);
2020 that.handleEvent('MouseWheel', e, win, wheel);
2022 //TODO(nico): this is a horrible check for non-gecko browsers!
2023 if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2024 $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2026 htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2030 register: function(obj) {
2031 this.registeredObjects.push(obj);
2034 handleEvent: function() {
2035 var args = Array.prototype.slice.call(arguments),
2036 type = args.shift();
2037 for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2038 regs[i]['on' + type].apply(regs[i], args);
2042 makeEventObject: function(e, win) {
2044 graph = this.viz.graph,
2046 ntypes = fx.nodeTypes,
2047 etypes = fx.edgeTypes;
2053 getNodeCalled: false,
2054 getEdgeCalled: false,
2055 getPos: function() {
2056 //TODO(nico): check why this can't be cache anymore when using edge detection
2057 //if(this.pos) return this.pos;
2058 var canvas = that.viz.canvas,
2059 s = canvas.getSize(),
2060 p = canvas.getPos(),
2061 ox = canvas.translateOffsetX,
2062 oy = canvas.translateOffsetY,
2063 sx = canvas.scaleOffsetX,
2064 sy = canvas.scaleOffsetY,
2065 pos = $.event.getPos(e, win);
2067 x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2068 y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2072 getNode: function() {
2073 if(this.getNodeCalled) return this.node;
2074 this.getNodeCalled = true;
2075 for(var id in graph.nodes) {
2076 var n = graph.nodes[id],
2077 geom = n && ntypes[n.getData('type')],
2078 contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2080 this.contains = contains;
2081 return that.node = this.node = n;
2084 return that.node = this.node = false;
2086 getEdge: function() {
2087 if(this.getEdgeCalled) return this.edge;
2088 this.getEdgeCalled = true;
2090 for(var id in graph.edges) {
2091 var edgeFrom = graph.edges[id];
2093 for(var edgeId in edgeFrom) {
2094 if(edgeId in hashset) continue;
2095 var e = edgeFrom[edgeId],
2096 geom = e && etypes[e.getData('type')],
2097 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2099 this.contains = contains;
2100 return that.edge = this.edge = e;
2104 return that.edge = this.edge = false;
2106 getContains: function() {
2107 if(this.getNodeCalled) return this.contains;
2109 return this.contains;
2116 * Provides the initialization function for <NodeStyles> and <Tips> implemented
2117 * by all main visualizations.
2121 initializeExtras: function() {
2122 var mem = new MouseEventsManager(this), that = this;
2123 $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2124 var obj = new Extras.Classes[k](k, that);
2125 if(obj.isEnabled()) {
2128 if(obj.setAsProperty()) {
2129 that[k.toLowerCase()] = obj;
2135 Extras.Classes = {};
2139 This class defines an Event API to be accessed by the user.
2140 The methods implemented are the ones defined in the <Options.Events> object.
2143 Extras.Classes.Events = new Class({
2144 Implements: [ExtrasInitializer, EventsInterface],
2146 initializePost: function() {
2147 this.fx = this.viz.fx;
2148 this.ntypes = this.viz.fx.nodeTypes;
2149 this.etypes = this.viz.fx.edgeTypes;
2151 this.hovered = false;
2152 this.pressed = false;
2153 this.touched = false;
2155 this.touchMoved = false;
2160 setAsProperty: $.lambda(true),
2162 onMouseUp: function(e, win, event, isRightClick) {
2163 var evt = $.event.get(e, win);
2166 this.config.onRightClick(this.hovered, event, evt);
2168 this.config.onClick(this.pressed, event, evt);
2173 this.config.onDragEnd(this.pressed, event, evt);
2175 this.config.onDragCancel(this.pressed, event, evt);
2177 this.pressed = this.moved = false;
2181 onMouseOut: function(e, win, event) {
2183 var evt = $.event.get(e, win), label;
2184 if(this.dom && (label = this.isLabel(e, win, true))) {
2185 this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2187 this.hovered = false;
2191 var rt = evt.relatedTarget,
2192 canvasWidget = this.canvas.getElement();
2193 while(rt && rt.parentNode) {
2194 if(canvasWidget == rt.parentNode) return;
2198 this.config.onMouseLeave(this.hovered,
2200 this.hovered = false;
2204 onMouseOver: function(e, win, event) {
2206 var evt = $.event.get(e, win), label;
2207 if(this.dom && (label = this.isLabel(e, win, true))) {
2208 this.hovered = this.viz.graph.getNode(label.id);
2209 this.config.onMouseEnter(this.hovered,
2214 onMouseMove: function(e, win, event) {
2215 var label, evt = $.event.get(e, win);
2218 this.config.onDragMove(this.pressed, event, evt);
2222 this.config.onMouseMove(this.hovered,
2226 var hn = this.hovered;
2227 var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2228 var contains = geom && geom.contains
2229 && geom.contains.call(this.fx, hn, event.getPos());
2231 this.config.onMouseMove(hn, event, evt);
2234 this.config.onMouseLeave(hn, event, evt);
2235 this.hovered = false;
2238 if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2239 this.config.onMouseEnter(this.hovered, event, evt);
2241 this.config.onMouseMove(false, event, evt);
2246 onMouseWheel: function(e, win, delta) {
2247 this.config.onMouseWheel(delta, $.event.get(e, win));
2250 onMouseDown: function(e, win, event) {
2251 var evt = $.event.get(e, win), label;
2253 if(label = this.isLabel(e, win)) {
2254 this.pressed = this.viz.graph.getNode(label.id);
2257 this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2259 this.pressed && this.config.onDragStart(this.pressed, event, evt);
2262 onTouchStart: function(e, win, event) {
2263 var evt = $.event.get(e, win), label;
2264 if(this.dom && (label = this.isLabel(e, win))) {
2265 this.touched = this.viz.graph.getNode(label.id);
2267 this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2269 this.touched && this.config.onTouchStart(this.touched, event, evt);
2272 onTouchMove: function(e, win, event) {
2273 var evt = $.event.get(e, win);
2275 this.touchMoved = true;
2276 this.config.onTouchMove(this.touched, event, evt);
2280 onTouchEnd: function(e, win, event) {
2281 var evt = $.event.get(e, win);
2283 if(this.touchMoved) {
2284 this.config.onTouchEnd(this.touched, event, evt);
2286 this.config.onTouchCancel(this.touched, event, evt);
2288 this.touched = this.touchMoved = false;
2296 A class containing tip related functions. This class is used internally.
2300 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2307 Extras.Classes.Tips = new Class({
2308 Implements: [ExtrasInitializer, EventsInterface],
2310 initializePost: function() {
2313 var tip = $('_tooltip') || document.createElement('div');
2314 tip.id = '_tooltip';
2315 tip.className = 'tip';
2316 $.extend(tip.style, {
2317 position: 'absolute',
2321 document.body.appendChild(tip);
2327 setAsProperty: $.lambda(true),
2329 onMouseOut: function(e, win) {
2331 var evt = $.event.get(e, win);
2332 if(this.dom && this.isLabel(e, win, true)) {
2337 var rt = e.relatedTarget,
2338 canvasWidget = this.canvas.getElement();
2339 while(rt && rt.parentNode) {
2340 if(canvasWidget == rt.parentNode) return;
2346 onMouseOver: function(e, win) {
2349 if(this.dom && (label = this.isLabel(e, win, false))) {
2350 this.node = this.viz.graph.getNode(label.id);
2351 this.config.onShow(this.tip, this.node, label);
2355 onMouseMove: function(e, win, opt) {
2356 if(this.dom && this.isLabel(e, win)) {
2357 this.setTooltipPosition($.event.getPos(e, win));
2360 var node = opt.getNode();
2365 if(this.config.force || !this.node || this.node.id != node.id) {
2367 this.config.onShow(this.tip, node, opt.getContains());
2369 this.setTooltipPosition($.event.getPos(e, win));
2373 setTooltipPosition: function(pos) {
2378 //get window dimensions
2380 'height': document.body.clientHeight,
2381 'width': document.body.clientWidth
2383 //get tooltip dimensions
2385 'width': tip.offsetWidth,
2386 'height': tip.offsetHeight
2388 //set tooltip position
2389 var x = cont.offsetX, y = cont.offsetY;
2390 style.top = ((pos.y + y + obj.height > win.height)?
2391 (pos.y - obj.height - y) : pos.y + y) + 'px';
2392 style.left = ((pos.x + obj.width + x > win.width)?
2393 (pos.x - obj.width - x) : pos.x + x) + 'px';
2396 hide: function(triggerCallback) {
2397 this.tip.style.display = 'none';
2398 triggerCallback && this.config.onHide();
2405 Change node styles when clicking or hovering a node. This class is used internally.
2409 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2413 <Options.NodeStyles>
2415 Extras.Classes.NodeStyles = new Class({
2416 Implements: [ExtrasInitializer, EventsInterface],
2418 initializePost: function() {
2419 this.fx = this.viz.fx;
2420 this.types = this.viz.fx.nodeTypes;
2421 this.nStyles = this.config;
2422 this.nodeStylesOnHover = this.nStyles.stylesHover;
2423 this.nodeStylesOnClick = this.nStyles.stylesClick;
2424 this.hoveredNode = false;
2425 this.fx.nodeFxAnimation = new Animation();
2431 onMouseOut: function(e, win) {
2432 this.down = this.move = false;
2433 if(!this.hoveredNode) return;
2435 if(this.dom && this.isLabel(e, win, true)) {
2436 this.toggleStylesOnHover(this.hoveredNode, false);
2439 var rt = e.relatedTarget,
2440 canvasWidget = this.canvas.getElement();
2441 while(rt && rt.parentNode) {
2442 if(canvasWidget == rt.parentNode) return;
2445 this.toggleStylesOnHover(this.hoveredNode, false);
2446 this.hoveredNode = false;
2449 onMouseOver: function(e, win) {
2452 if(this.dom && (label = this.isLabel(e, win, true))) {
2453 var node = this.viz.graph.getNode(label.id);
2454 if(node.selected) return;
2455 this.hoveredNode = node;
2456 this.toggleStylesOnHover(this.hoveredNode, true);
2460 onMouseDown: function(e, win, event, isRightClick) {
2461 if(isRightClick) return;
2463 if(this.dom && (label = this.isLabel(e, win))) {
2464 this.down = this.viz.graph.getNode(label.id);
2465 } else if(!this.dom) {
2466 this.down = event.getNode();
2471 onMouseUp: function(e, win, event, isRightClick) {
2472 if(isRightClick) return;
2474 this.onClick(event.getNode());
2476 this.down = this.move = false;
2479 getRestoredStyles: function(node, type) {
2480 var restoredStyles = {},
2481 nStyles = this['nodeStylesOn' + type];
2482 for(var prop in nStyles) {
2483 restoredStyles[prop] = node.styles['$' + prop];
2485 return restoredStyles;
2488 toggleStylesOnHover: function(node, set) {
2489 if(this.nodeStylesOnHover) {
2490 this.toggleStylesOn('Hover', node, set);
2494 toggleStylesOnClick: function(node, set) {
2495 if(this.nodeStylesOnClick) {
2496 this.toggleStylesOn('Click', node, set);
2500 toggleStylesOn: function(type, node, set) {
2502 var nStyles = this.nStyles;
2506 node.styles = $.merge(node.data, {});
2508 for(var s in this['nodeStylesOn' + type]) {
2510 if(!($s in node.styles)) {
2511 node.styles[$s] = node.getData(s);
2514 viz.fx.nodeFx($.extend({
2517 'properties': that['nodeStylesOn' + type]
2519 transition: Trans.Quart.easeOut,
2524 var restoredStyles = this.getRestoredStyles(node, type);
2525 viz.fx.nodeFx($.extend({
2528 'properties': restoredStyles
2530 transition: Trans.Quart.easeOut,
2537 onClick: function(node) {
2539 var nStyles = this.nodeStylesOnClick;
2540 if(!nStyles) return;
2541 //if the node is selected then unselect it
2543 this.toggleStylesOnClick(node, false);
2544 delete node.selected;
2546 //unselect all selected nodes...
2547 this.viz.graph.eachNode(function(n) {
2549 for(var s in nStyles) {
2550 n.setData(s, n.styles['$' + s], 'end');
2555 //select clicked node
2556 this.toggleStylesOnClick(node, true);
2557 node.selected = true;
2558 delete node.hovered;
2559 this.hoveredNode = false;
2563 onMouseMove: function(e, win, event) {
2564 //if mouse button is down and moving set move=true
2565 if(this.down) this.move = true;
2566 //already handled by mouseover/out
2567 if(this.dom && this.isLabel(e, win)) return;
2568 var nStyles = this.nodeStylesOnHover;
2569 if(!nStyles) return;
2572 if(this.hoveredNode) {
2573 var geom = this.types[this.hoveredNode.getData('type')];
2574 var contains = geom && geom.contains && geom.contains.call(this.fx,
2575 this.hoveredNode, event.getPos());
2576 if(contains) return;
2578 var node = event.getNode();
2579 //if no node is being hovered then just exit
2580 if(!this.hoveredNode && !node) return;
2581 //if the node is hovered then exit
2582 if(node.hovered) return;
2583 //select hovered node
2584 if(node && !node.selected) {
2585 //check if an animation is running and exit it
2586 this.fx.nodeFxAnimation.stopTimer();
2587 //unselect all hovered nodes...
2588 this.viz.graph.eachNode(function(n) {
2589 if(n.hovered && !n.selected) {
2590 for(var s in nStyles) {
2591 n.setData(s, n.styles['$' + s], 'end');
2596 //select hovered node
2597 node.hovered = true;
2598 this.hoveredNode = node;
2599 this.toggleStylesOnHover(node, true);
2600 } else if(this.hoveredNode && !this.hoveredNode.selected) {
2601 //check if an animation is running and exit it
2602 this.fx.nodeFxAnimation.stopTimer();
2603 //unselect hovered node
2604 this.toggleStylesOnHover(this.hoveredNode, false);
2605 delete this.hoveredNode.hovered;
2606 this.hoveredNode = false;
2612 Extras.Classes.Navigation = new Class({
2613 Implements: [ExtrasInitializer, EventsInterface],
2615 initializePost: function() {
2617 this.pressed = false;
2620 onMouseWheel: function(e, win, scroll) {
2621 if(!this.config.zooming) return;
2622 $.event.stop($.event.get(e, win));
2623 var val = this.config.zooming / 1000,
2624 ans = 1 + scroll * val;
2625 this.canvas.scale(ans, ans);
2628 onMouseDown: function(e, win, eventInfo) {
2629 if(!this.config.panning) return;
2630 if(this.config.panning == 'avoid nodes' && (this.dom? this.isLabel(e, win) : eventInfo.getNode())) return;
2631 this.pressed = true;
2632 this.pos = eventInfo.getPos();
2633 var canvas = this.canvas,
2634 ox = canvas.translateOffsetX,
2635 oy = canvas.translateOffsetY,
2636 sx = canvas.scaleOffsetX,
2637 sy = canvas.scaleOffsetY;
2644 onMouseMove: function(e, win, eventInfo) {
2645 if(!this.config.panning) return;
2646 if(!this.pressed) return;
2647 if(this.config.panning == 'avoid nodes' && (this.dom? this.isLabel(e, win) : eventInfo.getNode())) return;
2648 var thispos = this.pos,
2649 currentPos = eventInfo.getPos(),
2650 canvas = this.canvas,
2651 ox = canvas.translateOffsetX,
2652 oy = canvas.translateOffsetY,
2653 sx = canvas.scaleOffsetX,
2654 sy = canvas.scaleOffsetY;
2659 var x = currentPos.x - thispos.x,
2660 y = currentPos.y - thispos.y;
2661 this.pos = currentPos;
2662 this.canvas.translate(x * 1/sx, y * 1/sy);
2665 onMouseUp: function(e, win, eventInfo, isRightClick) {
2666 if(!this.config.panning) return;
2667 this.pressed = false;
2680 A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to
2681 know more about <Canvas> options take a look at <Options.Canvas>.
2683 A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior
2684 across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2688 Suppose we have this HTML
2691 <div id="infovis"></div>
2694 Now we create a new Visualization
2697 var viz = new $jit.Viz({
2698 //Where to inject the canvas. Any div container will do.
2699 'injectInto':'infovis',
2700 //width and height for canvas.
2701 //Default's to the container offsetWidth and Height.
2707 The generated HTML will look like this
2711 <div id="infovis-canvaswidget" style="position:relative;">
2712 <canvas id="infovis-canvas" width=900 height=500
2713 style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2714 <div id="infovis-label"
2715 style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2721 As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2722 of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2727 //check for native canvas support
2728 var canvasType = typeof HTMLCanvasElement,
2729 supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2730 //create element function
2731 function $E(tag, props) {
2732 var elem = document.createElement(tag);
2733 for(var p in props) {
2734 if(typeof props[p] == "object") {
2735 $.extend(elem[p], props[p]);
2740 if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2741 elem = G_vmlCanvasManager.initElement(document.body.appendChild(elem));
2745 //canvas widget which we will call just Canvas
2746 $jit.Canvas = Canvas = new Class({
2750 labelContainer: false,
2751 translateOffsetX: 0,
2752 translateOffsetY: 0,
2756 initialize: function(viz, opt) {
2758 this.opt = this.config = opt;
2759 var id = $.type(opt.injectInto) == 'string'?
2760 opt.injectInto:opt.injectInto.id,
2762 idLabel = id + "-label",
2764 width = opt.width || wrapper.offsetWidth,
2765 height = opt.height || wrapper.offsetHeight;
2768 var canvasOptions = {
2773 //create main wrapper
2774 this.element = $E('div', {
2775 'id': id + '-canvaswidget',
2777 'position': 'relative',
2778 'width': width + 'px',
2779 'height': height + 'px'
2782 //create label container
2783 this.labelContainer = this.createLabelContainer(opt.Label.type,
2784 idLabel, canvasOptions);
2785 //create primary canvas
2786 this.canvases.push(new Canvas.Base[type]({
2787 config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2788 plot: function(base) {
2791 resize: function() {
2795 //create secondary canvas
2796 var back = opt.background;
2798 var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2799 this.canvases.push(new Canvas.Base[type](backCanvas));
2802 var len = this.canvases.length;
2804 this.element.appendChild(this.canvases[len].canvas);
2806 this.canvases[len].plot();
2809 this.element.appendChild(this.labelContainer);
2810 wrapper.appendChild(this.element);
2811 //Update canvas position when the page is scrolled.
2812 var timer = null, that = this;
2813 $.addEvent(window, 'scroll', function() {
2814 clearTimeout(timer);
2815 timer = setTimeout(function() {
2816 that.getPos(true); //update canvas position
2823 Returns the main canvas context object
2828 var ctx = canvas.getCtx();
2829 //Now I can use the native canvas context
2830 //and for example change some canvas styles
2831 ctx.globalAlpha = 1;
2834 getCtx: function(i) {
2835 return this.canvases[i || 0].getCtx();
2840 Returns the current Configuration for this Canvas Widget.
2845 var config = canvas.getConfig();
2848 getConfig: function() {
2854 Returns the main Canvas DOM wrapper
2859 var wrapper = canvas.getElement();
2860 //Returns <div id="infovis-canvaswidget" ... >...</div> as element
2863 getElement: function() {
2864 return this.element;
2869 Returns canvas dimensions.
2873 An object with *width* and *height* properties.
2877 canvas.getSize(); //returns { width: 900, height: 500 }
2880 getSize: function(i) {
2881 return this.canvases[i || 0].getSize();
2890 width - New canvas width.
2891 height - New canvas height.
2896 canvas.resize(width, height);
2900 resize: function(width, height) {
2902 this.translateOffsetX = this.translateOffsetY = 0;
2903 this.scaleOffsetX = this.scaleOffsetY = 1;
2904 for(var i=0, l=this.canvases.length; i<l; i++) {
2905 this.canvases[i].resize(width, height);
2907 var style = this.element.style;
2908 style.width = width + 'px';
2909 style.height = height + 'px';
2910 if(this.labelContainer)
2911 this.labelContainer.style.width = width + 'px';
2916 Applies a translation to the canvas.
2920 x - (number) x offset.
2921 y - (number) y offset.
2922 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
2927 canvas.translate(30, 30);
2931 translate: function(x, y, disablePlot) {
2932 this.translateOffsetX += x*this.scaleOffsetX;
2933 this.translateOffsetY += y*this.scaleOffsetY;
2934 for(var i=0, l=this.canvases.length; i<l; i++) {
2935 this.canvases[i].translate(x, y, disablePlot);
2945 x - (number) scale value.
2946 y - (number) scale value.
2947 disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
2952 canvas.scale(0.5, 0.5);
2956 scale: function(x, y, disablePlot) {
2957 var px = this.scaleOffsetX * x,
2958 py = this.scaleOffsetY * y;
2959 var dx = this.translateOffsetX * (x -1) / px,
2960 dy = this.translateOffsetY * (y -1) / py;
2961 this.scaleOffsetX = px;
2962 this.scaleOffsetY = py;
2963 for(var i=0, l=this.canvases.length; i<l; i++) {
2964 this.canvases[i].scale(x, y, true);
2966 this.translate(dx, dy, false);
2971 Returns the canvas position as an *x, y* object.
2975 force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
2979 An object with *x* and *y* properties.
2983 canvas.getPos(true); //returns { x: 900, y: 500 }
2986 getPos: function(force){
2987 if(force || !this.pos) {
2988 return this.pos = $.getPos(this.getElement());
2998 this.canvases[i||0].clear();
3001 path: function(type, action){
3002 var ctx = this.canvases[0].getCtx();
3009 createLabelContainer: function(type, idLabel, dim) {
3010 var NS = 'http://www.w3.org/2000/svg';
3011 if(type == 'HTML' || type == 'Native') {
3015 'overflow': 'visible',
3016 'position': 'absolute',
3019 'width': dim.width + 'px',
3023 } else if(type == 'SVG') {
3024 var svgContainer = document.createElementNS(NS, 'svg:svg');
3025 svgContainer.setAttribute("width", dim.width);
3026 svgContainer.setAttribute('height', dim.height);
3027 var style = svgContainer.style;
3028 style.position = 'absolute';
3029 style.left = style.top = '0px';
3030 var labelContainer = document.createElementNS(NS, 'svg:g');
3031 labelContainer.setAttribute('width', dim.width);
3032 labelContainer.setAttribute('height', dim.height);
3033 labelContainer.setAttribute('x', 0);
3034 labelContainer.setAttribute('y', 0);
3035 labelContainer.setAttribute('id', idLabel);
3036 svgContainer.appendChild(labelContainer);
3037 return svgContainer;
3041 //base canvas wrapper
3043 Canvas.Base['2D'] = new Class({
3044 translateOffsetX: 0,
3045 translateOffsetY: 0,
3049 initialize: function(viz) {
3051 this.opt = viz.config;
3053 this.createCanvas();
3054 this.translateToCenter();
3056 createCanvas: function() {
3059 height = opt.height;
3060 this.canvas = $E('canvas', {
3061 'id': opt.injectInto + opt.idSuffix,
3065 'position': 'absolute',
3068 'width': width + 'px',
3069 'height': height + 'px'
3073 getCtx: function() {
3075 return this.ctx = this.canvas.getContext('2d');
3078 getSize: function() {
3079 if(this.size) return this.size;
3080 var canvas = this.canvas;
3081 return this.size = {
3082 width: canvas.width,
3083 height: canvas.height
3086 translateToCenter: function(ps) {
3087 var size = this.getSize(),
3088 width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3089 height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3090 var ctx = this.getCtx();
3091 ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3092 ctx.translate(width/2, height/2);
3094 resize: function(width, height) {
3095 var size = this.getSize(),
3096 canvas = this.canvas,
3097 styles = canvas.style;
3099 canvas.width = width;
3100 canvas.height = height;
3101 styles.width = width + "px";
3102 styles.height = height + "px";
3103 //small ExCanvas fix
3104 if(!supportsCanvas) {
3105 this.translateToCenter(size);
3107 this.translateToCenter();
3109 this.translateOffsetX =
3110 this.translateOffsetY = 0;
3112 this.scaleOffsetY = 1;
3114 this.viz.resize(width, height, this);
3116 translate: function(x, y, disablePlot) {
3117 var sx = this.scaleOffsetX,
3118 sy = this.scaleOffsetY;
3119 this.translateOffsetX += x*sx;
3120 this.translateOffsetY += y*sy;
3121 this.getCtx().translate(x, y);
3122 !disablePlot && this.plot();
3124 scale: function(x, y, disablePlot) {
3125 this.scaleOffsetX *= x;
3126 this.scaleOffsetY *= y;
3127 this.getCtx().scale(x, y);
3128 !disablePlot && this.plot();
3131 var size = this.getSize(),
3132 ox = this.translateOffsetX,
3133 oy = this.translateOffsetY,
3134 sx = this.scaleOffsetX,
3135 sy = this.scaleOffsetY;
3136 this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx,
3137 (-size.height / 2 - oy) * 1/sy,
3138 size.width * 1/sx, size.height * 1/sy);
3142 this.viz.plot(this);
3145 //background canvases
3146 //TODO(nico): document this!
3147 Canvas.Background = {};
3148 Canvas.Background.Circles = new Class({
3149 initialize: function(viz, options) {
3151 this.config = $.merge({
3152 idSuffix: '-bkcanvas',
3159 resize: function(width, height, base) {
3162 plot: function(base) {
3163 var canvas = base.canvas,
3164 ctx = base.getCtx(),
3166 styles = conf.CanvasStyles;
3168 for(var s in styles) ctx[s] = styles[s];
3169 var n = conf.numberOfCircles,
3170 rho = conf.levelDistance;
3171 for(var i=1; i<=n; i++) {
3173 ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3177 //TODO(nico): print labels too!
3186 * Defines the <Polar> class.
3190 * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3194 * <http://en.wikipedia.org/wiki/Polar_coordinates>
3201 A multi purpose polar representation.
3205 The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3209 <http://en.wikipedia.org/wiki/Polar_coordinates>
3217 var Polar = function(theta, rho) {
3218 this.theta = theta || 0;
3219 this.rho = rho || 0;
3228 Returns a complex number.
3232 simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3238 getc: function(simple) {
3239 return this.toComplex(simple);
3245 Returns a <Polar> representation.
3249 A variable in polar coordinates.
3263 v - A <Complex> or <Polar> instance.
3268 this.theta = v.theta; this.rho = v.rho;
3274 Sets a <Complex> number.
3278 x - A <Complex> number real part.
3279 y - A <Complex> number imaginary part.
3282 setc: function(x, y) {
3283 this.rho = Math.sqrt(x * x + y * y);
3284 this.theta = Math.atan2(y, x);
3285 if(this.theta < 0) this.theta += Math.PI * 2;
3291 Sets a polar number.
3295 theta - A <Polar> number angle property.
3296 rho - A <Polar> number rho property.
3299 setp: function(theta, rho) {
3307 Returns a copy of the current object.
3311 A copy of the real object.
3314 return new Polar(this.theta, this.rho);
3320 Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3324 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*.
3328 A new <Complex> instance.
3330 toComplex: function(simple) {
3331 var x = Math.cos(this.theta) * this.rho;
3332 var y = Math.sin(this.theta) * this.rho;
3333 if(simple) return { 'x': x, 'y': y};
3334 return new Complex(x, y);
3340 Adds two <Polar> instances.
3344 polar - A <Polar> number.
3348 A new Polar instance.
3350 add: function(polar) {
3351 return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3357 Scales a polar norm.
3361 number - A scale factor.
3365 A new Polar instance.
3367 scale: function(number) {
3368 return new Polar(this.theta, this.rho * number);
3376 Returns *true* if the theta and rho properties are equal.
3380 c - A <Polar> number.
3384 *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3386 equals: function(c) {
3387 return this.theta == c.theta && this.rho == c.rho;
3393 Adds two <Polar> instances affecting the current object.
3397 polar - A <Polar> instance.
3403 $add: function(polar) {
3404 this.theta = this.theta + polar.theta; this.rho += polar.rho;
3411 Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3415 polar - A <Polar> instance.
3421 $madd: function(polar) {
3422 this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3430 Scales a polar instance affecting the object.
3434 number - A scaling factor.
3440 $scale: function(number) {
3448 Returns *true* if the number is zero.
3451 isZero: function () {
3452 var almostZero = 0.0001, abs = Math.abs;
3453 return abs(this.theta) < almostZero && abs(this.rho) < almostZero;
3459 Calculates a polar interpolation between two points at a given delta moment.
3463 elem - A <Polar> instance.
3464 delta - A delta factor ranging [0, 1].
3468 A new <Polar> instance representing an interpolation between _this_ and _elem_
3470 interpolate: function(elem, delta) {
3471 var pi = Math.PI, pi2 = pi * 2;
3472 var ch = function(t) {
3473 var a = (t < 0)? (t % pi2) + pi2 : t % pi2;
3476 var tt = this.theta, et = elem.theta;
3477 var sum, diff = Math.abs(tt - et);
3480 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3482 sum = ch((et - pi2 + (tt - (et)) * delta));
3484 } else if(diff >= pi) {
3486 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3488 sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3491 sum = ch((et + (tt - et) * delta)) ;
3493 var r = (this.rho - elem.rho) * delta + elem.rho;
3502 var $P = function(a, b) { return new Polar(a, b); };
3504 Polar.KER = $P(0, 0);
3511 * Defines the <Complex> class.
3515 * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3519 * <http://en.wikipedia.org/wiki/Complex_number>
3526 A multi-purpose Complex Class with common methods.
3530 The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3534 <http://en.wikipedia.org/wiki/Complex_number>
3538 x - _optional_ A Complex number real part.
3539 y - _optional_ A Complex number imaginary part.
3543 var Complex = function(x, y) {
3548 $jit.Complex = Complex;
3550 Complex.prototype = {
3554 Returns a complex number.
3567 Returns a <Polar> representation of this number.
3571 simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3575 A variable in <Polar> coordinates.
3577 getp: function(simple) {
3578 return this.toPolar(simple);
3589 c - A <Complex> or <Polar> instance.
3601 Sets a complex number.
3605 x - A <Complex> number Real part.
3606 y - A <Complex> number Imaginary part.
3609 setc: function(x, y) {
3617 Sets a polar number.
3621 theta - A <Polar> number theta property.
3622 rho - A <Polar> number rho property.
3625 setp: function(theta, rho) {
3626 this.x = Math.cos(theta) * rho;
3627 this.y = Math.sin(theta) * rho;
3633 Returns a copy of the current object.
3637 A copy of the real object.
3640 return new Complex(this.x, this.y);
3646 Transforms cartesian to polar coordinates.
3650 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*.
3654 A new <Polar> instance.
3657 toPolar: function(simple) {
3658 var rho = this.norm();
3659 var atan = Math.atan2(this.y, this.x);
3660 if(atan < 0) atan += Math.PI * 2;
3661 if(simple) return { 'theta': atan, 'rho': rho };
3662 return new Polar(atan, rho);
3667 Calculates a <Complex> number norm.
3671 A real number representing the complex norm.
3674 return Math.sqrt(this.squaredNorm());
3680 Calculates a <Complex> number squared norm.
3684 A real number representing the complex squared norm.
3686 squaredNorm: function () {
3687 return this.x*this.x + this.y*this.y;
3693 Returns the result of adding two complex numbers.
3695 Does not alter the original object.
3699 pos - A <Complex> instance.
3703 The result of adding two complex numbers.
3705 add: function(pos) {
3706 return new Complex(this.x + pos.x, this.y + pos.y);
3712 Returns the result of multiplying two <Complex> numbers.
3714 Does not alter the original object.
3718 pos - A <Complex> instance.
3722 The result of multiplying two complex numbers.
3724 prod: function(pos) {
3725 return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3731 Returns the conjugate of this <Complex> number.
3733 Does not alter the original object.
3737 The conjugate of this <Complex> number.
3739 conjugate: function() {
3740 return new Complex(this.x, -this.y);
3747 Returns the result of scaling a <Complex> instance.
3749 Does not alter the original object.
3753 factor - A scale factor.
3757 The result of scaling this complex to a factor.
3759 scale: function(factor) {
3760 return new Complex(this.x * factor, this.y * factor);
3768 Returns *true* if both real and imaginary parts are equal.
3772 c - A <Complex> instance.
3776 A boolean instance indicating if both <Complex> numbers are equal.
3778 equals: function(c) {
3779 return this.x == c.x && this.y == c.y;
3785 Returns the result of adding two <Complex> numbers.
3787 Alters the original object.
3791 pos - A <Complex> instance.
3795 The result of adding two complex numbers.
3797 $add: function(pos) {
3798 this.x += pos.x; this.y += pos.y;
3805 Returns the result of multiplying two <Complex> numbers.
3807 Alters the original object.
3811 pos - A <Complex> instance.
3815 The result of multiplying two complex numbers.
3817 $prod:function(pos) {
3818 var x = this.x, y = this.y;
3819 this.x = x*pos.x - y*pos.y;
3820 this.y = y*pos.x + x*pos.y;
3827 Returns the conjugate for this <Complex>.
3829 Alters the original object.
3833 The conjugate for this complex.
3835 $conjugate: function() {
3843 Returns the result of scaling a <Complex> instance.
3845 Alters the original object.
3849 factor - A scale factor.
3853 The result of scaling this complex to a factor.
3855 $scale: function(factor) {
3856 this.x *= factor; this.y *= factor;
3863 Returns the division of two <Complex> numbers.
3865 Alters the original object.
3869 pos - A <Complex> number.
3873 The result of scaling this complex to a factor.
3875 $div: function(pos) {
3876 var x = this.x, y = this.y;
3877 var sq = pos.squaredNorm();
3878 this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
3879 return this.$scale(1 / sq);
3885 Returns *true* if the number is zero.
3888 isZero: function () {
3889 var almostZero = 0.0001, abs = Math.abs;
3890 return abs(this.x) < almostZero && abs(this.y) < almostZero;
3894 var $C = function(a, b) { return new Complex(a, b); };
3896 Complex.KER = $C(0, 0);
3908 A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
3910 An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
3915 //create new visualization
3916 var viz = new $jit.Viz(options);
3920 viz.graph; //<Graph> instance
3925 The following <Graph.Util> methods are implemented in <Graph>
3927 - <Graph.Util.getNode>
3928 - <Graph.Util.eachNode>
3929 - <Graph.Util.computeLevels>
3930 - <Graph.Util.eachBFS>
3931 - <Graph.Util.clean>
3932 - <Graph.Util.getClosestNodeToPos>
3933 - <Graph.Util.getClosestNodeToOrigin>
3937 $jit.Graph = new Class({
3939 initialize: function(opt, Node, Edge, Label) {
3940 var innerOptions = {
3947 this.opt = $.merge(innerOptions, opt || {});
3951 //add nodeList methods
3954 for(var p in Accessors) {
3955 that.nodeList[p] = (function(p) {
3957 var args = Array.prototype.slice.call(arguments);
3958 that.eachNode(function(n) {
3959 n[p].apply(n, args);
3970 Returns a <Graph.Node> by *id*.
3974 id - (string) A <Graph.Node> id.
3979 var node = graph.getNode('nodeId');
3982 getNode: function(id) {
3983 if(this.hasNode(id)) return this.nodes[id];
3990 An alias for <Graph.Util.getNode>. Returns a node by *id*.
3994 id - (string) A <Graph.Node> id.
3999 var node = graph.get('nodeId');
4003 return this.getNode(id);
4009 Returns a <Graph.Node> by *name*.
4013 name - (string) A <Graph.Node> name.
4018 var node = graph.getByName('someName');
4021 getByName: function(name) {
4022 for(var id in this.nodes) {
4023 var n = this.nodes[id];
4024 if(n.name == name) return n;
4030 Method: getAdjacence
4032 Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4036 id - (string) A <Graph.Node> id.
4037 id2 - (string) A <Graph.Node> id.
4039 getAdjacence: function (id, id2) {
4040 if(id in this.edges) {
4041 return this.edges[id][id2];
4053 obj - An object with the properties described below
4055 id - (string) A node id
4056 name - (string) A node's name
4057 data - (object) A node's data hash
4063 addNode: function(obj) {
4064 if(!this.nodes[obj.id]) {
4065 var edges = this.edges[obj.id] = {};
4066 this.nodes[obj.id] = new Graph.Node($.extend({
4069 'data': $.merge(obj.data || {}, {}),
4070 'adjacencies': edges
4077 return this.nodes[obj.id];
4081 Method: addAdjacence
4083 Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4087 obj - (object) A <Graph.Node> object.
4088 obj2 - (object) Another <Graph.Node> object.
4089 data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4093 <Graph.Node>, <Graph.Adjacence>
4095 addAdjacence: function (obj, obj2, data) {
4096 if(!this.hasNode(obj.id)) { this.addNode(obj); }
4097 if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4098 obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4099 if(!obj.adjacentTo(obj2)) {
4100 var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4101 var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4102 adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4103 return adjsObj[obj2.id];
4105 return this.edges[obj.id][obj2.id];
4111 Removes a <Graph.Node> matching the specified *id*.
4115 id - (string) A node's id.
4118 removeNode: function(id) {
4119 if(this.hasNode(id)) {
4120 delete this.nodes[id];
4121 var adjs = this.edges[id];
4122 for(var to in adjs) {
4123 delete this.edges[to][id];
4125 delete this.edges[id];
4130 Method: removeAdjacence
4132 Removes a <Graph.Adjacence> matching *id1* and *id2*.
4136 id1 - (string) A <Graph.Node> id.
4137 id2 - (string) A <Graph.Node> id.
4139 removeAdjacence: function(id1, id2) {
4140 delete this.edges[id1][id2];
4141 delete this.edges[id2][id1];
4147 Returns a boolean indicating if the node belongs to the <Graph> or not.
4151 id - (string) Node id.
4153 hasNode: function(id) {
4154 return id in this.nodes;
4163 empty: function() { this.nodes = {}; this.edges = {};}
4167 var Graph = $jit.Graph;
4172 Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4178 var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4180 type = type || 'current';
4181 prefix = "$" + (prefix ? prefix + "-" : "");
4183 if(type == 'current') {
4185 } else if(type == 'start') {
4186 data = this.startData;
4187 } else if(type == 'end') {
4188 data = this.endData;
4191 var dollar = prefix + prop;
4194 return data[dollar];
4197 if(!this.Config.overridable)
4198 return prefixConfig[prop] || 0;
4200 return (dollar in data) ?
4201 data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4204 var setDataInternal = function(prefix, prop, value, type) {
4205 type = type || 'current';
4206 prefix = '$' + (prefix ? prefix + '-' : '');
4210 if(type == 'current') {
4212 } else if(type == 'start') {
4213 data = this.startData;
4214 } else if(type == 'end') {
4215 data = this.endData;
4218 data[prefix + prop] = value;
4221 var removeDataInternal = function(prefix, properties) {
4222 prefix = '$' + (prefix ? prefix + '-' : '');
4224 $.each(properties, function(prop) {
4225 var pref = prefix + prop;
4226 delete that.data[pref];
4227 delete that.endData[pref];
4228 delete that.startData[pref];
4236 Returns the specified data value property.
4237 This is useful for querying special/reserved <Graph.Node> data properties
4238 (i.e dollar prefixed properties).
4242 prop - (string) The name of the property. The dollar sign is not needed. For
4243 example *getData(width)* will return *data.$width*.
4244 type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4245 data properties also. These properties are used when making animations.
4246 force - (boolean) Whether to obtain the true value of the property (equivalent to
4247 *data.$prop*) or to check for *node.overridable = true* first.
4251 The value of the dollar prefixed property or the global Node/Edge property
4252 value if *overridable=false*
4256 node.getData('width'); //will return node.data.$width if Node.overridable=true;
4259 getData: function(prop, type, force) {
4260 return getDataInternal.call(this, "", prop, type, force, this.Config);
4267 Sets the current data property with some specific value.
4268 This method is only useful for reserved (dollar prefixed) properties.
4272 prop - (string) The name of the property. The dollar sign is not necessary. For
4273 example *setData(width)* will set *data.$width*.
4274 value - (mixed) The value to store.
4275 type - (string) The type of the data property to store. Default's "current" but
4276 can also be "start" or "end".
4281 node.setData('width', 30);
4284 If we were to make an animation of a node/edge width then we could do
4287 var node = viz.getNode('nodeId');
4288 //set start and end values
4289 node.setData('width', 10, 'start');
4290 node.setData('width', 30, 'end');
4291 //will animate nodes width property
4293 modes: ['node-property:width'],
4298 setData: function(prop, value, type) {
4299 setDataInternal.call(this, "", prop, value, type);
4305 Convenience method to set multiple data values at once.
4309 types - (array|string) A set of 'current', 'end' or 'start' values.
4310 obj - (object) A hash containing the names and values of the properties to be altered.
4314 node.setDataset(['current', 'end'], {
4316 'color': ['#fff', '#ccc']
4319 node.setDataset('end', {
4330 setDataset: function(types, obj) {
4331 types = $.splat(types);
4332 for(var attr in obj) {
4333 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4334 this.setData(attr, val[i], types[i]);
4342 Remove data properties.
4346 One or more property names as arguments. The dollar sign is not needed.
4350 node.removeData('width'); //now the default width value is returned
4353 removeData: function() {
4354 removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4358 Method: getCanvasStyle
4360 Returns the specified canvas style data value property. This is useful for
4361 querying special/reserved <Graph.Node> canvas style data properties (i.e.
4362 dollar prefixed properties that match with $canvas-<name of canvas style>).
4366 prop - (string) The name of the property. The dollar sign is not needed. For
4367 example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4368 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4369 data properties also.
4373 node.getCanvasStyle('shadowBlur');
4380 getCanvasStyle: function(prop, type, force) {
4381 return getDataInternal.call(
4382 this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4386 Method: setCanvasStyle
4388 Sets the canvas style data property with some specific value.
4389 This method is only useful for reserved (dollar prefixed) properties.
4393 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4394 value - (mixed) The value to set to the property.
4395 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4400 node.setCanvasStyle('shadowBlur', 30);
4403 If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4406 var node = viz.getNode('nodeId');
4407 //set start and end values
4408 node.setCanvasStyle('shadowBlur', 10, 'start');
4409 node.setCanvasStyle('shadowBlur', 30, 'end');
4410 //will animate nodes canvas style property for nodes
4412 modes: ['node-style:shadowBlur'],
4419 <Accessors.setData>.
4421 setCanvasStyle: function(prop, value, type) {
4422 setDataInternal.call(this, 'canvas', prop, value, type);
4426 Method: setCanvasStyles
4428 Convenience method to set multiple styles at once.
4432 types - (array|string) A set of 'current', 'end' or 'start' values.
4433 obj - (object) A hash containing the names and values of the properties to be altered.
4437 <Accessors.setDataset>.
4439 setCanvasStyles: function(types, obj) {
4440 types = $.splat(types);
4441 for(var attr in obj) {
4442 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4443 this.setCanvasStyle(attr, val[i], types[i]);
4449 Method: removeCanvasStyle
4451 Remove canvas style properties from data.
4455 A variable number of canvas style strings.
4459 <Accessors.removeData>.
4461 removeCanvasStyle: function() {
4462 removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4466 Method: getLabelData
4468 Returns the specified label data value property. This is useful for
4469 querying special/reserved <Graph.Node> label options (i.e.
4470 dollar prefixed properties that match with $label-<name of label style>).
4474 prop - (string) The name of the property. The dollar sign prefix is not needed. For
4475 example *getLabelData(size)* will return *data[$label-size]*.
4476 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4477 data properties also.
4481 <Accessors.getData>.
4483 getLabelData: function(prop, type, force) {
4484 return getDataInternal.call(
4485 this, 'label', prop, type, force, this.Label);
4489 Method: setLabelData
4491 Sets the current label data with some specific value.
4492 This method is only useful for reserved (dollar prefixed) properties.
4496 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4497 value - (mixed) The value to set to the property.
4498 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4503 node.setLabelData('size', 30);
4506 If we were to make an animation of a node label size then we could do
4509 var node = viz.getNode('nodeId');
4510 //set start and end values
4511 node.setLabelData('size', 10, 'start');
4512 node.setLabelData('size', 30, 'end');
4513 //will animate nodes label size
4515 modes: ['label-property:size'],
4522 <Accessors.setData>.
4524 setLabelData: function(prop, value, type) {
4525 setDataInternal.call(this, 'label', prop, value, type);
4529 Method: setLabelDataset
4531 Convenience function to set multiple label data at once.
4535 types - (array|string) A set of 'current', 'end' or 'start' values.
4536 obj - (object) A hash containing the names and values of the properties to be altered.
4540 <Accessors.setDataset>.
4542 setLabelDataset: function(types, obj) {
4543 types = $.splat(types);
4544 for(var attr in obj) {
4545 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4546 this.setLabelData(attr, val[i], types[i]);
4552 Method: removeLabelData
4554 Remove label properties from data.
4558 A variable number of label property strings.
4562 <Accessors.removeData>.
4564 removeLabelData: function() {
4565 removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4577 <Accessors> methods.
4579 The following <Graph.Util> methods are implemented by <Graph.Node>
4581 - <Graph.Util.eachAdjacency>
4582 - <Graph.Util.eachLevel>
4583 - <Graph.Util.eachSubgraph>
4584 - <Graph.Util.eachSubnode>
4585 - <Graph.Util.anySubnode>
4586 - <Graph.Util.getSubnodes>
4587 - <Graph.Util.getParents>
4588 - <Graph.Util.isDescendantOf>
4590 Graph.Node = new Class({
4592 initialize: function(opt, klass, Node, Edge, Label) {
4593 var innerOptions = {
4611 'startPos': new klass,
4615 $.extend(this, $.extend(innerOptions, opt));
4616 this.Config = this.Node = Node;
4624 Indicates if the node is adjacent to the node specified by id
4628 id - (string) A node id.
4632 node.adjacentTo('nodeId') == true;
4635 adjacentTo: function(node) {
4636 return node.id in this.adjacencies;
4640 Method: getAdjacency
4642 Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4646 id - (string) A node id.
4648 getAdjacency: function(id) {
4649 return this.adjacencies[id];
4655 Returns the position of the node.
4659 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4663 A <Complex> or <Polar> instance.
4667 var pos = node.getPos('end');
4670 getPos: function(type) {
4671 type = type || "current";
4672 if(type == "current") {
4674 } else if(type == "end") {
4676 } else if(type == "start") {
4677 return this.startPos;
4683 Sets the node's position.
4687 value - (object) A <Complex> or <Polar> instance.
4688 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4692 node.setPos(new $jit.Complex(0, 0), 'end');
4695 setPos: function(value, type) {
4696 type = type || "current";
4698 if(type == "current") {
4700 } else if(type == "end") {
4702 } else if(type == "start") {
4703 pos = this.startPos;
4709 Graph.Node.implement(Accessors);
4712 Class: Graph.Adjacence
4714 A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4718 <Accessors> methods.
4722 <Graph>, <Graph.Node>
4726 nodeFrom - A <Graph.Node> connected by this edge.
4727 nodeTo - Another <Graph.Node> connected by this edge.
4728 data - Node data property containing a hash (i.e {}) with custom options.
4730 Graph.Adjacence = new Class({
4732 initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4733 this.nodeFrom = nodeFrom;
4734 this.nodeTo = nodeTo;
4735 this.data = data || {};
4736 this.startData = {};
4738 this.Config = this.Edge = Edge;
4743 Graph.Adjacence.implement(Accessors);
4748 <Graph> traversal and processing utility object.
4752 For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4758 For internal use only. Provides a filtering function based on flags.
4760 filter: function(param) {
4761 if(!param || !($.type(param) == 'string')) return function() { return true; };
4762 var props = param.split(" ");
4763 return function(elem) {
4764 for(var i=0; i<props.length; i++) {
4765 if(elem[props[i]]) {
4775 Returns a <Graph.Node> by *id*.
4777 Also implemented by:
4783 graph - (object) A <Graph> instance.
4784 id - (string) A <Graph.Node> id.
4789 $jit.Graph.Util.getNode(graph, 'nodeid');
4791 graph.getNode('nodeid');
4794 getNode: function(graph, id) {
4795 return graph.nodes[id];
4801 Iterates over <Graph> nodes performing an *action*.
4803 Also implemented by:
4809 graph - (object) A <Graph> instance.
4810 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4814 $jit.Graph.Util.eachNode(graph, function(node) {
4818 graph.eachNode(function(node) {
4823 eachNode: function(graph, action, flags) {
4824 var filter = this.filter(flags);
4825 for(var i in graph.nodes) {
4826 if(filter(graph.nodes[i])) action(graph.nodes[i]);
4833 Iterates over <Graph> nodes performing an *action*. It's an alias for <Graph.Util.eachNode>.
4835 Also implemented by:
4841 graph - (object) A <Graph> instance.
4842 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4846 $jit.Graph.Util.each(graph, function(node) {
4850 graph.each(function(node) {
4855 each: function(graph, action, flags) {
4856 this.eachNode(graph, action, flags);
4860 Method: eachAdjacency
4862 Iterates over <Graph.Node> adjacencies applying the *action* function.
4864 Also implemented by:
4870 node - (object) A <Graph.Node>.
4871 action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4875 $jit.Graph.Util.eachAdjacency(node, function(adj) {
4876 alert(adj.nodeTo.name);
4879 node.eachAdjacency(function(adj) {
4880 alert(adj.nodeTo.name);
4884 eachAdjacency: function(node, action, flags) {
4885 var adj = node.adjacencies, filter = this.filter(flags);
4886 for(var id in adj) {
4889 if(a.nodeFrom != node) {
4890 var tmp = a.nodeFrom;
4891 a.nodeFrom = a.nodeTo;
4900 Method: computeLevels
4902 Performs a BFS traversal setting the correct depth for each node.
4904 Also implemented by:
4910 The depth of each node can then be accessed by
4915 graph - (object) A <Graph>.
4916 id - (string) A starting node id for the BFS traversal.
4917 startDepth - (optional|number) A minimum depth value. Default's 0.
4920 computeLevels: function(graph, id, startDepth, flags) {
4921 startDepth = startDepth || 0;
4922 var filter = this.filter(flags);
4923 this.eachNode(graph, function(elem) {
4927 var root = graph.getNode(id);
4928 root._depth = startDepth;
4930 while(queue.length != 0) {
4931 var node = queue.pop();
4933 this.eachAdjacency(node, function(adj) {
4935 if(n._flag == false && filter(n)) {
4936 if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
4946 Performs a BFS traversal applying *action* to each <Graph.Node>.
4948 Also implemented by:
4954 graph - (object) A <Graph>.
4955 id - (string) A starting node id for the BFS traversal.
4956 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4960 $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
4964 graph.eachBFS('mynodeid', function(node) {
4969 eachBFS: function(graph, id, action, flags) {
4970 var filter = this.filter(flags);
4972 var queue = [graph.getNode(id)];
4973 while(queue.length != 0) {
4974 var node = queue.pop();
4976 action(node, node._depth);
4977 this.eachAdjacency(node, function(adj) {
4979 if(n._flag == false && filter(n)) {
4990 Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
4992 Also implemented by:
4998 node - (object) A <Graph.Node>.
4999 levelBegin - (number) A relative level value.
5000 levelEnd - (number) A relative level value.
5001 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5004 eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5005 var d = node._depth, filter = this.filter(flags), that = this;
5006 levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5007 (function loopLevel(node, levelBegin, levelEnd) {
5008 var d = node._depth;
5009 if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5011 that.eachAdjacency(node, function(adj) {
5013 if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5016 })(node, levelBegin + d, levelEnd + d);
5020 Method: eachSubgraph
5022 Iterates over a node's children recursively.
5024 Also implemented by:
5029 node - (object) A <Graph.Node>.
5030 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5034 $jit.Graph.Util.eachSubgraph(node, function(node) {
5038 node.eachSubgraph(function(node) {
5043 eachSubgraph: function(node, action, flags) {
5044 this.eachLevel(node, 0, false, action, flags);
5050 Iterates over a node's children (without deeper recursion).
5052 Also implemented by:
5057 node - (object) A <Graph.Node>.
5058 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5062 $jit.Graph.Util.eachSubnode(node, function(node) {
5066 node.eachSubnode(function(node) {
5071 eachSubnode: function(node, action, flags) {
5072 this.eachLevel(node, 1, 1, action, flags);
5078 Returns *true* if any subnode matches the given condition.
5080 Also implemented by:
5085 node - (object) A <Graph.Node>.
5086 cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5090 $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5092 node.anySubnode(function(node) { return node.name == 'mynodename'; });
5095 anySubnode: function(node, cond, flags) {
5097 cond = cond || $.lambda(true);
5098 var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5099 this.eachSubnode(node, function(elem) {
5100 if(c(elem)) flag = true;
5108 Collects all subnodes for a specified node.
5109 The *level* parameter filters nodes having relative depth of *level* from the root node.
5111 Also implemented by:
5116 node - (object) A <Graph.Node>.
5117 level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5123 getSubnodes: function(node, level, flags) {
5124 var ans = [], that = this;
5126 var levelStart, levelEnd;
5127 if($.type(level) == 'array') {
5128 levelStart = level[0];
5129 levelEnd = level[1];
5132 levelEnd = Number.MAX_VALUE - node._depth;
5134 this.eachLevel(node, levelStart, levelEnd, function(n) {
5144 Returns an Array of <Graph.Nodes> which are parents of the given node.
5146 Also implemented by:
5151 node - (object) A <Graph.Node>.
5154 An Array of <Graph.Nodes>.
5158 var pars = $jit.Graph.Util.getParents(node);
5160 var pars = node.getParents();
5162 if(pars.length > 0) {
5163 //do stuff with parents
5167 getParents: function(node) {
5169 this.eachAdjacency(node, function(adj) {
5171 if(n._depth < node._depth) ans.push(n);
5177 Method: isDescendantOf
5179 Returns a boolean indicating if some node is descendant of the node with the given id.
5181 Also implemented by:
5187 node - (object) A <Graph.Node>.
5188 id - (string) A <Graph.Node> id.
5192 $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5194 node.isDescendantOf('nodeid');//true|false
5197 isDescendantOf: function(node, id) {
5198 if(node.id == id) return true;
5199 var pars = this.getParents(node), ans = false;
5200 for ( var i = 0; !ans && i < pars.length; i++) {
5201 ans = ans || this.isDescendantOf(pars[i], id);
5209 Cleans flags from nodes.
5211 Also implemented by:
5216 graph - A <Graph> instance.
5218 clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5221 Method: getClosestNodeToOrigin
5223 Returns the closest node to the center of canvas.
5225 Also implemented by:
5231 graph - (object) A <Graph> instance.
5232 prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5235 getClosestNodeToOrigin: function(graph, prop, flags) {
5236 return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5240 Method: getClosestNodeToPos
5242 Returns the closest node to the given position.
5244 Also implemented by:
5250 graph - (object) A <Graph> instance.
5251 pos - (object) A <Complex> or <Polar> instance.
5252 prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5255 getClosestNodeToPos: function(graph, pos, prop, flags) {
5257 prop = prop || 'current';
5258 pos = pos && pos.getc(true) || Complex.KER;
5259 var distance = function(a, b) {
5260 var d1 = a.x - b.x, d2 = a.y - b.y;
5261 return d1 * d1 + d2 * d2;
5263 this.eachNode(graph, function(elem) {
5264 node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5265 node.getPos(prop).getc(true), pos)) ? elem : node;
5271 //Append graph methods to <Graph>
5272 $.each(['get', 'getNode', 'each', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5273 Graph.prototype[m] = function() {
5274 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5278 //Append node methods to <Graph.Node>
5279 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5280 Graph.Node.prototype[m] = function() {
5281 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5293 Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5294 morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5306 initialize: function(viz) {
5313 Removes one or more <Graph.Nodes> from the visualization.
5314 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5318 node - (string|array) The node's id. Can also be an array having many ids.
5319 opt - (object) Animation options. It's an object with optional properties described below
5320 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5321 duration - Described in <Options.Fx>.
5322 fps - Described in <Options.Fx>.
5323 transition - Described in <Options.Fx>.
5324 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5328 var viz = new $jit.Viz(options);
5329 viz.op.removeNode('nodeId', {
5333 transition: $jit.Trans.Quart.easeOut
5336 viz.op.removeNode(['someId', 'otherId'], {
5343 removeNode: function(node, opt) {
5345 var options = $.merge(this.options, viz.controller, opt);
5346 var n = $.splat(node);
5347 var i, that, nodeObj;
5348 switch(options.type) {
5350 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5354 this.removeNode(n, { type: 'nothing' });
5355 viz.labels.clearLabels();
5359 case 'fade:seq': case 'fade':
5361 //set alpha to 0 for nodes to remove.
5362 for(i=0; i<n.length; i++) {
5363 nodeObj = viz.graph.getNode(n[i]);
5364 nodeObj.setData('alpha', 0, 'end');
5366 viz.fx.animate($.merge(options, {
5367 modes: ['node-property:alpha'],
5368 onComplete: function() {
5369 that.removeNode(n, { type: 'nothing' });
5370 viz.labels.clearLabels();
5372 viz.fx.animate($.merge(options, {
5381 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5382 for(i=0; i<n.length; i++) {
5383 nodeObj = viz.graph.getNode(n[i]);
5384 nodeObj.setData('alpha', 0, 'end');
5385 nodeObj.ignore = true;
5388 viz.fx.animate($.merge(options, {
5389 modes: ['node-property:alpha', 'linear'],
5390 onComplete: function() {
5391 that.removeNode(n, { type: 'nothing' });
5392 options.onComplete && options.onComplete();
5400 condition: function() { return n.length != 0; },
5401 step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5402 onComplete: function() { options.onComplete && options.onComplete(); },
5403 duration: Math.ceil(options.duration / n.length)
5407 default: this.doError();
5414 Removes one or more <Graph.Adjacences> from the visualization.
5415 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5419 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'], ...]).
5420 opt - (object) Animation options. It's an object with optional properties described below
5421 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5422 duration - Described in <Options.Fx>.
5423 fps - Described in <Options.Fx>.
5424 transition - Described in <Options.Fx>.
5425 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5429 var viz = new $jit.Viz(options);
5430 viz.op.removeEdge(['nodeId', 'otherId'], {
5434 transition: $jit.Trans.Quart.easeOut
5437 viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5444 removeEdge: function(vertex, opt) {
5446 var options = $.merge(this.options, viz.controller, opt);
5447 var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5449 switch(options.type) {
5451 for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5455 this.removeEdge(v, { type: 'nothing' });
5459 case 'fade:seq': case 'fade':
5461 //set alpha to 0 for edges to remove.
5462 for(i=0; i<v.length; i++) {
5463 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5465 adj.setData('alpha', 0,'end');
5468 viz.fx.animate($.merge(options, {
5469 modes: ['edge-property:alpha'],
5470 onComplete: function() {
5471 that.removeEdge(v, { type: 'nothing' });
5473 viz.fx.animate($.merge(options, {
5482 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5483 for(i=0; i<v.length; i++) {
5484 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5486 adj.setData('alpha',0 ,'end');
5491 viz.fx.animate($.merge(options, {
5492 modes: ['edge-property:alpha', 'linear'],
5493 onComplete: function() {
5494 that.removeEdge(v, { type: 'nothing' });
5495 options.onComplete && options.onComplete();
5503 condition: function() { return v.length != 0; },
5504 step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5505 onComplete: function() { options.onComplete(); },
5506 duration: Math.ceil(options.duration / v.length)
5510 default: this.doError();
5517 Adds a new graph to the visualization.
5518 The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5519 The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5523 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5524 opt - (object) Animation options. It's an object with optional properties described below
5525 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5526 duration - Described in <Options.Fx>.
5527 fps - Described in <Options.Fx>.
5528 transition - Described in <Options.Fx>.
5529 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5533 //...json contains a tree or graph structure...
5535 var viz = new $jit.Viz(options);
5540 transition: $jit.Trans.Quart.easeOut
5550 sum: function(json, opt) {
5552 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5554 viz.root = opt.id || viz.root;
5555 switch(options.type) {
5557 graph = viz.construct(json);
5558 graph.eachNode(function(elem) {
5559 elem.eachAdjacency(function(adj) {
5560 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5567 this.sum(json, { type: 'nothing' });
5571 case 'fade:seq': case 'fade': case 'fade:con':
5573 graph = viz.construct(json);
5575 //set alpha to 0 for nodes to add.
5576 var fadeEdges = this.preprocessSum(graph);
5577 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5579 if(options.type != 'fade:con') {
5580 viz.fx.animate($.merge(options, {
5582 onComplete: function() {
5583 viz.fx.animate($.merge(options, {
5585 onComplete: function() {
5586 options.onComplete();
5592 viz.graph.eachNode(function(elem) {
5593 if (elem.id != root && elem.pos.isZero()) {
5594 elem.pos.set(elem.endPos);
5595 elem.startPos.set(elem.endPos);
5598 viz.fx.animate($.merge(options, {
5599 modes: ['linear'].concat(modes)
5604 default: this.doError();
5611 This method will transform the current visualized graph into the new JSON representation passed in the method.
5612 The JSON object must at least have the root node in common with the current visualized graph.
5616 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5617 opt - (object) Animation options. It's an object with optional properties described below
5618 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5619 duration - Described in <Options.Fx>.
5620 fps - Described in <Options.Fx>.
5621 transition - Described in <Options.Fx>.
5622 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5623 id - (string) The shared <Graph.Node> id between both graphs.
5625 extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5626 *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5627 For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5628 properties as values, just like specified in <Graph.Plot.animate>.
5632 //...json contains a tree or graph structure...
5634 var viz = new $jit.Viz(options);
5635 viz.op.morph(json, {
5639 transition: $jit.Trans.Quart.easeOut
5642 viz.op.morph(json, {
5646 //if the json data contains dollar prefixed params
5647 //like $width or $height these too can be animated
5648 viz.op.morph(json, {
5652 'node-property': ['width', 'height']
5657 morph: function(json, opt, extraModes) {
5658 extraModes = extraModes || {};
5660 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5662 //TODO(nico) this hack makes morphing work with the Hypertree.
5663 //Need to check if it has been solved and this can be removed.
5664 viz.root = opt.id || viz.root;
5665 switch(options.type) {
5667 graph = viz.construct(json);
5668 graph.eachNode(function(elem) {
5669 var nodeExists = viz.graph.hasNode(elem.id);
5670 elem.eachAdjacency(function(adj) {
5671 var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5672 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5673 //Update data properties if the node existed
5675 var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5676 for(var prop in (adj.data || {})) {
5677 addedAdj.data[prop] = adj.data[prop];
5681 //Update data properties if the node existed
5683 var addedNode = viz.graph.getNode(elem.id);
5684 for(var prop in (elem.data || {})) {
5685 addedNode.data[prop] = elem.data[prop];
5689 viz.graph.eachNode(function(elem) {
5690 elem.eachAdjacency(function(adj) {
5691 if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5692 viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5695 if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5701 viz.labels.clearLabels(true);
5702 this.morph(json, { type: 'nothing' });
5707 case 'fade:seq': case 'fade': case 'fade:con':
5709 graph = viz.construct(json);
5710 //preprocessing for nodes to delete.
5711 //get node property modes to interpolate
5712 var nodeModes = ('node-property' in extraModes)
5713 && $.map($.splat(extraModes['node-property']),
5714 function(n) { return '$' + n; });
5715 viz.graph.eachNode(function(elem) {
5716 var graphNode = graph.getNode(elem.id);
5718 elem.setData('alpha', 1);
5719 elem.setData('alpha', 1, 'start');
5720 elem.setData('alpha', 0, 'end');
5723 //Update node data information
5724 var graphNodeData = graphNode.data;
5725 for(var prop in graphNodeData) {
5726 if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5727 elem.endData[prop] = graphNodeData[prop];
5729 elem.data[prop] = graphNodeData[prop];
5734 viz.graph.eachNode(function(elem) {
5735 if(elem.ignore) return;
5736 elem.eachAdjacency(function(adj) {
5737 if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5738 var nodeFrom = graph.getNode(adj.nodeFrom.id);
5739 var nodeTo = graph.getNode(adj.nodeTo.id);
5740 if(!nodeFrom.adjacentTo(nodeTo)) {
5741 var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5743 adj.setData('alpha', 1);
5744 adj.setData('alpha', 1, 'start');
5745 adj.setData('alpha', 0, 'end');
5749 //preprocessing for adding nodes.
5750 var fadeEdges = this.preprocessSum(graph);
5752 var modes = !fadeEdges? ['node-property:alpha'] :
5753 ['node-property:alpha',
5754 'edge-property:alpha'];
5755 //Append extra node-property animations (if any)
5756 modes[0] = modes[0] + (('node-property' in extraModes)?
5757 (':' + $.splat(extraModes['node-property']).join(':')) : '');
5758 //Append extra edge-property animations (if any)
5759 modes[1] = (modes[1] || 'edge-property:alpha') + (('edge-property' in extraModes)?
5760 (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5761 //Add label-property animations (if any)
5762 if('label-property' in extraModes) {
5763 modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5765 //only use reposition if its implemented.
5766 if (viz.reposition) {
5771 viz.graph.eachNode(function(elem) {
5772 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5773 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5776 viz.fx.animate($.merge(options, {
5777 modes: [extraModes.position || 'polar'].concat(modes),
5778 onComplete: function() {
5779 viz.graph.eachNode(function(elem) {
5780 if(elem.ignore) viz.graph.removeNode(elem.id);
5782 viz.graph.eachNode(function(elem) {
5783 elem.eachAdjacency(function(adj) {
5784 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5787 options.onComplete();
5800 Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5804 node - (object) A <Graph.Node>.
5805 opt - (object) An object containing options described below
5806 type - (string) Whether to 'replot' or 'animate' the contraction.
5808 There are also a number of Animation options. For more information see <Options.Fx>.
5812 var viz = new $jit.Viz(options);
5813 viz.op.contract(node, {
5817 transition: $jit.Trans.Quart.easeOut
5822 contract: function(node, opt) {
5824 if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5825 opt = $.merge(this.options, viz.config, opt || {}, {
5826 'modes': ['node-property:alpha:span', 'linear']
5828 node.collapsed = true;
5830 n.eachSubnode(function(ch) {
5832 ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5836 if(opt.type == 'animate') {
5839 viz.rotate(viz.rotated, 'none', {
5844 n.eachSubnode(function(ch) {
5845 ch.setPos(node.getPos('end'), 'end');
5849 viz.fx.animate(opt);
5850 } else if(opt.type == 'replot'){
5858 Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5862 node - (object) A <Graph.Node>.
5863 opt - (object) An object containing options described below
5864 type - (string) Whether to 'replot' or 'animate'.
5866 There are also a number of Animation options. For more information see <Options.Fx>.
5870 var viz = new $jit.Viz(options);
5871 viz.op.expand(node, {
5875 transition: $jit.Trans.Quart.easeOut
5880 expand: function(node, opt) {
5881 if(!('collapsed' in node)) return;
5883 opt = $.merge(this.options, viz.config, opt || {}, {
5884 'modes': ['node-property:alpha:span', 'linear']
5886 delete node.collapsed;
5888 n.eachSubnode(function(ch) {
5890 ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5894 if(opt.type == 'animate') {
5897 viz.rotate(viz.rotated, 'none', {
5901 viz.fx.animate(opt);
5902 } else if(opt.type == 'replot'){
5907 preprocessSum: function(graph) {
5909 graph.eachNode(function(elem) {
5910 if(!viz.graph.hasNode(elem.id)) {
5911 viz.graph.addNode(elem);
5912 var n = viz.graph.getNode(elem.id);
5913 n.setData('alpha', 0);
5914 n.setData('alpha', 0, 'start');
5915 n.setData('alpha', 1, 'end');
5918 var fadeEdges = false;
5919 graph.eachNode(function(elem) {
5920 elem.eachAdjacency(function(adj) {
5921 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
5922 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
5923 if(!nodeFrom.adjacentTo(nodeTo)) {
5924 var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
5925 if(nodeFrom.startAlpha == nodeFrom.endAlpha
5926 && nodeTo.startAlpha == nodeTo.endAlpha) {
5928 adj.setData('alpha', 0);
5929 adj.setData('alpha', 0, 'start');
5930 adj.setData('alpha', 1, 'end');
5944 Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
5945 Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
5946 position is over the rendered shape.
5948 Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
5949 *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
5953 //implement a new node type
5954 $jit.Viz.Plot.NodeTypes.implement({
5956 'render': function(node, canvas) {
5957 this.nodeHelper.circle.render ...
5959 'contains': function(node, pos) {
5960 this.nodeHelper.circle.contains ...
5964 //implement an edge type
5965 $jit.Viz.Plot.EdgeTypes.implement({
5967 'render': function(node, canvas) {
5968 this.edgeHelper.circle.render ...
5971 'contains': function(node, pos) {
5972 this.edgeHelper.circle.contains ...
5983 Contains rendering and other type of primitives for simple shapes.
5988 'contains': $.lambda(false)
5991 Object: NodeHelper.circle
5997 Renders a circle into the canvas.
6001 type - (string) Possible options are 'fill' or 'stroke'.
6002 pos - (object) An *x*, *y* object with the position of the center of the circle.
6003 radius - (number) The radius of the circle to be rendered.
6004 canvas - (object) A <Canvas> instance.
6008 NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6011 'render': function(type, pos, radius, canvas){
6012 var ctx = canvas.getCtx();
6014 ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6021 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6025 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6026 pos - (object) An *x*, *y* object with the position to check.
6027 radius - (number) The radius of the rendered circle.
6031 NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6034 'contains': function(npos, pos, radius){
6035 var diffx = npos.x - pos.x,
6036 diffy = npos.y - pos.y,
6037 diff = diffx * diffx + diffy * diffy;
6038 return diff <= radius * radius;
6042 Object: NodeHelper.ellipse
6048 Renders an ellipse into the canvas.
6052 type - (string) Possible options are 'fill' or 'stroke'.
6053 pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6054 width - (number) The width of the ellipse.
6055 height - (number) The height of the ellipse.
6056 canvas - (object) A <Canvas> instance.
6060 NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6063 'render': function(type, pos, width, height, canvas){
6064 var ctx = canvas.getCtx(),
6071 if (width > height) {
6073 scaley = height / width;
6074 scaleposy = width / height;
6076 radius = height / 2;
6077 scalex = width / height;
6078 scaleposx = height / width;
6082 ctx.scale(scalex, scaley);
6084 ctx.arc(pos.x * scaleposx, pos.y * scaleposy, radius, 0, Math.PI * 2, true);
6092 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6096 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6097 pos - (object) An *x*, *y* object with the position to check.
6098 width - (number) The width of the rendered ellipse.
6099 height - (number) The height of the rendered ellipse.
6103 NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6106 'contains': function(npos, pos, width, height){
6114 if (width > height) {
6116 scaley = height / width;
6118 radius = height / 2;
6119 scalex = width / height;
6122 diffx = (npos.x - pos.x) * (1 / scalex);
6123 diffy = (npos.y - pos.y) * (1 / scaley);
6124 diff = diffx * diffx + diffy * diffy;
6125 return diff <= radius * radius;
6129 Object: NodeHelper.square
6135 Renders a square into the canvas.
6139 type - (string) Possible options are 'fill' or 'stroke'.
6140 pos - (object) An *x*, *y* object with the position of the center of the square.
6141 dim - (number) The radius (or half-diameter) of the square.
6142 canvas - (object) A <Canvas> instance.
6146 NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6149 'render': function(type, pos, dim, canvas){
6150 canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6155 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6159 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6160 pos - (object) An *x*, *y* object with the position to check.
6161 dim - (number) The radius (or half-diameter) of the square.
6165 NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6168 'contains': function(npos, pos, dim){
6169 return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6173 Object: NodeHelper.rectangle
6179 Renders a rectangle into the canvas.
6183 type - (string) Possible options are 'fill' or 'stroke'.
6184 pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6185 width - (number) The width of the rectangle.
6186 height - (number) The height of the rectangle.
6187 canvas - (object) A <Canvas> instance.
6191 NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6194 'render': function(type, pos, width, height, canvas){
6195 canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6201 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6205 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6206 pos - (object) An *x*, *y* object with the position to check.
6207 width - (number) The width of the rendered rectangle.
6208 height - (number) The height of the rendered rectangle.
6212 NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6215 'contains': function(npos, pos, width, height){
6216 return Math.abs(pos.x - npos.x) <= width / 2
6217 && Math.abs(pos.y - npos.y) <= height / 2;
6221 Object: NodeHelper.triangle
6227 Renders a triangle into the canvas.
6231 type - (string) Possible options are 'fill' or 'stroke'.
6232 pos - (object) An *x*, *y* object with the position of the center of the triangle.
6233 dim - (number) Half the base and half the height of the triangle.
6234 canvas - (object) A <Canvas> instance.
6238 NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6241 'render': function(type, pos, dim, canvas){
6242 var ctx = canvas.getCtx(),
6250 ctx.moveTo(c1x, c1y);
6251 ctx.lineTo(c2x, c2y);
6252 ctx.lineTo(c3x, c3y);
6259 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6263 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6264 pos - (object) An *x*, *y* object with the position to check.
6265 dim - (number) Half the base and half the height of the triangle.
6269 NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6272 'contains': function(npos, pos, dim) {
6273 return NodeHelper.circle.contains(npos, pos, dim);
6277 Object: NodeHelper.star
6283 Renders a star (concave decagon) into the canvas.
6287 type - (string) Possible options are 'fill' or 'stroke'.
6288 pos - (object) An *x*, *y* object with the position of the center of the star.
6289 dim - (number) The length of a side of a concave decagon.
6290 canvas - (object) A <Canvas> instance.
6294 NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6297 'render': function(type, pos, dim, canvas){
6298 var ctx = canvas.getCtx(),
6301 ctx.translate(pos.x, pos.y);
6304 for (var i = 0; i < 9; i++) {
6307 ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6319 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6323 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6324 pos - (object) An *x*, *y* object with the position to check.
6325 dim - (number) The length of a side of a concave decagon.
6329 NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6332 'contains': function(npos, pos, dim) {
6333 return NodeHelper.circle.contains(npos, pos, dim);
6341 Contains rendering primitives for simple edge shapes.
6345 Object: EdgeHelper.line
6351 Renders a line into the canvas.
6355 from - (object) An *x*, *y* object with the starting position of the line.
6356 to - (object) An *x*, *y* object with the ending position of the line.
6357 canvas - (object) A <Canvas> instance.
6361 EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6364 'render': function(from, to, canvas){
6365 var ctx = canvas.getCtx();
6367 ctx.moveTo(from.x, from.y);
6368 ctx.lineTo(to.x, to.y);
6374 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6378 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6379 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6380 pos - (object) An *x*, *y* object with the position to check.
6381 epsilon - (number) The dimension of the shape.
6385 EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6388 'contains': function(posFrom, posTo, pos, epsilon) {
6391 minPosX = min(posFrom.x, posTo.x),
6392 maxPosX = max(posFrom.x, posTo.x),
6393 minPosY = min(posFrom.y, posTo.y),
6394 maxPosY = max(posFrom.y, posTo.y);
6396 if(pos.x >= minPosX && pos.x <= maxPosX
6397 && pos.y >= minPosY && pos.y <= maxPosY) {
6398 if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6401 var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6402 return Math.abs(dist - pos.y) <= epsilon;
6408 Object: EdgeHelper.arrow
6414 Renders an arrow into the canvas.
6418 from - (object) An *x*, *y* object with the starting position of the arrow.
6419 to - (object) An *x*, *y* object with the ending position of the arrow.
6420 dim - (number) The dimension of the arrow.
6421 swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6422 canvas - (object) A <Canvas> instance.
6426 EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6429 'render': function(from, to, dim, swap, canvas){
6430 var ctx = canvas.getCtx();
6431 // invert edge direction
6437 var vect = new Complex(to.x - from.x, to.y - from.y);
6438 vect.$scale(dim / vect.norm());
6439 var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6440 normal = new Complex(-vect.y / 2, vect.x / 2),
6441 v1 = intermediatePoint.add(normal),
6442 v2 = intermediatePoint.$add(normal.$scale(-1));
6445 ctx.moveTo(from.x, from.y);
6446 ctx.lineTo(to.x, to.y);
6449 ctx.moveTo(v1.x, v1.y);
6450 ctx.lineTo(v2.x, v2.y);
6451 ctx.lineTo(to.x, to.y);
6458 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6462 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6463 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6464 pos - (object) An *x*, *y* object with the position to check.
6465 epsilon - (number) The dimension of the shape.
6469 EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6472 'contains': function(posFrom, posTo, pos, epsilon) {
6473 return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6477 Object: EdgeHelper.hyperline
6483 Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6487 from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6488 to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6489 r - (number) The scaling factor.
6490 canvas - (object) A <Canvas> instance.
6494 EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6497 'render': function(from, to, r, canvas){
6498 var ctx = canvas.getCtx();
6499 var centerOfCircle = computeArcThroughTwoPoints(from, to);
6500 if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6501 || centerOfCircle.ratio < 0) {
6503 ctx.moveTo(from.x * r, from.y * r);
6504 ctx.lineTo(to.x * r, to.y * r);
6507 var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6508 - centerOfCircle.x);
6509 var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6510 - centerOfCircle.x);
6511 var sense = sense(angleBegin, angleEnd);
6513 ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6514 * r, angleBegin, angleEnd, sense);
6518 Calculates the arc parameters through two points.
6520 More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6524 p1 - A <Complex> instance.
6525 p2 - A <Complex> instance.
6526 scale - The Disk's diameter.
6530 An object containing some arc properties.
6532 function computeArcThroughTwoPoints(p1, p2){
6533 var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6534 var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6535 // Fall back to a straight line
6543 var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6544 var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6547 var squaredRatio = (a * a + b * b) / 4 - 1;
6548 // Fall back to a straight line
6549 if (squaredRatio < 0)
6555 var ratio = Math.sqrt(squaredRatio);
6559 ratio: ratio > 1000? -1 : ratio,
6567 Sets angle direction to clockwise (true) or counterclockwise (false).
6571 angleBegin - Starting angle for drawing the arc.
6572 angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6576 A Boolean instance describing the sense for drawing the HyperLine.
6578 function sense(angleBegin, angleEnd){
6579 return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6580 : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6588 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6592 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6593 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6594 pos - (object) An *x*, *y* object with the position to check.
6595 epsilon - (number) The dimension of the shape.
6599 EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6602 'contains': $.lambda(false)
6608 * File: Graph.Plot.js
6614 <Graph> rendering and animation methods.
6618 nodeHelper - <NodeHelper> object.
6619 edgeHelper - <EdgeHelper> object.
6622 //Default initializer
6623 initialize: function(viz, klass){
6625 this.config = viz.config;
6626 this.node = viz.config.Node;
6627 this.edge = viz.config.Edge;
6628 this.animation = new Animation;
6629 this.nodeTypes = new klass.Plot.NodeTypes;
6630 this.edgeTypes = new klass.Plot.EdgeTypes;
6631 this.labels = viz.labels;
6635 nodeHelper: NodeHelper,
6636 edgeHelper: EdgeHelper,
6639 //node/edge property parsers
6647 'lineWidth': 'number',
6648 'angularWidth':'number',
6650 'valueArray':'array-number',
6651 'dimArray':'array-number'
6652 //'colorArray':'array-color'
6655 //canvas specific parsers
6657 'globalAlpha': 'number',
6658 'fillStyle': 'color',
6659 'strokeStyle': 'color',
6660 'lineWidth': 'number',
6661 'shadowBlur': 'number',
6662 'shadowColor': 'color',
6663 'shadowOffsetX': 'number',
6664 'shadowOffsetY': 'number',
6665 'miterLimit': 'number'
6674 //Number interpolator
6675 'compute': function(from, to, delta) {
6676 return from + (to - from) * delta;
6679 //Position interpolators
6680 'moebius': function(elem, props, delta, vector) {
6681 var v = vector.scale(-delta);
6683 var x = v.x, y = v.y;
6684 var ans = elem.startPos
6685 .getc().moebiusTransformation(v);
6686 elem.pos.setc(ans.x, ans.y);
6691 'linear': function(elem, props, delta) {
6692 var from = elem.startPos.getc(true);
6693 var to = elem.endPos.getc(true);
6694 elem.pos.setc(this.compute(from.x, to.x, delta),
6695 this.compute(from.y, to.y, delta));
6698 'polar': function(elem, props, delta) {
6699 var from = elem.startPos.getp(true);
6700 var to = elem.endPos.getp();
6701 var ans = to.interpolate(from, delta);
6702 elem.pos.setp(ans.theta, ans.rho);
6705 //Graph's Node/Edge interpolators
6706 'number': function(elem, prop, delta, getter, setter) {
6707 var from = elem[getter](prop, 'start');
6708 var to = elem[getter](prop, 'end');
6709 elem[setter](prop, this.compute(from, to, delta));
6712 'color': function(elem, prop, delta, getter, setter) {
6713 var from = $.hexToRgb(elem[getter](prop, 'start'));
6714 var to = $.hexToRgb(elem[getter](prop, 'end'));
6715 var comp = this.compute;
6716 var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6717 parseInt(comp(from[1], to[1], delta)),
6718 parseInt(comp(from[2], to[2], delta))]);
6720 elem[setter](prop, val);
6723 'array-number': function(elem, prop, delta, getter, setter) {
6724 var from = elem[getter](prop, 'start'),
6725 to = elem[getter](prop, 'end'),
6727 for(var i=0, l=from.length; i<l; i++) {
6728 var fromi = from[i], toi = to[i];
6730 for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6731 curi.push(this.compute(fromi[j], toi[j], delta));
6735 cur.push(this.compute(fromi, toi, delta));
6738 elem[setter](prop, cur);
6741 'node': function(elem, props, delta, map, getter, setter) {
6744 var len = props.length;
6745 for(var i=0; i<len; i++) {
6747 this[map[pi]](elem, pi, delta, getter, setter);
6750 for(var pi in map) {
6751 this[map[pi]](elem, pi, delta, getter, setter);
6756 'edge': function(elem, props, delta, mapKey, getter, setter) {
6757 var adjs = elem.adjacencies;
6758 for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6761 'node-property': function(elem, props, delta) {
6762 this['node'](elem, props, delta, 'map', 'getData', 'setData');
6765 'edge-property': function(elem, props, delta) {
6766 this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6769 'label-property': function(elem, props, delta) {
6770 this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6773 'node-style': function(elem, props, delta) {
6774 this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6777 'edge-style': function(elem, props, delta) {
6778 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6786 Iteratively performs an action while refreshing the state of the visualization.
6790 options - (object) An object containing some sequence options described below
6791 condition - (function) A function returning a boolean instance in order to stop iterations.
6792 step - (function) A function to execute on each step of the iteration.
6793 onComplete - (function) A function to execute when the sequence finishes.
6794 duration - (number) Duration (in milliseconds) of each step.
6798 var rg = new $jit.RGraph(options);
6801 condition: function() {
6807 onComplete: function() {
6814 sequence: function(options) {
6817 condition: $.lambda(false),
6819 onComplete: $.empty,
6823 var interval = setInterval(function() {
6824 if(options.condition()) {
6827 clearInterval(interval);
6828 options.onComplete();
6830 that.viz.refresh(true);
6831 }, options.duration);
6837 Prepare graph position and other attribute values before performing an Animation.
6838 This method is used internally by the Toolkit.
6842 <Animation>, <Graph.Plot.animate>
6845 prepare: function(modes) {
6846 var graph = this.viz.graph,
6849 'getter': 'getData',
6853 'getter': 'getData',
6857 'getter': 'getCanvasStyle',
6858 'setter': 'setCanvasStyle'
6861 'getter': 'getCanvasStyle',
6862 'setter': 'setCanvasStyle'
6868 if($.type(modes) == 'array') {
6869 for(var i=0, len=modes.length; i < len; i++) {
6870 var elems = modes[i].split(':');
6871 m[elems.shift()] = elems;
6874 for(var p in modes) {
6875 if(p == 'position') {
6876 m[modes.position] = [];
6878 m[p] = $.splat(modes[p]);
6883 graph.eachNode(function(node) {
6884 node.startPos.set(node.pos);
6885 $.each(['node-property', 'node-style'], function(p) {
6888 for(var i=0, l=prop.length; i < l; i++) {
6889 node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6893 $.each(['edge-property', 'edge-style'], function(p) {
6896 node.eachAdjacency(function(adj) {
6897 for(var i=0, l=prop.length; i < l; i++) {
6898 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6910 Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6914 opt - (object) Animation options. The object properties are described below
6915 duration - (optional) Described in <Options.Fx>.
6916 fps - (optional) Described in <Options.Fx>.
6917 hideLabels - (optional|boolean) Whether to hide labels during the animation.
6918 modes - (required|object) An object with animation modes (described below).
6922 Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6923 They are represented by an object that has as keys main categories of properties to animate and as values a list
6924 of these specific properties. The properties are described below
6926 position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6927 node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6928 edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6929 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.
6930 node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6931 edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6935 var viz = new $jit.Viz(options);
6936 //...tweak some Data, CanvasStyles or LabelData properties...
6939 'position': 'linear',
6940 'node-property': ['width', 'height'],
6941 'node-style': 'shadowColor',
6942 'label-property': 'size'
6946 //...can also be written like this...
6949 'node-property:width:height',
6950 'node-style:shadowColor',
6951 'label-property:size'],
6956 animate: function(opt, versor) {
6957 opt = $.merge(this.viz.config, opt || {});
6961 interp = this.Interpolator,
6962 animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
6963 //prepare graph values
6964 var m = this.prepare(opt.modes);
6967 if(opt.hideLabels) this.labels.hideLabels(true);
6968 animation.setOptions($.extend(opt, {
6970 compute: function(delta) {
6971 graph.eachNode(function(node) {
6973 interp[p](node, m[p], delta, versor);
6976 that.plot(opt, this.$animating, delta);
6977 this.$animating = true;
6979 complete: function() {
6980 if(opt.hideLabels) that.labels.hideLabels(false);
6983 //TODO(nico): This shouldn't be here!
6984 //opt.onAfterCompute();
6992 Apply animation to node properties like color, width, height, dim, etc.
6996 options - Animation options. This object properties is described below
6997 elements - The Elements to be transformed. This is an object that has a properties
7001 //can also be an array of ids
7002 'id': 'id-of-node-to-transform',
7003 //properties to be modified. All properties are optional.
7005 'color': '#ccc', //some color
7006 'width': 10, //some width
7007 'height': 10, //some height
7008 'dim': 20, //some dim
7009 'lineWidth': 10 //some line width
7014 - _reposition_ Whether to recalculate positions and add a motion animation.
7015 This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7017 - _onComplete_ A method that is called when the animation completes.
7019 ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7023 var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7030 'transition': Trans.Quart.easeOut
7035 nodeFx: function(opt) {
7038 animation = this.nodeFxAnimation,
7039 options = $.merge(this.viz.config, {
7046 opt = $.merge(options, opt || {}, {
7047 onBeforeCompute: $.empty,
7048 onAfterCompute: $.empty
7050 //check if an animation is running
7051 animation.stopTimer();
7052 var props = opt.elements.properties;
7053 //set end values for nodes
7054 if(!opt.elements.id) {
7055 graph.eachNode(function(n) {
7056 for(var prop in props) {
7057 n.setData(prop, props[prop], 'end');
7061 var ids = $.splat(opt.elements.id);
7062 $.each(ids, function(id) {
7063 var n = graph.getNode(id);
7065 for(var prop in props) {
7066 n.setData(prop, props[prop], 'end');
7073 for(var prop in props) propnames.push(prop);
7074 //add node properties modes
7075 var modes = ['node-property:' + propnames.join(':')];
7076 //set new node positions
7077 if(opt.reposition) {
7078 modes.push('linear');
7082 this.animate($.merge(opt, {
7096 opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7101 var viz = new $jit.Viz(options);
7106 plot: function(opt, animating) {
7109 canvas = viz.canvas,
7112 ctx = canvas.getCtx(),
7114 opt = opt || this.viz.controller;
7116 opt.clearCanvas && canvas.clear();
7118 var root = aGraph.getNode(id);
7121 var T = !!root.visited;
7122 aGraph.eachNode(function(node) {
7123 var nodeAlpha = node.getData('alpha');
7124 node.eachAdjacency(function(adj) {
7125 var nodeTo = adj.nodeTo;
7126 if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7127 !animating && opt.onBeforePlotLine(adj);
7128 that.plotLine(adj, canvas, animating);
7129 !animating && opt.onAfterPlotLine(adj);
7133 !animating && opt.onBeforePlotNode(node);
7134 that.plotNode(node, canvas, animating);
7135 !animating && opt.onAfterPlotNode(node);
7137 if(!that.labelsHidden && opt.withLabels) {
7138 if(node.drawn && nodeAlpha >= 0.95) {
7139 that.labels.plotLabel(canvas, node, opt);
7141 that.labels.hideLabel(node, false);
7151 plotTree: function(node, opt, animating) {
7154 canvas = viz.canvas,
7155 config = this.config,
7156 ctx = canvas.getCtx();
7157 var nodeAlpha = node.getData('alpha');
7158 node.eachSubnode(function(elem) {
7159 if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7160 var adj = node.getAdjacency(elem.id);
7161 !animating && opt.onBeforePlotLine(adj);
7162 that.plotLine(adj, canvas, animating);
7163 !animating && opt.onAfterPlotLine(adj);
7164 that.plotTree(elem, opt, animating);
7168 !animating && opt.onBeforePlotNode(node);
7169 this.plotNode(node, canvas, animating);
7170 !animating && opt.onAfterPlotNode(node);
7171 if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7172 this.labels.plotLabel(canvas, node, opt);
7174 this.labels.hideLabel(node, false);
7176 this.labels.hideLabel(node, true);
7183 Plots a <Graph.Node>.
7187 node - (object) A <Graph.Node>.
7188 canvas - (object) A <Canvas> element.
7191 plotNode: function(node, canvas, animating) {
7192 var f = node.getData('type'),
7193 ctxObj = this.node.CanvasStyles;
7195 var width = node.getData('lineWidth'),
7196 color = node.getData('color'),
7197 alpha = node.getData('alpha'),
7198 ctx = canvas.getCtx();
7200 ctx.lineWidth = width;
7201 ctx.fillStyle = ctx.strokeStyle = color;
7202 ctx.globalAlpha = alpha;
7204 for(var s in ctxObj) {
7205 ctx[s] = node.getCanvasStyle(s);
7208 this.nodeTypes[f].render.call(this, node, canvas, animating);
7216 Plots a <Graph.Adjacence>.
7220 adj - (object) A <Graph.Adjacence>.
7221 canvas - (object) A <Canvas> instance.
7224 plotLine: function(adj, canvas, animating) {
7225 var f = adj.getData('type'),
7226 ctxObj = this.edge.CanvasStyles;
7228 var width = adj.getData('lineWidth'),
7229 color = adj.getData('color'),
7230 ctx = canvas.getCtx(),
7231 nodeFrom = adj.nodeFrom,
7232 nodeTo = adj.nodeTo;
7235 ctx.lineWidth = width;
7236 ctx.fillStyle = ctx.strokeStyle = color;
7237 ctx.globalAlpha = Math.min(nodeFrom.getData('alpha'),
7238 nodeTo.getData('alpha'),
7239 adj.getData('alpha'));
7241 for(var s in ctxObj) {
7242 ctx[s] = adj.getCanvasStyle(s);
7245 this.edgeTypes[f].render.call(this, adj, canvas, animating);
7253 Object: Graph.Plot3D
7255 <Graph> 3D rendering and animation methods.
7259 nodeHelper - <NodeHelper> object.
7260 edgeHelper - <EdgeHelper> object.
7263 Graph.Plot3D = $.merge(Graph.Plot, {
7265 'linear': function(elem, props, delta) {
7266 var from = elem.startPos.getc(true);
7267 var to = elem.endPos.getc(true);
7268 elem.pos.setc(this.compute(from.x, to.x, delta),
7269 this.compute(from.y, to.y, delta),
7270 this.compute(from.z, to.z, delta));
7274 plotNode: function(node, canvas) {
7275 if(node.getData('type') == 'none') return;
7276 this.plotElement(node, canvas, {
7277 getAlpha: function() {
7278 return node.getData('alpha');
7283 plotLine: function(adj, canvas) {
7284 if(adj.getData('type') == 'none') return;
7285 this.plotElement(adj, canvas, {
7286 getAlpha: function() {
7287 return Math.min(adj.nodeFrom.getData('alpha'),
7288 adj.nodeTo.getData('alpha'),
7289 adj.getData('alpha'));
7294 plotElement: function(elem, canvas, opt) {
7295 var gl = canvas.getCtx(),
7296 viewMatrix = new Matrix4,
7297 lighting = canvas.config.Scene.Lighting,
7298 wcanvas = canvas.canvases[0],
7299 program = wcanvas.program,
7300 camera = wcanvas.camera;
7302 if(!elem.geometry) {
7303 elem.geometry = new O3D[elem.getData('type')];
7305 elem.geometry.update(elem);
7306 if(!elem.webGLVertexBuffer) {
7311 geom = elem.geometry;
7313 for(var i=0, vs=geom.vertices, fs=geom.faces, fsl=fs.length; i<fsl; i++) {
7318 v4 = face.d? vs[face.d] : false,
7321 vertices.push(v1.x, v1.y, v1.z);
7322 vertices.push(v2.x, v2.y, v2.z);
7323 vertices.push(v3.x, v3.y, v3.z);
7324 if(v4) vertices.push(v4.x, v4.y, v4.z);
7326 normals.push(n.x, n.y, n.z);
7327 normals.push(n.x, n.y, n.z);
7328 normals.push(n.x, n.y, n.z);
7329 if(v4) normals.push(n.x, n.y, n.z);
7331 faces.push(vertexIndex, vertexIndex +1, vertexIndex +2);
7333 faces.push(vertexIndex, vertexIndex +2, vertexIndex +3);
7339 //create and store vertex data
7340 elem.webGLVertexBuffer = gl.createBuffer();
7341 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLVertexBuffer);
7342 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
7343 //create and store faces index data
7344 elem.webGLFaceBuffer = gl.createBuffer();
7345 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem.webGLFaceBuffer);
7346 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(faces), gl.STATIC_DRAW);
7347 elem.webGLFaceCount = faces.length;
7348 //calculate vertex normals and store them
7349 elem.webGLNormalBuffer = gl.createBuffer();
7350 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLNormalBuffer);
7351 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
7353 viewMatrix.multiply(camera.matrix, elem.geometry.matrix);
7355 gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.flatten());
7356 gl.uniformMatrix4fv(program.projectionMatrix, false, camera.projectionMatrix.flatten());
7357 //send normal matrix for lighting
7358 var normalMatrix = Matrix4.makeInvert(viewMatrix);
7359 normalMatrix.$transpose();
7360 gl.uniformMatrix4fv(program.normalMatrix, false, normalMatrix.flatten());
7362 var color = $.hexToRgb(elem.getData('color'));
7363 color.push(opt.getAlpha());
7364 gl.uniform4f(program.color, color[0] / 255, color[1] / 255, color[2] / 255, color[3]);
7365 //send lighting data
7366 gl.uniform1i(program.enableLighting, lighting.enable);
7367 if(lighting.enable) {
7368 //set ambient light color
7369 if(lighting.ambient) {
7370 var acolor = lighting.ambient;
7371 gl.uniform3f(program.ambientColor, acolor[0], acolor[1], acolor[2]);
7373 //set directional light
7374 if(lighting.directional) {
7375 var dir = lighting.directional,
7377 pos = dir.direction,
7378 vd = new Vector3(pos.x, pos.y, pos.z).normalize().$scale(-1);
7379 gl.uniform3f(program.lightingDirection, vd.x, vd.y, vd.z);
7380 gl.uniform3f(program.directionalColor, color[0], color[1], color[2]);
7383 //send vertices data
7384 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLVertexBuffer);
7385 gl.vertexAttribPointer(program.position, 3, gl.FLOAT, false, 0, 0);
7387 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLNormalBuffer);
7388 gl.vertexAttribPointer(program.normal, 3, gl.FLOAT, false, 0, 0);
7390 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem.webGLFaceBuffer );
7391 gl.drawElements(gl.TRIANGLES, elem.webGLFaceCount, gl.UNSIGNED_SHORT, 0);
7397 * File: Graph.Label.js
7404 An interface for plotting/hiding/showing labels.
7408 This is a generic interface for plotting/hiding/showing labels.
7409 The <Graph.Label> interface is implemented in multiple ways to provide
7410 different label types.
7412 For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7413 HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7414 The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7416 All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7422 Class: Graph.Label.Native
7424 Implements labels natively, using the Canvas text API.
7426 Graph.Label.Native = new Class({
7427 initialize: function(viz) {
7434 Plots a label for a given node.
7438 canvas - (object) A <Canvas> instance.
7439 node - (object) A <Graph.Node>.
7440 controller - (object) A configuration object.
7445 var viz = new $jit.Viz(options);
7446 var node = viz.graph.getNode('nodeId');
7447 viz.labels.plotLabel(viz.canvas, node, viz.config);
7450 plotLabel: function(canvas, node, controller) {
7451 var ctx = canvas.getCtx();
7452 var pos = node.pos.getc(true);
7454 ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7455 ctx.textAlign = node.getLabelData('textAlign');
7456 ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7457 ctx.textBaseline = node.getLabelData('textBaseline');
7459 this.renderLabel(canvas, node, controller);
7465 Does the actual rendering of the label in the canvas. The default
7466 implementation renders the label close to the position of the node, this
7467 method should be overriden to position the labels differently.
7471 canvas - A <Canvas> instance.
7472 node - A <Graph.Node>.
7473 controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7475 renderLabel: function(canvas, node, controller) {
7476 var ctx = canvas.getCtx();
7477 var pos = node.pos.getc(true);
7478 ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7486 Class: Graph.Label.DOM
7488 Abstract Class implementing some DOM label methods.
7492 <Graph.Label.HTML> and <Graph.Label.SVG>.
7495 Graph.Label.DOM = new Class({
7496 //A flag value indicating if node labels are being displayed or not.
7497 labelsHidden: false,
7499 labelContainer: false,
7500 //Label elements hash.
7504 Method: getLabelContainer
7506 Lazy fetcher for the label container.
7510 The label container DOM element.
7515 var viz = new $jit.Viz(options);
7516 var labelContainer = viz.labels.getLabelContainer();
7517 alert(labelContainer.innerHTML);
7520 getLabelContainer: function() {
7521 return this.labelContainer ?
7522 this.labelContainer :
7523 this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7529 Lazy fetcher for the label element.
7533 id - (string) The label id (which is also a <Graph.Node> id).
7542 var viz = new $jit.Viz(options);
7543 var label = viz.labels.getLabel('someid');
7544 alert(label.innerHTML);
7548 getLabel: function(id) {
7549 return (id in this.labels && this.labels[id] != null) ?
7551 this.labels[id] = document.getElementById(id);
7557 Hides all labels (by hiding the label container).
7561 hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7565 var viz = new $jit.Viz(options);
7566 rg.labels.hideLabels(true);
7570 hideLabels: function (hide) {
7571 var container = this.getLabelContainer();
7573 container.style.display = 'none';
7575 container.style.display = '';
7576 this.labelsHidden = hide;
7582 Clears the label container.
7584 Useful when using a new visualization with the same canvas element/widget.
7588 force - (boolean) Forces deletion of all labels.
7592 var viz = new $jit.Viz(options);
7593 viz.labels.clearLabels();
7596 clearLabels: function(force) {
7597 for(var id in this.labels) {
7598 if (force || !this.viz.graph.hasNode(id)) {
7599 this.disposeLabel(id);
7600 delete this.labels[id];
7606 Method: disposeLabel
7612 id - (string) A label id (which generally is also a <Graph.Node> id).
7616 var viz = new $jit.Viz(options);
7617 viz.labels.disposeLabel('labelid');
7620 disposeLabel: function(id) {
7621 var elem = this.getLabel(id);
7622 if(elem && elem.parentNode) {
7623 elem.parentNode.removeChild(elem);
7630 Hides the corresponding <Graph.Node> label.
7634 node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7635 show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7639 var rg = new $jit.Viz(options);
7640 viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7643 hideLabel: function(node, show) {
7644 node = $.splat(node);
7645 var st = show ? "" : "none", lab, that = this;
7646 $.each(node, function(n) {
7647 var lab = that.getLabel(n.id);
7649 lab.style.display = st;
7657 Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7661 pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7662 canvas - A <Canvas> instance.
7666 A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7669 fitsInCanvas: function(pos, canvas) {
7670 var size = canvas.getSize();
7671 if(pos.x >= size.width || pos.x < 0
7672 || pos.y >= size.height || pos.y < 0) return false;
7678 Class: Graph.Label.HTML
7680 Implements HTML labels.
7684 All <Graph.Label.DOM> methods.
7687 Graph.Label.HTML = new Class({
7688 Implements: Graph.Label.DOM,
7693 Plots a label for a given node.
7697 canvas - (object) A <Canvas> instance.
7698 node - (object) A <Graph.Node>.
7699 controller - (object) A configuration object.
7704 var viz = new $jit.Viz(options);
7705 var node = viz.graph.getNode('nodeId');
7706 viz.labels.plotLabel(viz.canvas, node, viz.config);
7711 plotLabel: function(canvas, node, controller) {
7712 var id = node.id, tag = this.getLabel(id);
7714 if(!tag && !(tag = document.getElementById(id))) {
7715 tag = document.createElement('div');
7716 var container = this.getLabelContainer();
7718 tag.className = 'node';
7719 tag.style.position = 'absolute';
7720 controller.onCreateLabel(tag, node);
7721 container.appendChild(tag);
7722 this.labels[node.id] = tag;
7725 this.placeLabel(tag, node, controller);
7730 Class: Graph.Label.SVG
7732 Implements SVG labels.
7736 All <Graph.Label.DOM> methods.
7738 Graph.Label.SVG = new Class({
7739 Implements: Graph.Label.DOM,
7744 Plots a label for a given node.
7748 canvas - (object) A <Canvas> instance.
7749 node - (object) A <Graph.Node>.
7750 controller - (object) A configuration object.
7755 var viz = new $jit.Viz(options);
7756 var node = viz.graph.getNode('nodeId');
7757 viz.labels.plotLabel(viz.canvas, node, viz.config);
7762 plotLabel: function(canvas, node, controller) {
7763 var id = node.id, tag = this.getLabel(id);
7764 if(!tag && !(tag = document.getElementById(id))) {
7765 var ns = 'http://www.w3.org/2000/svg';
7766 tag = document.createElementNS(ns, 'svg:text');
7767 var tspan = document.createElementNS(ns, 'svg:tspan');
7768 tag.appendChild(tspan);
7769 var container = this.getLabelContainer();
7770 tag.setAttribute('id', id);
7771 tag.setAttribute('class', 'node');
7772 container.appendChild(tag);
7773 controller.onCreateLabel(tag, node);
7774 this.labels[node.id] = tag;
7776 this.placeLabel(tag, node, controller);
7782 Graph.Geom = new Class({
7784 initialize: function(viz) {
7786 this.config = viz.config;
7787 this.node = viz.config.Node;
7788 this.edge = viz.config.Edge;
7791 Applies a translation to the tree.
7795 pos - A <Complex> number specifying translation vector.
7796 prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7801 st.geom.translate(new Complex(300, 100), 'end');
7804 translate: function(pos, prop) {
7805 prop = $.splat(prop);
7806 this.viz.graph.eachNode(function(elem) {
7807 $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7811 Hides levels of the tree until it properly fits in canvas.
7813 setRightLevelToShow: function(node, canvas, callback) {
7814 var level = this.getRightLevelToShow(node, canvas),
7815 fx = this.viz.labels,
7822 node.eachLevel(0, this.config.levelsToShow, function(n) {
7823 var d = n._depth - node._depth;
7829 fx.hideLabel(n, false);
7841 Returns the right level to show for the current tree in order to fit in canvas.
7843 getRightLevelToShow: function(node, canvas) {
7844 var config = this.config;
7845 var level = config.levelsToShow;
7846 var constrained = config.constrained;
7847 if(!constrained) return level;
7848 while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7861 Provides methods for loading and serving JSON data.
7864 construct: function(json) {
7865 var isGraph = ($.type(json) == 'array');
7866 var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7869 (function (ans, json) {
7872 for(var i=0, ch = json.children; i<ch.length; i++) {
7873 ans.addAdjacence(json, ch[i]);
7874 arguments.callee(ans, ch[i]);
7880 (function (ans, json) {
7881 var getNode = function(id) {
7882 for(var i=0, l=json.length; i<l; i++) {
7883 if(json[i].id == id) {
7887 // The node was not defined in the JSON
7893 return ans.addNode(newNode);
7896 for(var i=0, l=json.length; i<l; i++) {
7897 ans.addNode(json[i]);
7898 var adj = json[i].adjacencies;
7900 for(var j=0, lj=adj.length; j<lj; j++) {
7901 var node = adj[j], data = {};
7902 if(typeof adj[j] != 'string') {
7903 data = $.merge(node.data, {});
7906 ans.addAdjacence(json[i], getNode(node), data);
7918 Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7920 A JSON tree or graph structure consists of nodes, each having as properties
7922 id - (string) A unique identifier for the node
7923 name - (string) A node's name
7924 data - (object) The data optional property contains a hash (i.e {})
7925 where you can store all the information you want about this node.
7927 For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7933 "id": "aUniqueIdentifier",
7934 "name": "usually a nodes name",
7936 "some key": "some value",
7937 "some other key": "some other value"
7939 "children": [ *other nodes or empty* ]
7943 JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7944 For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7946 There are two types of *Graph* structures, *simple* and *extended* graph structures.
7948 For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7949 id of the node connected to the main node.
7956 "id": "aUniqueIdentifier",
7957 "name": "usually a nodes name",
7959 "some key": "some value",
7960 "some other key": "some other value"
7962 "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7965 'other nodes go here...'
7969 For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7971 nodeTo - (string) The other node connected by this adjacency.
7972 data - (object) A data property, where we can store custom key/value information.
7979 "id": "aUniqueIdentifier",
7980 "name": "usually a nodes name",
7982 "some key": "some value",
7983 "some other key": "some other value"
7988 data: {} //put whatever you want here
7990 'other adjacencies go here...'
7993 'other nodes go here...'
7997 About the data property:
7999 As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
8000 You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
8001 have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
8003 For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
8004 <Options.Node> will override the general value for that option with that particular value. For this to work
8005 however, you do have to set *overridable = true* in <Options.Node>.
8007 The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
8008 if <Options.Edge> has *overridable = true*.
8010 When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
8011 since this is the value which will be taken into account when creating the layout.
8012 The same thing goes for the *$color* parameter.
8014 In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
8015 *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
8016 canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
8017 to the *shadowBlur* property.
8019 These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
8020 by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
8022 Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
8023 information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
8025 loadJSON Parameters:
8027 json - A JSON Tree or Graph structure.
8028 i - For Graph structures only. Sets the indexed node as root for the visualization.
8031 loadJSON: function(json, i) {
8033 //if they're canvas labels erase them.
8034 if(this.labels && this.labels.clearLabels) {
8035 this.labels.clearLabels(true);
8037 this.graph = this.construct(json);
8038 if($.type(json) != 'array'){
8039 this.root = json.id;
8041 this.root = json[i? i : 0].id;
8048 Returns a JSON tree/graph structure from the visualization's <Graph>.
8049 See <Loader.loadJSON> for the graph formats available.
8057 type - (string) Default's "tree". The type of the JSON structure to be returned.
8058 Possible options are "tree" or "graph".
8060 toJSON: function(type) {
8061 type = type || "tree";
8062 if(type == 'tree') {
8064 var rootNode = this.graph.getNode(this.root);
8065 var ans = (function recTree(node) {
8068 ans.name = node.name;
8069 ans.data = node.data;
8071 node.eachSubnode(function(n) {
8072 ch.push(recTree(n));
8080 var T = !!this.graph.getNode(this.root).visited;
8081 this.graph.eachNode(function(node) {
8083 ansNode.id = node.id;
8084 ansNode.name = node.name;
8085 ansNode.data = node.data;
8087 node.eachAdjacency(function(adj) {
8088 var nodeTo = adj.nodeTo;
8089 if(!!nodeTo.visited === T) {
8091 ansAdj.nodeTo = nodeTo.id;
8092 ansAdj.data = adj.data;
8096 ansNode.adjacencies = adjs;
8110 * Implements base Tree and Graph layouts.
8114 * Implements base Tree and Graph layouts like Radial, Tree, etc.
8121 * Parent object for common layouts.
8124 var Layouts = $jit.Layouts = {};
8127 //Some util shared layout functions are defined here.
8131 compute: function(graph, prop, opt) {
8132 this.initializeLabel(opt);
8133 var label = this.label, style = label.style;
8134 graph.eachNode(function(n) {
8135 var autoWidth = n.getData('autoWidth'),
8136 autoHeight = n.getData('autoHeight');
8137 if(autoWidth || autoHeight) {
8138 //delete dimensions since these are
8139 //going to be overridden now.
8140 delete n.data.$width;
8141 delete n.data.$height;
8144 var width = n.getData('width'),
8145 height = n.getData('height');
8146 //reset label dimensions
8147 style.width = autoWidth? 'auto' : width + 'px';
8148 style.height = autoHeight? 'auto' : height + 'px';
8150 //TODO(nico) should let the user choose what to insert here.
8151 label.innerHTML = n.name;
8153 var offsetWidth = label.offsetWidth,
8154 offsetHeight = label.offsetHeight;
8155 var type = n.getData('type');
8156 if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8157 n.setData('width', offsetWidth);
8158 n.setData('height', offsetHeight);
8160 var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8161 n.setData('width', dim);
8162 n.setData('height', dim);
8163 n.setData('dim', dim);
8169 initializeLabel: function(opt) {
8171 this.label = document.createElement('div');
8172 document.body.appendChild(this.label);
8174 this.setLabelStyles(opt);
8177 setLabelStyles: function(opt) {
8178 $.extend(this.label.style, {
8179 'visibility': 'hidden',
8180 'position': 'absolute',
8184 this.label.className = 'jit-autoadjust-label';
8190 * Class: Layouts.Tree
8192 * Implements a Tree Layout.
8200 * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8203 Layouts.Tree = (function() {
8205 var slice = Array.prototype.slice;
8208 Calculates the max width and height nodes for a tree level
8210 function getBoundaries(graph, config, level, orn, prop) {
8211 var dim = config.Node;
8212 var multitree = config.multitree;
8213 if (dim.overridable) {
8215 graph.eachNode(function(n) {
8216 if (n._depth == level
8217 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8218 var dw = n.getData('width', prop);
8219 var dh = n.getData('height', prop);
8220 w = (w < dw) ? dw : w;
8221 h = (h < dh) ? dh : h;
8225 'width' : w < 0 ? dim.width : w,
8226 'height' : h < 0 ? dim.height : h
8234 function movetree(node, prop, val, orn) {
8235 var p = (orn == "left" || orn == "right") ? "y" : "x";
8236 node.getPos(prop)[p] += val;
8240 function moveextent(extent, val) {
8242 $.each(extent, function(elem) {
8243 elem = slice.call(elem);
8252 function merge(ps, qs) {
8257 var p = ps.shift(), q = qs.shift();
8258 return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8262 function mergelist(ls, def) {
8267 return mergelist(ls, merge(ps, def));
8271 function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8272 if (ext1.length <= i || ext2.length <= i)
8275 var p = ext1[i][1], q = ext2[i][0];
8276 return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8277 + subtreeOffset, p - q + siblingOffset);
8281 function fitlistl(es, subtreeOffset, siblingOffset) {
8282 function $fitlistl(acc, es, i) {
8285 var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8286 return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8289 return $fitlistl( [], es, 0);
8293 function fitlistr(es, subtreeOffset, siblingOffset) {
8294 function $fitlistr(acc, es, i) {
8297 var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8298 return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8301 es = slice.call(es);
8302 var ans = $fitlistr( [], es.reverse(), 0);
8303 return ans.reverse();
8307 function fitlist(es, subtreeOffset, siblingOffset, align) {
8308 var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8309 subtreeOffset, siblingOffset);
8311 if (align == "left")
8313 else if (align == "right")
8316 for ( var i = 0, ans = []; i < esl.length; i++) {
8317 ans[i] = (esl[i] + esr[i]) / 2;
8323 function design(graph, node, prop, config, orn) {
8324 var multitree = config.multitree;
8325 var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8326 var ind = +(orn == "left" || orn == "right");
8327 var p = auxp[ind], notp = auxp[1 - ind];
8329 var cnode = config.Node;
8330 var s = auxs[ind], nots = auxs[1 - ind];
8332 var siblingOffset = config.siblingOffset;
8333 var subtreeOffset = config.subtreeOffset;
8334 var align = config.align;
8336 function $design(node, maxsize, acum) {
8337 var sval = node.getData(s, prop);
8338 var notsval = maxsize
8339 || (node.getData(nots, prop));
8341 var trees = [], extents = [], chmaxsize = false;
8342 var chacum = notsval + config.levelDistance;
8343 node.eachSubnode(function(n) {
8345 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8348 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8350 var s = $design(n, chmaxsize[nots], acum + chacum);
8352 extents.push(s.extent);
8355 var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8356 for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8357 movetree(trees[i], prop, positions[i], orn);
8358 pextents.push(moveextent(extents[i], positions[i]));
8360 var resultextent = [ [ -sval / 2, sval / 2 ] ]
8361 .concat(mergelist(pextents));
8362 node.getPos(prop)[p] = 0;
8364 if (orn == "top" || orn == "left") {
8365 node.getPos(prop)[notp] = acum;
8367 node.getPos(prop)[notp] = -acum;
8372 extent : resultextent
8376 $design(node, false, 0);
8384 Computes nodes' positions.
8387 compute : function(property, computeLevels) {
8388 var prop = property || 'start';
8389 var node = this.graph.getNode(this.root);
8395 NodeDim.compute(this.graph, prop, this.config);
8396 if (!!computeLevels || !("_depth" in node)) {
8397 this.graph.computeLevels(this.root, 0, "ignore");
8400 this.computePositions(node, prop);
8403 computePositions : function(node, prop) {
8404 var config = this.config;
8405 var multitree = config.multitree;
8406 var align = config.align;
8407 var indent = align !== 'center' && config.indent;
8408 var orn = config.orientation;
8409 var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8411 $.each(orns, function(orn) {
8413 design(that.graph, node, prop, that.config, orn, prop);
8414 var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8416 (function red(node) {
8417 node.eachSubnode(function(n) {
8419 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8421 n.getPos(prop)[i] += node.getPos(prop)[i];
8423 n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8436 * File: Spacetree.js
8442 A Tree layout with advanced contraction and expansion animations.
8446 SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8447 <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8449 Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8453 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.
8457 All <Loader> methods
8459 Constructor Options:
8461 Inherits options from
8464 - <Options.Controller>
8471 - <Options.NodeStyles>
8472 - <Options.Navigation>
8474 Additionally, there are other parameters and some default values changed
8476 constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8477 levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8478 levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8479 Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8480 offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8481 offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8482 duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8484 Instance Properties:
8486 canvas - Access a <Canvas> instance.
8487 graph - Access a <Graph> instance.
8488 op - Access a <ST.Op> instance.
8489 fx - Access a <ST.Plot> instance.
8490 labels - Access a <ST.Label> interface implementation.
8494 $jit.ST= (function() {
8495 // Define some private methods first...
8497 var nodesInPath = [];
8498 // Nodes to contract
8499 function getNodesToHide(node) {
8500 node = node || this.clickedNode;
8501 if(!this.config.constrained) {
8504 var Geom = this.geom;
8505 var graph = this.graph;
8506 var canvas = this.canvas;
8507 var level = node._depth, nodeArray = [];
8508 graph.eachNode(function(n) {
8509 if(n.exist && !n.selected) {
8510 if(n.isDescendantOf(node.id)) {
8511 if(n._depth <= level) nodeArray.push(n);
8517 var leafLevel = Geom.getRightLevelToShow(node, canvas);
8518 node.eachLevel(leafLevel, leafLevel, function(n) {
8519 if(n.exist && !n.selected) nodeArray.push(n);
8522 for (var i = 0; i < nodesInPath.length; i++) {
8523 var n = this.graph.getNode(nodesInPath[i]);
8524 if(!n.isDescendantOf(node.id)) {
8531 function getNodesToShow(node) {
8532 var nodeArray = [], config = this.config;
8533 node = node || this.clickedNode;
8534 this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8535 if(config.multitree && !('$orn' in n.data)
8536 && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8538 } else if(n.drawn && !n.anySubnode("drawn")) {
8544 // Now define the actual class.
8547 Implements: [Loader, Extras, Layouts.Tree],
8549 initialize: function(controller) {
8564 this.controller = this.config = $.merge(
8565 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8566 "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8568 var canvasConfig = this.config;
8569 if(canvasConfig.useCanvas) {
8570 this.canvas = canvasConfig.useCanvas;
8571 this.config.labelContainer = this.canvas.id + '-label';
8573 if(canvasConfig.background) {
8574 canvasConfig.background = $.merge({
8576 }, canvasConfig.background);
8578 this.canvas = new Canvas(this, canvasConfig);
8579 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8582 this.graphOptions = {
8585 this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8586 this.labels = new $ST.Label[canvasConfig.Label.type](this);
8587 this.fx = new $ST.Plot(this, $ST);
8588 this.op = new $ST.Op(this);
8589 this.group = new $ST.Group(this);
8590 this.geom = new $ST.Geom(this);
8591 this.clickedNode= null;
8592 // initialize extras
8593 this.initializeExtras();
8599 Plots the <ST>. This is a shortcut to *fx.plot*.
8602 plot: function() { this.fx.plot(this.controller); },
8606 Method: switchPosition
8608 Switches the tree orientation.
8612 pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8613 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.
8614 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8619 st.switchPosition("right", "animate", {
8620 onComplete: function() {
8621 alert('completed!');
8626 switchPosition: function(pos, method, onComplete) {
8627 var Geom = this.geom, Plot = this.fx, that = this;
8631 onComplete: function() {
8632 Geom.switchOrientation(pos);
8633 that.compute('end', false);
8635 if(method == 'animate') {
8636 that.onClick(that.clickedNode.id, onComplete);
8637 } else if(method == 'replot') {
8638 that.select(that.clickedNode.id, onComplete);
8646 Method: switchAlignment
8648 Switches the tree alignment.
8652 align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8653 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.
8654 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8659 st.switchAlignment("right", "animate", {
8660 onComplete: function() {
8661 alert('completed!');
8666 switchAlignment: function(align, method, onComplete) {
8667 this.config.align = align;
8668 if(method == 'animate') {
8669 this.select(this.clickedNode.id, onComplete);
8670 } else if(method == 'replot') {
8671 this.onClick(this.clickedNode.id, onComplete);
8676 Method: addNodeInPath
8678 Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8683 id - (string) A <Graph.Node> id.
8688 st.addNodeInPath("nodeId");
8691 addNodeInPath: function(id) {
8692 nodesInPath.push(id);
8693 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8697 Method: clearNodesInPath
8699 Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8708 st.clearNodesInPath();
8711 clearNodesInPath: function(id) {
8712 nodesInPath.length = 0;
8713 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8719 Computes positions and plots the tree.
8722 refresh: function() {
8724 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8727 reposition: function() {
8728 this.graph.computeLevels(this.root, 0, "ignore");
8729 this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8730 this.graph.eachNode(function(n) {
8731 if(n.exist) n.drawn = true;
8733 this.compute('end');
8736 requestNodes: function(node, onComplete) {
8737 var handler = $.merge(this.controller, onComplete),
8738 lev = this.config.levelsToShow;
8739 if(handler.request) {
8740 var leaves = [], d = node._depth;
8741 node.eachLevel(0, lev, function(n) {
8745 n._level = lev - (n._depth - d);
8748 this.group.requestNodes(leaves, handler);
8751 handler.onComplete();
8754 contract: function(onComplete, switched) {
8755 var orn = this.config.orientation;
8756 var Geom = this.geom, Group = this.group;
8757 if(switched) Geom.switchOrientation(switched);
8758 var nodes = getNodesToHide.call(this);
8759 if(switched) Geom.switchOrientation(orn);
8760 Group.contract(nodes, $.merge(this.controller, onComplete));
8763 move: function(node, onComplete) {
8764 this.compute('end', false);
8765 var move = onComplete.Move, offset = {
8770 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8772 this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8775 expand: function (node, onComplete) {
8776 var nodeArray = getNodesToShow.call(this, node);
8777 this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8780 selectPath: function(node) {
8782 this.graph.eachNode(function(n) { n.selected = false; });
8783 function path(node) {
8784 if(node == null || node.selected) return;
8785 node.selected = true;
8786 $.each(that.group.getSiblings([node])[node.id],
8791 var parents = node.getParents();
8792 parents = (parents.length > 0)? parents[0] : null;
8795 for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8796 path(this.graph.getNode(ns[i]));
8803 Switches the current root node. Changes the topology of the Tree.
8806 id - (string) The id of the node to be set as root.
8807 method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8808 onComplete - (optional|object) An action to perform after the animation (if any).
8813 st.setRoot('nodeId', 'animate', {
8814 onComplete: function() {
8820 setRoot: function(id, method, onComplete) {
8821 if(this.busy) return;
8823 var that = this, canvas = this.canvas;
8824 var rootNode = this.graph.getNode(this.root);
8825 var clickedNode = this.graph.getNode(id);
8826 function $setRoot() {
8827 if(this.config.multitree && clickedNode.data.$orn) {
8828 var orn = clickedNode.data.$orn;
8835 rootNode.data.$orn = opp;
8836 (function tag(rootNode) {
8837 rootNode.eachSubnode(function(n) {
8844 delete clickedNode.data.$orn;
8847 this.clickedNode = clickedNode;
8848 this.graph.computeLevels(this.root, 0, "ignore");
8849 this.geom.setRightLevelToShow(clickedNode, canvas, {
8851 onShow: function(node) {
8854 node.setData('alpha', 1, 'end');
8855 node.setData('alpha', 0);
8856 node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8860 this.compute('end');
8863 modes: ['linear', 'node-property:alpha'],
8864 onComplete: function() {
8867 onComplete: function() {
8868 onComplete && onComplete.onComplete();
8875 // delete previous orientations (if any)
8876 delete rootNode.data.$orns;
8878 if(method == 'animate') {
8879 $setRoot.call(this);
8880 that.selectPath(clickedNode);
8881 } else if(method == 'replot') {
8882 $setRoot.call(this);
8883 this.select(this.root);
8893 subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8894 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.
8895 onComplete - (optional|object) An action to perform after the animation (if any).
8900 st.addSubtree(json, 'animate', {
8901 onComplete: function() {
8907 addSubtree: function(subtree, method, onComplete) {
8908 if(method == 'replot') {
8909 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8910 } else if (method == 'animate') {
8911 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8916 Method: removeSubtree
8921 id - (string) The _id_ of the subtree to be removed.
8922 removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8923 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.
8924 onComplete - (optional|object) An action to perform after the animation (if any).
8929 st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8930 onComplete: function() {
8937 removeSubtree: function(id, removeRoot, method, onComplete) {
8938 var node = this.graph.getNode(id), subids = [];
8939 node.eachLevel(+!removeRoot, false, function(n) {
8942 if(method == 'replot') {
8943 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8944 } else if (method == 'animate') {
8945 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8952 Selects a node in the <ST> without performing an animation. Useful when selecting
8953 nodes which are currently hidden or deep inside the tree.
8956 id - (string) The id of the node to select.
8957 onComplete - (optional|object) an onComplete callback.
8961 st.select('mynodeid', {
8962 onComplete: function() {
8968 select: function(id, onComplete) {
8969 var group = this.group, geom = this.geom;
8970 var node= this.graph.getNode(id), canvas = this.canvas;
8971 var root = this.graph.getNode(this.root);
8972 var complete = $.merge(this.controller, onComplete);
8975 complete.onBeforeCompute(node);
8976 this.selectPath(node);
8977 this.clickedNode= node;
8978 this.requestNodes(node, {
8979 onComplete: function(){
8980 group.hide(group.prepare(getNodesToHide.call(that)), complete);
8981 geom.setRightLevelToShow(node, canvas);
8982 that.compute("current");
8983 that.graph.eachNode(function(n) {
8984 var pos = n.pos.getc(true);
8985 n.startPos.setc(pos.x, pos.y);
8986 n.endPos.setc(pos.x, pos.y);
8989 var offset = { x: complete.offsetX, y: complete.offsetY };
8990 that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8991 group.show(getNodesToShow.call(that));
8993 complete.onAfterCompute(that.clickedNode);
8994 complete.onComplete();
9002 Animates the <ST> to center the node specified by *id*.
9006 id - (string) A node id.
9007 options - (optional|object) A group of options and callbacks described below.
9008 onComplete - (object) An object callback called when the animation finishes.
9009 Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
9014 st.onClick('mynodeid', {
9020 onComplete: function() {
9027 onClick: function (id, options) {
9028 var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
9029 var innerController = {
9032 offsetX: config.offsetX || 0,
9033 offsetY: config.offsetY || 0
9035 setRightLevelToShowConfig: false,
9036 onBeforeRequest: $.empty,
9037 onBeforeContract: $.empty,
9038 onBeforeMove: $.empty,
9039 onBeforeExpand: $.empty
9041 var complete = $.merge(this.controller, innerController, options);
9045 var node = this.graph.getNode(id);
9046 this.selectPath(node, this.clickedNode);
9047 this.clickedNode = node;
9048 complete.onBeforeCompute(node);
9049 complete.onBeforeRequest(node);
9050 this.requestNodes(node, {
9051 onComplete: function() {
9052 complete.onBeforeContract(node);
9054 onComplete: function() {
9055 Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
9056 complete.onBeforeMove(node);
9058 Move: complete.Move,
9059 onComplete: function() {
9060 complete.onBeforeExpand(node);
9062 onComplete: function() {
9064 complete.onAfterCompute(id);
9065 complete.onComplete();
9080 $jit.ST.$extend = true;
9085 Custom extension of <Graph.Op>.
9089 All <Graph.Op> methods
9096 $jit.ST.Op = new Class({
9098 Implements: Graph.Op
9104 Performs operations on group of nodes.
9107 $jit.ST.Group = new Class({
9109 initialize: function(viz) {
9111 this.canvas = viz.canvas;
9112 this.config = viz.config;
9113 this.animation = new Animation;
9119 Calls the request method on the controller to request a subtree for each node.
9121 requestNodes: function(nodes, controller) {
9122 var counter = 0, len = nodes.length, nodeSelected = {};
9123 var complete = function() { controller.onComplete(); };
9125 if(len == 0) complete();
9126 for(var i=0; i<len; i++) {
9127 nodeSelected[nodes[i].id] = nodes[i];
9128 controller.request(nodes[i].id, nodes[i]._level, {
9129 onComplete: function(nodeId, data) {
9130 if(data && data.children) {
9132 viz.op.sum(data, { type: 'nothing' });
9134 if(++counter == len) {
9135 viz.graph.computeLevels(viz.root, 0);
9145 Collapses group of nodes.
9147 contract: function(nodes, controller) {
9151 nodes = this.prepare(nodes);
9152 this.animation.setOptions($.merge(controller, {
9154 compute: function(delta) {
9155 if(delta == 1) delta = 0.99;
9156 that.plotStep(1 - delta, controller, this.$animating);
9157 this.$animating = 'contract';
9160 complete: function() {
9161 that.hide(nodes, controller);
9166 hide: function(nodes, controller) {
9168 for(var i=0; i<nodes.length; i++) {
9169 // TODO nodes are requested on demand, but not
9170 // deleted when hidden. Would that be a good feature?
9171 // Currently that feature is buggy, so I'll turn it off
9172 // Actually this feature is buggy because trimming should take
9173 // place onAfterCompute and not right after collapsing nodes.
9174 if (true || !controller || !controller.request) {
9175 nodes[i].eachLevel(1, false, function(elem){
9185 nodes[i].eachLevel(1, false, function(n) {
9188 viz.op.removeNode(ids, { 'type': 'nothing' });
9189 viz.labels.clearLabels();
9192 controller.onComplete();
9197 Expands group of nodes.
9199 expand: function(nodes, controller) {
9202 this.animation.setOptions($.merge(controller, {
9204 compute: function(delta) {
9205 that.plotStep(delta, controller, this.$animating);
9206 this.$animating = 'expand';
9209 complete: function() {
9210 that.plotStep(undefined, controller, false);
9211 controller.onComplete();
9217 show: function(nodes) {
9218 var config = this.config;
9219 this.prepare(nodes);
9220 $.each(nodes, function(n) {
9221 // check for root nodes if multitree
9222 if(config.multitree && !('$orn' in n.data)) {
9223 delete n.data.$orns;
9225 n.eachSubnode(function(ch) {
9226 if(('$orn' in ch.data)
9227 && orns.indexOf(ch.data.$orn) < 0
9228 && ch.exist && !ch.drawn) {
9229 orns += ch.data.$orn + ' ';
9232 n.data.$orns = orns;
9234 n.eachLevel(0, config.levelsToShow, function(n) {
9235 if(n.exist) n.drawn = true;
9240 prepare: function(nodes) {
9241 this.nodes = this.getNodesWithChildren(nodes);
9246 Filters an array of nodes leaving only nodes with children.
9248 getNodesWithChildren: function(nodes) {
9249 var ans = [], config = this.config, root = this.viz.root;
9250 nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9251 for(var i=0; i<nodes.length; i++) {
9252 if(nodes[i].anySubnode("exist")) {
9253 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9254 if(!config.multitree || '$orn' in nodes[j].data) {
9255 desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9258 if(!desc) ans.push(nodes[i]);
9264 plotStep: function(delta, controller, animating) {
9266 config = this.config,
9267 canvas = viz.canvas,
9268 ctx = canvas.getCtx(),
9271 // hide nodes that are meant to be collapsed/expanded
9273 for(i=0; i<nodes.length; i++) {
9276 var root = config.multitree && !('$orn' in node.data);
9277 var orns = root && node.data.$orns;
9278 node.eachSubgraph(function(n) {
9279 // TODO(nico): Cleanup
9280 // special check for root node subnodes when
9281 // multitree is checked.
9282 if(root && orns && orns.indexOf(n.data.$orn) > 0
9285 nds[node.id].push(n);
9286 } else if((!root || !orns) && n.drawn) {
9288 nds[node.id].push(n);
9293 // plot the whole (non-scaled) tree
9294 if(nodes.length > 0) viz.fx.plot();
9295 // show nodes that were previously hidden
9297 $.each(nds[i], function(n) { n.drawn = true; });
9299 // plot each scaled subtree
9300 for(i=0; i<nodes.length; i++) {
9303 viz.fx.plotSubtree(node, controller, delta, animating);
9308 getSiblings: function(nodes) {
9310 $.each(nodes, function(n) {
9311 var par = n.getParents();
9312 if (par.length == 0) {
9313 siblings[n.id] = [n];
9316 par[0].eachSubnode(function(sn) {
9319 siblings[n.id] = ans;
9329 Performs low level geometrical computations.
9333 This instance can be accessed with the _geom_ parameter of the st instance created.
9338 var st = new ST(canvas, config);
9339 st.geom.translate //or can also call any other <ST.Geom> method
9344 $jit.ST.Geom = new Class({
9345 Implements: Graph.Geom,
9347 Changes the tree current orientation to the one specified.
9349 You should usually use <ST.switchPosition> instead.
9351 switchOrientation: function(orn) {
9352 this.config.orientation = orn;
9356 Makes a value dispatch according to the current layout
9357 Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9359 dispatch: function() {
9360 // TODO(nico) should store Array.prototype.slice.call somewhere.
9361 var args = Array.prototype.slice.call(arguments);
9362 var s = args.shift(), len = args.length;
9363 var val = function(a) { return typeof a == 'function'? a() : a; };
9365 return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9366 } else if(len == 4) {
9368 case "top": return val(args[0]);
9369 case "right": return val(args[1]);
9370 case "bottom": return val(args[2]);
9371 case "left": return val(args[3]);
9378 Returns label height or with, depending on the tree current orientation.
9380 getSize: function(n, invert) {
9381 var data = n.data, config = this.config;
9382 var siblingOffset = config.siblingOffset;
9383 var s = (config.multitree
9385 && data.$orn) || config.orientation;
9386 var w = n.getData('width') + siblingOffset;
9387 var h = n.getData('height') + siblingOffset;
9389 return this.dispatch(s, h, w);
9391 return this.dispatch(s, w, h);
9395 Calculates a subtree base size. This is an utility function used by _getBaseSize_
9397 getTreeBaseSize: function(node, level, leaf) {
9398 var size = this.getSize(node, true), baseHeight = 0, that = this;
9399 if(leaf(level, node)) return size;
9400 if(level === 0) return 0;
9401 node.eachSubnode(function(elem) {
9402 baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9404 return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9411 Returns a Complex instance with the begin or end position of the edge to be plotted.
9415 node - A <Graph.Node> that is connected to this edge.
9416 type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9420 A <Complex> number specifying the begin or end position.
9422 getEdge: function(node, type, s) {
9423 var $C = function(a, b) {
9425 return node.pos.add(new Complex(a, b));
9428 var dim = this.node;
9429 var w = node.getData('width');
9430 var h = node.getData('height');
9432 if(type == 'begin') {
9433 if(dim.align == "center") {
9434 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9435 $C(0, -h/2),$C(w/2, 0));
9436 } else if(dim.align == "left") {
9437 return this.dispatch(s, $C(0, h), $C(0, 0),
9438 $C(0, 0), $C(w, 0));
9439 } else if(dim.align == "right") {
9440 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9441 $C(0, -h),$C(0, 0));
9442 } else throw "align: not implemented";
9445 } else if(type == 'end') {
9446 if(dim.align == "center") {
9447 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9448 $C(0, h/2), $C(-w/2, 0));
9449 } else if(dim.align == "left") {
9450 return this.dispatch(s, $C(0, 0), $C(w, 0),
9451 $C(0, h), $C(0, 0));
9452 } else if(dim.align == "right") {
9453 return this.dispatch(s, $C(0, -h),$C(0, 0),
9454 $C(0, 0), $C(-w, 0));
9455 } else throw "align: not implemented";
9460 Adjusts the tree position due to canvas scaling or translation.
9462 getScaledTreePosition: function(node, scale) {
9463 var dim = this.node;
9464 var w = node.getData('width');
9465 var h = node.getData('height');
9466 var s = (this.config.multitree
9467 && ('$orn' in node.data)
9468 && node.data.$orn) || this.config.orientation;
9470 var $C = function(a, b) {
9472 return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9475 if(dim.align == "left") {
9476 return this.dispatch(s, $C(0, h), $C(0, 0),
9477 $C(0, 0), $C(w, 0));
9478 } else if(dim.align == "center") {
9479 return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9480 $C(0, -h / 2),$C(w / 2, 0));
9481 } else if(dim.align == "right") {
9482 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9483 $C(0, -h),$C(0, 0));
9484 } else throw "align: not implemented";
9490 Returns a Boolean if the current subtree fits in canvas.
9494 node - A <Graph.Node> which is the current root of the subtree.
9495 canvas - The <Canvas> object.
9496 level - The depth of the subtree to be considered.
9498 treeFitsInCanvas: function(node, canvas, level) {
9499 var csize = canvas.getSize();
9500 var s = (this.config.multitree
9501 && ('$orn' in node.data)
9502 && node.data.$orn) || this.config.orientation;
9504 var size = this.dispatch(s, csize.width, csize.height);
9505 var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9506 return level === 0 || !node.anySubnode();
9508 return (baseSize < size);
9515 Custom extension of <Graph.Plot>.
9519 All <Graph.Plot> methods
9526 $jit.ST.Plot = new Class({
9528 Implements: Graph.Plot,
9531 Plots a subtree from the spacetree.
9533 plotSubtree: function(node, opt, scale, animating) {
9534 var viz = this.viz, canvas = viz.canvas, config = viz.config;
9535 scale = Math.min(Math.max(0.001, scale), 1);
9538 var ctx = canvas.getCtx();
9539 var diff = viz.geom.getScaledTreePosition(node, scale);
9540 ctx.translate(diff.x, diff.y);
9541 ctx.scale(scale, scale);
9543 this.plotTree(node, $.merge(opt, {
9545 'hideLabels': !!scale,
9546 'plotSubtree': function(n, ch) {
9547 var root = config.multitree && !('$orn' in node.data);
9548 var orns = root && node.getData('orns');
9549 return !root || orns.indexOf(node.getData('orn')) > -1;
9552 if(scale >= 0) node.drawn = true;
9556 Method: getAlignedPos
9558 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9562 pos - (object) A <Graph.Node> position.
9563 width - (number) The width of the node.
9564 height - (number) The height of the node.
9567 getAlignedPos: function(pos, width, height) {
9568 var nconfig = this.node;
9570 if(nconfig.align == "center") {
9572 x: pos.x - width / 2,
9573 y: pos.y - height / 2
9575 } else if (nconfig.align == "left") {
9576 orn = this.config.orientation;
9577 if(orn == "bottom" || orn == "top") {
9579 x: pos.x - width / 2,
9585 y: pos.y - height / 2
9588 } else if(nconfig.align == "right") {
9589 orn = this.config.orientation;
9590 if(orn == "bottom" || orn == "top") {
9592 x: pos.x - width / 2,
9598 y: pos.y - height / 2
9601 } else throw "align: not implemented";
9606 getOrientation: function(adj) {
9607 var config = this.config;
9608 var orn = config.orientation;
9610 if(config.multitree) {
9611 var nodeFrom = adj.nodeFrom;
9612 var nodeTo = adj.nodeTo;
9613 orn = (('$orn' in nodeFrom.data)
9614 && nodeFrom.data.$orn)
9615 || (('$orn' in nodeTo.data)
9616 && nodeTo.data.$orn);
9626 Custom extension of <Graph.Label>.
9627 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9631 All <Graph.Label> methods and subclasses.
9635 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9642 Custom extension of <Graph.Label.Native>.
9646 All <Graph.Label.Native> methods
9650 <Graph.Label.Native>
9652 $jit.ST.Label.Native = new Class({
9653 Implements: Graph.Label.Native,
9655 renderLabel: function(canvas, node, controller) {
9656 var ctx = canvas.getCtx(),
9657 coord = node.pos.getc(true),
9658 width = node.getData('width'),
9659 height = node.getData('height'),
9660 pos = this.viz.fx.getAlignedPos(coord, width, height);
9661 ctx.fillText(node.name, pos.x + width / 2, pos.y + height / 2);
9665 $jit.ST.Label.DOM = new Class({
9666 Implements: Graph.Label.DOM,
9671 Overrides abstract method placeLabel in <Graph.Plot>.
9675 tag - A DOM label element.
9676 node - A <Graph.Node>.
9677 controller - A configuration/controller object passed to the visualization.
9680 placeLabel: function(tag, node, controller) {
9681 var pos = node.pos.getc(true),
9682 config = this.viz.config,
9684 canvas = this.viz.canvas,
9685 w = node.getData('width'),
9686 h = node.getData('height'),
9687 radius = canvas.getSize(),
9690 var ox = canvas.translateOffsetX,
9691 oy = canvas.translateOffsetY,
9692 sx = canvas.scaleOffsetX,
9693 sy = canvas.scaleOffsetY,
9694 posx = pos.x * sx + ox,
9695 posy = pos.y * sy + oy;
9697 if(dim.align == "center") {
9699 x: Math.round(posx - w / 2 + radius.width/2),
9700 y: Math.round(posy - h / 2 + radius.height/2)
9702 } else if (dim.align == "left") {
9703 orn = config.orientation;
9704 if(orn == "bottom" || orn == "top") {
9706 x: Math.round(posx - w / 2 + radius.width/2),
9707 y: Math.round(posy + radius.height/2)
9711 x: Math.round(posx + radius.width/2),
9712 y: Math.round(posy - h / 2 + radius.height/2)
9715 } else if(dim.align == "right") {
9716 orn = config.orientation;
9717 if(orn == "bottom" || orn == "top") {
9719 x: Math.round(posx - w / 2 + radius.width/2),
9720 y: Math.round(posy - h + radius.height/2)
9724 x: Math.round(posx - w + radius.width/2),
9725 y: Math.round(posy - h / 2 + radius.height/2)
9728 } else throw "align: not implemented";
9730 var style = tag.style;
9731 style.left = labelPos.x + 'px';
9732 style.top = labelPos.y + 'px';
9733 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9734 controller.onPlaceLabel(tag, node);
9741 Custom extension of <Graph.Label.SVG>.
9745 All <Graph.Label.SVG> methods
9751 $jit.ST.Label.SVG = new Class({
9752 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9754 initialize: function(viz) {
9762 Custom extension of <Graph.Label.HTML>.
9766 All <Graph.Label.HTML> methods.
9773 $jit.ST.Label.HTML = new Class({
9774 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9776 initialize: function(viz) {
9783 Class: ST.Plot.NodeTypes
9785 This class contains a list of <Graph.Node> built-in types.
9786 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9788 You can add your custom node types, customizing your visualization to the extreme.
9793 ST.Plot.NodeTypes.implement({
9795 'render': function(node, canvas) {
9796 //print your custom node to canvas
9799 'contains': function(node, pos) {
9800 //return true if pos is inside the node or false otherwise
9807 $jit.ST.Plot.NodeTypes = new Class({
9810 'contains': $.lambda(false)
9813 'render': function(node, canvas) {
9814 var dim = node.getData('dim'),
9815 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9817 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9819 'contains': function(node, pos) {
9820 var dim = node.getData('dim'),
9821 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9823 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, pos, dim2);
9827 'render': function(node, canvas) {
9828 var dim = node.getData('dim'),
9830 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9831 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9833 'contains': function(node, pos) {
9834 var dim = node.getData('dim'),
9835 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9837 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, pos, dim2);
9841 'render': function(node, canvas) {
9842 var width = node.getData('width'),
9843 height = node.getData('height'),
9844 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9845 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9847 'contains': function(node, pos) {
9848 var width = node.getData('width'),
9849 height = node.getData('height'),
9850 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9851 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, pos, width, height);
9855 'render': function(node, canvas) {
9856 var width = node.getData('width'),
9857 height = node.getData('height'),
9858 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9859 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9861 'contains': function(node, pos) {
9862 var width = node.getData('width'),
9863 height = node.getData('height'),
9864 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9865 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, pos, width, height);
9871 Class: ST.Plot.EdgeTypes
9873 This class contains a list of <Graph.Adjacence> built-in types.
9874 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9876 You can add your custom edge types, customizing your visualization to the extreme.
9881 ST.Plot.EdgeTypes.implement({
9883 'render': function(adj, canvas) {
9884 //print your custom edge to canvas
9887 'contains': function(adj, pos) {
9888 //return true if pos is inside the arc or false otherwise
9895 $jit.ST.Plot.EdgeTypes = new Class({
9898 'render': function(adj, canvas) {
9899 var orn = this.getOrientation(adj),
9900 nodeFrom = adj.nodeFrom,
9901 nodeTo = adj.nodeTo,
9902 rel = nodeFrom._depth < nodeTo._depth,
9903 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9904 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9905 this.edgeHelper.line.render(from, to, canvas);
9907 'contains': function(adj, pos) {
9908 var orn = this.getOrientation(adj),
9909 nodeFrom = adj.nodeFrom,
9910 nodeTo = adj.nodeTo,
9911 rel = nodeFrom._depth < nodeTo._depth,
9912 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9913 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9914 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9918 'render': function(adj, canvas) {
9919 var orn = this.getOrientation(adj),
9920 node = adj.nodeFrom,
9922 dim = adj.getData('dim'),
9923 from = this.viz.geom.getEdge(node, 'begin', orn),
9924 to = this.viz.geom.getEdge(child, 'end', orn),
9925 direction = adj.data.$direction,
9926 inv = (direction && direction.length>1 && direction[0] != node.id);
9927 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9929 'contains': function(adj, pos) {
9930 var orn = this.getOrientation(adj),
9931 nodeFrom = adj.nodeFrom,
9932 nodeTo = adj.nodeTo,
9933 rel = nodeFrom._depth < nodeTo._depth,
9934 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9935 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9936 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9939 'quadratic:begin': {
9940 'render': function(adj, canvas) {
9941 var orn = this.getOrientation(adj);
9942 var nodeFrom = adj.nodeFrom,
9943 nodeTo = adj.nodeTo,
9944 rel = nodeFrom._depth < nodeTo._depth,
9945 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9946 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9947 dim = adj.getData('dim'),
9948 ctx = canvas.getCtx();
9950 ctx.moveTo(begin.x, begin.y);
9953 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9956 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9959 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9962 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9969 'render': function(adj, canvas) {
9970 var orn = this.getOrientation(adj);
9971 var nodeFrom = adj.nodeFrom,
9972 nodeTo = adj.nodeTo,
9973 rel = nodeFrom._depth < nodeTo._depth,
9974 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9975 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9976 dim = adj.getData('dim'),
9977 ctx = canvas.getCtx();
9979 ctx.moveTo(begin.x, begin.y);
9982 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9985 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9988 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9991 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9998 'render': function(adj, canvas) {
9999 var orn = this.getOrientation(adj),
10000 nodeFrom = adj.nodeFrom,
10001 nodeTo = adj.nodeTo,
10002 rel = nodeFrom._depth < nodeTo._depth,
10003 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10004 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10005 dim = adj.getData('dim'),
10006 ctx = canvas.getCtx();
10008 ctx.moveTo(begin.x, begin.y);
10011 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
10014 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
10017 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
10020 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
10031 * File: AreaChart.js
10035 $jit.ST.Plot.NodeTypes.implement({
10036 'areachart-stacked' : {
10037 'render' : function(node, canvas) {
10038 var pos = node.pos.getc(true),
10039 width = node.getData('width'),
10040 height = node.getData('height'),
10041 algnPos = this.getAlignedPos(pos, width, height),
10042 x = algnPos.x, y = algnPos.y,
10043 stringArray = node.getData('stringArray'),
10044 dimArray = node.getData('dimArray'),
10045 valArray = node.getData('valueArray'),
10046 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10047 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10048 colorArray = node.getData('colorArray'),
10049 colorLength = colorArray.length,
10050 config = node.getData('config'),
10051 gradient = node.getData('gradient'),
10052 showLabels = config.showLabels,
10053 aggregates = config.showAggregates,
10054 label = config.Label,
10055 prev = node.getData('prev');
10057 var ctx = canvas.getCtx(), border = node.getData('border');
10058 if (colorArray && dimArray && stringArray) {
10059 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10060 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10062 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10063 var h1 = acumLeft + dimArray[i][0],
10064 h2 = acumRight + dimArray[i][1],
10065 alpha = Math.atan((h2 - h1) / width),
10067 var linear = ctx.createLinearGradient(x + width/2,
10069 x + width/2 + delta * Math.sin(alpha),
10070 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10071 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10072 function(v) { return (v * 0.85) >> 0; }));
10073 linear.addColorStop(0, colorArray[i % colorLength]);
10074 linear.addColorStop(1, color);
10075 ctx.fillStyle = linear;
10078 ctx.moveTo(x, y - acumLeft);
10079 ctx.lineTo(x + width, y - acumRight);
10080 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10081 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10082 ctx.lineTo(x, y - acumLeft);
10086 var strong = border.name == stringArray[i];
10087 var perc = strong? 0.7 : 0.8;
10088 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10089 function(v) { return (v * perc) >> 0; }));
10090 ctx.strokeStyle = color;
10091 ctx.lineWidth = strong? 4 : 1;
10094 if(border.index === 0) {
10095 ctx.moveTo(x, y - acumLeft);
10096 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10098 ctx.moveTo(x + width, y - acumRight);
10099 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10104 acumLeft += (dimArray[i][0] || 0);
10105 acumRight += (dimArray[i][1] || 0);
10107 if(dimArray[i][0] > 0)
10108 valAcum += (valArray[i][0] || 0);
10110 if(prev && label.type == 'Native') {
10113 ctx.fillStyle = ctx.strokeStyle = label.color;
10114 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10115 ctx.textAlign = 'center';
10116 ctx.textBaseline = 'middle';
10117 var aggValue = aggregates(node.name, valLeft, valRight, node, valAcum);
10118 if(aggValue !== false) {
10119 ctx.fillText(aggValue !== true? aggValue : valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10121 if(showLabels(node.name, valLeft, valRight, node)) {
10122 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10128 'contains': function(node, mpos) {
10129 var pos = node.pos.getc(true),
10130 width = node.getData('width'),
10131 height = node.getData('height'),
10132 algnPos = this.getAlignedPos(pos, width, height),
10133 x = algnPos.x, y = algnPos.y,
10134 dimArray = node.getData('dimArray'),
10136 //bounding box check
10137 if(mpos.x < x || mpos.x > x + width
10138 || mpos.y > y || mpos.y < y - height) {
10142 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10143 var dimi = dimArray[i];
10146 var intersec = lAcum + (rAcum - lAcum) * rx / width;
10147 if(mpos.y >= intersec) {
10148 var index = +(rx > width/2);
10150 'name': node.getData('stringArray')[i],
10151 'color': node.getData('colorArray')[i],
10152 'value': node.getData('valueArray')[i][index],
10165 A visualization that displays stacked area charts.
10167 Constructor Options:
10169 See <Options.AreaChart>.
10172 $jit.AreaChart = new Class({
10174 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10178 initialize: function(opt) {
10179 this.controller = this.config =
10180 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10181 Label: { type: 'Native' }
10183 //set functions for showLabels and showAggregates
10184 var showLabels = this.config.showLabels,
10185 typeLabels = $.type(showLabels),
10186 showAggregates = this.config.showAggregates,
10187 typeAggregates = $.type(showAggregates);
10188 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10189 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10191 this.initializeViz();
10194 initializeViz: function() {
10195 var config = this.config,
10197 nodeType = config.type.split(":")[0],
10200 var delegate = new $jit.ST({
10201 injectInto: config.injectInto,
10202 width: config.width,
10203 height: config.height,
10204 orientation: "bottom",
10208 withLabels: config.Label.type != 'Native',
10209 useCanvas: config.useCanvas,
10211 type: config.Label.type
10215 type: 'areachart-' + nodeType,
10224 enable: config.Tips.enable,
10227 onShow: function(tip, node, contains) {
10228 var elem = contains;
10229 config.Tips.onShow(tip, elem, node);
10235 onClick: function(node, eventInfo, evt) {
10236 if(!config.filterOnClick && !config.Events.enable) return;
10237 var elem = eventInfo.getContains();
10238 if(elem) config.filterOnClick && that.filter(elem.name);
10239 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10241 onRightClick: function(node, eventInfo, evt) {
10242 if(!config.restoreOnRightClick) return;
10245 onMouseMove: function(node, eventInfo, evt) {
10246 if(!config.selectOnHover) return;
10248 var elem = eventInfo.getContains();
10249 that.select(node.id, elem.name, elem.index);
10251 that.select(false, false, false);
10255 onCreateLabel: function(domElement, node) {
10256 var labelConf = config.Label,
10257 valueArray = node.getData('valueArray'),
10258 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10259 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10260 if(node.getData('prev')) {
10262 wrapper: document.createElement('div'),
10263 aggregate: document.createElement('div'),
10264 label: document.createElement('div')
10266 var wrapper = nlbs.wrapper,
10267 label = nlbs.label,
10268 aggregate = nlbs.aggregate,
10269 wrapperStyle = wrapper.style,
10270 labelStyle = label.style,
10271 aggregateStyle = aggregate.style;
10272 //store node labels
10273 nodeLabels[node.id] = nlbs;
10275 wrapper.appendChild(label);
10276 wrapper.appendChild(aggregate);
10277 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10278 label.style.display = 'none';
10280 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10281 aggregate.style.display = 'none';
10283 wrapperStyle.position = 'relative';
10284 wrapperStyle.overflow = 'visible';
10285 wrapperStyle.fontSize = labelConf.size + 'px';
10286 wrapperStyle.fontFamily = labelConf.family;
10287 wrapperStyle.color = labelConf.color;
10288 wrapperStyle.textAlign = 'center';
10289 aggregateStyle.position = labelStyle.position = 'absolute';
10291 domElement.style.width = node.getData('width') + 'px';
10292 domElement.style.height = node.getData('height') + 'px';
10293 label.innerHTML = node.name;
10295 domElement.appendChild(wrapper);
10298 onPlaceLabel: function(domElement, node) {
10299 if(!node.getData('prev')) return;
10300 var labels = nodeLabels[node.id],
10301 wrapperStyle = labels.wrapper.style,
10302 labelStyle = labels.label.style,
10303 aggregateStyle = labels.aggregate.style,
10304 width = node.getData('width'),
10305 height = node.getData('height'),
10306 dimArray = node.getData('dimArray'),
10307 valArray = node.getData('valueArray'),
10308 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10309 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10310 font = parseInt(wrapperStyle.fontSize, 10),
10311 domStyle = domElement.style;
10313 if(dimArray && valArray) {
10314 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10315 labelStyle.display = '';
10317 labelStyle.display = 'none';
10319 var aggValue = config.showAggregates(node.name, acumLeft, acumRight, node);
10320 if(aggValue !== false) {
10321 aggregateStyle.display = '';
10323 aggregateStyle.display = 'none';
10325 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10326 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10327 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10328 if(dimArray[i][0] > 0) {
10329 acum+= valArray[i][0];
10330 leftAcum+= dimArray[i][0];
10333 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10334 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10335 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10336 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10337 labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
10342 var size = delegate.canvas.getSize(),
10343 margin = config.Margin;
10344 delegate.config.offsetY = -size.height/2 + margin.bottom
10345 + (config.showLabels && (config.labelOffset + config.Label.size));
10346 delegate.config.offsetX = (margin.right - margin.left)/2;
10347 this.delegate = delegate;
10348 this.canvas = this.delegate.canvas;
10354 Loads JSON data into the visualization.
10358 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>.
10362 var areaChart = new $jit.AreaChart(options);
10363 areaChart.loadJSON(json);
10366 loadJSON: function(json) {
10367 var prefix = $.time(),
10369 delegate = this.delegate,
10370 name = $.splat(json.label),
10371 color = $.splat(json.color || this.colors),
10372 config = this.config,
10373 gradient = !!config.type.split(":")[1],
10374 animate = config.animate;
10376 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
10377 var val = values[i], prev = values[i-1], next = values[i+1];
10378 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
10379 var valArray = $.zip(valLeft, valRight);
10380 var acumLeft = 0, acumRight = 0;
10382 'id': prefix + val.label,
10386 '$valueArray': valArray,
10387 '$colorArray': color,
10388 '$stringArray': name,
10389 '$next': next.label,
10390 '$prev': prev? prev.label:false,
10392 '$gradient': gradient
10398 'id': prefix + '$root',
10407 delegate.loadJSON(root);
10409 this.normalizeDims();
10410 delegate.compute();
10411 delegate.select(delegate.root);
10413 delegate.fx.animate({
10414 modes: ['node-property:height:dimArray'],
10423 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.
10427 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10428 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10433 areaChart.updateJSON(json, {
10434 onComplete: function() {
10435 alert('update complete!');
10440 updateJSON: function(json, onComplete) {
10441 if(this.busy) return;
10444 var delegate = this.delegate,
10445 graph = delegate.graph,
10446 labels = json.label && $.splat(json.label),
10447 values = json.values,
10448 animate = this.config.animate,
10452 //convert the whole thing into a hash
10453 for (var i = 0, l = values.length; i < l; i++) {
10454 hashValues[values[i].label] = values[i];
10457 graph.eachNode(function(n) {
10458 var v = hashValues[n.name],
10459 stringArray = n.getData('stringArray'),
10460 valArray = n.getData('valueArray'),
10461 next = n.getData('next');
10464 v.values = $.splat(v.values);
10465 $.each(valArray, function(a, i) {
10466 a[0] = v.values[i];
10467 if(labels) stringArray[i] = labels[i];
10469 n.setData('valueArray', valArray);
10473 v = hashValues[next];
10475 $.each(valArray, function(a, i) {
10476 a[1] = v.values[i];
10481 this.normalizeDims();
10482 delegate.compute();
10483 delegate.select(delegate.root);
10485 delegate.fx.animate({
10486 modes: ['node-property:height:dimArray'],
10488 onComplete: function() {
10490 onComplete && onComplete.onComplete();
10499 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10503 filters - (array) An array of strings with the name of the stacks to be filtered.
10504 callback - (object) An object with an *onComplete* callback method.
10509 areaChart.filter(['label A', 'label C'], {
10510 onComplete: function() {
10511 console.log('done!');
10518 <AreaChart.restore>.
10520 filter: function(filters, callback) {
10521 if(this.busy) return;
10523 if(this.config.Tips.enable) this.delegate.tips.hide();
10524 this.select(false, false, false);
10525 var args = $.splat(filters);
10526 var rt = this.delegate.graph.getNode(this.delegate.root);
10528 this.normalizeDims();
10529 rt.eachAdjacency(function(adj) {
10530 var n = adj.nodeTo,
10531 dimArray = n.getData('dimArray', 'end'),
10532 stringArray = n.getData('stringArray');
10533 n.setData('dimArray', $.map(dimArray, function(d, i) {
10534 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10537 this.delegate.fx.animate({
10538 modes: ['node-property:dimArray'],
10540 onComplete: function() {
10542 callback && callback.onComplete();
10550 Sets all stacks that could have been filtered visible.
10555 areaChart.restore();
10560 <AreaChart.filter>.
10562 restore: function(callback) {
10563 if(this.busy) return;
10565 if(this.config.Tips.enable) this.delegate.tips.hide();
10566 this.select(false, false, false);
10567 this.normalizeDims();
10569 this.delegate.fx.animate({
10570 modes: ['node-property:height:dimArray'],
10572 onComplete: function() {
10574 callback && callback.onComplete();
10578 //adds the little brown bar when hovering the node
10579 select: function(id, name, index) {
10580 if(!this.config.selectOnHover) return;
10581 var s = this.selected;
10582 if(s.id != id || s.name != name
10583 || s.index != index) {
10587 this.delegate.graph.eachNode(function(n) {
10588 n.setData('border', false);
10591 var n = this.delegate.graph.getNode(id);
10592 n.setData('border', s);
10593 var link = index === 0? 'prev':'next';
10594 link = n.getData(link);
10596 n = this.delegate.graph.getByName(link);
10598 n.setData('border', {
10605 this.delegate.plot();
10612 Returns an object containing as keys the legend names and as values hex strings with color values.
10617 var legend = areaChart.getLegend();
10620 getLegend: function() {
10623 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
10626 var colors = n.getData('colorArray'),
10627 len = colors.length;
10628 $.each(n.getData('stringArray'), function(s, i) {
10629 legend[s] = colors[i % len];
10635 Method: getMaxValue
10637 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10642 var ans = areaChart.getMaxValue();
10645 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10650 //will return 100 for all AreaChart instances,
10651 //displaying all of them with the same scale
10652 $jit.AreaChart.implement({
10653 'getMaxValue': function() {
10660 getMaxValue: function() {
10662 this.delegate.graph.eachNode(function(n) {
10663 var valArray = n.getData('valueArray'),
10664 acumLeft = 0, acumRight = 0;
10665 $.each(valArray, function(v) {
10667 acumRight += +v[1];
10669 var acum = acumRight>acumLeft? acumRight:acumLeft;
10670 maxValue = maxValue>acum? maxValue:acum;
10675 normalizeDims: function() {
10676 //number of elements
10677 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
10678 root.eachAdjacency(function() {
10681 var maxValue = this.getMaxValue() || 1,
10682 size = this.delegate.canvas.getSize(),
10683 config = this.config,
10684 margin = config.Margin,
10685 labelOffset = config.labelOffset + config.Label.size,
10686 fixedDim = (size.width - (margin.left + margin.right)) / l,
10687 animate = config.animate,
10688 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10689 - (config.showLabels && labelOffset);
10690 this.delegate.graph.eachNode(function(n) {
10691 var acumLeft = 0, acumRight = 0, animateValue = [];
10692 $.each(n.getData('valueArray'), function(v) {
10694 acumRight += +v[1];
10695 animateValue.push([0, 0]);
10697 var acum = acumRight>acumLeft? acumRight:acumLeft;
10698 n.setData('width', fixedDim);
10700 n.setData('height', acum * height / maxValue, 'end');
10701 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10702 return [n[0] * height / maxValue, n[1] * height / maxValue];
10704 var dimArray = n.getData('dimArray');
10706 n.setData('dimArray', animateValue);
10709 n.setData('height', acum * height / maxValue);
10710 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10711 return [n[0] * height / maxValue, n[1] * height / maxValue];
10720 * File: Options.BarChart.js
10725 Object: Options.BarChart
10727 <BarChart> options.
10728 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
10734 Options.BarChart = {
10739 hoveredColor: '#9fd4ff',
10740 orientation: 'horizontal',
10741 showAggregates: true,
10751 var barChart = new $jit.BarChart({
10754 type: 'stacked:gradient'
10761 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
10762 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
10763 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
10764 barsOffset - (number) Default's *0*. Separation between bars.
10765 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
10766 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
10767 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
10768 showAggregates - (boolean, function) Default's *true*. Display the sum the values of each bar. Can also be a function that returns *true* or *false* to display the value of the bar or not. That same function can also return a string with the formatted data to be added.
10769 showLabels - (boolean, function) Default's *true*. Display the name of the slots. Can also be a function that returns *true* or *false* for each bar to decide whether to show the label or not.
10773 Options.BarChart = {
10777 type: 'stacked', //stacked, grouped, : gradient
10778 labelOffset: 3, //label offset
10779 barsOffset: 0, //distance between bars
10780 hoveredColor: '#9fd4ff',
10781 orientation: 'horizontal',
10782 showAggregates: true,
10796 * File: BarChart.js
10800 $jit.ST.Plot.NodeTypes.implement({
10801 'barchart-stacked' : {
10802 'render' : function(node, canvas) {
10803 var pos = node.pos.getc(true),
10804 width = node.getData('width'),
10805 height = node.getData('height'),
10806 algnPos = this.getAlignedPos(pos, width, height),
10807 x = algnPos.x, y = algnPos.y,
10808 dimArray = node.getData('dimArray'),
10809 valueArray = node.getData('valueArray'),
10810 colorArray = node.getData('colorArray'),
10811 colorLength = colorArray.length,
10812 stringArray = node.getData('stringArray');
10814 var ctx = canvas.getCtx(),
10816 border = node.getData('border'),
10817 gradient = node.getData('gradient'),
10818 config = node.getData('config'),
10819 horz = config.orientation == 'horizontal',
10820 aggregates = config.showAggregates,
10821 showLabels = config.showLabels,
10822 label = config.Label;
10824 if (colorArray && dimArray && stringArray) {
10825 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
10826 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10830 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
10831 x + acum + dimArray[i]/2, y + height);
10833 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
10834 x + width, y - acum- dimArray[i]/2);
10836 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10837 function(v) { return (v * 0.5) >> 0; }));
10838 linear.addColorStop(0, color);
10839 linear.addColorStop(0.5, colorArray[i % colorLength]);
10840 linear.addColorStop(1, color);
10841 ctx.fillStyle = linear;
10844 ctx.fillRect(x + acum, y, dimArray[i], height);
10846 ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
10848 if(border && border.name == stringArray[i]) {
10850 opt.dimValue = dimArray[i];
10852 acum += (dimArray[i] || 0);
10853 valAcum += (valueArray[i] || 0);
10858 ctx.strokeStyle = border.color;
10860 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
10862 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
10866 if(label.type == 'Native') {
10868 ctx.fillStyle = ctx.strokeStyle = label.color;
10869 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10870 ctx.textBaseline = 'middle';
10871 var aggValue = aggregates(node.name, valAcum, node);
10872 if(aggValue !== false) {
10873 aggValue = aggValue !== true? aggValue : valAcum;
10875 ctx.textAlign = 'right';
10876 ctx.fillText(aggValue, x + acum - config.labelOffset, y + height/2);
10878 ctx.textAlign = 'center';
10879 ctx.fillText(aggValue, x + width/2, y - height - label.size/2 - config.labelOffset);
10882 if(showLabels(node.name, valAcum, node)) {
10884 ctx.textAlign = 'center';
10885 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
10886 ctx.rotate(Math.PI / 2);
10887 ctx.fillText(node.name, 0, 0);
10889 ctx.textAlign = 'center';
10890 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
10897 'contains': function(node, mpos) {
10898 var pos = node.pos.getc(true),
10899 width = node.getData('width'),
10900 height = node.getData('height'),
10901 algnPos = this.getAlignedPos(pos, width, height),
10902 x = algnPos.x, y = algnPos.y,
10903 dimArray = node.getData('dimArray'),
10904 config = node.getData('config'),
10906 horz = config.orientation == 'horizontal';
10907 //bounding box check
10909 if(mpos.x < x || mpos.x > x + width
10910 || mpos.y > y + height || mpos.y < y) {
10914 if(mpos.x < x || mpos.x > x + width
10915 || mpos.y > y || mpos.y < y - height) {
10920 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
10921 var dimi = dimArray[i];
10924 var intersec = acum;
10925 if(mpos.x <= intersec) {
10927 'name': node.getData('stringArray')[i],
10928 'color': node.getData('colorArray')[i],
10929 'value': node.getData('valueArray')[i],
10935 var intersec = acum;
10936 if(mpos.y >= intersec) {
10938 'name': node.getData('stringArray')[i],
10939 'color': node.getData('colorArray')[i],
10940 'value': node.getData('valueArray')[i],
10949 'barchart-grouped' : {
10950 'render' : function(node, canvas) {
10951 var pos = node.pos.getc(true),
10952 width = node.getData('width'),
10953 height = node.getData('height'),
10954 algnPos = this.getAlignedPos(pos, width, height),
10955 x = algnPos.x, y = algnPos.y,
10956 dimArray = node.getData('dimArray'),
10957 valueArray = node.getData('valueArray'),
10958 valueLength = valueArray.length,
10959 colorArray = node.getData('colorArray'),
10960 colorLength = colorArray.length,
10961 stringArray = node.getData('stringArray');
10963 var ctx = canvas.getCtx(),
10965 border = node.getData('border'),
10966 gradient = node.getData('gradient'),
10967 config = node.getData('config'),
10968 horz = config.orientation == 'horizontal',
10969 aggregates = config.showAggregates,
10970 showLabels = config.showLabels,
10971 label = config.Label,
10972 fixedDim = (horz? height : width) / valueLength;
10974 if (colorArray && dimArray && stringArray) {
10975 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
10976 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10980 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
10981 x + dimArray[i]/2, y + fixedDim * (i + 1));
10983 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
10984 x + fixedDim * (i + 1), y - dimArray[i]/2);
10986 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10987 function(v) { return (v * 0.5) >> 0; }));
10988 linear.addColorStop(0, color);
10989 linear.addColorStop(0.5, colorArray[i % colorLength]);
10990 linear.addColorStop(1, color);
10991 ctx.fillStyle = linear;
10994 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
10996 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
10998 if(border && border.name == stringArray[i]) {
10999 opt.acum = fixedDim * i;
11000 opt.dimValue = dimArray[i];
11002 acum += (dimArray[i] || 0);
11003 valAcum += (valueArray[i] || 0);
11008 ctx.strokeStyle = border.color;
11010 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
11012 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
11016 if(label.type == 'Native') {
11018 ctx.fillStyle = ctx.strokeStyle = label.color;
11019 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11020 ctx.textBaseline = 'middle';
11021 var aggValue = aggregates(node.name, valAcum, node);
11022 if(aggValue !== false) {
11023 aggValue = aggValue !== true? aggValue : valAcum;
11025 ctx.textAlign = 'right';
11026 ctx.fillText(aggValue, x + Math.max.apply(null, dimArray) - config.labelOffset, y + height/2);
11028 ctx.textAlign = 'center';
11029 ctx.fillText(aggValue, x + width/2, y - Math.max.apply(null, dimArray) - label.size/2 - config.labelOffset);
11032 if(showLabels(node.name, valAcum, node)) {
11034 ctx.textAlign = 'center';
11035 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
11036 ctx.rotate(Math.PI / 2);
11037 ctx.fillText(node.name, 0, 0);
11039 ctx.textAlign = 'center';
11040 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11047 'contains': function(node, mpos) {
11048 var pos = node.pos.getc(true),
11049 width = node.getData('width'),
11050 height = node.getData('height'),
11051 algnPos = this.getAlignedPos(pos, width, height),
11052 x = algnPos.x, y = algnPos.y,
11053 dimArray = node.getData('dimArray'),
11054 len = dimArray.length,
11055 config = node.getData('config'),
11057 horz = config.orientation == 'horizontal',
11058 fixedDim = (horz? height : width) / len;
11059 //bounding box check
11061 if(mpos.x < x || mpos.x > x + width
11062 || mpos.y > y + height || mpos.y < y) {
11066 if(mpos.x < x || mpos.x > x + width
11067 || mpos.y > y || mpos.y < y - height) {
11072 for(var i=0, l=dimArray.length; i<l; i++) {
11073 var dimi = dimArray[i];
11075 var limit = y + fixedDim * i;
11076 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
11078 'name': node.getData('stringArray')[i],
11079 'color': node.getData('colorArray')[i],
11080 'value': node.getData('valueArray')[i],
11085 var limit = x + fixedDim * i;
11086 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
11088 'name': node.getData('stringArray')[i],
11089 'color': node.getData('colorArray')[i],
11090 'value': node.getData('valueArray')[i],
11104 A visualization that displays stacked bar charts.
11106 Constructor Options:
11108 See <Options.BarChart>.
11111 $jit.BarChart = new Class({
11113 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
11117 initialize: function(opt) {
11118 this.controller = this.config =
11119 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
11120 Label: { type: 'Native' }
11122 //set functions for showLabels and showAggregates
11123 var showLabels = this.config.showLabels,
11124 typeLabels = $.type(showLabels),
11125 showAggregates = this.config.showAggregates,
11126 typeAggregates = $.type(showAggregates);
11127 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
11128 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
11130 this.initializeViz();
11133 initializeViz: function() {
11134 var config = this.config, that = this;
11135 var nodeType = config.type.split(":")[0],
11136 horz = config.orientation == 'horizontal',
11139 var delegate = new $jit.ST({
11140 injectInto: config.injectInto,
11141 width: config.width,
11142 height: config.height,
11143 orientation: horz? 'left' : 'bottom',
11145 siblingOffset: config.barsOffset,
11147 withLabels: config.Label.type != 'Native',
11148 useCanvas: config.useCanvas,
11150 type: config.Label.type
11154 type: 'barchart-' + nodeType,
11163 enable: config.Tips.enable,
11166 onShow: function(tip, node, contains) {
11167 var elem = contains;
11168 config.Tips.onShow(tip, elem, node);
11174 onClick: function(node, eventInfo, evt) {
11175 if(!config.Events.enable) return;
11176 var elem = eventInfo.getContains();
11177 config.Events.onClick(elem, eventInfo, evt);
11179 onMouseMove: function(node, eventInfo, evt) {
11180 if(!config.hoveredColor) return;
11182 var elem = eventInfo.getContains();
11183 that.select(node.id, elem.name, elem.index);
11185 that.select(false, false, false);
11189 onCreateLabel: function(domElement, node) {
11190 var labelConf = config.Label,
11191 valueArray = node.getData('valueArray'),
11192 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0);
11194 wrapper: document.createElement('div'),
11195 aggregate: document.createElement('div'),
11196 label: document.createElement('div')
11198 var wrapper = nlbs.wrapper,
11199 label = nlbs.label,
11200 aggregate = nlbs.aggregate,
11201 wrapperStyle = wrapper.style,
11202 labelStyle = label.style,
11203 aggregateStyle = aggregate.style;
11204 //store node labels
11205 nodeLabels[node.id] = nlbs;
11207 wrapper.appendChild(label);
11208 wrapper.appendChild(aggregate);
11209 if(!config.showLabels(node.name, acum, node)) {
11210 labelStyle.display = 'none';
11212 if(!config.showAggregates(node.name, acum, node)) {
11213 aggregateStyle.display = 'none';
11215 wrapperStyle.position = 'relative';
11216 wrapperStyle.overflow = 'visible';
11217 wrapperStyle.fontSize = labelConf.size + 'px';
11218 wrapperStyle.fontFamily = labelConf.family;
11219 wrapperStyle.color = labelConf.color;
11220 wrapperStyle.textAlign = 'center';
11221 aggregateStyle.position = labelStyle.position = 'absolute';
11223 domElement.style.width = node.getData('width') + 'px';
11224 domElement.style.height = node.getData('height') + 'px';
11225 aggregateStyle.left = labelStyle.left = '0px';
11227 label.innerHTML = node.name;
11229 domElement.appendChild(wrapper);
11231 onPlaceLabel: function(domElement, node) {
11232 if(!nodeLabels[node.id]) return;
11233 var labels = nodeLabels[node.id],
11234 wrapperStyle = labels.wrapper.style,
11235 labelStyle = labels.label.style,
11236 aggregateStyle = labels.aggregate.style,
11237 grouped = config.type.split(':')[0] == 'grouped',
11238 horz = config.orientation == 'horizontal',
11239 dimArray = node.getData('dimArray'),
11240 valArray = node.getData('valueArray'),
11241 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
11242 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
11243 font = parseInt(wrapperStyle.fontSize, 10),
11244 domStyle = domElement.style;
11247 if(dimArray && valArray) {
11248 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11249 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
11250 if(dimArray[i] > 0) {
11251 acum+= valArray[i];
11254 if(config.showLabels(node.name, acum, node)) {
11255 labelStyle.display = '';
11257 labelStyle.display = 'none';
11259 var aggValue = config.showAggregates(node.name, acum, node);
11260 if(aggValue !== false) {
11261 aggregateStyle.display = '';
11263 aggregateStyle.display = 'none';
11265 if(config.orientation == 'horizontal') {
11266 aggregateStyle.textAlign = 'right';
11267 labelStyle.textAlign = 'left';
11268 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
11269 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
11270 domElement.style.height = wrapperStyle.height = height + 'px';
11272 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11273 labelStyle.top = (config.labelOffset + height) + 'px';
11274 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
11275 domElement.style.height = wrapperStyle.height = height + 'px';
11277 labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
11282 var size = delegate.canvas.getSize(),
11283 margin = config.Margin;
11285 delegate.config.offsetX = size.width/2 - margin.left
11286 - (config.showLabels && (config.labelOffset + config.Label.size));
11287 delegate.config.offsetY = (margin.bottom - margin.top)/2;
11289 delegate.config.offsetY = -size.height/2 + margin.bottom
11290 + (config.showLabels && (config.labelOffset + config.Label.size));
11291 delegate.config.offsetX = (margin.right - margin.left)/2;
11293 this.delegate = delegate;
11294 this.canvas = this.delegate.canvas;
11300 Loads JSON data into the visualization.
11304 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>.
11308 var barChart = new $jit.BarChart(options);
11309 barChart.loadJSON(json);
11312 loadJSON: function(json) {
11313 if(this.busy) return;
11316 var prefix = $.time(),
11318 delegate = this.delegate,
11319 name = $.splat(json.label),
11320 color = $.splat(json.color || this.colors),
11321 config = this.config,
11322 gradient = !!config.type.split(":")[1],
11323 animate = config.animate,
11324 horz = config.orientation == 'horizontal',
11327 for(var i=0, values=json.values, l=values.length; i<l; i++) {
11328 var val = values[i]
11329 var valArray = $.splat(values[i].values);
11332 'id': prefix + val.label,
11336 '$valueArray': valArray,
11337 '$colorArray': color,
11338 '$stringArray': name,
11339 '$gradient': gradient,
11346 'id': prefix + '$root',
11355 delegate.loadJSON(root);
11357 this.normalizeDims();
11358 delegate.compute();
11359 delegate.select(delegate.root);
11362 delegate.fx.animate({
11363 modes: ['node-property:width:dimArray'],
11365 onComplete: function() {
11370 delegate.fx.animate({
11371 modes: ['node-property:height:dimArray'],
11373 onComplete: function() {
11386 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.
11390 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
11391 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11396 barChart.updateJSON(json, {
11397 onComplete: function() {
11398 alert('update complete!');
11403 updateJSON: function(json, onComplete) {
11404 if(this.busy) return;
11406 this.select(false, false, false);
11407 var delegate = this.delegate;
11408 var graph = delegate.graph;
11409 var values = json.values;
11410 var animate = this.config.animate;
11412 var horz = this.config.orientation == 'horizontal';
11413 $.each(values, function(v) {
11414 var n = graph.getByName(v.label);
11416 n.setData('valueArray', $.splat(v.values));
11418 n.setData('stringArray', $.splat(json.label));
11422 this.normalizeDims();
11423 delegate.compute();
11424 delegate.select(delegate.root);
11427 delegate.fx.animate({
11428 modes: ['node-property:width:dimArray'],
11430 onComplete: function() {
11432 onComplete && onComplete.onComplete();
11436 delegate.fx.animate({
11437 modes: ['node-property:height:dimArray'],
11439 onComplete: function() {
11441 onComplete && onComplete.onComplete();
11448 //adds the little brown bar when hovering the node
11449 select: function(id, name) {
11450 if(!this.config.hoveredColor) return;
11451 var s = this.selected;
11452 if(s.id != id || s.name != name) {
11455 s.color = this.config.hoveredColor;
11456 this.delegate.graph.eachNode(function(n) {
11458 n.setData('border', s);
11460 n.setData('border', false);
11463 this.delegate.plot();
11470 Returns an object containing as keys the legend names and as values hex strings with color values.
11475 var legend = barChart.getLegend();
11478 getLegend: function() {
11481 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
11484 var colors = n.getData('colorArray'),
11485 len = colors.length;
11486 $.each(n.getData('stringArray'), function(s, i) {
11487 legend[s] = colors[i % len];
11493 Method: getMaxValue
11495 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11500 var ans = barChart.getMaxValue();
11503 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
11508 //will return 100 for all BarChart instances,
11509 //displaying all of them with the same scale
11510 $jit.BarChart.implement({
11511 'getMaxValue': function() {
11518 getMaxValue: function() {
11519 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
11520 this.delegate.graph.eachNode(function(n) {
11521 var valArray = n.getData('valueArray'),
11523 if(!valArray) return;
11525 $.each(valArray, function(v) {
11529 acum = Math.max.apply(null, valArray);
11531 maxValue = maxValue>acum? maxValue:acum;
11536 setBarType: function(type) {
11537 this.config.type = type;
11538 this.delegate.config.Node.type = 'barchart-' + type.split(':')[0];
11541 normalizeDims: function() {
11542 //number of elements
11543 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
11544 root.eachAdjacency(function() {
11547 var maxValue = this.getMaxValue() || 1,
11548 size = this.delegate.canvas.getSize(),
11549 config = this.config,
11550 margin = config.Margin,
11551 marginWidth = margin.left + margin.right,
11552 marginHeight = margin.top + margin.bottom,
11553 horz = config.orientation == 'horizontal',
11554 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (l -1) * config.barsOffset) / l,
11555 animate = config.animate,
11556 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
11557 - (!horz && config.showAggregates && (config.Label.size + config.labelOffset))
11558 - (config.showLabels && (config.Label.size + config.labelOffset)),
11559 dim1 = horz? 'height':'width',
11560 dim2 = horz? 'width':'height';
11561 this.delegate.graph.eachNode(function(n) {
11562 var acum = 0, animateValue = [];
11563 $.each(n.getData('valueArray'), function(v) {
11565 animateValue.push(0);
11567 n.setData(dim1, fixedDim);
11569 n.setData(dim2, acum * height / maxValue, 'end');
11570 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11571 return n * height / maxValue;
11573 var dimArray = n.getData('dimArray');
11575 n.setData('dimArray', animateValue);
11578 n.setData(dim2, acum * height / maxValue);
11579 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11580 return n * height / maxValue;
11589 * File: Options.PieChart.js
11593 Object: Options.PieChart
11595 <PieChart> options.
11596 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
11602 Options.PieChart = {
11608 hoveredColor: '#9fd4ff',
11610 resizeLabels: false,
11611 updateHeights: false
11620 var pie = new $jit.PieChart({
11623 type: 'stacked:gradient'
11630 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
11631 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11632 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
11633 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11634 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
11635 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
11636 showLabels - (boolean) Default's *true*. Display the name of the slots.
11637 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.
11638 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.
11641 Options.PieChart = {
11645 offset: 25, // page offset
11647 labelOffset: 3, // label offset
11648 type: 'stacked', // gradient
11649 hoveredColor: '#9fd4ff',
11660 resizeLabels: false,
11662 //only valid for mono-valued datasets
11663 updateHeights: false
11667 * Class: Layouts.Radial
11669 * Implements a Radial Layout.
11673 * <RGraph>, <Hypertree>
11676 Layouts.Radial = new Class({
11681 * Computes nodes' positions.
11685 * property - _optional_ A <Graph.Node> position property to store the new
11686 * positions. Possible values are 'pos', 'end' or 'start'.
11689 compute : function(property) {
11690 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
11691 NodeDim.compute(this.graph, prop, this.config);
11692 this.graph.computeLevels(this.root, 0, "ignore");
11693 var lengthFunc = this.createLevelDistanceFunc();
11694 this.computeAngularWidths(prop);
11695 this.computePositions(prop, lengthFunc);
11701 * Performs the main algorithm for computing node positions.
11703 computePositions : function(property, getLength) {
11704 var propArray = property;
11705 var graph = this.graph;
11706 var root = graph.getNode(this.root);
11707 var parent = this.parent;
11708 var config = this.config;
11710 for ( var i=0, l=propArray.length; i < l; i++) {
11711 var pi = propArray[i];
11712 root.setPos($P(0, 0), pi);
11713 root.setData('span', Math.PI * 2, pi);
11721 graph.eachBFS(this.root, function(elem) {
11722 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
11723 var angleInit = elem.angleSpan.begin;
11724 var len = getLength(elem);
11725 //Calculate the sum of all angular widths
11726 var totalAngularWidths = 0, subnodes = [], maxDim = {};
11727 elem.eachSubnode(function(sib) {
11728 totalAngularWidths += sib._treeAngularWidth;
11730 for ( var i=0, l=propArray.length; i < l; i++) {
11731 var pi = propArray[i], dim = sib.getData('dim', pi);
11732 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
11734 subnodes.push(sib);
11736 //Maintain children order
11737 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
11738 if (parent && parent.id == elem.id && subnodes.length > 0
11739 && subnodes[0].dist) {
11740 subnodes.sort(function(a, b) {
11741 return (a.dist >= b.dist) - (a.dist <= b.dist);
11744 //Calculate nodes positions.
11745 for (var k = 0, ls=subnodes.length; k < ls; k++) {
11746 var child = subnodes[k];
11747 if (!child._flag) {
11748 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
11749 var theta = angleInit + angleProportion / 2;
11751 for ( var i=0, l=propArray.length; i < l; i++) {
11752 var pi = propArray[i];
11753 child.setPos($P(theta, len), pi);
11754 child.setData('span', angleProportion, pi);
11755 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
11758 child.angleSpan = {
11760 end : angleInit + angleProportion
11762 angleInit += angleProportion;
11769 * Method: setAngularWidthForNodes
11771 * Sets nodes angular widths.
11773 setAngularWidthForNodes : function(prop) {
11774 this.graph.eachBFS(this.root, function(elem, i) {
11775 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
11776 elem._angularWidth = diamValue / i;
11781 * Method: setSubtreesAngularWidth
11783 * Sets subtrees angular widths.
11785 setSubtreesAngularWidth : function() {
11787 this.graph.eachNode(function(elem) {
11788 that.setSubtreeAngularWidth(elem);
11793 * Method: setSubtreeAngularWidth
11795 * Sets the angular width for a subtree.
11797 setSubtreeAngularWidth : function(elem) {
11798 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
11799 elem.eachSubnode(function(child) {
11800 that.setSubtreeAngularWidth(child);
11801 sumAW += child._treeAngularWidth;
11803 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
11807 * Method: computeAngularWidths
11809 * Computes nodes and subtrees angular widths.
11811 computeAngularWidths : function(prop) {
11812 this.setAngularWidthForNodes(prop);
11813 this.setSubtreesAngularWidth();
11820 * File: Sunburst.js
11826 A radial space filling tree visualization.
11830 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
11834 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.
11838 All <Loader> methods
11840 Constructor Options:
11842 Inherits options from
11845 - <Options.Controller>
11851 - <Options.NodeStyles>
11852 - <Options.Navigation>
11854 Additionally, there are other parameters and some default values changed
11856 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
11857 levelDistance - (number) Default's *100*. The distance between levels of the tree.
11858 Node.type - Described in <Options.Node>. Default's to *multipie*.
11859 Node.height - Described in <Options.Node>. Default's *0*.
11860 Edge.type - Described in <Options.Edge>. Default's *none*.
11861 Label.textAlign - Described in <Options.Label>. Default's *start*.
11862 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
11864 Instance Properties:
11866 canvas - Access a <Canvas> instance.
11867 graph - Access a <Graph> instance.
11868 op - Access a <Sunburst.Op> instance.
11869 fx - Access a <Sunburst.Plot> instance.
11870 labels - Access a <Sunburst.Label> interface implementation.
11874 $jit.Sunburst = new Class({
11876 Implements: [ Loader, Extras, Layouts.Radial ],
11878 initialize: function(controller) {
11879 var $Sunburst = $jit.Sunburst;
11882 interpolation: 'linear',
11883 levelDistance: 100,
11885 'type': 'multipie',
11892 textAlign: 'start',
11893 textBaseline: 'middle'
11897 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
11898 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
11900 var canvasConfig = this.config;
11901 if(canvasConfig.useCanvas) {
11902 this.canvas = canvasConfig.useCanvas;
11903 this.config.labelContainer = this.canvas.id + '-label';
11905 if(canvasConfig.background) {
11906 canvasConfig.background = $.merge({
11908 }, canvasConfig.background);
11910 this.canvas = new Canvas(this, canvasConfig);
11911 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
11914 this.graphOptions = {
11922 this.graph = new Graph(this.graphOptions, this.config.Node,
11924 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
11925 this.fx = new $Sunburst.Plot(this, $Sunburst);
11926 this.op = new $Sunburst.Op(this);
11929 this.rotated = null;
11931 // initialize extras
11932 this.initializeExtras();
11937 createLevelDistanceFunc
11939 Returns the levelDistance function used for calculating a node distance
11940 to its origin. This function returns a function that is computed
11941 per level and not per node, such that all nodes with the same depth will have the
11942 same distance to the origin. The resulting function gets the
11943 parent node as parameter and returns a float.
11946 createLevelDistanceFunc: function() {
11947 var ld = this.config.levelDistance;
11948 return function(elem) {
11949 return (elem._depth + 1) * ld;
11956 Computes positions and plots the tree.
11959 refresh: function() {
11967 An alias for computing new positions to _endPos_
11974 reposition: function() {
11975 this.compute('end');
11981 Rotates the graph so that the selected node is horizontal on the right.
11985 node - (object) A <Graph.Node>.
11986 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
11987 opt - (object) Configuration options merged with this visualization configuration options.
11991 <Sunburst.rotateAngle>
11994 rotate: function(node, method, opt) {
11995 var theta = node.getPos(opt.property || 'current').getp(true).theta;
11996 this.rotated = node;
11997 this.rotateAngle(-theta, method, opt);
12001 Method: rotateAngle
12003 Rotates the graph of an angle theta.
12007 node - (object) A <Graph.Node>.
12008 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
12009 opt - (object) Configuration options merged with this visualization configuration options.
12016 rotateAngle: function(theta, method, opt) {
12018 var options = $.merge(this.config, opt || {}, {
12021 var prop = opt.property || (method === "animate" ? 'end' : 'current');
12022 if(method === 'animate') {
12023 this.fx.animation.pause();
12025 this.graph.eachNode(function(n) {
12026 var p = n.getPos(prop);
12029 p.theta += Math.PI * 2;
12032 if (method == 'animate') {
12033 this.fx.animate(options);
12034 } else if (method == 'replot') {
12043 Plots the Sunburst. This is a shortcut to *fx.plot*.
12050 $jit.Sunburst.$extend = true;
12052 (function(Sunburst) {
12057 Custom extension of <Graph.Op>.
12061 All <Graph.Op> methods
12068 Sunburst.Op = new Class( {
12070 Implements: Graph.Op
12075 Class: Sunburst.Plot
12077 Custom extension of <Graph.Plot>.
12081 All <Graph.Plot> methods
12088 Sunburst.Plot = new Class( {
12090 Implements: Graph.Plot
12095 Class: Sunburst.Label
12097 Custom extension of <Graph.Label>.
12098 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
12102 All <Graph.Label> methods and subclasses.
12106 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
12109 Sunburst.Label = {};
12112 Sunburst.Label.Native
12114 Custom extension of <Graph.Label.Native>.
12118 All <Graph.Label.Native> methods
12122 <Graph.Label.Native>
12124 Sunburst.Label.Native = new Class( {
12125 Implements: Graph.Label.Native,
12127 initialize: function(viz) {
12129 this.label = viz.config.Label;
12130 this.config = viz.config;
12133 renderLabel: function(canvas, node, controller) {
12134 var span = node.getData('span');
12135 if(span < Math.PI /2 && Math.tan(span) *
12136 this.config.levelDistance * node._depth < 10) {
12139 var ctx = canvas.getCtx();
12140 var measure = ctx.measureText(node.name);
12141 if (node.id == this.viz.root) {
12142 var x = -measure.width / 2, y = 0, thetap = 0;
12146 var ld = controller.levelDistance - indent;
12147 var clone = node.pos.clone();
12148 clone.rho += indent;
12149 var p = clone.getp(true);
12150 var ct = clone.getc(true);
12151 var x = ct.x, y = ct.y;
12152 // get angle in degrees
12154 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
12155 var thetap = cond ? p.theta + pi : p.theta;
12157 x -= Math.abs(Math.cos(p.theta) * measure.width);
12158 y += Math.sin(p.theta) * measure.width;
12159 } else if (node.id == this.viz.root) {
12160 x -= measure.width / 2;
12164 ctx.translate(x, y);
12165 ctx.rotate(thetap);
12166 ctx.fillText(node.name, 0, 0);
12174 Custom extension of <Graph.Label.SVG>.
12178 All <Graph.Label.SVG> methods
12185 Sunburst.Label.SVG = new Class( {
12186 Implements: Graph.Label.SVG,
12188 initialize: function(viz) {
12195 Overrides abstract method placeLabel in <Graph.Plot>.
12199 tag - A DOM label element.
12200 node - A <Graph.Node>.
12201 controller - A configuration/controller object passed to the visualization.
12204 placeLabel: function(tag, node, controller) {
12205 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
12206 var radius = canvas.getSize();
12208 x: Math.round(pos.x + radius.width / 2),
12209 y: Math.round(pos.y + radius.height / 2)
12211 tag.setAttribute('x', labelPos.x);
12212 tag.setAttribute('y', labelPos.y);
12214 var bb = tag.getBBox();
12216 // center the label
12217 var x = tag.getAttribute('x');
12218 var y = tag.getAttribute('y');
12219 // get polar coordinates
12220 var p = node.pos.getp(true);
12221 // get angle in degrees
12223 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
12225 tag.setAttribute('x', x - bb.width);
12226 tag.setAttribute('y', y - bb.height);
12227 } else if (node.id == viz.root) {
12228 tag.setAttribute('x', x - bb.width / 2);
12231 var thetap = cond ? p.theta + pi : p.theta;
12233 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
12237 controller.onPlaceLabel(tag, node);
12242 Sunburst.Label.HTML
12244 Custom extension of <Graph.Label.HTML>.
12248 All <Graph.Label.HTML> methods.
12255 Sunburst.Label.HTML = new Class( {
12256 Implements: Graph.Label.HTML,
12258 initialize: function(viz) {
12264 Overrides abstract method placeLabel in <Graph.Plot>.
12268 tag - A DOM label element.
12269 node - A <Graph.Node>.
12270 controller - A configuration/controller object passed to the visualization.
12273 placeLabel: function(tag, node, controller) {
12274 var pos = node.pos.clone(),
12275 canvas = this.viz.canvas,
12276 height = node.getData('height'),
12277 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
12278 radius = canvas.getSize();
12280 pos = pos.getc(true);
12283 x: Math.round(pos.x + radius.width / 2),
12284 y: Math.round(pos.y + radius.height / 2)
12287 var style = tag.style;
12288 style.left = labelPos.x + 'px';
12289 style.top = labelPos.y + 'px';
12290 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
12292 controller.onPlaceLabel(tag, node);
12297 Class: Sunburst.Plot.NodeTypes
12299 This class contains a list of <Graph.Node> built-in types.
12300 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
12302 You can add your custom node types, customizing your visualization to the extreme.
12307 Sunburst.Plot.NodeTypes.implement({
12309 'render': function(node, canvas) {
12310 //print your custom node to canvas
12313 'contains': function(node, pos) {
12314 //return true if pos is inside the node or false otherwise
12321 Sunburst.Plot.NodeTypes = new Class( {
12324 'contains': $.lambda(false),
12325 'anglecontains': function(node, pos) {
12326 var span = node.getData('span') / 2, theta = node.pos.theta;
12327 var begin = theta - span, end = theta + span;
12329 begin += Math.PI * 2;
12330 var atan = Math.atan2(pos.y, pos.x);
12332 atan += Math.PI * 2;
12334 return (atan > begin && atan <= Math.PI * 2) || atan < end;
12336 return atan > begin && atan < end;
12342 'render': function(node, canvas) {
12343 var span = node.getData('span') / 2, theta = node.pos.theta;
12344 var begin = theta - span, end = theta + span;
12345 var polarNode = node.pos.getp(true);
12346 var polar = new Polar(polarNode.rho, begin);
12347 var p1coord = polar.getc(true);
12349 var p2coord = polar.getc(true);
12351 var ctx = canvas.getCtx();
12354 ctx.lineTo(p1coord.x, p1coord.y);
12356 ctx.lineTo(p2coord.x, p2coord.y);
12358 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
12362 'contains': function(node, pos) {
12363 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12364 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12365 var ld = this.config.levelDistance, d = node._depth;
12366 return (rho <= ld * d);
12372 'render': function(node, canvas) {
12373 var height = node.getData('height');
12374 var ldist = height? height : this.config.levelDistance;
12375 var span = node.getData('span') / 2, theta = node.pos.theta;
12376 var begin = theta - span, end = theta + span;
12377 var polarNode = node.pos.getp(true);
12379 var polar = new Polar(polarNode.rho, begin);
12380 var p1coord = polar.getc(true);
12383 var p2coord = polar.getc(true);
12385 polar.rho += ldist;
12386 var p3coord = polar.getc(true);
12388 polar.theta = begin;
12389 var p4coord = polar.getc(true);
12391 var ctx = canvas.getCtx();
12394 ctx.arc(0, 0, polarNode.rho, begin, end, false);
12395 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
12396 ctx.moveTo(p1coord.x, p1coord.y);
12397 ctx.lineTo(p4coord.x, p4coord.y);
12398 ctx.moveTo(p2coord.x, p2coord.y);
12399 ctx.lineTo(p3coord.x, p3coord.y);
12402 if (node.collapsed) {
12407 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
12413 'contains': function(node, pos) {
12414 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12415 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12416 var height = node.getData('height');
12417 var ldist = height? height : this.config.levelDistance;
12418 var ld = this.config.levelDistance, d = node._depth;
12419 return (rho >= ld * d) && (rho <= (ld * d + ldist));
12425 'gradient-multipie': {
12426 'render': function(node, canvas) {
12427 var ctx = canvas.getCtx();
12428 var height = node.getData('height');
12429 var ldist = height? height : this.config.levelDistance;
12430 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
12431 0, 0, node.getPos().rho + ldist);
12433 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
12434 $.each(colorArray, function(i) {
12435 ans.push(parseInt(i * 0.5, 10));
12437 var endColor = $.rgbToHex(ans);
12438 radialGradient.addColorStop(0, endColor);
12439 radialGradient.addColorStop(1, node.getData('color'));
12440 ctx.fillStyle = radialGradient;
12441 this.nodeTypes['multipie'].render.call(this, node, canvas);
12443 'contains': function(node, pos) {
12444 return this.nodeTypes['multipie'].contains.call(this, node, pos);
12449 'render': function(node, canvas) {
12450 var ctx = canvas.getCtx();
12451 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
12454 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
12455 $.each(colorArray, function(i) {
12456 ans.push(parseInt(i * 0.5, 10));
12458 var endColor = $.rgbToHex(ans);
12459 radialGradient.addColorStop(1, endColor);
12460 radialGradient.addColorStop(0, node.getData('color'));
12461 ctx.fillStyle = radialGradient;
12462 this.nodeTypes['pie'].render.call(this, node, canvas);
12464 'contains': function(node, pos) {
12465 return this.nodeTypes['pie'].contains.call(this, node, pos);
12471 Class: Sunburst.Plot.EdgeTypes
12473 This class contains a list of <Graph.Adjacence> built-in types.
12474 Edge types implemented are 'none', 'line' and 'arrow'.
12476 You can add your custom edge types, customizing your visualization to the extreme.
12481 Sunburst.Plot.EdgeTypes.implement({
12483 'render': function(adj, canvas) {
12484 //print your custom edge to canvas
12487 'contains': function(adj, pos) {
12488 //return true if pos is inside the arc or false otherwise
12495 Sunburst.Plot.EdgeTypes = new Class({
12498 'render': function(adj, canvas) {
12499 var from = adj.nodeFrom.pos.getc(true),
12500 to = adj.nodeTo.pos.getc(true);
12501 this.edgeHelper.line.render(from, to, canvas);
12503 'contains': function(adj, pos) {
12504 var from = adj.nodeFrom.pos.getc(true),
12505 to = adj.nodeTo.pos.getc(true);
12506 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
12510 'render': function(adj, canvas) {
12511 var from = adj.nodeFrom.pos.getc(true),
12512 to = adj.nodeTo.pos.getc(true),
12513 dim = adj.getData('dim'),
12514 direction = adj.data.$direction,
12515 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
12516 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
12518 'contains': function(adj, pos) {
12519 var from = adj.nodeFrom.pos.getc(true),
12520 to = adj.nodeTo.pos.getc(true);
12521 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
12525 'render': function(adj, canvas) {
12526 var from = adj.nodeFrom.pos.getc(),
12527 to = adj.nodeTo.pos.getc(),
12528 dim = Math.max(from.norm(), to.norm());
12529 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
12531 'contains': $.lambda(false) //TODO(nico): Implement this!
12539 * File: PieChart.js
12543 $jit.Sunburst.Plot.NodeTypes.implement({
12544 'piechart-stacked' : {
12545 'render' : function(node, canvas) {
12546 var pos = node.pos.getp(true),
12547 dimArray = node.getData('dimArray'),
12548 valueArray = node.getData('valueArray'),
12549 colorArray = node.getData('colorArray'),
12550 colorLength = colorArray.length,
12551 stringArray = node.getData('stringArray'),
12552 span = node.getData('span') / 2,
12553 theta = node.pos.theta,
12554 begin = theta - span,
12555 end = theta + span,
12558 var ctx = canvas.getCtx(),
12560 gradient = node.getData('gradient'),
12561 border = node.getData('border'),
12562 config = node.getData('config'),
12563 showLabels = config.showLabels,
12564 resizeLabels = config.resizeLabels,
12565 label = config.Label;
12567 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
12568 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
12570 if (colorArray && dimArray && stringArray) {
12571 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
12572 var dimi = dimArray[i], colori = colorArray[i % colorLength];
12573 if(dimi <= 0) continue;
12574 ctx.fillStyle = ctx.strokeStyle = colori;
12575 if(gradient && dimi) {
12576 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
12577 xpos, ypos, acum + dimi + config.sliceOffset);
12578 var colorRgb = $.hexToRgb(colori),
12579 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
12580 endColor = $.rgbToHex(ans);
12582 radialGradient.addColorStop(0, colori);
12583 radialGradient.addColorStop(0.5, colori);
12584 radialGradient.addColorStop(1, endColor);
12585 ctx.fillStyle = radialGradient;
12588 polar.rho = acum + config.sliceOffset;
12589 polar.theta = begin;
12590 var p1coord = polar.getc(true);
12592 var p2coord = polar.getc(true);
12594 var p3coord = polar.getc(true);
12595 polar.theta = begin;
12596 var p4coord = polar.getc(true);
12599 //fixing FF arc method + fill
12600 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
12601 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
12603 if(border && border.name == stringArray[i]) {
12605 opt.dimValue = dimArray[i];
12609 acum += (dimi || 0);
12610 valAcum += (valueArray[i] || 0);
12614 ctx.globalCompositeOperation = "source-over";
12616 ctx.strokeStyle = border.color;
12617 var s = begin < end? 1 : -1;
12619 //fixing FF arc method + fill
12620 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
12621 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
12626 if(showLabels && label.type == 'Native') {
12628 ctx.fillStyle = ctx.strokeStyle = label.color;
12629 var scale = resizeLabels? node.getData('normalizedDim') : 1,
12630 fontSize = (label.size * scale) >> 0;
12631 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
12633 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
12634 ctx.textBaseline = 'middle';
12635 ctx.textAlign = 'center';
12637 polar.rho = acum + config.labelOffset + config.sliceOffset;
12638 polar.theta = node.pos.theta;
12639 var cart = polar.getc(true);
12641 ctx.fillText(node.name, cart.x, cart.y);
12646 'contains': function(node, pos) {
12647 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12648 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12649 var ld = this.config.levelDistance, d = node._depth;
12650 var config = node.getData('config');
12651 if(rho <=ld * d + config.sliceOffset) {
12652 var dimArray = node.getData('dimArray');
12653 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
12654 var dimi = dimArray[i];
12655 if(rho >= acum && rho <= acum + dimi) {
12657 name: node.getData('stringArray')[i],
12658 color: node.getData('colorArray')[i],
12659 value: node.getData('valueArray')[i],
12677 A visualization that displays stacked bar charts.
12679 Constructor Options:
12681 See <Options.PieChart>.
12684 $jit.PieChart = new Class({
12686 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
12690 initialize: function(opt) {
12691 this.controller = this.config =
12692 $.merge(Options("Canvas", "PieChart", "Label"), {
12693 Label: { type: 'Native' }
12695 this.initializeViz();
12698 initializeViz: function() {
12699 var config = this.config, that = this;
12700 var nodeType = config.type.split(":")[0];
12701 var delegate = new $jit.Sunburst({
12702 injectInto: config.injectInto,
12703 width: config.width,
12704 height: config.height,
12705 useCanvas: config.useCanvas,
12706 withLabels: config.Label.type != 'Native',
12708 type: config.Label.type
12712 type: 'piechart-' + nodeType,
12720 enable: config.Tips.enable,
12723 onShow: function(tip, node, contains) {
12724 var elem = contains;
12725 config.Tips.onShow(tip, elem, node);
12731 onClick: function(node, eventInfo, evt) {
12732 if(!config.Events.enable) return;
12733 var elem = eventInfo.getContains();
12734 config.Events.onClick(elem, eventInfo, evt);
12736 onMouseMove: function(node, eventInfo, evt) {
12737 if(!config.hoveredColor) return;
12739 var elem = eventInfo.getContains();
12740 that.select(node.id, elem.name, elem.index);
12742 that.select(false, false, false);
12746 onCreateLabel: function(domElement, node) {
12747 var labelConf = config.Label;
12748 if(config.showLabels) {
12749 var style = domElement.style;
12750 style.fontSize = labelConf.size + 'px';
12751 style.fontFamily = labelConf.family;
12752 style.color = labelConf.color;
12753 style.textAlign = 'center';
12754 domElement.innerHTML = node.name;
12757 onPlaceLabel: function(domElement, node) {
12758 if(!config.showLabels) return;
12759 var pos = node.pos.getp(true),
12760 dimArray = node.getData('dimArray'),
12761 span = node.getData('span') / 2,
12762 theta = node.pos.theta,
12763 begin = theta - span,
12764 end = theta + span,
12767 var showLabels = config.showLabels,
12768 resizeLabels = config.resizeLabels,
12769 label = config.Label;
12772 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
12773 acum += dimArray[i];
12775 var scale = resizeLabels? node.getData('normalizedDim') : 1,
12776 fontSize = (label.size * scale) >> 0;
12777 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
12778 domElement.style.fontSize = fontSize + 'px';
12779 polar.rho = acum + config.labelOffset + config.sliceOffset;
12780 polar.theta = (begin + end) / 2;
12781 var pos = polar.getc(true);
12782 var radius = that.canvas.getSize();
12784 x: Math.round(pos.x + radius.width / 2),
12785 y: Math.round(pos.y + radius.height / 2)
12787 domElement.style.left = labelPos.x + 'px';
12788 domElement.style.top = labelPos.y + 'px';
12793 var size = delegate.canvas.getSize(),
12795 delegate.config.levelDistance = min(size.width, size.height)/2
12796 - config.offset - config.sliceOffset;
12797 this.delegate = delegate;
12798 this.canvas = this.delegate.canvas;
12799 this.canvas.getCtx().globalCompositeOperation = 'lighter';
12805 Loads JSON data into the visualization.
12809 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>.
12813 var pieChart = new $jit.PieChart(options);
12814 pieChart.loadJSON(json);
12817 loadJSON: function(json) {
12818 var prefix = $.time(),
12820 delegate = this.delegate,
12821 name = $.splat(json.label),
12822 nameLength = name.length,
12823 color = $.splat(json.color || this.colors),
12824 colorLength = color.length,
12825 config = this.config,
12826 gradient = !!config.type.split(":")[1],
12827 animate = config.animate,
12828 mono = nameLength == 1;
12830 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12831 var val = values[i];
12832 var valArray = $.splat(val.values);
12834 'id': prefix + val.label,
12838 '$valueArray': valArray,
12839 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
12840 '$stringArray': name,
12841 '$gradient': gradient,
12843 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
12849 'id': prefix + '$root',
12858 delegate.loadJSON(root);
12860 this.normalizeDims();
12861 delegate.refresh();
12863 delegate.fx.animate({
12864 modes: ['node-property:dimArray'],
12873 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.
12877 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
12878 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
12883 pieChart.updateJSON(json, {
12884 onComplete: function() {
12885 alert('update complete!');
12890 updateJSON: function(json, onComplete) {
12891 if(this.busy) return;
12894 var delegate = this.delegate;
12895 var graph = delegate.graph;
12896 var values = json.values;
12897 var animate = this.config.animate;
12899 $.each(values, function(v) {
12900 var n = graph.getByName(v.label),
12901 vals = $.splat(v.values);
12903 n.setData('valueArray', vals);
12904 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
12906 n.setData('stringArray', $.splat(json.label));
12910 this.normalizeDims();
12912 delegate.compute('end');
12913 delegate.fx.animate({
12914 modes: ['node-property:dimArray:span', 'linear'],
12916 onComplete: function() {
12918 onComplete && onComplete.onComplete();
12922 delegate.refresh();
12926 //adds the little brown bar when hovering the node
12927 select: function(id, name) {
12928 if(!this.config.hoveredColor) return;
12929 var s = this.selected;
12930 if(s.id != id || s.name != name) {
12933 s.color = this.config.hoveredColor;
12934 this.delegate.graph.eachNode(function(n) {
12936 n.setData('border', s);
12938 n.setData('border', false);
12941 this.delegate.plot();
12948 Returns an object containing as keys the legend names and as values hex strings with color values.
12953 var legend = pieChart.getLegend();
12956 getLegend: function() {
12959 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
12962 var colors = n.getData('colorArray'),
12963 len = colors.length;
12964 $.each(n.getData('stringArray'), function(s, i) {
12965 legend[s] = colors[i % len];
12971 Method: getMaxValue
12973 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
12978 var ans = pieChart.getMaxValue();
12981 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
12986 //will return 100 for all PieChart instances,
12987 //displaying all of them with the same scale
12988 $jit.PieChart.implement({
12989 'getMaxValue': function() {
12996 getMaxValue: function() {
12998 this.delegate.graph.eachNode(function(n) {
12999 var valArray = n.getData('valueArray'),
13001 $.each(valArray, function(v) {
13004 maxValue = maxValue>acum? maxValue:acum;
13009 normalizeDims: function() {
13010 //number of elements
13011 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
13012 root.eachAdjacency(function() {
13015 var maxValue = this.getMaxValue() || 1,
13016 config = this.config,
13017 animate = config.animate,
13018 rho = this.delegate.config.levelDistance;
13019 this.delegate.graph.eachNode(function(n) {
13020 var acum = 0, animateValue = [];
13021 $.each(n.getData('valueArray'), function(v) {
13023 animateValue.push(1);
13025 var stat = (animateValue.length == 1) && !config.updateHeights;
13027 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13028 return stat? rho: (n * rho / maxValue);
13030 var dimArray = n.getData('dimArray');
13032 n.setData('dimArray', animateValue);
13035 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13036 return stat? rho : (n * rho / maxValue);
13039 n.setData('normalizedDim', acum / maxValue);
13046 * Class: Layouts.TM
13048 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
13057 Layouts.TM.SliceAndDice = new Class({
13058 compute: function(prop) {
13059 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
13060 this.controller.onBeforeCompute(root);
13061 var size = this.canvas.getSize(),
13062 config = this.config,
13063 width = size.width,
13064 height = size.height;
13065 this.graph.computeLevels(this.root, 0, "ignore");
13066 //set root position and dimensions
13067 root.getPos(prop).setc(-width/2, -height/2);
13068 root.setData('width', width, prop);
13069 root.setData('height', height + config.titleHeight, prop);
13070 this.computePositions(root, root, this.layout.orientation, prop);
13071 this.controller.onAfterCompute(root);
13074 computePositions: function(par, ch, orn, prop) {
13075 //compute children areas
13077 par.eachSubnode(function(n) {
13078 totalArea += n.getData('area', prop);
13081 var config = this.config,
13082 offst = config.offset,
13083 width = par.getData('width', prop),
13084 height = Math.max(par.getData('height', prop) - config.titleHeight, 0),
13085 fact = par == ch? 1 : (ch.getData('area', prop) / totalArea);
13087 var otherSize, size, dim, pos, pos2, posth, pos2th;
13088 var horizontal = (orn == "h");
13091 otherSize = height;
13092 size = width * fact;
13096 posth = config.titleHeight;
13100 otherSize = height * fact;
13106 pos2th = config.titleHeight;
13108 var cpos = ch.getPos(prop);
13109 ch.setData('width', size, prop);
13110 ch.setData('height', otherSize, prop);
13111 var offsetSize = 0, tm = this;
13112 ch.eachSubnode(function(n) {
13113 var p = n.getPos(prop);
13114 p[pos] = offsetSize + cpos[pos] + posth;
13115 p[pos2] = cpos[pos2] + pos2th;
13116 tm.computePositions(ch, n, orn, prop);
13117 offsetSize += n.getData(dim, prop);
13123 Layouts.TM.Area = {
13127 Called by loadJSON to calculate recursively all node positions and lay out the tree.
13131 json - A JSON tree. See also <Loader.loadJSON>.
13132 coord - A coordinates object specifying width, height, left and top style properties.
13134 compute: function(prop) {
13135 prop = prop || "current";
13136 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
13137 this.controller.onBeforeCompute(root);
13138 var config = this.config,
13139 size = this.canvas.getSize(),
13140 width = size.width,
13141 height = size.height,
13142 offst = config.offset,
13143 offwdth = width - offst,
13144 offhght = height - offst;
13145 this.graph.computeLevels(this.root, 0, "ignore");
13146 //set root position and dimensions
13147 root.getPos(prop).setc(-width/2, -height/2);
13148 root.setData('width', width, prop);
13149 root.setData('height', height, prop);
13150 //create a coordinates object
13152 'top': -height/2 + config.titleHeight,
13155 'height': offhght - config.titleHeight
13157 this.computePositions(root, coord, prop);
13158 this.controller.onAfterCompute(root);
13164 Computes dimensions and positions of a group of nodes
13165 according to a custom layout row condition.
13169 tail - An array of nodes.
13170 initElem - An array of nodes (containing the initial node to be laid).
13171 w - A fixed dimension where nodes will be layed out.
13172 coord - A coordinates object specifying width, height, left and top style properties.
13173 comp - A custom comparison function
13175 computeDim: function(tail, initElem, w, coord, comp, prop) {
13176 if(tail.length + initElem.length == 1) {
13177 var l = (tail.length == 1)? tail : initElem;
13178 this.layoutLast(l, w, coord, prop);
13181 if(tail.length >= 2 && initElem.length == 0) {
13182 initElem = [tail.shift()];
13184 if(tail.length == 0) {
13185 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
13189 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
13190 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
13192 var newCoords = this.layoutRow(initElem, w, coord, prop);
13193 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
13199 Method: worstAspectRatio
13201 Calculates the worst aspect ratio of a group of rectangles.
13205 <http://en.wikipedia.org/wiki/Aspect_ratio>
13209 ch - An array of nodes.
13210 w - The fixed dimension where rectangles are being laid out.
13214 The worst aspect ratio.
13218 worstAspectRatio: function(ch, w) {
13219 if(!ch || ch.length == 0) return Number.MAX_VALUE;
13220 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
13221 for(var i=0, l=ch.length; i<l; i++) {
13222 var area = ch[i]._area;
13224 minArea = minArea < area? minArea : area;
13225 maxArea = maxArea > area? maxArea : area;
13227 var sqw = w * w, sqAreaSum = areaSum * areaSum;
13228 return Math.max(sqw * maxArea / sqAreaSum,
13229 sqAreaSum / (sqw * minArea));
13233 Method: avgAspectRatio
13235 Calculates the average aspect ratio of a group of rectangles.
13239 <http://en.wikipedia.org/wiki/Aspect_ratio>
13243 ch - An array of nodes.
13244 w - The fixed dimension where rectangles are being laid out.
13248 The average aspect ratio.
13252 avgAspectRatio: function(ch, w) {
13253 if(!ch || ch.length == 0) return Number.MAX_VALUE;
13255 for(var i=0, l=ch.length; i<l; i++) {
13256 var area = ch[i]._area;
13258 arSum += w > h? w / h : h / w;
13266 Performs the layout of the last computed sibling.
13270 ch - An array of nodes.
13271 w - A fixed dimension where nodes will be layed out.
13272 coord - A coordinates object specifying width, height, left and top style properties.
13274 layoutLast: function(ch, w, coord, prop) {
13276 child.getPos(prop).setc(coord.left, coord.top);
13277 child.setData('width', coord.width, prop);
13278 child.setData('height', coord.height, prop);
13283 Layouts.TM.Squarified = new Class({
13284 Implements: Layouts.TM.Area,
13286 computePositions: function(node, coord, prop) {
13287 var config = this.config,
13290 if (coord.width >= coord.height)
13291 this.layout.orientation = 'h';
13293 this.layout.orientation = 'v';
13295 var ch = node.getSubnodes([1, 1], "ignore");
13296 if(ch.length > 0) {
13297 this.processChildrenLayout(node, ch, coord, prop);
13298 for(var i=0, l=ch.length; i<l; i++) {
13300 offst = config.offset,
13301 height = max(chi.getData('height', prop) - offst - config.titleHeight, 0),
13302 width = max(chi.getData('width', prop) - offst, 0),
13303 chipos = chi.getPos(prop);
13308 'top': chipos.y + config.titleHeight,
13311 this.computePositions(chi, coord, prop);
13317 Method: processChildrenLayout
13319 Computes children real areas and other useful parameters for performing the Squarified algorithm.
13323 par - The parent node of the json subtree.
13324 ch - An Array of nodes
13325 coord - A coordinates object specifying width, height, left and top style properties.
13327 processChildrenLayout: function(par, ch, coord, prop) {
13328 //compute children real areas
13329 var parentArea = coord.width * coord.height;
13330 var i, l=ch.length, totalChArea=0, chArea = [];
13331 for(i=0; i<l; i++) {
13332 chArea[i] = parseFloat(ch[i].getData('area', prop));
13333 totalChArea += chArea[i];
13335 for(i=0; i<l; i++) {
13336 ch[i]._area = parentArea * chArea[i] / totalChArea;
13338 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
13339 ch.sort(function(a, b) {
13340 var diff = b._area - a._area;
13341 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
13343 var initElem = [ch[0]];
13344 var tail = ch.slice(1);
13345 this.squarify(tail, initElem, minimumSideValue, coord, prop);
13351 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
13355 tail - An array of nodes.
13356 initElem - An array of nodes, containing the initial node to be laid out.
13357 w - A fixed dimension where nodes will be laid out.
13358 coord - A coordinates object specifying width, height, left and top style properties.
13360 squarify: function(tail, initElem, w, coord, prop) {
13361 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
13367 Performs the layout of an array of nodes.
13371 ch - An array of nodes.
13372 w - A fixed dimension where nodes will be laid out.
13373 coord - A coordinates object specifying width, height, left and top style properties.
13375 layoutRow: function(ch, w, coord, prop) {
13376 if(this.layout.horizontal()) {
13377 return this.layoutV(ch, w, coord, prop);
13379 return this.layoutH(ch, w, coord, prop);
13383 layoutV: function(ch, w, coord, prop) {
13384 var totalArea = 0, rnd = function(x) { return x; };
13385 $.each(ch, function(elem) { totalArea += elem._area; });
13386 var width = rnd(totalArea / w), top = 0;
13387 for(var i=0, l=ch.length; i<l; i++) {
13388 var h = rnd(ch[i]._area / width);
13390 chi.getPos(prop).setc(coord.left, coord.top + top);
13391 chi.setData('width', width, prop);
13392 chi.setData('height', h, prop);
13396 'height': coord.height,
13397 'width': coord.width - width,
13399 'left': coord.left + width
13401 //take minimum side value.
13402 ans.dim = Math.min(ans.width, ans.height);
13403 if(ans.dim != ans.height) this.layout.change();
13407 layoutH: function(ch, w, coord, prop) {
13409 $.each(ch, function(elem) { totalArea += elem._area; });
13410 var height = totalArea / w,
13414 for(var i=0, l=ch.length; i<l; i++) {
13416 var w = chi._area / height;
13417 chi.getPos(prop).setc(coord.left + left, top);
13418 chi.setData('width', w, prop);
13419 chi.setData('height', height, prop);
13423 'height': coord.height - height,
13424 'width': coord.width,
13425 'top': coord.top + height,
13428 ans.dim = Math.min(ans.width, ans.height);
13429 if(ans.dim != ans.width) this.layout.change();
13434 Layouts.TM.Strip = new Class({
13435 Implements: Layouts.TM.Area,
13440 Called by loadJSON to calculate recursively all node positions and lay out the tree.
13444 json - A JSON subtree. See also <Loader.loadJSON>.
13445 coord - A coordinates object specifying width, height, left and top style properties.
13447 computePositions: function(node, coord, prop) {
13448 var ch = node.getSubnodes([1, 1], "ignore"),
13449 config = this.config,
13451 if(ch.length > 0) {
13452 this.processChildrenLayout(node, ch, coord, prop);
13453 for(var i=0, l=ch.length; i<l; i++) {
13455 var offst = config.offset,
13456 height = max(chi.getData('height', prop) - offst - config.titleHeight, 0),
13457 width = max(chi.getData('width', prop) - offst, 0);
13458 var chipos = chi.getPos(prop);
13462 'top': chipos.y + config.titleHeight,
13465 this.computePositions(chi, coord, prop);
13471 Method: processChildrenLayout
13473 Computes children real areas and other useful parameters for performing the Strip algorithm.
13477 par - The parent node of the json subtree.
13478 ch - An Array of nodes
13479 coord - A coordinates object specifying width, height, left and top style properties.
13481 processChildrenLayout: function(par, ch, coord, prop) {
13482 //compute children real areas
13483 var parentArea = coord.width * coord.height;
13484 var i, l=ch.length, totalChArea=0, chArea = [];
13485 for(i=0; i<l; i++) {
13486 chArea[i] = +ch[i].getData('area', prop);
13487 totalChArea += chArea[i];
13489 for(i=0; i<l; i++) {
13490 ch[i]._area = parentArea * chArea[i] / totalChArea;
13492 var side = this.layout.horizontal()? coord.width : coord.height;
13493 var initElem = [ch[0]];
13494 var tail = ch.slice(1);
13495 this.stripify(tail, initElem, side, coord, prop);
13501 Performs an heuristic method to calculate div elements sizes in order to have
13502 a good compromise between aspect ratio and order.
13506 tail - An array of nodes.
13507 initElem - An array of nodes.
13508 w - A fixed dimension where nodes will be layed out.
13509 coord - A coordinates object specifying width, height, left and top style properties.
13511 stripify: function(tail, initElem, w, coord, prop) {
13512 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
13518 Performs the layout of an array of nodes.
13522 ch - An array of nodes.
13523 w - A fixed dimension where nodes will be laid out.
13524 coord - A coordinates object specifying width, height, left and top style properties.
13526 layoutRow: function(ch, w, coord, prop) {
13527 if(this.layout.horizontal()) {
13528 return this.layoutH(ch, w, coord, prop);
13530 return this.layoutV(ch, w, coord, prop);
13534 layoutV: function(ch, w, coord, prop) {
13536 $.each(ch, function(elem) { totalArea += elem._area; });
13537 var width = totalArea / w, top = 0;
13538 for(var i=0, l=ch.length; i<l; i++) {
13540 var h = chi._area / width;
13541 chi.getPos(prop).setc(coord.left,
13542 coord.top + (w - h - top));
13543 chi.setData('width', width, prop);
13544 chi.setData('height', h, prop);
13549 'height': coord.height,
13550 'width': coord.width - width,
13552 'left': coord.left + width,
13557 layoutH: function(ch, w, coord, prop) {
13559 $.each(ch, function(elem) { totalArea += elem._area; });
13560 var height = totalArea / w,
13561 top = coord.height - height,
13564 for(var i=0, l=ch.length; i<l; i++) {
13566 var s = chi._area / height;
13567 chi.getPos(prop).setc(coord.left + left, coord.top + top);
13568 chi.setData('width', s, prop);
13569 chi.setData('height', height, prop);
13573 'height': coord.height - height,
13574 'width': coord.width,
13576 'left': coord.left,
13584 * Class: Layouts.Icicle
13586 * Implements the icicle tree layout.
13594 Layouts.Icicle = new Class({
13598 * Called by loadJSON to calculate all node positions.
13602 * posType - The nodes' position to compute. Either "start", "end" or
13603 * "current". Defaults to "current".
13605 compute: function(posType) {
13606 posType = posType || "current";
13608 var root = this.graph.getNode(this.root),
13609 config = this.config,
13610 size = this.canvas.getSize(),
13611 width = size.width,
13612 height = size.height,
13613 offset = config.offset,
13614 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
13616 this.controller.onBeforeCompute(root);
13618 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
13622 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
13624 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
13625 var maxDepth = Math.min(treeDepth, levelsToShow-1);
13626 var initialDepth = startNode._depth;
13627 if(this.layout.horizontal()) {
13628 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
13630 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
13634 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
13635 root.getPos(posType).setc(x, y);
13636 root.setData('width', width, posType);
13637 root.setData('height', height, posType);
13639 var nodeLength, prevNodeLength = 0, totalDim = 0;
13640 var children = Graph.Util.getSubnodes(root, [1, 1], 'ignore'); // next level from this node
13642 if(!children.length)
13645 $.each(children, function(e) { totalDim += e.getData('dim'); });
13647 for(var i=0, l=children.length; i < l; i++) {
13648 if(this.layout.horizontal()) {
13649 nodeLength = height * children[i].getData('dim') / totalDim;
13650 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
13653 nodeLength = width * children[i].getData('dim') / totalDim;
13654 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
13671 Icicle space filling visualization.
13675 All <Loader> methods
13677 Constructor Options:
13679 Inherits options from
13682 - <Options.Controller>
13688 - <Options.NodeStyles>
13689 - <Options.Navigation>
13691 Additionally, there are other parameters and some default values changed
13693 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
13694 offset - (number) Default's *2*. Boxes offset.
13695 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
13696 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
13697 animate - (boolean) Default's *false*. Whether to animate transitions.
13698 Node.type - Described in <Options.Node>. Default's *rectangle*.
13699 Label.type - Described in <Options.Label>. Default's *Native*.
13700 duration - Described in <Options.Fx>. Default's *700*.
13701 fps - Described in <Options.Fx>. Default's *45*.
13703 Instance Properties:
13705 canvas - Access a <Canvas> instance.
13706 graph - Access a <Graph> instance.
13707 op - Access a <Icicle.Op> instance.
13708 fx - Access a <Icicle.Plot> instance.
13709 labels - Access a <Icicle.Label> interface implementation.
13713 $jit.Icicle = new Class({
13714 Implements: [ Loader, Extras, Layouts.Icicle ],
13718 vertical: function(){
13719 return this.orientation == "v";
13721 horizontal: function(){
13722 return this.orientation == "h";
13724 change: function(){
13725 this.orientation = this.vertical()? "h" : "v";
13729 initialize: function(controller) {
13734 levelsToShow: Number.MAX_VALUE,
13735 constrained: false,
13750 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
13751 "Events", "Navigation", "Controller", "Label");
13752 this.controller = this.config = $.merge(opts, config, controller);
13753 this.layout.orientation = this.config.orientation;
13755 var canvasConfig = this.config;
13756 if (canvasConfig.useCanvas) {
13757 this.canvas = canvasConfig.useCanvas;
13758 this.config.labelContainer = this.canvas.id + '-label';
13760 this.canvas = new Canvas(this, canvasConfig);
13761 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
13764 this.graphOptions = {
13773 this.graph = new Graph(
13774 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
13776 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
13777 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
13778 this.op = new $jit.Icicle.Op(this);
13779 this.group = new $jit.Icicle.Group(this);
13780 this.clickedNode = null;
13782 this.initializeExtras();
13788 Computes positions and plots the tree.
13790 refresh: function(){
13791 var labelType = this.config.Label.type;
13792 if(labelType != 'Native') {
13794 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
13803 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
13807 this.fx.plot(this.config);
13813 Sets the node as root.
13817 node - (object) A <Graph.Node>.
13820 enter: function (node) {
13826 config = this.config;
13829 onComplete: function() {
13830 //compute positions of newly inserted nodes
13834 if(config.animate) {
13835 that.graph.nodeList.setDataset(['current', 'end'], {
13836 'alpha': [1, 0] //fade nodes
13839 Graph.Util.eachSubgraph(node, function(n) {
13840 n.setData('alpha', 1, 'end');
13845 modes:['node-property:alpha'],
13846 onComplete: function() {
13847 that.clickedNode = node;
13848 that.compute('end');
13851 modes:['linear', 'node-property:width:height'],
13853 onComplete: function() {
13855 that.clickedNode = node;
13861 that.clickedNode = node;
13868 if(config.request) {
13869 this.requestNodes(clickedNode, callback);
13871 callback.onComplete();
13878 Sets the parent node of the current selected node as root.
13886 GUtil = Graph.Util,
13887 config = this.config,
13888 graph = this.graph,
13889 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
13890 parent = parents[0],
13891 clickedNode = parent,
13892 previousClickedNode = this.clickedNode;
13895 this.events.hoveredNode = false;
13902 //final plot callback
13904 onComplete: function() {
13905 that.clickedNode = parent;
13906 if(config.request) {
13907 that.requestNodes(parent, {
13908 onComplete: function() {
13922 //animate node positions
13923 if(config.animate) {
13924 this.clickedNode = clickedNode;
13925 this.compute('end');
13926 //animate the visible subtree only
13927 this.clickedNode = previousClickedNode;
13929 modes:['linear', 'node-property:width:height'],
13931 onComplete: function() {
13932 //animate the parent subtree
13933 that.clickedNode = clickedNode;
13934 //change nodes alpha
13935 graph.nodeList.setDataset(['current', 'end'], {
13938 GUtil.eachSubgraph(previousClickedNode, function(node) {
13939 node.setData('alpha', 1);
13943 modes:['node-property:alpha'],
13944 onComplete: function() {
13945 callback.onComplete();
13951 callback.onComplete();
13954 requestNodes: function(node, onComplete){
13955 var handler = $.merge(this.controller, onComplete),
13956 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
13958 if (handler.request) {
13959 var leaves = [], d = node._depth;
13960 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
13961 if (n.drawn && !Graph.Util.anySubnode(n)) {
13963 n._level = n._depth - d;
13964 if (this.config.constrained)
13965 n._level = levelsToShow - n._level;
13969 this.group.requestNodes(leaves, handler);
13971 handler.onComplete();
13979 Custom extension of <Graph.Op>.
13983 All <Graph.Op> methods
13990 $jit.Icicle.Op = new Class({
13992 Implements: Graph.Op
13997 * Performs operations on group of nodes.
13999 $jit.Icicle.Group = new Class({
14001 initialize: function(viz){
14003 this.canvas = viz.canvas;
14004 this.config = viz.config;
14008 * Calls the request method on the controller to request a subtree for each node.
14010 requestNodes: function(nodes, controller){
14011 var counter = 0, len = nodes.length, nodeSelected = {};
14012 var complete = function(){
14013 controller.onComplete();
14015 var viz = this.viz;
14018 for(var i = 0; i < len; i++) {
14019 nodeSelected[nodes[i].id] = nodes[i];
14020 controller.request(nodes[i].id, nodes[i]._level, {
14021 onComplete: function(nodeId, data){
14022 if (data && data.children) {
14028 if (++counter == len) {
14029 Graph.Util.computeLevels(viz.graph, viz.root, 0);
14041 Custom extension of <Graph.Plot>.
14045 All <Graph.Plot> methods
14052 $jit.Icicle.Plot = new Class({
14053 Implements: Graph.Plot,
14055 plot: function(opt, animating){
14056 opt = opt || this.viz.controller;
14057 var viz = this.viz,
14059 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
14060 initialDepth = root._depth;
14062 viz.canvas.clear();
14063 this.plotTree(root, $.merge(opt, {
14064 'withLabels': true,
14065 'hideLabels': false,
14066 'plotSubtree': function(root, node) {
14067 return !viz.config.constrained ||
14068 (node._depth - initialDepth < viz.config.levelsToShow);
14075 Class: Icicle.Label
14077 Custom extension of <Graph.Label>.
14078 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14082 All <Graph.Label> methods and subclasses.
14086 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14089 $jit.Icicle.Label = {};
14092 Icicle.Label.Native
14094 Custom extension of <Graph.Label.Native>.
14098 All <Graph.Label.Native> methods
14102 <Graph.Label.Native>
14105 $jit.Icicle.Label.Native = new Class({
14106 Implements: Graph.Label.Native,
14108 renderLabel: function(canvas, node, controller) {
14109 var ctx = canvas.getCtx(),
14110 width = node.getData('width'),
14111 height = node.getData('height'),
14112 size = node.getLabelData('size'),
14113 m = ctx.measureText(node.name);
14115 // Guess as much as possible if the label will fit in the node
14116 if(height < (size * 1.5) || width < m.width)
14119 var pos = node.pos.getc(true);
14120 ctx.fillText(node.name,
14122 pos.y + height / 2);
14129 Custom extension of <Graph.Label.SVG>.
14133 All <Graph.Label.SVG> methods
14139 $jit.Icicle.Label.SVG = new Class( {
14140 Implements: Graph.Label.SVG,
14142 initialize: function(viz){
14149 Overrides abstract method placeLabel in <Graph.Plot>.
14153 tag - A DOM label element.
14154 node - A <Graph.Node>.
14155 controller - A configuration/controller object passed to the visualization.
14157 placeLabel: function(tag, node, controller){
14158 var pos = node.pos.getc(true), canvas = this.viz.canvas;
14159 var radius = canvas.getSize();
14161 x: Math.round(pos.x + radius.width / 2),
14162 y: Math.round(pos.y + radius.height / 2)
14164 tag.setAttribute('x', labelPos.x);
14165 tag.setAttribute('y', labelPos.y);
14167 controller.onPlaceLabel(tag, node);
14174 Custom extension of <Graph.Label.HTML>.
14178 All <Graph.Label.HTML> methods.
14185 $jit.Icicle.Label.HTML = new Class( {
14186 Implements: Graph.Label.HTML,
14188 initialize: function(viz){
14195 Overrides abstract method placeLabel in <Graph.Plot>.
14199 tag - A DOM label element.
14200 node - A <Graph.Node>.
14201 controller - A configuration/controller object passed to the visualization.
14203 placeLabel: function(tag, node, controller){
14204 var pos = node.pos.getc(true), canvas = this.viz.canvas;
14205 var radius = canvas.getSize();
14207 x: Math.round(pos.x + radius.width / 2),
14208 y: Math.round(pos.y + radius.height / 2)
14211 var style = tag.style;
14212 style.left = labelPos.x + 'px';
14213 style.top = labelPos.y + 'px';
14214 style.display = '';
14216 controller.onPlaceLabel(tag, node);
14221 Class: Icicle.Plot.NodeTypes
14223 This class contains a list of <Graph.Node> built-in types.
14224 Node types implemented are 'none', 'rectangle'.
14226 You can add your custom node types, customizing your visualization to the extreme.
14231 Icicle.Plot.NodeTypes.implement({
14233 'render': function(node, canvas) {
14234 //print your custom node to canvas
14237 'contains': function(node, pos) {
14238 //return true if pos is inside the node or false otherwise
14245 $jit.Icicle.Plot.NodeTypes = new Class( {
14251 'render': function(node, canvas, animating) {
14252 var config = this.viz.config;
14253 var offset = config.offset;
14254 var width = node.getData('width');
14255 var height = node.getData('height');
14256 var border = node.getData('border');
14257 var pos = node.pos.getc(true);
14258 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
14259 var ctx = canvas.getCtx();
14261 if(width - offset < 2 || height - offset < 2) return;
14263 if(config.cushion) {
14264 var color = node.getData('color');
14265 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
14266 posy + (height - offset)/2, 1,
14267 posx + (width-offset)/2, posy + (height-offset)/2,
14268 width < height? height : width);
14269 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
14270 function(r) { return r * 0.3 >> 0; }));
14271 lg.addColorStop(0, color);
14272 lg.addColorStop(1, colorGrad);
14273 ctx.fillStyle = lg;
14277 ctx.strokeStyle = border;
14281 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
14282 border && ctx.strokeRect(pos.x, pos.y, width, height);
14285 'contains': function(node, pos) {
14286 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
14287 var npos = node.pos.getc(true),
14288 width = node.getData('width'),
14289 height = node.getData('height');
14290 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
14295 $jit.Icicle.Plot.EdgeTypes = new Class( {
14302 * File: Layouts.ForceDirected.js
14307 * Class: Layouts.ForceDirected
14309 * Implements a Force Directed Layout.
14317 * Marcus Cobden <http://marcuscobden.co.uk>
14320 Layouts.ForceDirected = new Class({
14322 getOptions: function(random) {
14323 var s = this.canvas.getSize();
14324 var w = s.width, h = s.height;
14327 this.graph.eachNode(function(n) {
14330 var k2 = w * h / count, k = Math.sqrt(k2);
14331 var l = this.config.levelDistance;
14337 nodef: function(x) { return k2 / (x || 1); },
14338 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
14342 compute: function(property, incremental) {
14343 var prop = $.splat(property || ['current', 'start', 'end']);
14344 var opt = this.getOptions();
14345 NodeDim.compute(this.graph, prop, this.config);
14346 this.graph.computeLevels(this.root, 0, "ignore");
14347 this.graph.eachNode(function(n) {
14348 $.each(prop, function(p) {
14349 var pos = n.getPos(p);
14350 if(pos.equals(Complex.KER)) {
14351 pos.x = opt.width/5 * (Math.random() - 0.5);
14352 pos.y = opt.height/5 * (Math.random() - 0.5);
14354 //initialize disp vector
14356 $.each(prop, function(p) {
14357 n.disp[p] = $C(0, 0);
14361 this.computePositions(prop, opt, incremental);
14364 computePositions: function(property, opt, incremental) {
14365 var times = this.config.iterations, i = 0, that = this;
14368 for(var total=incremental.iter, j=0; j<total; j++) {
14369 opt.t = opt.tstart;
14370 if(times) opt.t *= (1 - i++/(times -1));
14371 that.computePositionStep(property, opt);
14372 if(times && i >= times) {
14373 incremental.onComplete();
14377 incremental.onStep(Math.round(i / (times -1) * 100));
14378 setTimeout(iter, 1);
14381 for(; i < times; i++) {
14382 opt.t = opt.tstart * (1 - i/(times -1));
14383 this.computePositionStep(property, opt);
14388 computePositionStep: function(property, opt) {
14389 var graph = this.graph;
14390 var min = Math.min, max = Math.max;
14391 var dpos = $C(0, 0);
14392 //calculate repulsive forces
14393 graph.eachNode(function(v) {
14395 $.each(property, function(p) {
14396 v.disp[p].x = 0; v.disp[p].y = 0;
14398 graph.eachNode(function(u) {
14400 $.each(property, function(p) {
14401 var vp = v.getPos(p), up = u.getPos(p);
14402 dpos.x = vp.x - up.x;
14403 dpos.y = vp.y - up.y;
14404 var norm = dpos.norm() || 1;
14405 v.disp[p].$add(dpos
14406 .$scale(opt.nodef(norm) / norm));
14411 //calculate attractive forces
14412 var T = !!graph.getNode(this.root).visited;
14413 graph.eachNode(function(node) {
14414 node.eachAdjacency(function(adj) {
14415 var nodeTo = adj.nodeTo;
14416 if(!!nodeTo.visited === T) {
14417 $.each(property, function(p) {
14418 var vp = node.getPos(p), up = nodeTo.getPos(p);
14419 dpos.x = vp.x - up.x;
14420 dpos.y = vp.y - up.y;
14421 var norm = dpos.norm() || 1;
14422 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
14423 nodeTo.disp[p].$add(dpos.$scale(-1));
14429 //arrange positions to fit the canvas
14430 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
14431 graph.eachNode(function(u) {
14432 $.each(property, function(p) {
14433 var disp = u.disp[p];
14434 var norm = disp.norm() || 1;
14435 var p = u.getPos(p);
14436 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
14437 disp.y * min(Math.abs(disp.y), t) / norm));
14438 p.x = min(w2, max(-w2, p.x));
14439 p.y = min(h2, max(-h2, p.y));
14446 * File: ForceDirected.js
14450 Class: ForceDirected
14452 A visualization that lays graphs using a Force-Directed layout algorithm.
14456 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
14460 All <Loader> methods
14462 Constructor Options:
14464 Inherits options from
14467 - <Options.Controller>
14473 - <Options.NodeStyles>
14474 - <Options.Navigation>
14476 Additionally, there are two parameters
14478 levelDistance - (number) Default's *50*. The natural length desired for the edges.
14479 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*.
14481 Instance Properties:
14483 canvas - Access a <Canvas> instance.
14484 graph - Access a <Graph> instance.
14485 op - Access a <ForceDirected.Op> instance.
14486 fx - Access a <ForceDirected.Plot> instance.
14487 labels - Access a <ForceDirected.Label> interface implementation.
14491 $jit.ForceDirected = new Class( {
14493 Implements: [ Loader, Extras, Layouts.ForceDirected ],
14495 initialize: function(controller) {
14496 var $ForceDirected = $jit.ForceDirected;
14503 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14504 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14506 var canvasConfig = this.config;
14507 if(canvasConfig.useCanvas) {
14508 this.canvas = canvasConfig.useCanvas;
14509 this.config.labelContainer = this.canvas.id + '-label';
14511 if(canvasConfig.background) {
14512 canvasConfig.background = $.merge({
14514 }, canvasConfig.background);
14516 this.canvas = new Canvas(this, canvasConfig);
14517 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14520 this.graphOptions = {
14528 this.graph = new Graph(this.graphOptions, this.config.Node,
14530 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
14531 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
14532 this.op = new $ForceDirected.Op(this);
14535 // initialize extras
14536 this.initializeExtras();
14542 Computes positions and plots the tree.
14544 refresh: function() {
14549 reposition: function() {
14550 this.compute('end');
14554 Method: computeIncremental
14556 Performs the Force Directed algorithm incrementally.
14560 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
14561 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
14562 avoiding browser messages such as "This script is taking too long to complete".
14566 opt - (object) The object properties are described below
14568 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
14569 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
14571 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
14572 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
14573 computations for final animation positions then you can just choose 'end'.
14575 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
14576 parameter a percentage value.
14578 onComplete - A callback function called when the algorithm completed.
14582 In this example I calculate the end positions and then animate the graph to those positions
14585 var fd = new $jit.ForceDirected(...);
14586 fd.computeIncremental({
14589 onStep: function(perc) {
14590 Log.write("loading " + perc + "%");
14592 onComplete: function() {
14599 In this example I calculate all positions and (re)plot the graph
14602 var fd = new ForceDirected(...);
14603 fd.computeIncremental({
14605 property: ['end', 'start', 'current'],
14606 onStep: function(perc) {
14607 Log.write("loading " + perc + "%");
14609 onComplete: function() {
14617 computeIncremental: function(opt) {
14622 onComplete: $.empty
14625 this.config.onBeforeCompute(this.graph.getNode(this.root));
14626 this.compute(opt.property, opt);
14632 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
14641 Animates the graph from the current positions to the 'end' node positions.
14643 animate: function(opt) {
14644 this.fx.animate($.merge( {
14645 modes: [ 'linear' ]
14650 $jit.ForceDirected.$extend = true;
14652 (function(ForceDirected) {
14655 Class: ForceDirected.Op
14657 Custom extension of <Graph.Op>.
14661 All <Graph.Op> methods
14668 ForceDirected.Op = new Class( {
14670 Implements: Graph.Op
14675 Class: ForceDirected.Plot
14677 Custom extension of <Graph.Plot>.
14681 All <Graph.Plot> methods
14688 ForceDirected.Plot = new Class( {
14690 Implements: Graph.Plot
14695 Class: ForceDirected.Label
14697 Custom extension of <Graph.Label>.
14698 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14702 All <Graph.Label> methods and subclasses.
14706 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14709 ForceDirected.Label = {};
14712 ForceDirected.Label.Native
14714 Custom extension of <Graph.Label.Native>.
14718 All <Graph.Label.Native> methods
14722 <Graph.Label.Native>
14725 ForceDirected.Label.Native = new Class( {
14726 Implements: Graph.Label.Native
14730 ForceDirected.Label.SVG
14732 Custom extension of <Graph.Label.SVG>.
14736 All <Graph.Label.SVG> methods
14743 ForceDirected.Label.SVG = new Class( {
14744 Implements: Graph.Label.SVG,
14746 initialize: function(viz) {
14753 Overrides abstract method placeLabel in <Graph.Label>.
14757 tag - A DOM label element.
14758 node - A <Graph.Node>.
14759 controller - A configuration/controller object passed to the visualization.
14762 placeLabel: function(tag, node, controller) {
14763 var pos = node.pos.getc(true),
14764 canvas = this.viz.canvas,
14765 ox = canvas.translateOffsetX,
14766 oy = canvas.translateOffsetY,
14767 sx = canvas.scaleOffsetX,
14768 sy = canvas.scaleOffsetY,
14769 radius = canvas.getSize();
14771 x: Math.round(pos.x * sx + ox + radius.width / 2),
14772 y: Math.round(pos.y * sy + oy + radius.height / 2)
14774 tag.setAttribute('x', labelPos.x);
14775 tag.setAttribute('y', labelPos.y);
14777 controller.onPlaceLabel(tag, node);
14782 ForceDirected.Label.HTML
14784 Custom extension of <Graph.Label.HTML>.
14788 All <Graph.Label.HTML> methods.
14795 ForceDirected.Label.HTML = new Class( {
14796 Implements: Graph.Label.HTML,
14798 initialize: function(viz) {
14804 Overrides abstract method placeLabel in <Graph.Plot>.
14808 tag - A DOM label element.
14809 node - A <Graph.Node>.
14810 controller - A configuration/controller object passed to the visualization.
14813 placeLabel: function(tag, node, controller) {
14814 var pos = node.pos.getc(true),
14815 canvas = this.viz.canvas,
14816 ox = canvas.translateOffsetX,
14817 oy = canvas.translateOffsetY,
14818 sx = canvas.scaleOffsetX,
14819 sy = canvas.scaleOffsetY,
14820 radius = canvas.getSize();
14822 x: Math.round(pos.x * sx + ox + radius.width / 2),
14823 y: Math.round(pos.y * sy + oy + radius.height / 2)
14825 var style = tag.style;
14826 style.left = labelPos.x + 'px';
14827 style.top = labelPos.y + 'px';
14828 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14830 controller.onPlaceLabel(tag, node);
14835 Class: ForceDirected.Plot.NodeTypes
14837 This class contains a list of <Graph.Node> built-in types.
14838 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
14840 You can add your custom node types, customizing your visualization to the extreme.
14845 ForceDirected.Plot.NodeTypes.implement({
14847 'render': function(node, canvas) {
14848 //print your custom node to canvas
14851 'contains': function(node, pos) {
14852 //return true if pos is inside the node or false otherwise
14859 ForceDirected.Plot.NodeTypes = new Class({
14862 'contains': $.lambda(false)
14865 'render': function(node, canvas){
14866 var pos = node.pos.getc(true),
14867 dim = node.getData('dim');
14868 this.nodeHelper.circle.render('fill', pos, dim, canvas);
14870 'contains': function(node, pos){
14871 var npos = node.pos.getc(true),
14872 dim = node.getData('dim');
14873 return this.nodeHelper.circle.contains(npos, pos, dim);
14877 'render': function(node, canvas){
14878 var pos = node.pos.getc(true),
14879 width = node.getData('width'),
14880 height = node.getData('height');
14881 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
14883 'contains': function(node, pos){
14884 var npos = node.pos.getc(true),
14885 width = node.getData('width'),
14886 height = node.getData('height');
14887 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
14891 'render': function(node, canvas){
14892 var pos = node.pos.getc(true),
14893 dim = node.getData('dim');
14894 this.nodeHelper.square.render('fill', pos, dim, canvas);
14896 'contains': function(node, pos){
14897 var npos = node.pos.getc(true),
14898 dim = node.getData('dim');
14899 return this.nodeHelper.square.contains(npos, pos, dim);
14903 'render': function(node, canvas){
14904 var pos = node.pos.getc(true),
14905 width = node.getData('width'),
14906 height = node.getData('height');
14907 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
14909 'contains': function(node, pos){
14910 var npos = node.pos.getc(true),
14911 width = node.getData('width'),
14912 height = node.getData('height');
14913 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
14917 'render': function(node, canvas){
14918 var pos = node.pos.getc(true),
14919 dim = node.getData('dim');
14920 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
14922 'contains': function(node, pos) {
14923 var npos = node.pos.getc(true),
14924 dim = node.getData('dim');
14925 return this.nodeHelper.triangle.contains(npos, pos, dim);
14929 'render': function(node, canvas){
14930 var pos = node.pos.getc(true),
14931 dim = node.getData('dim');
14932 this.nodeHelper.star.render('fill', pos, dim, canvas);
14934 'contains': function(node, pos) {
14935 var npos = node.pos.getc(true),
14936 dim = node.getData('dim');
14937 return this.nodeHelper.star.contains(npos, pos, dim);
14943 Class: ForceDirected.Plot.EdgeTypes
14945 This class contains a list of <Graph.Adjacence> built-in types.
14946 Edge types implemented are 'none', 'line' and 'arrow'.
14948 You can add your custom edge types, customizing your visualization to the extreme.
14953 ForceDirected.Plot.EdgeTypes.implement({
14955 'render': function(adj, canvas) {
14956 //print your custom edge to canvas
14959 'contains': function(adj, pos) {
14960 //return true if pos is inside the arc or false otherwise
14967 ForceDirected.Plot.EdgeTypes = new Class({
14970 'render': function(adj, canvas) {
14971 var from = adj.nodeFrom.pos.getc(true),
14972 to = adj.nodeTo.pos.getc(true);
14973 this.edgeHelper.line.render(from, to, canvas);
14975 'contains': function(adj, pos) {
14976 var from = adj.nodeFrom.pos.getc(true),
14977 to = adj.nodeTo.pos.getc(true);
14978 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
14982 'render': function(adj, canvas) {
14983 var from = adj.nodeFrom.pos.getc(true),
14984 to = adj.nodeTo.pos.getc(true),
14985 dim = adj.getData('dim'),
14986 direction = adj.data.$direction,
14987 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
14988 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
14990 'contains': function(adj, pos) {
14991 var from = adj.nodeFrom.pos.getc(true),
14992 to = adj.nodeTo.pos.getc(true);
14993 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
14998 })($jit.ForceDirected);
15010 $jit.TM.$extend = true;
15015 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
15019 All <Loader> methods
15021 Constructor Options:
15023 Inherits options from
15026 - <Options.Controller>
15032 - <Options.NodeStyles>
15033 - <Options.Navigation>
15035 Additionally, there are other parameters and some default values changed
15037 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
15038 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
15039 offset - (number) Default's *2*. Boxes offset.
15040 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
15041 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
15042 animate - (boolean) Default's *false*. Whether to animate transitions.
15043 Node.type - Described in <Options.Node>. Default's *rectangle*.
15044 duration - Described in <Options.Fx>. Default's *700*.
15045 fps - Described in <Options.Fx>. Default's *45*.
15047 Instance Properties:
15049 canvas - Access a <Canvas> instance.
15050 graph - Access a <Graph> instance.
15051 op - Access a <TM.Op> instance.
15052 fx - Access a <TM.Plot> instance.
15053 labels - Access a <TM.Label> interface implementation.
15057 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
15059 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
15063 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.
15069 vertical: function(){
15070 return this.orientation == "v";
15072 horizontal: function(){
15073 return this.orientation == "h";
15075 change: function(){
15076 this.orientation = this.vertical()? "h" : "v";
15080 initialize: function(controller){
15086 constrained: false,
15091 //we all know why this is not zero,
15098 textAlign: 'center',
15099 textBaseline: 'top'
15108 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
15109 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
15110 this.layout.orientation = this.config.orientation;
15112 var canvasConfig = this.config;
15113 if (canvasConfig.useCanvas) {
15114 this.canvas = canvasConfig.useCanvas;
15115 this.config.labelContainer = this.canvas.id + '-label';
15117 if(canvasConfig.background) {
15118 canvasConfig.background = $.merge({
15120 }, canvasConfig.background);
15122 this.canvas = new Canvas(this, canvasConfig);
15123 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
15126 this.graphOptions = {
15134 this.graph = new Graph(this.graphOptions, this.config.Node,
15136 this.labels = new TM.Label[canvasConfig.Label.type](this);
15137 this.fx = new TM.Plot(this);
15138 this.op = new TM.Op(this);
15139 this.group = new TM.Group(this);
15140 this.geom = new TM.Geom(this);
15141 this.clickedNode = null;
15143 // initialize extras
15144 this.initializeExtras();
15150 Computes positions and plots the tree.
15152 refresh: function(){
15153 if(this.busy) return;
15156 if(this.config.animate) {
15157 this.compute('end');
15158 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
15159 && this.clickedNode.id || this.root));
15160 this.fx.animate($.merge(this.config, {
15161 modes: ['linear', 'node-property:width:height'],
15162 onComplete: function() {
15167 var labelType = this.config.Label.type;
15168 if(labelType != 'Native') {
15170 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
15174 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
15175 && this.clickedNode.id || this.root));
15183 Plots the TreeMap. This is a shortcut to *fx.plot*.
15193 Returns whether the node is a leaf.
15197 n - (object) A <Graph.Node>.
15201 return n.getSubnodes([
15203 ], "ignore").length == 0;
15209 Sets the node as root.
15213 n - (object) A <Graph.Node>.
15216 enter: function(n){
15217 if(this.busy) return;
15221 config = this.config,
15222 graph = this.graph,
15224 previousClickedNode = this.clickedNode;
15227 onComplete: function() {
15228 //ensure that nodes are shown for that level
15229 if(config.levelsToShow > 0) {
15230 that.geom.setRightLevelToShow(n);
15232 //compute positions of newly inserted nodes
15233 if(config.levelsToShow > 0 || config.request) that.compute();
15234 if(config.animate) {
15236 graph.nodeList.setData('alpha', 0, 'end');
15237 n.eachSubgraph(function(n) {
15238 n.setData('alpha', 1, 'end');
15242 modes:['node-property:alpha'],
15243 onComplete: function() {
15244 //compute end positions
15245 that.clickedNode = clickedNode;
15246 that.compute('end');
15247 //animate positions
15248 //TODO(nico) commenting this line didn't seem to throw errors...
15249 that.clickedNode = previousClickedNode;
15251 modes:['linear', 'node-property:width:height'],
15253 onComplete: function() {
15255 //TODO(nico) check comment above
15256 that.clickedNode = clickedNode;
15263 that.clickedNode = n;
15268 if(config.request) {
15269 this.requestNodes(clickedNode, callback);
15271 callback.onComplete();
15278 Sets the parent node of the current selected node as root.
15282 if(this.busy) return;
15284 this.events.hoveredNode = false;
15286 config = this.config,
15287 graph = this.graph,
15288 parents = graph.getNode(this.clickedNode
15289 && this.clickedNode.id || this.root).getParents(),
15290 parent = parents[0],
15291 clickedNode = parent,
15292 previousClickedNode = this.clickedNode;
15294 //if no parents return
15299 //final plot callback
15301 onComplete: function() {
15302 that.clickedNode = parent;
15303 if(config.request) {
15304 that.requestNodes(parent, {
15305 onComplete: function() {
15319 if (config.levelsToShow > 0)
15320 this.geom.setRightLevelToShow(parent);
15321 //animate node positions
15322 if(config.animate) {
15323 this.clickedNode = clickedNode;
15324 this.compute('end');
15325 //animate the visible subtree only
15326 this.clickedNode = previousClickedNode;
15328 modes:['linear', 'node-property:width:height'],
15330 onComplete: function() {
15331 //animate the parent subtree
15332 that.clickedNode = clickedNode;
15333 //change nodes alpha
15334 graph.eachNode(function(n) {
15335 n.setDataset(['current', 'end'], {
15339 previousClickedNode.eachSubgraph(function(node) {
15340 node.setData('alpha', 1);
15344 modes:['node-property:alpha'],
15345 onComplete: function() {
15346 callback.onComplete();
15352 callback.onComplete();
15356 requestNodes: function(node, onComplete){
15357 var handler = $.merge(this.controller, onComplete),
15358 lev = this.config.levelsToShow;
15359 if (handler.request) {
15360 var leaves = [], d = node._depth;
15361 node.eachLevel(0, lev, function(n){
15362 var nodeLevel = lev - (n._depth - d);
15363 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
15365 n._level = nodeLevel;
15368 this.group.requestNodes(leaves, handler);
15370 handler.onComplete();
15374 reposition: function() {
15375 this.compute('end');
15382 Custom extension of <Graph.Op>.
15386 All <Graph.Op> methods
15393 TM.Op = new Class({
15394 Implements: Graph.Op,
15396 initialize: function(viz){
15401 //extend level methods of Graph.Geom
15402 TM.Geom = new Class({
15403 Implements: Graph.Geom,
15405 getRightLevelToShow: function() {
15406 return this.viz.config.levelsToShow;
15409 setRightLevelToShow: function(node) {
15410 var level = this.getRightLevelToShow(),
15411 fx = this.viz.labels;
15412 node.eachLevel(0, level+1, function(n) {
15413 var d = n._depth - node._depth;
15418 fx.hideLabel(n, false);
15426 delete node.ignore;
15432 Performs operations on group of nodes.
15435 TM.Group = new Class( {
15437 initialize: function(viz){
15439 this.canvas = viz.canvas;
15440 this.config = viz.config;
15445 Calls the request method on the controller to request a subtree for each node.
15447 requestNodes: function(nodes, controller){
15448 var counter = 0, len = nodes.length, nodeSelected = {};
15449 var complete = function(){
15450 controller.onComplete();
15452 var viz = this.viz;
15455 for ( var i = 0; i < len; i++) {
15456 nodeSelected[nodes[i].id] = nodes[i];
15457 controller.request(nodes[i].id, nodes[i]._level, {
15458 onComplete: function(nodeId, data){
15459 if (data && data.children) {
15465 if (++counter == len) {
15466 viz.graph.computeLevels(viz.root, 0);
15478 Custom extension of <Graph.Plot>.
15482 All <Graph.Plot> methods
15489 TM.Plot = new Class({
15491 Implements: Graph.Plot,
15493 initialize: function(viz){
15495 this.config = viz.config;
15496 this.node = this.config.Node;
15497 this.edge = this.config.Edge;
15498 this.animation = new Animation;
15499 this.nodeTypes = new TM.Plot.NodeTypes;
15500 this.edgeTypes = new TM.Plot.EdgeTypes;
15501 this.labels = viz.labels;
15504 plot: function(opt, animating){
15505 var viz = this.viz,
15507 viz.canvas.clear();
15508 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
15509 'withLabels': true,
15510 'hideLabels': false,
15511 'plotSubtree': function(n, ch){
15512 return n.anySubnode("exist");
15521 Custom extension of <Graph.Label>.
15522 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
15526 All <Graph.Label> methods and subclasses.
15530 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
15538 Custom extension of <Graph.Label.Native>.
15542 All <Graph.Label.Native> methods
15546 <Graph.Label.Native>
15548 TM.Label.Native = new Class({
15549 Implements: Graph.Label.Native,
15551 initialize: function(viz) {
15552 this.config = viz.config;
15553 this.leaf = viz.leaf;
15556 renderLabel: function(canvas, node, controller){
15557 if(!this.leaf(node) && !this.config.titleHeight) return;
15558 var pos = node.pos.getc(true),
15559 ctx = canvas.getCtx(),
15560 width = node.getData('width'),
15561 height = node.getData('height'),
15562 x = pos.x + width/2,
15565 ctx.fillText(node.name, x, y, width);
15572 Custom extension of <Graph.Label.SVG>.
15576 All <Graph.Label.SVG> methods
15582 TM.Label.SVG = new Class( {
15583 Implements: Graph.Label.SVG,
15585 initialize: function(viz){
15587 this.leaf = viz.leaf;
15588 this.config = viz.config;
15594 Overrides abstract method placeLabel in <Graph.Plot>.
15598 tag - A DOM label element.
15599 node - A <Graph.Node>.
15600 controller - A configuration/controller object passed to the visualization.
15603 placeLabel: function(tag, node, controller){
15604 var pos = node.pos.getc(true),
15605 canvas = this.viz.canvas,
15606 ox = canvas.translateOffsetX,
15607 oy = canvas.translateOffsetY,
15608 sx = canvas.scaleOffsetX,
15609 sy = canvas.scaleOffsetY,
15610 radius = canvas.getSize();
15612 x: Math.round(pos.x * sx + ox + radius.width / 2),
15613 y: Math.round(pos.y * sy + oy + radius.height / 2)
15615 tag.setAttribute('x', labelPos.x);
15616 tag.setAttribute('y', labelPos.y);
15618 if(!this.leaf(node) && !this.config.titleHeight) {
15619 tag.style.display = 'none';
15621 controller.onPlaceLabel(tag, node);
15628 Custom extension of <Graph.Label.HTML>.
15632 All <Graph.Label.HTML> methods.
15639 TM.Label.HTML = new Class( {
15640 Implements: Graph.Label.HTML,
15642 initialize: function(viz){
15644 this.leaf = viz.leaf;
15645 this.config = viz.config;
15651 Overrides abstract method placeLabel in <Graph.Plot>.
15655 tag - A DOM label element.
15656 node - A <Graph.Node>.
15657 controller - A configuration/controller object passed to the visualization.
15660 placeLabel: function(tag, node, controller){
15661 var pos = node.pos.getc(true),
15662 canvas = this.viz.canvas,
15663 ox = canvas.translateOffsetX,
15664 oy = canvas.translateOffsetY,
15665 sx = canvas.scaleOffsetX,
15666 sy = canvas.scaleOffsetY,
15667 radius = canvas.getSize();
15669 x: Math.round(pos.x * sx + ox + radius.width / 2),
15670 y: Math.round(pos.y * sy + oy + radius.height / 2)
15673 var style = tag.style;
15674 style.left = labelPos.x + 'px';
15675 style.top = labelPos.y + 'px';
15676 style.width = node.getData('width') * sx + 'px';
15677 style.height = node.getData('height') * sy + 'px';
15678 style.zIndex = node._depth * 100;
15679 style.display = '';
15681 if(!this.leaf(node) && !this.config.titleHeight) {
15682 tag.style.display = 'none';
15684 controller.onPlaceLabel(tag, node);
15689 Class: TM.Plot.NodeTypes
15691 This class contains a list of <Graph.Node> built-in types.
15692 Node types implemented are 'none', 'rectangle'.
15694 You can add your custom node types, customizing your visualization to the extreme.
15699 TM.Plot.NodeTypes.implement({
15701 'render': function(node, canvas) {
15702 //print your custom node to canvas
15705 'contains': function(node, pos) {
15706 //return true if pos is inside the node or false otherwise
15713 TM.Plot.NodeTypes = new Class( {
15719 'render': function(node, canvas, animating){
15720 var leaf = this.viz.leaf(node),
15721 config = this.config,
15722 offst = config.offset,
15723 titleHeight = config.titleHeight,
15724 pos = node.pos.getc(true),
15725 width = node.getData('width'),
15726 height = node.getData('height'),
15727 border = node.getData('border'),
15728 ctx = canvas.getCtx(),
15729 posx = pos.x + offst / 2,
15730 posy = pos.y + offst / 2;
15731 if(width <= offst || height <= offst) return;
15733 if(config.cushion) {
15734 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
15735 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
15736 var color = node.getData('color');
15737 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
15738 function(r) { return r * 0.2 >> 0; }));
15739 lg.addColorStop(0, color);
15740 lg.addColorStop(1, colorGrad);
15741 ctx.fillStyle = lg;
15743 ctx.fillRect(posx, posy, width - offst, height - offst);
15746 ctx.strokeStyle = border;
15747 ctx.strokeRect(posx, posy, width - offst, height - offst);
15750 } else if(titleHeight > 0){
15751 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
15752 titleHeight - offst);
15755 ctx.strokeStyle = border;
15756 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
15762 'contains': function(node, pos) {
15763 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
15764 var npos = node.pos.getc(true),
15765 width = node.getData('width'),
15766 leaf = this.viz.leaf(node),
15767 height = leaf? node.getData('height') : this.config.titleHeight;
15768 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
15773 TM.Plot.EdgeTypes = new Class( {
15778 Class: TM.SliceAndDice
15780 A slice and dice TreeMap visualization.
15784 All <TM.Base> methods and properties.
15786 TM.SliceAndDice = new Class( {
15788 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
15793 Class: TM.Squarified
15795 A squarified TreeMap visualization.
15799 All <TM.Base> methods and properties.
15801 TM.Squarified = new Class( {
15803 Loader, Extras, TM.Base, Layouts.TM.Squarified
15810 A strip TreeMap visualization.
15814 All <TM.Base> methods and properties.
15816 TM.Strip = new Class( {
15818 Loader, Extras, TM.Base, Layouts.TM.Strip
15831 A radial graph visualization with advanced animations.
15835 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>
15839 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.
15843 All <Loader> methods
15845 Constructor Options:
15847 Inherits options from
15850 - <Options.Controller>
15856 - <Options.NodeStyles>
15857 - <Options.Navigation>
15859 Additionally, there are other parameters and some default values changed
15861 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
15862 levelDistance - (number) Default's *100*. The distance between levels of the tree.
15864 Instance Properties:
15866 canvas - Access a <Canvas> instance.
15867 graph - Access a <Graph> instance.
15868 op - Access a <RGraph.Op> instance.
15869 fx - Access a <RGraph.Plot> instance.
15870 labels - Access a <RGraph.Label> interface implementation.
15873 $jit.RGraph = new Class( {
15876 Loader, Extras, Layouts.Radial
15879 initialize: function(controller){
15880 var $RGraph = $jit.RGraph;
15883 interpolation: 'linear',
15887 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
15888 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
15890 var canvasConfig = this.config;
15891 if(canvasConfig.useCanvas) {
15892 this.canvas = canvasConfig.useCanvas;
15893 this.config.labelContainer = this.canvas.id + '-label';
15895 if(canvasConfig.background) {
15896 canvasConfig.background = $.merge({
15898 }, canvasConfig.background);
15900 this.canvas = new Canvas(this, canvasConfig);
15901 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
15904 this.graphOptions = {
15912 this.graph = new Graph(this.graphOptions, this.config.Node,
15914 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
15915 this.fx = new $RGraph.Plot(this, $RGraph);
15916 this.op = new $RGraph.Op(this);
15920 this.parent = false;
15921 // initialize extras
15922 this.initializeExtras();
15927 createLevelDistanceFunc
15929 Returns the levelDistance function used for calculating a node distance
15930 to its origin. This function returns a function that is computed
15931 per level and not per node, such that all nodes with the same depth will have the
15932 same distance to the origin. The resulting function gets the
15933 parent node as parameter and returns a float.
15936 createLevelDistanceFunc: function(){
15937 var ld = this.config.levelDistance;
15938 return function(elem){
15939 return (elem._depth + 1) * ld;
15946 Computes positions and plots the tree.
15949 refresh: function(){
15954 reposition: function(){
15955 this.compute('end');
15961 Plots the RGraph. This is a shortcut to *fx.plot*.
15967 getNodeAndParentAngle
15969 Returns the _parent_ of the given node, also calculating its angle span.
15971 getNodeAndParentAngle: function(id){
15973 var n = this.graph.getNode(id);
15974 var ps = n.getParents();
15975 var p = (ps.length > 0)? ps[0] : false;
15977 var posParent = p.pos.getc(), posChild = n.pos.getc();
15978 var newPos = posParent.add(posChild.scale(-1));
15979 theta = Math.atan2(newPos.y, newPos.x);
15981 theta += 2 * Math.PI;
15991 Enumerates the children in order to maintain child ordering (second constraint of the paper).
15993 tagChildren: function(par, id){
15994 if (par.angleSpan) {
15996 par.eachAdjacency(function(elem){
15997 adjs.push(elem.nodeTo);
15999 var len = adjs.length;
16000 for ( var i = 0; i < len && id != adjs[i].id; i++)
16002 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
16003 adjs[j].dist = k++;
16010 Animates the <RGraph> to center the node specified by *id*.
16014 id - A <Graph.Node> id.
16015 opt - (optional|object) An object containing some extra properties described below
16016 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
16021 rgraph.onClick('someid');
16023 rgraph.onClick('someid', {
16029 onClick: function(id, opt){
16030 if (this.root != id && !this.busy) {
16034 this.controller.onBeforeCompute(this.graph.getNode(id));
16035 var obj = this.getNodeAndParentAngle(id);
16037 // second constraint
16038 this.tagChildren(obj.parent, id);
16039 this.parent = obj.parent;
16040 this.compute('end');
16042 // first constraint
16043 var thetaDiff = obj.theta - obj.parent.endPos.theta;
16044 this.graph.eachNode(function(elem){
16045 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
16048 var mode = this.config.interpolation;
16050 onComplete: $.empty
16053 this.fx.animate($.merge( {
16059 onComplete: function(){
16068 $jit.RGraph.$extend = true;
16075 Custom extension of <Graph.Op>.
16079 All <Graph.Op> methods
16086 RGraph.Op = new Class( {
16088 Implements: Graph.Op
16095 Custom extension of <Graph.Plot>.
16099 All <Graph.Plot> methods
16106 RGraph.Plot = new Class( {
16108 Implements: Graph.Plot
16113 Object: RGraph.Label
16115 Custom extension of <Graph.Label>.
16116 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
16120 All <Graph.Label> methods and subclasses.
16124 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
16130 RGraph.Label.Native
16132 Custom extension of <Graph.Label.Native>.
16136 All <Graph.Label.Native> methods
16140 <Graph.Label.Native>
16143 RGraph.Label.Native = new Class( {
16144 Implements: Graph.Label.Native
16150 Custom extension of <Graph.Label.SVG>.
16154 All <Graph.Label.SVG> methods
16161 RGraph.Label.SVG = new Class( {
16162 Implements: Graph.Label.SVG,
16164 initialize: function(viz){
16171 Overrides abstract method placeLabel in <Graph.Plot>.
16175 tag - A DOM label element.
16176 node - A <Graph.Node>.
16177 controller - A configuration/controller object passed to the visualization.
16180 placeLabel: function(tag, node, controller){
16181 var pos = node.pos.getc(true),
16182 canvas = this.viz.canvas,
16183 ox = canvas.translateOffsetX,
16184 oy = canvas.translateOffsetY,
16185 sx = canvas.scaleOffsetX,
16186 sy = canvas.scaleOffsetY,
16187 radius = canvas.getSize();
16189 x: Math.round(pos.x * sx + ox + radius.width / 2),
16190 y: Math.round(pos.y * sy + oy + radius.height / 2)
16192 tag.setAttribute('x', labelPos.x);
16193 tag.setAttribute('y', labelPos.y);
16195 controller.onPlaceLabel(tag, node);
16202 Custom extension of <Graph.Label.HTML>.
16206 All <Graph.Label.HTML> methods.
16213 RGraph.Label.HTML = new Class( {
16214 Implements: Graph.Label.HTML,
16216 initialize: function(viz){
16222 Overrides abstract method placeLabel in <Graph.Plot>.
16226 tag - A DOM label element.
16227 node - A <Graph.Node>.
16228 controller - A configuration/controller object passed to the visualization.
16231 placeLabel: function(tag, node, controller){
16232 var pos = node.pos.getc(true),
16233 canvas = this.viz.canvas,
16234 ox = canvas.translateOffsetX,
16235 oy = canvas.translateOffsetY,
16236 sx = canvas.scaleOffsetX,
16237 sy = canvas.scaleOffsetY,
16238 radius = canvas.getSize();
16240 x: Math.round(pos.x * sx + ox + radius.width / 2),
16241 y: Math.round(pos.y * sy + oy + radius.height / 2)
16244 var style = tag.style;
16245 style.left = labelPos.x + 'px';
16246 style.top = labelPos.y + 'px';
16247 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
16249 controller.onPlaceLabel(tag, node);
16254 Class: RGraph.Plot.NodeTypes
16256 This class contains a list of <Graph.Node> built-in types.
16257 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
16259 You can add your custom node types, customizing your visualization to the extreme.
16264 RGraph.Plot.NodeTypes.implement({
16266 'render': function(node, canvas) {
16267 //print your custom node to canvas
16270 'contains': function(node, pos) {
16271 //return true if pos is inside the node or false otherwise
16278 RGraph.Plot.NodeTypes = new Class({
16281 'contains': $.lambda(false)
16284 'render': function(node, canvas){
16285 var pos = node.pos.getc(true),
16286 dim = node.getData('dim');
16287 this.nodeHelper.circle.render('fill', pos, dim, canvas);
16289 'contains': function(node, pos){
16290 var npos = node.pos.getc(true),
16291 dim = node.getData('dim');
16292 return this.nodeHelper.circle.contains(npos, pos, dim);
16296 'render': function(node, canvas){
16297 var pos = node.pos.getc(true),
16298 width = node.getData('width'),
16299 height = node.getData('height');
16300 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
16302 'contains': function(node, pos){
16303 var npos = node.pos.getc(true),
16304 width = node.getData('width'),
16305 height = node.getData('height');
16306 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
16310 'render': function(node, canvas){
16311 var pos = node.pos.getc(true),
16312 dim = node.getData('dim');
16313 this.nodeHelper.square.render('fill', pos, dim, canvas);
16315 'contains': function(node, pos){
16316 var npos = node.pos.getc(true),
16317 dim = node.getData('dim');
16318 return this.nodeHelper.square.contains(npos, pos, dim);
16322 'render': function(node, canvas){
16323 var pos = node.pos.getc(true),
16324 width = node.getData('width'),
16325 height = node.getData('height');
16326 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
16328 'contains': function(node, pos){
16329 var npos = node.pos.getc(true),
16330 width = node.getData('width'),
16331 height = node.getData('height');
16332 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
16336 'render': function(node, canvas){
16337 var pos = node.pos.getc(true),
16338 dim = node.getData('dim');
16339 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
16341 'contains': function(node, pos) {
16342 var npos = node.pos.getc(true),
16343 dim = node.getData('dim');
16344 return this.nodeHelper.triangle.contains(npos, pos, dim);
16348 'render': function(node, canvas){
16349 var pos = node.pos.getc(true),
16350 dim = node.getData('dim');
16351 this.nodeHelper.star.render('fill', pos, dim, canvas);
16353 'contains': function(node, pos) {
16354 var npos = node.pos.getc(true),
16355 dim = node.getData('dim');
16356 return this.nodeHelper.star.contains(npos, pos, dim);
16362 Class: RGraph.Plot.EdgeTypes
16364 This class contains a list of <Graph.Adjacence> built-in types.
16365 Edge types implemented are 'none', 'line' and 'arrow'.
16367 You can add your custom edge types, customizing your visualization to the extreme.
16372 RGraph.Plot.EdgeTypes.implement({
16374 'render': function(adj, canvas) {
16375 //print your custom edge to canvas
16378 'contains': function(adj, pos) {
16379 //return true if pos is inside the arc or false otherwise
16386 RGraph.Plot.EdgeTypes = new Class({
16389 'render': function(adj, canvas) {
16390 var from = adj.nodeFrom.pos.getc(true),
16391 to = adj.nodeTo.pos.getc(true);
16392 this.edgeHelper.line.render(from, to, canvas);
16394 'contains': function(adj, pos) {
16395 var from = adj.nodeFrom.pos.getc(true),
16396 to = adj.nodeTo.pos.getc(true);
16397 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
16401 'render': function(adj, canvas) {
16402 var from = adj.nodeFrom.pos.getc(true),
16403 to = adj.nodeTo.pos.getc(true),
16404 dim = adj.getData('dim'),
16405 direction = adj.data.$direction,
16406 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
16407 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
16409 'contains': function(adj, pos) {
16410 var from = adj.nodeFrom.pos.getc(true),
16411 to = adj.nodeTo.pos.getc(true);
16412 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
16421 * File: Hypertree.js
16428 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
16432 moebiusTransformation
16434 Calculates a moebius transformation for this point / complex.
16435 For more information go to:
16436 http://en.wikipedia.org/wiki/Moebius_transformation.
16440 c - An initialized Complex instance representing a translation Vector.
16443 Complex.prototype.moebiusTransformation = function(c) {
16444 var num = this.add(c);
16445 var den = c.$conjugate().$prod(this);
16447 return num.$div(den);
16451 moebiusTransformation
16453 Calculates a moebius transformation for the hyperbolic tree.
16455 <http://en.wikipedia.org/wiki/Moebius_transformation>
16459 graph - A <Graph> instance.
16461 prop - A property array.
16462 theta - Rotation angle.
16463 startPos - _optional_ start position.
16465 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
16466 this.eachNode(graph, function(elem) {
16467 for ( var i = 0; i < prop.length; i++) {
16468 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
16469 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
16477 A Hyperbolic Tree/Graph visualization.
16481 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
16482 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
16486 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.
16490 All <Loader> methods
16492 Constructor Options:
16494 Inherits options from
16497 - <Options.Controller>
16503 - <Options.NodeStyles>
16504 - <Options.Navigation>
16506 Additionally, there are other parameters and some default values changed
16508 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*.
16509 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.
16510 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
16511 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
16512 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
16514 Instance Properties:
16516 canvas - Access a <Canvas> instance.
16517 graph - Access a <Graph> instance.
16518 op - Access a <Hypertree.Op> instance.
16519 fx - Access a <Hypertree.Plot> instance.
16520 labels - Access a <Hypertree.Label> interface implementation.
16524 $jit.Hypertree = new Class( {
16526 Implements: [ Loader, Extras, Layouts.Radial ],
16528 initialize: function(controller) {
16529 var $Hypertree = $jit.Hypertree;
16540 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
16541 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
16543 var canvasConfig = this.config;
16544 if(canvasConfig.useCanvas) {
16545 this.canvas = canvasConfig.useCanvas;
16546 this.config.labelContainer = this.canvas.id + '-label';
16548 if(canvasConfig.background) {
16549 canvasConfig.background = $.merge({
16551 }, canvasConfig.background);
16553 this.canvas = new Canvas(this, canvasConfig);
16554 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
16557 this.graphOptions = {
16565 this.graph = new Graph(this.graphOptions, this.config.Node,
16567 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
16568 this.fx = new $Hypertree.Plot(this, $Hypertree);
16569 this.op = new $Hypertree.Op(this);
16573 // initialize extras
16574 this.initializeExtras();
16579 createLevelDistanceFunc
16581 Returns the levelDistance function used for calculating a node distance
16582 to its origin. This function returns a function that is computed
16583 per level and not per node, such that all nodes with the same depth will have the
16584 same distance to the origin. The resulting function gets the
16585 parent node as parameter and returns a float.
16588 createLevelDistanceFunc: function() {
16589 // get max viz. length.
16590 var r = this.getRadius();
16592 var depth = 0, max = Math.max, config = this.config;
16593 this.graph.eachNode(function(node) {
16594 depth = max(node._depth, depth);
16597 // node distance generator
16598 var genDistFunc = function(a) {
16599 return function(node) {
16601 var d = node._depth + 1;
16602 var acum = 0, pow = Math.pow;
16604 acum += pow(a, d--);
16606 return acum - config.offset;
16609 // estimate better edge length.
16610 for ( var i = 0.51; i <= 1; i += 0.01) {
16611 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
16612 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
16614 return genDistFunc(0.75);
16620 Returns the current radius of the visualization. If *config.radius* is *auto* then it
16621 calculates the radius by taking the smaller size of the <Canvas> widget.
16628 getRadius: function() {
16629 var rad = this.config.radius;
16630 if (rad !== "auto") { return rad; }
16631 var s = this.canvas.getSize();
16632 return Math.min(s.width, s.height) / 2;
16638 Computes positions and plots the tree.
16642 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
16645 refresh: function(reposition) {
16648 this.graph.eachNode(function(node) {
16649 node.startPos.rho = node.pos.rho = node.endPos.rho;
16650 node.startPos.theta = node.pos.theta = node.endPos.theta;
16661 Computes nodes' positions and restores the tree to its previous position.
16663 For calculating nodes' positions the root must be placed on its origin. This method does this
16664 and then attemps to restore the hypertree to its previous position.
16667 reposition: function() {
16668 this.compute('end');
16669 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
16670 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
16672 this.graph.eachNode(function(node) {
16674 node.endPos.rho = node.pos.rho;
16675 node.endPos.theta = node.pos.theta;
16683 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
16693 Animates the <Hypertree> to center the node specified by *id*.
16697 id - A <Graph.Node> id.
16698 opt - (optional|object) An object containing some extra properties described below
16699 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
16704 ht.onClick('someid');
16706 ht.onClick('someid', {
16712 onClick: function(id, opt) {
16713 var pos = this.graph.getNode(id).pos.getc(true);
16714 this.move(pos, opt);
16720 Translates the tree to the given position.
16724 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
16725 opt - This object has been defined in <Hypertree.onClick>
16730 ht.move({ x: 0, y: 0.7 }, {
16736 move: function(pos, opt) {
16737 var versor = $C(pos.x, pos.y);
16738 if (this.busy === false && versor.norm() < 1) {
16740 var root = this.graph.getClosestNodeToPos(versor), that = this;
16741 this.graph.computeLevels(root.id, 0);
16742 this.controller.onBeforeCompute(root);
16744 onComplete: $.empty
16746 this.fx.animate($.merge( {
16747 modes: [ 'moebius' ],
16750 onComplete: function() {
16759 $jit.Hypertree.$extend = true;
16761 (function(Hypertree) {
16764 Class: Hypertree.Op
16766 Custom extension of <Graph.Op>.
16770 All <Graph.Op> methods
16777 Hypertree.Op = new Class( {
16779 Implements: Graph.Op
16784 Class: Hypertree.Plot
16786 Custom extension of <Graph.Plot>.
16790 All <Graph.Plot> methods
16797 Hypertree.Plot = new Class( {
16799 Implements: Graph.Plot
16804 Object: Hypertree.Label
16806 Custom extension of <Graph.Label>.
16807 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
16811 All <Graph.Label> methods and subclasses.
16815 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
16818 Hypertree.Label = {};
16821 Hypertree.Label.Native
16823 Custom extension of <Graph.Label.Native>.
16827 All <Graph.Label.Native> methods
16831 <Graph.Label.Native>
16834 Hypertree.Label.Native = new Class( {
16835 Implements: Graph.Label.Native,
16837 initialize: function(viz) {
16841 renderLabel: function(canvas, node, controller) {
16842 var ctx = canvas.getCtx();
16843 var coord = node.pos.getc(true);
16844 var s = this.viz.getRadius();
16845 ctx.fillText(node.name, coord.x * s, coord.y * s);
16850 Hypertree.Label.SVG
16852 Custom extension of <Graph.Label.SVG>.
16856 All <Graph.Label.SVG> methods
16863 Hypertree.Label.SVG = new Class( {
16864 Implements: Graph.Label.SVG,
16866 initialize: function(viz) {
16873 Overrides abstract method placeLabel in <Graph.Plot>.
16877 tag - A DOM label element.
16878 node - A <Graph.Node>.
16879 controller - A configuration/controller object passed to the visualization.
16882 placeLabel: function(tag, node, controller) {
16883 var pos = node.pos.getc(true),
16884 canvas = this.viz.canvas,
16885 ox = canvas.translateOffsetX,
16886 oy = canvas.translateOffsetY,
16887 sx = canvas.scaleOffsetX,
16888 sy = canvas.scaleOffsetY,
16889 radius = canvas.getSize(),
16890 r = this.viz.getRadius();
16892 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
16893 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
16895 tag.setAttribute('x', labelPos.x);
16896 tag.setAttribute('y', labelPos.y);
16897 controller.onPlaceLabel(tag, node);
16902 Hypertree.Label.HTML
16904 Custom extension of <Graph.Label.HTML>.
16908 All <Graph.Label.HTML> methods.
16915 Hypertree.Label.HTML = new Class( {
16916 Implements: Graph.Label.HTML,
16918 initialize: function(viz) {
16924 Overrides abstract method placeLabel in <Graph.Plot>.
16928 tag - A DOM label element.
16929 node - A <Graph.Node>.
16930 controller - A configuration/controller object passed to the visualization.
16933 placeLabel: function(tag, node, controller) {
16934 var pos = node.pos.getc(true),
16935 canvas = this.viz.canvas,
16936 ox = canvas.translateOffsetX,
16937 oy = canvas.translateOffsetY,
16938 sx = canvas.scaleOffsetX,
16939 sy = canvas.scaleOffsetY,
16940 radius = canvas.getSize(),
16941 r = this.viz.getRadius();
16943 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
16944 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
16946 var style = tag.style;
16947 style.left = labelPos.x + 'px';
16948 style.top = labelPos.y + 'px';
16949 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
16951 controller.onPlaceLabel(tag, node);
16956 Class: Hypertree.Plot.NodeTypes
16958 This class contains a list of <Graph.Node> built-in types.
16959 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
16961 You can add your custom node types, customizing your visualization to the extreme.
16966 Hypertree.Plot.NodeTypes.implement({
16968 'render': function(node, canvas) {
16969 //print your custom node to canvas
16972 'contains': function(node, pos) {
16973 //return true if pos is inside the node or false otherwise
16980 Hypertree.Plot.NodeTypes = new Class({
16983 'contains': $.lambda(false)
16986 'render': function(node, canvas) {
16987 var nconfig = this.node,
16988 dim = node.getData('dim'),
16989 p = node.pos.getc();
16990 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
16991 p.$scale(node.scale);
16993 this.nodeHelper.circle.render('fill', p, dim, canvas);
16996 'contains': function(node, pos) {
16997 var dim = node.getData('dim'),
16998 npos = node.pos.getc().$scale(node.scale);
16999 return this.nodeHelper.circle.contains(npos, pos, dim);
17003 'render': function(node, canvas) {
17004 var pos = node.pos.getc().$scale(node.scale),
17005 width = node.getData('width'),
17006 height = node.getData('height');
17007 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
17009 'contains': function(node, pos) {
17010 var width = node.getData('width'),
17011 height = node.getData('height'),
17012 npos = node.pos.getc().$scale(node.scale);
17013 return this.nodeHelper.circle.contains(npos, pos, width, height);
17017 'render': function(node, canvas) {
17018 var nconfig = this.node,
17019 dim = node.getData('dim'),
17020 p = node.pos.getc();
17021 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17022 p.$scale(node.scale);
17024 this.nodeHelper.square.render('fill', p, dim, canvas);
17027 'contains': function(node, pos) {
17028 var dim = node.getData('dim'),
17029 npos = node.pos.getc().$scale(node.scale);
17030 return this.nodeHelper.square.contains(npos, pos, dim);
17034 'render': function(node, canvas) {
17035 var nconfig = this.node,
17036 width = node.getData('width'),
17037 height = node.getData('height'),
17038 pos = node.pos.getc();
17039 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
17040 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
17041 pos.$scale(node.scale);
17042 if (width > 0.2 && height > 0.2) {
17043 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
17046 'contains': function(node, pos) {
17047 var width = node.getData('width'),
17048 height = node.getData('height'),
17049 npos = node.pos.getc().$scale(node.scale);
17050 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
17054 'render': function(node, canvas) {
17055 var nconfig = this.node,
17056 dim = node.getData('dim'),
17057 p = node.pos.getc();
17058 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17059 p.$scale(node.scale);
17061 this.nodeHelper.triangle.render('fill', p, dim, canvas);
17064 'contains': function(node, pos) {
17065 var dim = node.getData('dim'),
17066 npos = node.pos.getc().$scale(node.scale);
17067 return this.nodeHelper.triangle.contains(npos, pos, dim);
17071 'render': function(node, canvas) {
17072 var nconfig = this.node,
17073 dim = node.getData('dim'),
17074 p = node.pos.getc();
17075 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17076 p.$scale(node.scale);
17078 this.nodeHelper.star.render('fill', p, dim, canvas);
17081 'contains': function(node, pos) {
17082 var dim = node.getData('dim'),
17083 npos = node.pos.getc().$scale(node.scale);
17084 return this.nodeHelper.star.contains(npos, pos, dim);
17090 Class: Hypertree.Plot.EdgeTypes
17092 This class contains a list of <Graph.Adjacence> built-in types.
17093 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
17095 You can add your custom edge types, customizing your visualization to the extreme.
17100 Hypertree.Plot.EdgeTypes.implement({
17102 'render': function(adj, canvas) {
17103 //print your custom edge to canvas
17106 'contains': function(adj, pos) {
17107 //return true if pos is inside the arc or false otherwise
17114 Hypertree.Plot.EdgeTypes = new Class({
17117 'render': function(adj, canvas) {
17118 var from = adj.nodeFrom.pos.getc(true),
17119 to = adj.nodeTo.pos.getc(true),
17120 r = adj.nodeFrom.scale;
17121 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
17123 'contains': function(adj, pos) {
17124 var from = adj.nodeFrom.pos.getc(true),
17125 to = adj.nodeTo.pos.getc(true),
17126 r = adj.nodeFrom.scale;
17127 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
17131 'render': function(adj, canvas) {
17132 var from = adj.nodeFrom.pos.getc(true),
17133 to = adj.nodeTo.pos.getc(true),
17134 r = adj.nodeFrom.scale,
17135 dim = adj.getData('dim'),
17136 direction = adj.data.$direction,
17137 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
17138 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
17140 'contains': function(adj, pos) {
17141 var from = adj.nodeFrom.pos.getc(true),
17142 to = adj.nodeTo.pos.getc(true),
17143 r = adj.nodeFrom.scale;
17144 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
17148 'render': function(adj, canvas) {
17149 var from = adj.nodeFrom.pos.getc(),
17150 to = adj.nodeTo.pos.getc(),
17151 dim = this.viz.getRadius();
17152 this.edgeHelper.hyperline.render(from, to, dim, canvas);
17154 'contains': $.lambda(false)
17158 })($jit.Hypertree);