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;
4197 else if(!this.Config.overridable) {
4198 val = prefixConfig[prop] || 0;
4201 val = (dollar in data) ?
4202 data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4204 if (dollar == 'XXX$area') {
4205 val = Math.log(val);
4210 var setDataInternal = function(prefix, prop, value, type) {
4211 type = type || 'current';
4212 prefix = '$' + (prefix ? prefix + '-' : '');
4216 if(type == 'current') {
4218 } else if(type == 'start') {
4219 data = this.startData;
4220 } else if(type == 'end') {
4221 data = this.endData;
4224 data[prefix + prop] = value;
4227 var removeDataInternal = function(prefix, properties) {
4228 prefix = '$' + (prefix ? prefix + '-' : '');
4230 $.each(properties, function(prop) {
4231 var pref = prefix + prop;
4232 delete that.data[pref];
4233 delete that.endData[pref];
4234 delete that.startData[pref];
4242 Returns the specified data value property.
4243 This is useful for querying special/reserved <Graph.Node> data properties
4244 (i.e dollar prefixed properties).
4248 prop - (string) The name of the property. The dollar sign is not needed. For
4249 example *getData(width)* will return *data.$width*.
4250 type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4251 data properties also. These properties are used when making animations.
4252 force - (boolean) Whether to obtain the true value of the property (equivalent to
4253 *data.$prop*) or to check for *node.overridable = true* first.
4257 The value of the dollar prefixed property or the global Node/Edge property
4258 value if *overridable=false*
4262 node.getData('width'); //will return node.data.$width if Node.overridable=true;
4265 getData: function(prop, type, force) {
4266 return getDataInternal.call(this, "", prop, type, force, this.Config);
4273 Sets the current data property with some specific value.
4274 This method is only useful for reserved (dollar prefixed) properties.
4278 prop - (string) The name of the property. The dollar sign is not necessary. For
4279 example *setData(width)* will set *data.$width*.
4280 value - (mixed) The value to store.
4281 type - (string) The type of the data property to store. Default's "current" but
4282 can also be "start" or "end".
4287 node.setData('width', 30);
4290 If we were to make an animation of a node/edge width then we could do
4293 var node = viz.getNode('nodeId');
4294 //set start and end values
4295 node.setData('width', 10, 'start');
4296 node.setData('width', 30, 'end');
4297 //will animate nodes width property
4299 modes: ['node-property:width'],
4304 setData: function(prop, value, type) {
4305 setDataInternal.call(this, "", prop, value, type);
4311 Convenience method to set multiple data values at once.
4315 types - (array|string) A set of 'current', 'end' or 'start' values.
4316 obj - (object) A hash containing the names and values of the properties to be altered.
4320 node.setDataset(['current', 'end'], {
4322 'color': ['#fff', '#ccc']
4325 node.setDataset('end', {
4336 setDataset: function(types, obj) {
4337 types = $.splat(types);
4338 for(var attr in obj) {
4339 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4340 this.setData(attr, val[i], types[i]);
4348 Remove data properties.
4352 One or more property names as arguments. The dollar sign is not needed.
4356 node.removeData('width'); //now the default width value is returned
4359 removeData: function() {
4360 removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4364 Method: getCanvasStyle
4366 Returns the specified canvas style data value property. This is useful for
4367 querying special/reserved <Graph.Node> canvas style data properties (i.e.
4368 dollar prefixed properties that match with $canvas-<name of canvas style>).
4372 prop - (string) The name of the property. The dollar sign is not needed. For
4373 example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4374 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4375 data properties also.
4379 node.getCanvasStyle('shadowBlur');
4386 getCanvasStyle: function(prop, type, force) {
4387 return getDataInternal.call(
4388 this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4392 Method: setCanvasStyle
4394 Sets the canvas style data property with some specific value.
4395 This method is only useful for reserved (dollar prefixed) properties.
4399 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4400 value - (mixed) The value to set to the property.
4401 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4406 node.setCanvasStyle('shadowBlur', 30);
4409 If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4412 var node = viz.getNode('nodeId');
4413 //set start and end values
4414 node.setCanvasStyle('shadowBlur', 10, 'start');
4415 node.setCanvasStyle('shadowBlur', 30, 'end');
4416 //will animate nodes canvas style property for nodes
4418 modes: ['node-style:shadowBlur'],
4425 <Accessors.setData>.
4427 setCanvasStyle: function(prop, value, type) {
4428 setDataInternal.call(this, 'canvas', prop, value, type);
4432 Method: setCanvasStyles
4434 Convenience method to set multiple styles at once.
4438 types - (array|string) A set of 'current', 'end' or 'start' values.
4439 obj - (object) A hash containing the names and values of the properties to be altered.
4443 <Accessors.setDataset>.
4445 setCanvasStyles: function(types, obj) {
4446 types = $.splat(types);
4447 for(var attr in obj) {
4448 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4449 this.setCanvasStyle(attr, val[i], types[i]);
4455 Method: removeCanvasStyle
4457 Remove canvas style properties from data.
4461 A variable number of canvas style strings.
4465 <Accessors.removeData>.
4467 removeCanvasStyle: function() {
4468 removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4472 Method: getLabelData
4474 Returns the specified label data value property. This is useful for
4475 querying special/reserved <Graph.Node> label options (i.e.
4476 dollar prefixed properties that match with $label-<name of label style>).
4480 prop - (string) The name of the property. The dollar sign prefix is not needed. For
4481 example *getLabelData(size)* will return *data[$label-size]*.
4482 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4483 data properties also.
4487 <Accessors.getData>.
4489 getLabelData: function(prop, type, force) {
4490 return getDataInternal.call(
4491 this, 'label', prop, type, force, this.Label);
4495 Method: setLabelData
4497 Sets the current label data with some specific value.
4498 This method is only useful for reserved (dollar prefixed) properties.
4502 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4503 value - (mixed) The value to set to the property.
4504 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4509 node.setLabelData('size', 30);
4512 If we were to make an animation of a node label size then we could do
4515 var node = viz.getNode('nodeId');
4516 //set start and end values
4517 node.setLabelData('size', 10, 'start');
4518 node.setLabelData('size', 30, 'end');
4519 //will animate nodes label size
4521 modes: ['label-property:size'],
4528 <Accessors.setData>.
4530 setLabelData: function(prop, value, type) {
4531 setDataInternal.call(this, 'label', prop, value, type);
4535 Method: setLabelDataset
4537 Convenience function to set multiple label data at once.
4541 types - (array|string) A set of 'current', 'end' or 'start' values.
4542 obj - (object) A hash containing the names and values of the properties to be altered.
4546 <Accessors.setDataset>.
4548 setLabelDataset: function(types, obj) {
4549 types = $.splat(types);
4550 for(var attr in obj) {
4551 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4552 this.setLabelData(attr, val[i], types[i]);
4558 Method: removeLabelData
4560 Remove label properties from data.
4564 A variable number of label property strings.
4568 <Accessors.removeData>.
4570 removeLabelData: function() {
4571 removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4583 <Accessors> methods.
4585 The following <Graph.Util> methods are implemented by <Graph.Node>
4587 - <Graph.Util.eachAdjacency>
4588 - <Graph.Util.eachLevel>
4589 - <Graph.Util.eachSubgraph>
4590 - <Graph.Util.eachSubnode>
4591 - <Graph.Util.anySubnode>
4592 - <Graph.Util.getSubnodes>
4593 - <Graph.Util.getParents>
4594 - <Graph.Util.isDescendantOf>
4596 Graph.Node = new Class({
4598 initialize: function(opt, klass, Node, Edge, Label) {
4599 var innerOptions = {
4617 'startPos': new klass,
4621 $.extend(this, $.extend(innerOptions, opt));
4622 this.Config = this.Node = Node;
4630 Indicates if the node is adjacent to the node specified by id
4634 id - (string) A node id.
4638 node.adjacentTo('nodeId') == true;
4641 adjacentTo: function(node) {
4642 return node.id in this.adjacencies;
4646 Method: getAdjacency
4648 Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4652 id - (string) A node id.
4654 getAdjacency: function(id) {
4655 return this.adjacencies[id];
4661 Returns the position of the node.
4665 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4669 A <Complex> or <Polar> instance.
4673 var pos = node.getPos('end');
4676 getPos: function(type) {
4677 type = type || "current";
4678 if(type == "current") {
4680 } else if(type == "end") {
4682 } else if(type == "start") {
4683 return this.startPos;
4689 Sets the node's position.
4693 value - (object) A <Complex> or <Polar> instance.
4694 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4698 node.setPos(new $jit.Complex(0, 0), 'end');
4701 setPos: function(value, type) {
4702 type = type || "current";
4704 if(type == "current") {
4706 } else if(type == "end") {
4708 } else if(type == "start") {
4709 pos = this.startPos;
4715 Graph.Node.implement(Accessors);
4718 Class: Graph.Adjacence
4720 A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4724 <Accessors> methods.
4728 <Graph>, <Graph.Node>
4732 nodeFrom - A <Graph.Node> connected by this edge.
4733 nodeTo - Another <Graph.Node> connected by this edge.
4734 data - Node data property containing a hash (i.e {}) with custom options.
4736 Graph.Adjacence = new Class({
4738 initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4739 this.nodeFrom = nodeFrom;
4740 this.nodeTo = nodeTo;
4741 this.data = data || {};
4742 this.startData = {};
4744 this.Config = this.Edge = Edge;
4749 Graph.Adjacence.implement(Accessors);
4754 <Graph> traversal and processing utility object.
4758 For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4764 For internal use only. Provides a filtering function based on flags.
4766 filter: function(param) {
4767 if(!param || !($.type(param) == 'string')) return function() { return true; };
4768 var props = param.split(" ");
4769 return function(elem) {
4770 for(var i=0; i<props.length; i++) {
4771 if(elem[props[i]]) {
4781 Returns a <Graph.Node> by *id*.
4783 Also implemented by:
4789 graph - (object) A <Graph> instance.
4790 id - (string) A <Graph.Node> id.
4795 $jit.Graph.Util.getNode(graph, 'nodeid');
4797 graph.getNode('nodeid');
4800 getNode: function(graph, id) {
4801 return graph.nodes[id];
4807 Iterates over <Graph> nodes performing an *action*.
4809 Also implemented by:
4815 graph - (object) A <Graph> instance.
4816 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4820 $jit.Graph.Util.eachNode(graph, function(node) {
4824 graph.eachNode(function(node) {
4829 eachNode: function(graph, action, flags) {
4830 var filter = this.filter(flags);
4831 for(var i in graph.nodes) {
4832 if(filter(graph.nodes[i])) action(graph.nodes[i]);
4839 Iterates over <Graph> nodes performing an *action*. It's an alias for <Graph.Util.eachNode>.
4841 Also implemented by:
4847 graph - (object) A <Graph> instance.
4848 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4852 $jit.Graph.Util.each(graph, function(node) {
4856 graph.each(function(node) {
4861 each: function(graph, action, flags) {
4862 this.eachNode(graph, action, flags);
4866 Method: eachAdjacency
4868 Iterates over <Graph.Node> adjacencies applying the *action* function.
4870 Also implemented by:
4876 node - (object) A <Graph.Node>.
4877 action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4881 $jit.Graph.Util.eachAdjacency(node, function(adj) {
4882 alert(adj.nodeTo.name);
4885 node.eachAdjacency(function(adj) {
4886 alert(adj.nodeTo.name);
4890 eachAdjacency: function(node, action, flags) {
4891 var adj = node.adjacencies, filter = this.filter(flags);
4892 for(var id in adj) {
4895 if(a.nodeFrom != node) {
4896 var tmp = a.nodeFrom;
4897 a.nodeFrom = a.nodeTo;
4906 Method: computeLevels
4908 Performs a BFS traversal setting the correct depth for each node.
4910 Also implemented by:
4916 The depth of each node can then be accessed by
4921 graph - (object) A <Graph>.
4922 id - (string) A starting node id for the BFS traversal.
4923 startDepth - (optional|number) A minimum depth value. Default's 0.
4926 computeLevels: function(graph, id, startDepth, flags) {
4927 startDepth = startDepth || 0;
4928 var filter = this.filter(flags);
4929 this.eachNode(graph, function(elem) {
4933 var root = graph.getNode(id);
4934 root._depth = startDepth;
4936 while(queue.length != 0) {
4937 var node = queue.pop();
4939 this.eachAdjacency(node, function(adj) {
4941 if(n._flag == false && filter(n)) {
4942 if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
4952 Performs a BFS traversal applying *action* to each <Graph.Node>.
4954 Also implemented by:
4960 graph - (object) A <Graph>.
4961 id - (string) A starting node id for the BFS traversal.
4962 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4966 $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
4970 graph.eachBFS('mynodeid', function(node) {
4975 eachBFS: function(graph, id, action, flags) {
4976 var filter = this.filter(flags);
4978 var queue = [graph.getNode(id)];
4979 while(queue.length != 0) {
4980 var node = queue.pop();
4982 action(node, node._depth);
4983 this.eachAdjacency(node, function(adj) {
4985 if(n._flag == false && filter(n)) {
4996 Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
4998 Also implemented by:
5004 node - (object) A <Graph.Node>.
5005 levelBegin - (number) A relative level value.
5006 levelEnd - (number) A relative level value.
5007 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5010 eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5011 var d = node._depth, filter = this.filter(flags), that = this;
5012 levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5013 (function loopLevel(node, levelBegin, levelEnd) {
5014 var d = node._depth;
5015 if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5017 that.eachAdjacency(node, function(adj) {
5019 if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5022 })(node, levelBegin + d, levelEnd + d);
5026 Method: eachSubgraph
5028 Iterates over a node's children recursively.
5030 Also implemented by:
5035 node - (object) A <Graph.Node>.
5036 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5040 $jit.Graph.Util.eachSubgraph(node, function(node) {
5044 node.eachSubgraph(function(node) {
5049 eachSubgraph: function(node, action, flags) {
5050 this.eachLevel(node, 0, false, action, flags);
5056 Iterates over a node's children (without deeper recursion).
5058 Also implemented by:
5063 node - (object) A <Graph.Node>.
5064 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5068 $jit.Graph.Util.eachSubnode(node, function(node) {
5072 node.eachSubnode(function(node) {
5077 eachSubnode: function(node, action, flags) {
5078 this.eachLevel(node, 1, 1, action, flags);
5084 Returns *true* if any subnode matches the given condition.
5086 Also implemented by:
5091 node - (object) A <Graph.Node>.
5092 cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5096 $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5098 node.anySubnode(function(node) { return node.name == 'mynodename'; });
5101 anySubnode: function(node, cond, flags) {
5103 cond = cond || $.lambda(true);
5104 var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5105 this.eachSubnode(node, function(elem) {
5106 if(c(elem)) flag = true;
5114 Collects all subnodes for a specified node.
5115 The *level* parameter filters nodes having relative depth of *level* from the root node.
5117 Also implemented by:
5122 node - (object) A <Graph.Node>.
5123 level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5129 getSubnodes: function(node, level, flags) {
5130 var ans = [], that = this;
5132 var levelStart, levelEnd;
5133 if($.type(level) == 'array') {
5134 levelStart = level[0];
5135 levelEnd = level[1];
5138 levelEnd = Number.MAX_VALUE - node._depth;
5140 this.eachLevel(node, levelStart, levelEnd, function(n) {
5150 Returns an Array of <Graph.Nodes> which are parents of the given node.
5152 Also implemented by:
5157 node - (object) A <Graph.Node>.
5160 An Array of <Graph.Nodes>.
5164 var pars = $jit.Graph.Util.getParents(node);
5166 var pars = node.getParents();
5168 if(pars.length > 0) {
5169 //do stuff with parents
5173 getParents: function(node) {
5175 this.eachAdjacency(node, function(adj) {
5177 if(n._depth < node._depth) ans.push(n);
5183 Method: isDescendantOf
5185 Returns a boolean indicating if some node is descendant of the node with the given id.
5187 Also implemented by:
5193 node - (object) A <Graph.Node>.
5194 id - (string) A <Graph.Node> id.
5198 $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5200 node.isDescendantOf('nodeid');//true|false
5203 isDescendantOf: function(node, id) {
5204 if(node.id == id) return true;
5205 var pars = this.getParents(node), ans = false;
5206 for ( var i = 0; !ans && i < pars.length; i++) {
5207 ans = ans || this.isDescendantOf(pars[i], id);
5215 Cleans flags from nodes.
5217 Also implemented by:
5222 graph - A <Graph> instance.
5224 clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5227 Method: getClosestNodeToOrigin
5229 Returns the closest node to the center of canvas.
5231 Also implemented by:
5237 graph - (object) A <Graph> instance.
5238 prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5241 getClosestNodeToOrigin: function(graph, prop, flags) {
5242 return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5246 Method: getClosestNodeToPos
5248 Returns the closest node to the given position.
5250 Also implemented by:
5256 graph - (object) A <Graph> instance.
5257 pos - (object) A <Complex> or <Polar> instance.
5258 prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5261 getClosestNodeToPos: function(graph, pos, prop, flags) {
5263 prop = prop || 'current';
5264 pos = pos && pos.getc(true) || Complex.KER;
5265 var distance = function(a, b) {
5266 var d1 = a.x - b.x, d2 = a.y - b.y;
5267 return d1 * d1 + d2 * d2;
5269 this.eachNode(graph, function(elem) {
5270 node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5271 node.getPos(prop).getc(true), pos)) ? elem : node;
5277 //Append graph methods to <Graph>
5278 $.each(['get', 'getNode', 'each', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5279 Graph.prototype[m] = function() {
5280 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5284 //Append node methods to <Graph.Node>
5285 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5286 Graph.Node.prototype[m] = function() {
5287 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5299 Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5300 morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5312 initialize: function(viz) {
5319 Removes one or more <Graph.Nodes> from the visualization.
5320 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5324 node - (string|array) The node's id. Can also be an array having many ids.
5325 opt - (object) Animation options. It's an object with optional properties described below
5326 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5327 duration - Described in <Options.Fx>.
5328 fps - Described in <Options.Fx>.
5329 transition - Described in <Options.Fx>.
5330 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5334 var viz = new $jit.Viz(options);
5335 viz.op.removeNode('nodeId', {
5339 transition: $jit.Trans.Quart.easeOut
5342 viz.op.removeNode(['someId', 'otherId'], {
5349 removeNode: function(node, opt) {
5351 var options = $.merge(this.options, viz.controller, opt);
5352 var n = $.splat(node);
5353 var i, that, nodeObj;
5354 switch(options.type) {
5356 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5360 this.removeNode(n, { type: 'nothing' });
5361 viz.labels.clearLabels();
5365 case 'fade:seq': case 'fade':
5367 //set alpha to 0 for nodes to remove.
5368 for(i=0; i<n.length; i++) {
5369 nodeObj = viz.graph.getNode(n[i]);
5370 nodeObj.setData('alpha', 0, 'end');
5372 viz.fx.animate($.merge(options, {
5373 modes: ['node-property:alpha'],
5374 onComplete: function() {
5375 that.removeNode(n, { type: 'nothing' });
5376 viz.labels.clearLabels();
5378 viz.fx.animate($.merge(options, {
5387 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5388 for(i=0; i<n.length; i++) {
5389 nodeObj = viz.graph.getNode(n[i]);
5390 nodeObj.setData('alpha', 0, 'end');
5391 nodeObj.ignore = true;
5394 viz.fx.animate($.merge(options, {
5395 modes: ['node-property:alpha', 'linear'],
5396 onComplete: function() {
5397 that.removeNode(n, { type: 'nothing' });
5398 options.onComplete && options.onComplete();
5406 condition: function() { return n.length != 0; },
5407 step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5408 onComplete: function() { options.onComplete && options.onComplete(); },
5409 duration: Math.ceil(options.duration / n.length)
5413 default: this.doError();
5420 Removes one or more <Graph.Adjacences> from the visualization.
5421 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5425 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'], ...]).
5426 opt - (object) Animation options. It's an object with optional properties described below
5427 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5428 duration - Described in <Options.Fx>.
5429 fps - Described in <Options.Fx>.
5430 transition - Described in <Options.Fx>.
5431 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5435 var viz = new $jit.Viz(options);
5436 viz.op.removeEdge(['nodeId', 'otherId'], {
5440 transition: $jit.Trans.Quart.easeOut
5443 viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5450 removeEdge: function(vertex, opt) {
5452 var options = $.merge(this.options, viz.controller, opt);
5453 var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5455 switch(options.type) {
5457 for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5461 this.removeEdge(v, { type: 'nothing' });
5465 case 'fade:seq': case 'fade':
5467 //set alpha to 0 for edges to remove.
5468 for(i=0; i<v.length; i++) {
5469 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5471 adj.setData('alpha', 0,'end');
5474 viz.fx.animate($.merge(options, {
5475 modes: ['edge-property:alpha'],
5476 onComplete: function() {
5477 that.removeEdge(v, { type: 'nothing' });
5479 viz.fx.animate($.merge(options, {
5488 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5489 for(i=0; i<v.length; i++) {
5490 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5492 adj.setData('alpha',0 ,'end');
5497 viz.fx.animate($.merge(options, {
5498 modes: ['edge-property:alpha', 'linear'],
5499 onComplete: function() {
5500 that.removeEdge(v, { type: 'nothing' });
5501 options.onComplete && options.onComplete();
5509 condition: function() { return v.length != 0; },
5510 step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5511 onComplete: function() { options.onComplete(); },
5512 duration: Math.ceil(options.duration / v.length)
5516 default: this.doError();
5523 Adds a new graph to the visualization.
5524 The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5525 The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5529 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5530 opt - (object) Animation options. It's an object with optional properties described below
5531 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5532 duration - Described in <Options.Fx>.
5533 fps - Described in <Options.Fx>.
5534 transition - Described in <Options.Fx>.
5535 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5539 //...json contains a tree or graph structure...
5541 var viz = new $jit.Viz(options);
5546 transition: $jit.Trans.Quart.easeOut
5556 sum: function(json, opt) {
5558 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5560 viz.root = opt.id || viz.root;
5561 switch(options.type) {
5563 graph = viz.construct(json);
5564 graph.eachNode(function(elem) {
5565 elem.eachAdjacency(function(adj) {
5566 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5573 this.sum(json, { type: 'nothing' });
5577 case 'fade:seq': case 'fade': case 'fade:con':
5579 graph = viz.construct(json);
5581 //set alpha to 0 for nodes to add.
5582 var fadeEdges = this.preprocessSum(graph);
5583 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5585 if(options.type != 'fade:con') {
5586 viz.fx.animate($.merge(options, {
5588 onComplete: function() {
5589 viz.fx.animate($.merge(options, {
5591 onComplete: function() {
5592 options.onComplete();
5598 viz.graph.eachNode(function(elem) {
5599 if (elem.id != root && elem.pos.isZero()) {
5600 elem.pos.set(elem.endPos);
5601 elem.startPos.set(elem.endPos);
5604 viz.fx.animate($.merge(options, {
5605 modes: ['linear'].concat(modes)
5610 default: this.doError();
5617 This method will transform the current visualized graph into the new JSON representation passed in the method.
5618 The JSON object must at least have the root node in common with the current visualized graph.
5622 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5623 opt - (object) Animation options. It's an object with optional properties described below
5624 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5625 duration - Described in <Options.Fx>.
5626 fps - Described in <Options.Fx>.
5627 transition - Described in <Options.Fx>.
5628 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5629 id - (string) The shared <Graph.Node> id between both graphs.
5631 extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5632 *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5633 For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5634 properties as values, just like specified in <Graph.Plot.animate>.
5638 //...json contains a tree or graph structure...
5640 var viz = new $jit.Viz(options);
5641 viz.op.morph(json, {
5645 transition: $jit.Trans.Quart.easeOut
5648 viz.op.morph(json, {
5652 //if the json data contains dollar prefixed params
5653 //like $width or $height these too can be animated
5654 viz.op.morph(json, {
5658 'node-property': ['width', 'height']
5663 morph: function(json, opt, extraModes) {
5664 extraModes = extraModes || {};
5666 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5668 //TODO(nico) this hack makes morphing work with the Hypertree.
5669 //Need to check if it has been solved and this can be removed.
5670 viz.root = opt.id || viz.root;
5671 switch(options.type) {
5673 graph = viz.construct(json);
5674 graph.eachNode(function(elem) {
5675 var nodeExists = viz.graph.hasNode(elem.id);
5676 elem.eachAdjacency(function(adj) {
5677 var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5678 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5679 //Update data properties if the node existed
5681 var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5682 for(var prop in (adj.data || {})) {
5683 addedAdj.data[prop] = adj.data[prop];
5687 //Update data properties if the node existed
5689 var addedNode = viz.graph.getNode(elem.id);
5690 for(var prop in (elem.data || {})) {
5691 addedNode.data[prop] = elem.data[prop];
5695 viz.graph.eachNode(function(elem) {
5696 elem.eachAdjacency(function(adj) {
5697 if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5698 viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5701 if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5707 viz.labels.clearLabels(true);
5708 this.morph(json, { type: 'nothing' });
5713 case 'fade:seq': case 'fade': case 'fade:con':
5715 graph = viz.construct(json);
5716 //preprocessing for nodes to delete.
5717 //get node property modes to interpolate
5718 var nodeModes = ('node-property' in extraModes)
5719 && $.map($.splat(extraModes['node-property']),
5720 function(n) { return '$' + n; });
5721 viz.graph.eachNode(function(elem) {
5722 var graphNode = graph.getNode(elem.id);
5724 elem.setData('alpha', 1);
5725 elem.setData('alpha', 1, 'start');
5726 elem.setData('alpha', 0, 'end');
5729 //Update node data information
5730 var graphNodeData = graphNode.data;
5731 for(var prop in graphNodeData) {
5732 if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5733 elem.endData[prop] = graphNodeData[prop];
5735 elem.data[prop] = graphNodeData[prop];
5740 viz.graph.eachNode(function(elem) {
5741 if(elem.ignore) return;
5742 elem.eachAdjacency(function(adj) {
5743 if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5744 var nodeFrom = graph.getNode(adj.nodeFrom.id);
5745 var nodeTo = graph.getNode(adj.nodeTo.id);
5746 if(!nodeFrom.adjacentTo(nodeTo)) {
5747 var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5749 adj.setData('alpha', 1);
5750 adj.setData('alpha', 1, 'start');
5751 adj.setData('alpha', 0, 'end');
5755 //preprocessing for adding nodes.
5756 var fadeEdges = this.preprocessSum(graph);
5758 var modes = !fadeEdges? ['node-property:alpha'] :
5759 ['node-property:alpha',
5760 'edge-property:alpha'];
5761 //Append extra node-property animations (if any)
5762 modes[0] = modes[0] + (('node-property' in extraModes)?
5763 (':' + $.splat(extraModes['node-property']).join(':')) : '');
5764 //Append extra edge-property animations (if any)
5765 modes[1] = (modes[1] || 'edge-property:alpha') + (('edge-property' in extraModes)?
5766 (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5767 //Add label-property animations (if any)
5768 if('label-property' in extraModes) {
5769 modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5771 //only use reposition if its implemented.
5772 if (viz.reposition) {
5777 viz.graph.eachNode(function(elem) {
5778 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5779 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5782 viz.fx.animate($.merge(options, {
5783 modes: [extraModes.position || 'polar'].concat(modes),
5784 onComplete: function() {
5785 viz.graph.eachNode(function(elem) {
5786 if(elem.ignore) viz.graph.removeNode(elem.id);
5788 viz.graph.eachNode(function(elem) {
5789 elem.eachAdjacency(function(adj) {
5790 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5793 options.onComplete();
5806 Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5810 node - (object) A <Graph.Node>.
5811 opt - (object) An object containing options described below
5812 type - (string) Whether to 'replot' or 'animate' the contraction.
5814 There are also a number of Animation options. For more information see <Options.Fx>.
5818 var viz = new $jit.Viz(options);
5819 viz.op.contract(node, {
5823 transition: $jit.Trans.Quart.easeOut
5828 contract: function(node, opt) {
5830 if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5831 opt = $.merge(this.options, viz.config, opt || {}, {
5832 'modes': ['node-property:alpha:span', 'linear']
5834 node.collapsed = true;
5836 n.eachSubnode(function(ch) {
5838 ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5842 if(opt.type == 'animate') {
5845 viz.rotate(viz.rotated, 'none', {
5850 n.eachSubnode(function(ch) {
5851 ch.setPos(node.getPos('end'), 'end');
5855 viz.fx.animate(opt);
5856 } else if(opt.type == 'replot'){
5864 Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5868 node - (object) A <Graph.Node>.
5869 opt - (object) An object containing options described below
5870 type - (string) Whether to 'replot' or 'animate'.
5872 There are also a number of Animation options. For more information see <Options.Fx>.
5876 var viz = new $jit.Viz(options);
5877 viz.op.expand(node, {
5881 transition: $jit.Trans.Quart.easeOut
5886 expand: function(node, opt) {
5887 if(!('collapsed' in node)) return;
5889 opt = $.merge(this.options, viz.config, opt || {}, {
5890 'modes': ['node-property:alpha:span', 'linear']
5892 delete node.collapsed;
5894 n.eachSubnode(function(ch) {
5896 ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5900 if(opt.type == 'animate') {
5903 viz.rotate(viz.rotated, 'none', {
5907 viz.fx.animate(opt);
5908 } else if(opt.type == 'replot'){
5913 preprocessSum: function(graph) {
5915 graph.eachNode(function(elem) {
5916 if(!viz.graph.hasNode(elem.id)) {
5917 viz.graph.addNode(elem);
5918 var n = viz.graph.getNode(elem.id);
5919 n.setData('alpha', 0);
5920 n.setData('alpha', 0, 'start');
5921 n.setData('alpha', 1, 'end');
5924 var fadeEdges = false;
5925 graph.eachNode(function(elem) {
5926 elem.eachAdjacency(function(adj) {
5927 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
5928 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
5929 if(!nodeFrom.adjacentTo(nodeTo)) {
5930 var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
5931 if(nodeFrom.startAlpha == nodeFrom.endAlpha
5932 && nodeTo.startAlpha == nodeTo.endAlpha) {
5934 adj.setData('alpha', 0);
5935 adj.setData('alpha', 0, 'start');
5936 adj.setData('alpha', 1, 'end');
5950 Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
5951 Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
5952 position is over the rendered shape.
5954 Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
5955 *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
5959 //implement a new node type
5960 $jit.Viz.Plot.NodeTypes.implement({
5962 'render': function(node, canvas) {
5963 this.nodeHelper.circle.render ...
5965 'contains': function(node, pos) {
5966 this.nodeHelper.circle.contains ...
5970 //implement an edge type
5971 $jit.Viz.Plot.EdgeTypes.implement({
5973 'render': function(node, canvas) {
5974 this.edgeHelper.circle.render ...
5977 'contains': function(node, pos) {
5978 this.edgeHelper.circle.contains ...
5989 Contains rendering and other type of primitives for simple shapes.
5994 'contains': $.lambda(false)
5997 Object: NodeHelper.circle
6003 Renders a circle into the canvas.
6007 type - (string) Possible options are 'fill' or 'stroke'.
6008 pos - (object) An *x*, *y* object with the position of the center of the circle.
6009 radius - (number) The radius of the circle to be rendered.
6010 canvas - (object) A <Canvas> instance.
6014 NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6017 'render': function(type, pos, radius, canvas){
6018 var ctx = canvas.getCtx();
6020 ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6027 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6031 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6032 pos - (object) An *x*, *y* object with the position to check.
6033 radius - (number) The radius of the rendered circle.
6037 NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6040 'contains': function(npos, pos, radius){
6041 var diffx = npos.x - pos.x,
6042 diffy = npos.y - pos.y,
6043 diff = diffx * diffx + diffy * diffy;
6044 return diff <= radius * radius;
6048 Object: NodeHelper.ellipse
6054 Renders an ellipse into the canvas.
6058 type - (string) Possible options are 'fill' or 'stroke'.
6059 pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6060 width - (number) The width of the ellipse.
6061 height - (number) The height of the ellipse.
6062 canvas - (object) A <Canvas> instance.
6066 NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6069 'render': function(type, pos, width, height, canvas){
6070 var ctx = canvas.getCtx(),
6077 if (width > height) {
6079 scaley = height / width;
6080 scaleposy = width / height;
6082 radius = height / 2;
6083 scalex = width / height;
6084 scaleposx = height / width;
6088 ctx.scale(scalex, scaley);
6090 ctx.arc(pos.x * scaleposx, pos.y * scaleposy, radius, 0, Math.PI * 2, true);
6098 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6102 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6103 pos - (object) An *x*, *y* object with the position to check.
6104 width - (number) The width of the rendered ellipse.
6105 height - (number) The height of the rendered ellipse.
6109 NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6112 'contains': function(npos, pos, width, height){
6120 if (width > height) {
6122 scaley = height / width;
6124 radius = height / 2;
6125 scalex = width / height;
6128 diffx = (npos.x - pos.x) * (1 / scalex);
6129 diffy = (npos.y - pos.y) * (1 / scaley);
6130 diff = diffx * diffx + diffy * diffy;
6131 return diff <= radius * radius;
6135 Object: NodeHelper.square
6141 Renders a square into the canvas.
6145 type - (string) Possible options are 'fill' or 'stroke'.
6146 pos - (object) An *x*, *y* object with the position of the center of the square.
6147 dim - (number) The radius (or half-diameter) of the square.
6148 canvas - (object) A <Canvas> instance.
6152 NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6155 'render': function(type, pos, dim, canvas){
6156 canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6161 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6165 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6166 pos - (object) An *x*, *y* object with the position to check.
6167 dim - (number) The radius (or half-diameter) of the square.
6171 NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6174 'contains': function(npos, pos, dim){
6175 return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6179 Object: NodeHelper.rectangle
6185 Renders a rectangle into the canvas.
6189 type - (string) Possible options are 'fill' or 'stroke'.
6190 pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6191 width - (number) The width of the rectangle.
6192 height - (number) The height of the rectangle.
6193 canvas - (object) A <Canvas> instance.
6197 NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6200 'render': function(type, pos, width, height, canvas){
6201 canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6207 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6211 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6212 pos - (object) An *x*, *y* object with the position to check.
6213 width - (number) The width of the rendered rectangle.
6214 height - (number) The height of the rendered rectangle.
6218 NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6221 'contains': function(npos, pos, width, height){
6222 return Math.abs(pos.x - npos.x) <= width / 2
6223 && Math.abs(pos.y - npos.y) <= height / 2;
6227 Object: NodeHelper.triangle
6233 Renders a triangle into the canvas.
6237 type - (string) Possible options are 'fill' or 'stroke'.
6238 pos - (object) An *x*, *y* object with the position of the center of the triangle.
6239 dim - (number) Half the base and half the height of the triangle.
6240 canvas - (object) A <Canvas> instance.
6244 NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6247 'render': function(type, pos, dim, canvas){
6248 var ctx = canvas.getCtx(),
6256 ctx.moveTo(c1x, c1y);
6257 ctx.lineTo(c2x, c2y);
6258 ctx.lineTo(c3x, c3y);
6265 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6269 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6270 pos - (object) An *x*, *y* object with the position to check.
6271 dim - (number) Half the base and half the height of the triangle.
6275 NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6278 'contains': function(npos, pos, dim) {
6279 return NodeHelper.circle.contains(npos, pos, dim);
6283 Object: NodeHelper.star
6289 Renders a star (concave decagon) into the canvas.
6293 type - (string) Possible options are 'fill' or 'stroke'.
6294 pos - (object) An *x*, *y* object with the position of the center of the star.
6295 dim - (number) The length of a side of a concave decagon.
6296 canvas - (object) A <Canvas> instance.
6300 NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6303 'render': function(type, pos, dim, canvas){
6304 var ctx = canvas.getCtx(),
6307 ctx.translate(pos.x, pos.y);
6310 for (var i = 0; i < 9; i++) {
6313 ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6325 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6329 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6330 pos - (object) An *x*, *y* object with the position to check.
6331 dim - (number) The length of a side of a concave decagon.
6335 NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6338 'contains': function(npos, pos, dim) {
6339 return NodeHelper.circle.contains(npos, pos, dim);
6347 Contains rendering primitives for simple edge shapes.
6351 Object: EdgeHelper.line
6357 Renders a line into the canvas.
6361 from - (object) An *x*, *y* object with the starting position of the line.
6362 to - (object) An *x*, *y* object with the ending position of the line.
6363 canvas - (object) A <Canvas> instance.
6367 EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6370 'render': function(from, to, canvas){
6371 var ctx = canvas.getCtx();
6373 ctx.moveTo(from.x, from.y);
6374 ctx.lineTo(to.x, to.y);
6380 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6384 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6385 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6386 pos - (object) An *x*, *y* object with the position to check.
6387 epsilon - (number) The dimension of the shape.
6391 EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6394 'contains': function(posFrom, posTo, pos, epsilon) {
6397 minPosX = min(posFrom.x, posTo.x),
6398 maxPosX = max(posFrom.x, posTo.x),
6399 minPosY = min(posFrom.y, posTo.y),
6400 maxPosY = max(posFrom.y, posTo.y);
6402 if(pos.x >= minPosX && pos.x <= maxPosX
6403 && pos.y >= minPosY && pos.y <= maxPosY) {
6404 if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6407 var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6408 return Math.abs(dist - pos.y) <= epsilon;
6414 Object: EdgeHelper.arrow
6420 Renders an arrow into the canvas.
6424 from - (object) An *x*, *y* object with the starting position of the arrow.
6425 to - (object) An *x*, *y* object with the ending position of the arrow.
6426 dim - (number) The dimension of the arrow.
6427 swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6428 canvas - (object) A <Canvas> instance.
6432 EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6435 'render': function(from, to, dim, swap, canvas){
6436 var ctx = canvas.getCtx();
6437 // invert edge direction
6443 var vect = new Complex(to.x - from.x, to.y - from.y);
6444 vect.$scale(dim / vect.norm());
6445 var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6446 normal = new Complex(-vect.y / 2, vect.x / 2),
6447 v1 = intermediatePoint.add(normal),
6448 v2 = intermediatePoint.$add(normal.$scale(-1));
6451 ctx.moveTo(from.x, from.y);
6452 ctx.lineTo(to.x, to.y);
6455 ctx.moveTo(v1.x, v1.y);
6456 ctx.lineTo(v2.x, v2.y);
6457 ctx.lineTo(to.x, to.y);
6464 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6468 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6469 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6470 pos - (object) An *x*, *y* object with the position to check.
6471 epsilon - (number) The dimension of the shape.
6475 EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6478 'contains': function(posFrom, posTo, pos, epsilon) {
6479 return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6483 Object: EdgeHelper.hyperline
6489 Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6493 from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6494 to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6495 r - (number) The scaling factor.
6496 canvas - (object) A <Canvas> instance.
6500 EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6503 'render': function(from, to, r, canvas){
6504 var ctx = canvas.getCtx();
6505 var centerOfCircle = computeArcThroughTwoPoints(from, to);
6506 if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6507 || centerOfCircle.ratio < 0) {
6509 ctx.moveTo(from.x * r, from.y * r);
6510 ctx.lineTo(to.x * r, to.y * r);
6513 var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6514 - centerOfCircle.x);
6515 var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6516 - centerOfCircle.x);
6517 var sense = sense(angleBegin, angleEnd);
6519 ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6520 * r, angleBegin, angleEnd, sense);
6524 Calculates the arc parameters through two points.
6526 More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6530 p1 - A <Complex> instance.
6531 p2 - A <Complex> instance.
6532 scale - The Disk's diameter.
6536 An object containing some arc properties.
6538 function computeArcThroughTwoPoints(p1, p2){
6539 var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6540 var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6541 // Fall back to a straight line
6549 var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6550 var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6553 var squaredRatio = (a * a + b * b) / 4 - 1;
6554 // Fall back to a straight line
6555 if (squaredRatio < 0)
6561 var ratio = Math.sqrt(squaredRatio);
6565 ratio: ratio > 1000? -1 : ratio,
6573 Sets angle direction to clockwise (true) or counterclockwise (false).
6577 angleBegin - Starting angle for drawing the arc.
6578 angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6582 A Boolean instance describing the sense for drawing the HyperLine.
6584 function sense(angleBegin, angleEnd){
6585 return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6586 : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6594 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6598 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6599 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6600 pos - (object) An *x*, *y* object with the position to check.
6601 epsilon - (number) The dimension of the shape.
6605 EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6608 'contains': $.lambda(false)
6614 * File: Graph.Plot.js
6620 <Graph> rendering and animation methods.
6624 nodeHelper - <NodeHelper> object.
6625 edgeHelper - <EdgeHelper> object.
6628 //Default initializer
6629 initialize: function(viz, klass){
6631 this.config = viz.config;
6632 this.node = viz.config.Node;
6633 this.edge = viz.config.Edge;
6634 this.animation = new Animation;
6635 this.nodeTypes = new klass.Plot.NodeTypes;
6636 this.edgeTypes = new klass.Plot.EdgeTypes;
6637 this.labels = viz.labels;
6641 nodeHelper: NodeHelper,
6642 edgeHelper: EdgeHelper,
6645 //node/edge property parsers
6653 'lineWidth': 'number',
6654 'angularWidth':'number',
6656 'valueArray':'array-number',
6657 'dimArray':'array-number'
6658 //'colorArray':'array-color'
6661 //canvas specific parsers
6663 'globalAlpha': 'number',
6664 'fillStyle': 'color',
6665 'strokeStyle': 'color',
6666 'lineWidth': 'number',
6667 'shadowBlur': 'number',
6668 'shadowColor': 'color',
6669 'shadowOffsetX': 'number',
6670 'shadowOffsetY': 'number',
6671 'miterLimit': 'number'
6680 //Number interpolator
6681 'compute': function(from, to, delta) {
6682 return from + (to - from) * delta;
6685 //Position interpolators
6686 'moebius': function(elem, props, delta, vector) {
6687 var v = vector.scale(-delta);
6689 var x = v.x, y = v.y;
6690 var ans = elem.startPos
6691 .getc().moebiusTransformation(v);
6692 elem.pos.setc(ans.x, ans.y);
6697 'linear': function(elem, props, delta) {
6698 var from = elem.startPos.getc(true);
6699 var to = elem.endPos.getc(true);
6700 elem.pos.setc(this.compute(from.x, to.x, delta),
6701 this.compute(from.y, to.y, delta));
6704 'polar': function(elem, props, delta) {
6705 var from = elem.startPos.getp(true);
6706 var to = elem.endPos.getp();
6707 var ans = to.interpolate(from, delta);
6708 elem.pos.setp(ans.theta, ans.rho);
6711 //Graph's Node/Edge interpolators
6712 'number': function(elem, prop, delta, getter, setter) {
6713 var from = elem[getter](prop, 'start');
6714 var to = elem[getter](prop, 'end');
6715 elem[setter](prop, this.compute(from, to, delta));
6718 'color': function(elem, prop, delta, getter, setter) {
6719 var from = $.hexToRgb(elem[getter](prop, 'start'));
6720 var to = $.hexToRgb(elem[getter](prop, 'end'));
6721 var comp = this.compute;
6722 var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6723 parseInt(comp(from[1], to[1], delta)),
6724 parseInt(comp(from[2], to[2], delta))]);
6726 elem[setter](prop, val);
6729 'array-number': function(elem, prop, delta, getter, setter) {
6730 var from = elem[getter](prop, 'start'),
6731 to = elem[getter](prop, 'end'),
6733 for(var i=0, l=from.length; i<l; i++) {
6734 var fromi = from[i], toi = to[i];
6736 for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6737 curi.push(this.compute(fromi[j], toi[j], delta));
6741 cur.push(this.compute(fromi, toi, delta));
6744 elem[setter](prop, cur);
6747 'node': function(elem, props, delta, map, getter, setter) {
6750 var len = props.length;
6751 for(var i=0; i<len; i++) {
6753 this[map[pi]](elem, pi, delta, getter, setter);
6756 for(var pi in map) {
6757 this[map[pi]](elem, pi, delta, getter, setter);
6762 'edge': function(elem, props, delta, mapKey, getter, setter) {
6763 var adjs = elem.adjacencies;
6764 for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6767 'node-property': function(elem, props, delta) {
6768 this['node'](elem, props, delta, 'map', 'getData', 'setData');
6771 'edge-property': function(elem, props, delta) {
6772 this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6775 'label-property': function(elem, props, delta) {
6776 this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6779 'node-style': function(elem, props, delta) {
6780 this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6783 'edge-style': function(elem, props, delta) {
6784 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6792 Iteratively performs an action while refreshing the state of the visualization.
6796 options - (object) An object containing some sequence options described below
6797 condition - (function) A function returning a boolean instance in order to stop iterations.
6798 step - (function) A function to execute on each step of the iteration.
6799 onComplete - (function) A function to execute when the sequence finishes.
6800 duration - (number) Duration (in milliseconds) of each step.
6804 var rg = new $jit.RGraph(options);
6807 condition: function() {
6813 onComplete: function() {
6820 sequence: function(options) {
6823 condition: $.lambda(false),
6825 onComplete: $.empty,
6829 var interval = setInterval(function() {
6830 if(options.condition()) {
6833 clearInterval(interval);
6834 options.onComplete();
6836 that.viz.refresh(true);
6837 }, options.duration);
6843 Prepare graph position and other attribute values before performing an Animation.
6844 This method is used internally by the Toolkit.
6848 <Animation>, <Graph.Plot.animate>
6851 prepare: function(modes) {
6852 var graph = this.viz.graph,
6855 'getter': 'getData',
6859 'getter': 'getData',
6863 'getter': 'getCanvasStyle',
6864 'setter': 'setCanvasStyle'
6867 'getter': 'getCanvasStyle',
6868 'setter': 'setCanvasStyle'
6874 if($.type(modes) == 'array') {
6875 for(var i=0, len=modes.length; i < len; i++) {
6876 var elems = modes[i].split(':');
6877 m[elems.shift()] = elems;
6880 for(var p in modes) {
6881 if(p == 'position') {
6882 m[modes.position] = [];
6884 m[p] = $.splat(modes[p]);
6889 graph.eachNode(function(node) {
6890 node.startPos.set(node.pos);
6891 $.each(['node-property', 'node-style'], function(p) {
6894 for(var i=0, l=prop.length; i < l; i++) {
6895 node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6899 $.each(['edge-property', 'edge-style'], function(p) {
6902 node.eachAdjacency(function(adj) {
6903 for(var i=0, l=prop.length; i < l; i++) {
6904 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6916 Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6920 opt - (object) Animation options. The object properties are described below
6921 duration - (optional) Described in <Options.Fx>.
6922 fps - (optional) Described in <Options.Fx>.
6923 hideLabels - (optional|boolean) Whether to hide labels during the animation.
6924 modes - (required|object) An object with animation modes (described below).
6928 Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6929 They are represented by an object that has as keys main categories of properties to animate and as values a list
6930 of these specific properties. The properties are described below
6932 position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6933 node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6934 edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6935 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.
6936 node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6937 edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6941 var viz = new $jit.Viz(options);
6942 //...tweak some Data, CanvasStyles or LabelData properties...
6945 'position': 'linear',
6946 'node-property': ['width', 'height'],
6947 'node-style': 'shadowColor',
6948 'label-property': 'size'
6952 //...can also be written like this...
6955 'node-property:width:height',
6956 'node-style:shadowColor',
6957 'label-property:size'],
6962 animate: function(opt, versor) {
6963 opt = $.merge(this.viz.config, opt || {});
6967 interp = this.Interpolator,
6968 animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
6969 //prepare graph values
6970 var m = this.prepare(opt.modes);
6973 if(opt.hideLabels) this.labels.hideLabels(true);
6974 animation.setOptions($.extend(opt, {
6976 compute: function(delta) {
6977 graph.eachNode(function(node) {
6979 interp[p](node, m[p], delta, versor);
6982 that.plot(opt, this.$animating, delta);
6983 this.$animating = true;
6985 complete: function() {
6986 if(opt.hideLabels) that.labels.hideLabels(false);
6989 //TODO(nico): This shouldn't be here!
6990 //opt.onAfterCompute();
6998 Apply animation to node properties like color, width, height, dim, etc.
7002 options - Animation options. This object properties is described below
7003 elements - The Elements to be transformed. This is an object that has a properties
7007 //can also be an array of ids
7008 'id': 'id-of-node-to-transform',
7009 //properties to be modified. All properties are optional.
7011 'color': '#ccc', //some color
7012 'width': 10, //some width
7013 'height': 10, //some height
7014 'dim': 20, //some dim
7015 'lineWidth': 10 //some line width
7020 - _reposition_ Whether to recalculate positions and add a motion animation.
7021 This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7023 - _onComplete_ A method that is called when the animation completes.
7025 ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7029 var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7036 'transition': Trans.Quart.easeOut
7041 nodeFx: function(opt) {
7044 animation = this.nodeFxAnimation,
7045 options = $.merge(this.viz.config, {
7052 opt = $.merge(options, opt || {}, {
7053 onBeforeCompute: $.empty,
7054 onAfterCompute: $.empty
7056 //check if an animation is running
7057 animation.stopTimer();
7058 var props = opt.elements.properties;
7059 //set end values for nodes
7060 if(!opt.elements.id) {
7061 graph.eachNode(function(n) {
7062 for(var prop in props) {
7063 n.setData(prop, props[prop], 'end');
7067 var ids = $.splat(opt.elements.id);
7068 $.each(ids, function(id) {
7069 var n = graph.getNode(id);
7071 for(var prop in props) {
7072 n.setData(prop, props[prop], 'end');
7079 for(var prop in props) propnames.push(prop);
7080 //add node properties modes
7081 var modes = ['node-property:' + propnames.join(':')];
7082 //set new node positions
7083 if(opt.reposition) {
7084 modes.push('linear');
7088 this.animate($.merge(opt, {
7102 opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7107 var viz = new $jit.Viz(options);
7112 plot: function(opt, animating) {
7115 canvas = viz.canvas,
7118 ctx = canvas.getCtx(),
7120 opt = opt || this.viz.controller;
7122 opt.clearCanvas && canvas.clear();
7124 var root = aGraph.getNode(id);
7127 var T = !!root.visited;
7128 aGraph.eachNode(function(node) {
7129 var nodeAlpha = node.getData('alpha');
7130 node.eachAdjacency(function(adj) {
7131 var nodeTo = adj.nodeTo;
7132 if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7133 !animating && opt.onBeforePlotLine(adj);
7134 that.plotLine(adj, canvas, animating);
7135 !animating && opt.onAfterPlotLine(adj);
7139 !animating && opt.onBeforePlotNode(node);
7140 that.plotNode(node, canvas, animating);
7141 !animating && opt.onAfterPlotNode(node);
7143 if(!that.labelsHidden && opt.withLabels) {
7144 if(node.drawn && nodeAlpha >= 0.95) {
7145 that.labels.plotLabel(canvas, node, opt);
7147 that.labels.hideLabel(node, false);
7157 plotTree: function(node, opt, animating) {
7160 canvas = viz.canvas,
7161 config = this.config,
7162 ctx = canvas.getCtx();
7163 var nodeAlpha = node.getData('alpha');
7164 node.eachSubnode(function(elem) {
7165 if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7166 var adj = node.getAdjacency(elem.id);
7167 !animating && opt.onBeforePlotLine(adj);
7168 that.plotLine(adj, canvas, animating);
7169 !animating && opt.onAfterPlotLine(adj);
7170 that.plotTree(elem, opt, animating);
7174 !animating && opt.onBeforePlotNode(node);
7175 this.plotNode(node, canvas, animating);
7176 !animating && opt.onAfterPlotNode(node);
7177 if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7178 this.labels.plotLabel(canvas, node, opt);
7180 this.labels.hideLabel(node, false);
7182 this.labels.hideLabel(node, true);
7189 Plots a <Graph.Node>.
7193 node - (object) A <Graph.Node>.
7194 canvas - (object) A <Canvas> element.
7197 plotNode: function(node, canvas, animating) {
7198 var f = node.getData('type'),
7199 ctxObj = this.node.CanvasStyles;
7201 var width = node.getData('lineWidth'),
7202 color = node.getData('color'),
7203 alpha = node.getData('alpha'),
7204 ctx = canvas.getCtx();
7206 ctx.lineWidth = width;
7207 ctx.fillStyle = ctx.strokeStyle = color;
7208 ctx.globalAlpha = alpha;
7210 for(var s in ctxObj) {
7211 ctx[s] = node.getCanvasStyle(s);
7214 this.nodeTypes[f].render.call(this, node, canvas, animating);
7222 Plots a <Graph.Adjacence>.
7226 adj - (object) A <Graph.Adjacence>.
7227 canvas - (object) A <Canvas> instance.
7230 plotLine: function(adj, canvas, animating) {
7231 var f = adj.getData('type'),
7232 ctxObj = this.edge.CanvasStyles;
7234 var width = adj.getData('lineWidth'),
7235 color = adj.getData('color'),
7236 ctx = canvas.getCtx(),
7237 nodeFrom = adj.nodeFrom,
7238 nodeTo = adj.nodeTo;
7241 ctx.lineWidth = width;
7242 ctx.fillStyle = ctx.strokeStyle = color;
7243 ctx.globalAlpha = Math.min(nodeFrom.getData('alpha'),
7244 nodeTo.getData('alpha'),
7245 adj.getData('alpha'));
7247 for(var s in ctxObj) {
7248 ctx[s] = adj.getCanvasStyle(s);
7251 this.edgeTypes[f].render.call(this, adj, canvas, animating);
7259 Object: Graph.Plot3D
7261 <Graph> 3D rendering and animation methods.
7265 nodeHelper - <NodeHelper> object.
7266 edgeHelper - <EdgeHelper> object.
7269 Graph.Plot3D = $.merge(Graph.Plot, {
7271 'linear': function(elem, props, delta) {
7272 var from = elem.startPos.getc(true);
7273 var to = elem.endPos.getc(true);
7274 elem.pos.setc(this.compute(from.x, to.x, delta),
7275 this.compute(from.y, to.y, delta),
7276 this.compute(from.z, to.z, delta));
7280 plotNode: function(node, canvas) {
7281 if(node.getData('type') == 'none') return;
7282 this.plotElement(node, canvas, {
7283 getAlpha: function() {
7284 return node.getData('alpha');
7289 plotLine: function(adj, canvas) {
7290 if(adj.getData('type') == 'none') return;
7291 this.plotElement(adj, canvas, {
7292 getAlpha: function() {
7293 return Math.min(adj.nodeFrom.getData('alpha'),
7294 adj.nodeTo.getData('alpha'),
7295 adj.getData('alpha'));
7300 plotElement: function(elem, canvas, opt) {
7301 var gl = canvas.getCtx(),
7302 viewMatrix = new Matrix4,
7303 lighting = canvas.config.Scene.Lighting,
7304 wcanvas = canvas.canvases[0],
7305 program = wcanvas.program,
7306 camera = wcanvas.camera;
7308 if(!elem.geometry) {
7309 elem.geometry = new O3D[elem.getData('type')];
7311 elem.geometry.update(elem);
7312 if(!elem.webGLVertexBuffer) {
7317 geom = elem.geometry;
7319 for(var i=0, vs=geom.vertices, fs=geom.faces, fsl=fs.length; i<fsl; i++) {
7324 v4 = face.d? vs[face.d] : false,
7327 vertices.push(v1.x, v1.y, v1.z);
7328 vertices.push(v2.x, v2.y, v2.z);
7329 vertices.push(v3.x, v3.y, v3.z);
7330 if(v4) vertices.push(v4.x, v4.y, v4.z);
7332 normals.push(n.x, n.y, n.z);
7333 normals.push(n.x, n.y, n.z);
7334 normals.push(n.x, n.y, n.z);
7335 if(v4) normals.push(n.x, n.y, n.z);
7337 faces.push(vertexIndex, vertexIndex +1, vertexIndex +2);
7339 faces.push(vertexIndex, vertexIndex +2, vertexIndex +3);
7345 //create and store vertex data
7346 elem.webGLVertexBuffer = gl.createBuffer();
7347 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLVertexBuffer);
7348 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
7349 //create and store faces index data
7350 elem.webGLFaceBuffer = gl.createBuffer();
7351 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem.webGLFaceBuffer);
7352 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(faces), gl.STATIC_DRAW);
7353 elem.webGLFaceCount = faces.length;
7354 //calculate vertex normals and store them
7355 elem.webGLNormalBuffer = gl.createBuffer();
7356 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLNormalBuffer);
7357 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
7359 viewMatrix.multiply(camera.matrix, elem.geometry.matrix);
7361 gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.flatten());
7362 gl.uniformMatrix4fv(program.projectionMatrix, false, camera.projectionMatrix.flatten());
7363 //send normal matrix for lighting
7364 var normalMatrix = Matrix4.makeInvert(viewMatrix);
7365 normalMatrix.$transpose();
7366 gl.uniformMatrix4fv(program.normalMatrix, false, normalMatrix.flatten());
7368 var color = $.hexToRgb(elem.getData('color'));
7369 color.push(opt.getAlpha());
7370 gl.uniform4f(program.color, color[0] / 255, color[1] / 255, color[2] / 255, color[3]);
7371 //send lighting data
7372 gl.uniform1i(program.enableLighting, lighting.enable);
7373 if(lighting.enable) {
7374 //set ambient light color
7375 if(lighting.ambient) {
7376 var acolor = lighting.ambient;
7377 gl.uniform3f(program.ambientColor, acolor[0], acolor[1], acolor[2]);
7379 //set directional light
7380 if(lighting.directional) {
7381 var dir = lighting.directional,
7383 pos = dir.direction,
7384 vd = new Vector3(pos.x, pos.y, pos.z).normalize().$scale(-1);
7385 gl.uniform3f(program.lightingDirection, vd.x, vd.y, vd.z);
7386 gl.uniform3f(program.directionalColor, color[0], color[1], color[2]);
7389 //send vertices data
7390 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLVertexBuffer);
7391 gl.vertexAttribPointer(program.position, 3, gl.FLOAT, false, 0, 0);
7393 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLNormalBuffer);
7394 gl.vertexAttribPointer(program.normal, 3, gl.FLOAT, false, 0, 0);
7396 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem.webGLFaceBuffer );
7397 gl.drawElements(gl.TRIANGLES, elem.webGLFaceCount, gl.UNSIGNED_SHORT, 0);
7403 * File: Graph.Label.js
7410 An interface for plotting/hiding/showing labels.
7414 This is a generic interface for plotting/hiding/showing labels.
7415 The <Graph.Label> interface is implemented in multiple ways to provide
7416 different label types.
7418 For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7419 HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7420 The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7422 All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7428 Class: Graph.Label.Native
7430 Implements labels natively, using the Canvas text API.
7432 Graph.Label.Native = new Class({
7433 initialize: function(viz) {
7440 Plots a label for a given node.
7444 canvas - (object) A <Canvas> instance.
7445 node - (object) A <Graph.Node>.
7446 controller - (object) A configuration object.
7451 var viz = new $jit.Viz(options);
7452 var node = viz.graph.getNode('nodeId');
7453 viz.labels.plotLabel(viz.canvas, node, viz.config);
7456 plotLabel: function(canvas, node, controller) {
7457 var ctx = canvas.getCtx();
7458 var pos = node.pos.getc(true);
7460 ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7461 ctx.textAlign = node.getLabelData('textAlign');
7462 ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7463 ctx.textBaseline = node.getLabelData('textBaseline');
7465 this.renderLabel(canvas, node, controller);
7471 Does the actual rendering of the label in the canvas. The default
7472 implementation renders the label close to the position of the node, this
7473 method should be overriden to position the labels differently.
7477 canvas - A <Canvas> instance.
7478 node - A <Graph.Node>.
7479 controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7481 renderLabel: function(canvas, node, controller) {
7482 var ctx = canvas.getCtx();
7483 var pos = node.pos.getc(true);
7484 ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7492 Class: Graph.Label.DOM
7494 Abstract Class implementing some DOM label methods.
7498 <Graph.Label.HTML> and <Graph.Label.SVG>.
7501 Graph.Label.DOM = new Class({
7502 //A flag value indicating if node labels are being displayed or not.
7503 labelsHidden: false,
7505 labelContainer: false,
7506 //Label elements hash.
7510 Method: getLabelContainer
7512 Lazy fetcher for the label container.
7516 The label container DOM element.
7521 var viz = new $jit.Viz(options);
7522 var labelContainer = viz.labels.getLabelContainer();
7523 alert(labelContainer.innerHTML);
7526 getLabelContainer: function() {
7527 return this.labelContainer ?
7528 this.labelContainer :
7529 this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7535 Lazy fetcher for the label element.
7539 id - (string) The label id (which is also a <Graph.Node> id).
7548 var viz = new $jit.Viz(options);
7549 var label = viz.labels.getLabel('someid');
7550 alert(label.innerHTML);
7554 getLabel: function(id) {
7555 return (id in this.labels && this.labels[id] != null) ?
7557 this.labels[id] = document.getElementById(id);
7563 Hides all labels (by hiding the label container).
7567 hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7571 var viz = new $jit.Viz(options);
7572 rg.labels.hideLabels(true);
7576 hideLabels: function (hide) {
7577 var container = this.getLabelContainer();
7579 container.style.display = 'none';
7581 container.style.display = '';
7582 this.labelsHidden = hide;
7588 Clears the label container.
7590 Useful when using a new visualization with the same canvas element/widget.
7594 force - (boolean) Forces deletion of all labels.
7598 var viz = new $jit.Viz(options);
7599 viz.labels.clearLabels();
7602 clearLabels: function(force) {
7603 for(var id in this.labels) {
7604 if (force || !this.viz.graph.hasNode(id)) {
7605 this.disposeLabel(id);
7606 delete this.labels[id];
7612 Method: disposeLabel
7618 id - (string) A label id (which generally is also a <Graph.Node> id).
7622 var viz = new $jit.Viz(options);
7623 viz.labels.disposeLabel('labelid');
7626 disposeLabel: function(id) {
7627 var elem = this.getLabel(id);
7628 if(elem && elem.parentNode) {
7629 elem.parentNode.removeChild(elem);
7636 Hides the corresponding <Graph.Node> label.
7640 node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7641 show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7645 var rg = new $jit.Viz(options);
7646 viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7649 hideLabel: function(node, show) {
7650 node = $.splat(node);
7651 var st = show ? "" : "none", lab, that = this;
7652 $.each(node, function(n) {
7653 var lab = that.getLabel(n.id);
7655 lab.style.display = st;
7663 Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7667 pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7668 canvas - A <Canvas> instance.
7672 A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7675 fitsInCanvas: function(pos, canvas) {
7676 var size = canvas.getSize();
7677 if(pos.x >= size.width || pos.x < 0
7678 || pos.y >= size.height || pos.y < 0) return false;
7684 Class: Graph.Label.HTML
7686 Implements HTML labels.
7690 All <Graph.Label.DOM> methods.
7693 Graph.Label.HTML = new Class({
7694 Implements: Graph.Label.DOM,
7699 Plots a label for a given node.
7703 canvas - (object) A <Canvas> instance.
7704 node - (object) A <Graph.Node>.
7705 controller - (object) A configuration object.
7710 var viz = new $jit.Viz(options);
7711 var node = viz.graph.getNode('nodeId');
7712 viz.labels.plotLabel(viz.canvas, node, viz.config);
7717 plotLabel: function(canvas, node, controller) {
7718 var id = node.id, tag = this.getLabel(id);
7720 if(!tag && !(tag = document.getElementById(id))) {
7721 tag = document.createElement('div');
7722 var container = this.getLabelContainer();
7724 tag.className = 'node';
7725 tag.style.position = 'absolute';
7726 controller.onCreateLabel(tag, node);
7727 container.appendChild(tag);
7728 this.labels[node.id] = tag;
7731 this.placeLabel(tag, node, controller);
7736 Class: Graph.Label.SVG
7738 Implements SVG labels.
7742 All <Graph.Label.DOM> methods.
7744 Graph.Label.SVG = new Class({
7745 Implements: Graph.Label.DOM,
7750 Plots a label for a given node.
7754 canvas - (object) A <Canvas> instance.
7755 node - (object) A <Graph.Node>.
7756 controller - (object) A configuration object.
7761 var viz = new $jit.Viz(options);
7762 var node = viz.graph.getNode('nodeId');
7763 viz.labels.plotLabel(viz.canvas, node, viz.config);
7768 plotLabel: function(canvas, node, controller) {
7769 var id = node.id, tag = this.getLabel(id);
7770 if(!tag && !(tag = document.getElementById(id))) {
7771 var ns = 'http://www.w3.org/2000/svg';
7772 tag = document.createElementNS(ns, 'svg:text');
7773 var tspan = document.createElementNS(ns, 'svg:tspan');
7774 tag.appendChild(tspan);
7775 var container = this.getLabelContainer();
7776 tag.setAttribute('id', id);
7777 tag.setAttribute('class', 'node');
7778 container.appendChild(tag);
7779 controller.onCreateLabel(tag, node);
7780 this.labels[node.id] = tag;
7782 this.placeLabel(tag, node, controller);
7788 Graph.Geom = new Class({
7790 initialize: function(viz) {
7792 this.config = viz.config;
7793 this.node = viz.config.Node;
7794 this.edge = viz.config.Edge;
7797 Applies a translation to the tree.
7801 pos - A <Complex> number specifying translation vector.
7802 prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7807 st.geom.translate(new Complex(300, 100), 'end');
7810 translate: function(pos, prop) {
7811 prop = $.splat(prop);
7812 this.viz.graph.eachNode(function(elem) {
7813 $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7817 Hides levels of the tree until it properly fits in canvas.
7819 setRightLevelToShow: function(node, canvas, callback) {
7820 var level = this.getRightLevelToShow(node, canvas),
7821 fx = this.viz.labels,
7828 node.eachLevel(0, this.config.levelsToShow, function(n) {
7829 var d = n._depth - node._depth;
7835 fx.hideLabel(n, false);
7847 Returns the right level to show for the current tree in order to fit in canvas.
7849 getRightLevelToShow: function(node, canvas) {
7850 var config = this.config;
7851 var level = config.levelsToShow;
7852 var constrained = config.constrained;
7853 if(!constrained) return level;
7854 while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7867 Provides methods for loading and serving JSON data.
7870 construct: function(json) {
7871 var isGraph = ($.type(json) == 'array');
7872 var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7875 (function (ans, json) {
7878 for(var i=0, ch = json.children; i<ch.length; i++) {
7879 ans.addAdjacence(json, ch[i]);
7880 arguments.callee(ans, ch[i]);
7886 (function (ans, json) {
7887 var getNode = function(id) {
7888 for(var i=0, l=json.length; i<l; i++) {
7889 if(json[i].id == id) {
7893 // The node was not defined in the JSON
7899 return ans.addNode(newNode);
7902 for(var i=0, l=json.length; i<l; i++) {
7903 ans.addNode(json[i]);
7904 var adj = json[i].adjacencies;
7906 for(var j=0, lj=adj.length; j<lj; j++) {
7907 var node = adj[j], data = {};
7908 if(typeof adj[j] != 'string') {
7909 data = $.merge(node.data, {});
7912 ans.addAdjacence(json[i], getNode(node), data);
7924 Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7926 A JSON tree or graph structure consists of nodes, each having as properties
7928 id - (string) A unique identifier for the node
7929 name - (string) A node's name
7930 data - (object) The data optional property contains a hash (i.e {})
7931 where you can store all the information you want about this node.
7933 For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7939 "id": "aUniqueIdentifier",
7940 "name": "usually a nodes name",
7942 "some key": "some value",
7943 "some other key": "some other value"
7945 "children": [ *other nodes or empty* ]
7949 JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7950 For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7952 There are two types of *Graph* structures, *simple* and *extended* graph structures.
7954 For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7955 id of the node connected to the main node.
7962 "id": "aUniqueIdentifier",
7963 "name": "usually a nodes name",
7965 "some key": "some value",
7966 "some other key": "some other value"
7968 "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7971 'other nodes go here...'
7975 For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7977 nodeTo - (string) The other node connected by this adjacency.
7978 data - (object) A data property, where we can store custom key/value information.
7985 "id": "aUniqueIdentifier",
7986 "name": "usually a nodes name",
7988 "some key": "some value",
7989 "some other key": "some other value"
7994 data: {} //put whatever you want here
7996 'other adjacencies go here...'
7999 'other nodes go here...'
8003 About the data property:
8005 As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
8006 You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
8007 have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
8009 For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
8010 <Options.Node> will override the general value for that option with that particular value. For this to work
8011 however, you do have to set *overridable = true* in <Options.Node>.
8013 The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
8014 if <Options.Edge> has *overridable = true*.
8016 When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
8017 since this is the value which will be taken into account when creating the layout.
8018 The same thing goes for the *$color* parameter.
8020 In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
8021 *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
8022 canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
8023 to the *shadowBlur* property.
8025 These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
8026 by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
8028 Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
8029 information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
8031 loadJSON Parameters:
8033 json - A JSON Tree or Graph structure.
8034 i - For Graph structures only. Sets the indexed node as root for the visualization.
8037 loadJSON: function(json, i) {
8039 //if they're canvas labels erase them.
8040 if(this.labels && this.labels.clearLabels) {
8041 this.labels.clearLabels(true);
8043 this.graph = this.construct(json);
8044 if($.type(json) != 'array'){
8045 this.root = json.id;
8047 this.root = json[i? i : 0].id;
8054 Returns a JSON tree/graph structure from the visualization's <Graph>.
8055 See <Loader.loadJSON> for the graph formats available.
8063 type - (string) Default's "tree". The type of the JSON structure to be returned.
8064 Possible options are "tree" or "graph".
8066 toJSON: function(type) {
8067 type = type || "tree";
8068 if(type == 'tree') {
8070 var rootNode = this.graph.getNode(this.root);
8071 var ans = (function recTree(node) {
8074 ans.name = node.name;
8075 ans.data = node.data;
8077 node.eachSubnode(function(n) {
8078 ch.push(recTree(n));
8086 var T = !!this.graph.getNode(this.root).visited;
8087 this.graph.eachNode(function(node) {
8089 ansNode.id = node.id;
8090 ansNode.name = node.name;
8091 ansNode.data = node.data;
8093 node.eachAdjacency(function(adj) {
8094 var nodeTo = adj.nodeTo;
8095 if(!!nodeTo.visited === T) {
8097 ansAdj.nodeTo = nodeTo.id;
8098 ansAdj.data = adj.data;
8102 ansNode.adjacencies = adjs;
8116 * Implements base Tree and Graph layouts.
8120 * Implements base Tree and Graph layouts like Radial, Tree, etc.
8127 * Parent object for common layouts.
8130 var Layouts = $jit.Layouts = {};
8133 //Some util shared layout functions are defined here.
8137 compute: function(graph, prop, opt) {
8138 this.initializeLabel(opt);
8139 var label = this.label, style = label.style;
8140 graph.eachNode(function(n) {
8141 var autoWidth = n.getData('autoWidth'),
8142 autoHeight = n.getData('autoHeight');
8143 if(autoWidth || autoHeight) {
8144 //delete dimensions since these are
8145 //going to be overridden now.
8146 delete n.data.$width;
8147 delete n.data.$height;
8150 var width = n.getData('width'),
8151 height = n.getData('height');
8152 //reset label dimensions
8153 style.width = autoWidth? 'auto' : width + 'px';
8154 style.height = autoHeight? 'auto' : height + 'px';
8156 //TODO(nico) should let the user choose what to insert here.
8157 label.innerHTML = n.name;
8159 var offsetWidth = label.offsetWidth,
8160 offsetHeight = label.offsetHeight;
8161 var type = n.getData('type');
8162 if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8163 n.setData('width', offsetWidth);
8164 n.setData('height', offsetHeight);
8166 var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8167 n.setData('width', dim);
8168 n.setData('height', dim);
8169 n.setData('dim', dim);
8175 initializeLabel: function(opt) {
8177 this.label = document.createElement('div');
8178 document.body.appendChild(this.label);
8180 this.setLabelStyles(opt);
8183 setLabelStyles: function(opt) {
8184 $.extend(this.label.style, {
8185 'visibility': 'hidden',
8186 'position': 'absolute',
8190 this.label.className = 'jit-autoadjust-label';
8196 * Class: Layouts.Tree
8198 * Implements a Tree Layout.
8206 * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8209 Layouts.Tree = (function() {
8211 var slice = Array.prototype.slice;
8214 Calculates the max width and height nodes for a tree level
8216 function getBoundaries(graph, config, level, orn, prop) {
8217 var dim = config.Node;
8218 var multitree = config.multitree;
8219 if (dim.overridable) {
8221 graph.eachNode(function(n) {
8222 if (n._depth == level
8223 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8224 var dw = n.getData('width', prop);
8225 var dh = n.getData('height', prop);
8226 w = (w < dw) ? dw : w;
8227 h = (h < dh) ? dh : h;
8231 'width' : w < 0 ? dim.width : w,
8232 'height' : h < 0 ? dim.height : h
8240 function movetree(node, prop, val, orn) {
8241 var p = (orn == "left" || orn == "right") ? "y" : "x";
8242 node.getPos(prop)[p] += val;
8246 function moveextent(extent, val) {
8248 $.each(extent, function(elem) {
8249 elem = slice.call(elem);
8258 function merge(ps, qs) {
8263 var p = ps.shift(), q = qs.shift();
8264 return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8268 function mergelist(ls, def) {
8273 return mergelist(ls, merge(ps, def));
8277 function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8278 if (ext1.length <= i || ext2.length <= i)
8281 var p = ext1[i][1], q = ext2[i][0];
8282 return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8283 + subtreeOffset, p - q + siblingOffset);
8287 function fitlistl(es, subtreeOffset, siblingOffset) {
8288 function $fitlistl(acc, es, i) {
8291 var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8292 return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8295 return $fitlistl( [], es, 0);
8299 function fitlistr(es, subtreeOffset, siblingOffset) {
8300 function $fitlistr(acc, es, i) {
8303 var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8304 return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8307 es = slice.call(es);
8308 var ans = $fitlistr( [], es.reverse(), 0);
8309 return ans.reverse();
8313 function fitlist(es, subtreeOffset, siblingOffset, align) {
8314 var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8315 subtreeOffset, siblingOffset);
8317 if (align == "left")
8319 else if (align == "right")
8322 for ( var i = 0, ans = []; i < esl.length; i++) {
8323 ans[i] = (esl[i] + esr[i]) / 2;
8329 function design(graph, node, prop, config, orn) {
8330 var multitree = config.multitree;
8331 var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8332 var ind = +(orn == "left" || orn == "right");
8333 var p = auxp[ind], notp = auxp[1 - ind];
8335 var cnode = config.Node;
8336 var s = auxs[ind], nots = auxs[1 - ind];
8338 var siblingOffset = config.siblingOffset;
8339 var subtreeOffset = config.subtreeOffset;
8340 var align = config.align;
8342 function $design(node, maxsize, acum) {
8343 var sval = node.getData(s, prop);
8344 var notsval = maxsize
8345 || (node.getData(nots, prop));
8347 var trees = [], extents = [], chmaxsize = false;
8348 var chacum = notsval + config.levelDistance;
8349 node.eachSubnode(function(n) {
8351 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8354 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8356 var s = $design(n, chmaxsize[nots], acum + chacum);
8358 extents.push(s.extent);
8361 var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8362 for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8363 movetree(trees[i], prop, positions[i], orn);
8364 pextents.push(moveextent(extents[i], positions[i]));
8366 var resultextent = [ [ -sval / 2, sval / 2 ] ]
8367 .concat(mergelist(pextents));
8368 node.getPos(prop)[p] = 0;
8370 if (orn == "top" || orn == "left") {
8371 node.getPos(prop)[notp] = acum;
8373 node.getPos(prop)[notp] = -acum;
8378 extent : resultextent
8382 $design(node, false, 0);
8390 Computes nodes' positions.
8393 compute : function(property, computeLevels) {
8394 var prop = property || 'start';
8395 var node = this.graph.getNode(this.root);
8401 NodeDim.compute(this.graph, prop, this.config);
8402 if (!!computeLevels || !("_depth" in node)) {
8403 this.graph.computeLevels(this.root, 0, "ignore");
8406 this.computePositions(node, prop);
8409 computePositions : function(node, prop) {
8410 var config = this.config;
8411 var multitree = config.multitree;
8412 var align = config.align;
8413 var indent = align !== 'center' && config.indent;
8414 var orn = config.orientation;
8415 var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8417 $.each(orns, function(orn) {
8419 design(that.graph, node, prop, that.config, orn, prop);
8420 var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8422 (function red(node) {
8423 node.eachSubnode(function(n) {
8425 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8427 n.getPos(prop)[i] += node.getPos(prop)[i];
8429 n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8442 * File: Spacetree.js
8448 A Tree layout with advanced contraction and expansion animations.
8452 SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8453 <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8455 Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8459 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.
8463 All <Loader> methods
8465 Constructor Options:
8467 Inherits options from
8470 - <Options.Controller>
8477 - <Options.NodeStyles>
8478 - <Options.Navigation>
8480 Additionally, there are other parameters and some default values changed
8482 constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8483 levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8484 levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8485 Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8486 offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8487 offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8488 duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8490 Instance Properties:
8492 canvas - Access a <Canvas> instance.
8493 graph - Access a <Graph> instance.
8494 op - Access a <ST.Op> instance.
8495 fx - Access a <ST.Plot> instance.
8496 labels - Access a <ST.Label> interface implementation.
8500 $jit.ST= (function() {
8501 // Define some private methods first...
8503 var nodesInPath = [];
8504 // Nodes to contract
8505 function getNodesToHide(node) {
8506 node = node || this.clickedNode;
8507 if(!this.config.constrained) {
8510 var Geom = this.geom;
8511 var graph = this.graph;
8512 var canvas = this.canvas;
8513 var level = node._depth, nodeArray = [];
8514 graph.eachNode(function(n) {
8515 if(n.exist && !n.selected) {
8516 if(n.isDescendantOf(node.id)) {
8517 if(n._depth <= level) nodeArray.push(n);
8523 var leafLevel = Geom.getRightLevelToShow(node, canvas);
8524 node.eachLevel(leafLevel, leafLevel, function(n) {
8525 if(n.exist && !n.selected) nodeArray.push(n);
8528 for (var i = 0; i < nodesInPath.length; i++) {
8529 var n = this.graph.getNode(nodesInPath[i]);
8530 if(!n.isDescendantOf(node.id)) {
8537 function getNodesToShow(node) {
8538 var nodeArray = [], config = this.config;
8539 node = node || this.clickedNode;
8540 this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8541 if(config.multitree && !('$orn' in n.data)
8542 && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8544 } else if(n.drawn && !n.anySubnode("drawn")) {
8550 // Now define the actual class.
8553 Implements: [Loader, Extras, Layouts.Tree],
8555 initialize: function(controller) {
8570 this.controller = this.config = $.merge(
8571 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8572 "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8574 var canvasConfig = this.config;
8575 if(canvasConfig.useCanvas) {
8576 this.canvas = canvasConfig.useCanvas;
8577 this.config.labelContainer = this.canvas.id + '-label';
8579 if(canvasConfig.background) {
8580 canvasConfig.background = $.merge({
8582 }, canvasConfig.background);
8584 this.canvas = new Canvas(this, canvasConfig);
8585 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8588 this.graphOptions = {
8591 this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8592 this.labels = new $ST.Label[canvasConfig.Label.type](this);
8593 this.fx = new $ST.Plot(this, $ST);
8594 this.op = new $ST.Op(this);
8595 this.group = new $ST.Group(this);
8596 this.geom = new $ST.Geom(this);
8597 this.clickedNode= null;
8598 // initialize extras
8599 this.initializeExtras();
8605 Plots the <ST>. This is a shortcut to *fx.plot*.
8608 plot: function() { this.fx.plot(this.controller); },
8612 Method: switchPosition
8614 Switches the tree orientation.
8618 pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8619 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.
8620 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8625 st.switchPosition("right", "animate", {
8626 onComplete: function() {
8627 alert('completed!');
8632 switchPosition: function(pos, method, onComplete) {
8633 var Geom = this.geom, Plot = this.fx, that = this;
8637 onComplete: function() {
8638 Geom.switchOrientation(pos);
8639 that.compute('end', false);
8641 if(method == 'animate') {
8642 that.onClick(that.clickedNode.id, onComplete);
8643 } else if(method == 'replot') {
8644 that.select(that.clickedNode.id, onComplete);
8652 Method: switchAlignment
8654 Switches the tree alignment.
8658 align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8659 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.
8660 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8665 st.switchAlignment("right", "animate", {
8666 onComplete: function() {
8667 alert('completed!');
8672 switchAlignment: function(align, method, onComplete) {
8673 this.config.align = align;
8674 if(method == 'animate') {
8675 this.select(this.clickedNode.id, onComplete);
8676 } else if(method == 'replot') {
8677 this.onClick(this.clickedNode.id, onComplete);
8682 Method: addNodeInPath
8684 Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8689 id - (string) A <Graph.Node> id.
8694 st.addNodeInPath("nodeId");
8697 addNodeInPath: function(id) {
8698 nodesInPath.push(id);
8699 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8703 Method: clearNodesInPath
8705 Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8714 st.clearNodesInPath();
8717 clearNodesInPath: function(id) {
8718 nodesInPath.length = 0;
8719 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8725 Computes positions and plots the tree.
8728 refresh: function() {
8730 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8733 reposition: function() {
8734 this.graph.computeLevels(this.root, 0, "ignore");
8735 this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8736 this.graph.eachNode(function(n) {
8737 if(n.exist) n.drawn = true;
8739 this.compute('end');
8742 requestNodes: function(node, onComplete) {
8743 var handler = $.merge(this.controller, onComplete),
8744 lev = this.config.levelsToShow;
8745 if(handler.request) {
8746 var leaves = [], d = node._depth;
8747 node.eachLevel(0, lev, function(n) {
8751 n._level = lev - (n._depth - d);
8754 this.group.requestNodes(leaves, handler);
8757 handler.onComplete();
8760 contract: function(onComplete, switched) {
8761 var orn = this.config.orientation;
8762 var Geom = this.geom, Group = this.group;
8763 if(switched) Geom.switchOrientation(switched);
8764 var nodes = getNodesToHide.call(this);
8765 if(switched) Geom.switchOrientation(orn);
8766 Group.contract(nodes, $.merge(this.controller, onComplete));
8769 move: function(node, onComplete) {
8770 this.compute('end', false);
8771 var move = onComplete.Move, offset = {
8776 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8778 this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8781 expand: function (node, onComplete) {
8782 var nodeArray = getNodesToShow.call(this, node);
8783 this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8786 selectPath: function(node) {
8788 this.graph.eachNode(function(n) { n.selected = false; });
8789 function path(node) {
8790 if(node == null || node.selected) return;
8791 node.selected = true;
8792 $.each(that.group.getSiblings([node])[node.id],
8797 var parents = node.getParents();
8798 parents = (parents.length > 0)? parents[0] : null;
8801 for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8802 path(this.graph.getNode(ns[i]));
8809 Switches the current root node. Changes the topology of the Tree.
8812 id - (string) The id of the node to be set as root.
8813 method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8814 onComplete - (optional|object) An action to perform after the animation (if any).
8819 st.setRoot('nodeId', 'animate', {
8820 onComplete: function() {
8826 setRoot: function(id, method, onComplete) {
8827 if(this.busy) return;
8829 var that = this, canvas = this.canvas;
8830 var rootNode = this.graph.getNode(this.root);
8831 var clickedNode = this.graph.getNode(id);
8832 function $setRoot() {
8833 if(this.config.multitree && clickedNode.data.$orn) {
8834 var orn = clickedNode.data.$orn;
8841 rootNode.data.$orn = opp;
8842 (function tag(rootNode) {
8843 rootNode.eachSubnode(function(n) {
8850 delete clickedNode.data.$orn;
8853 this.clickedNode = clickedNode;
8854 this.graph.computeLevels(this.root, 0, "ignore");
8855 this.geom.setRightLevelToShow(clickedNode, canvas, {
8857 onShow: function(node) {
8860 node.setData('alpha', 1, 'end');
8861 node.setData('alpha', 0);
8862 node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8866 this.compute('end');
8869 modes: ['linear', 'node-property:alpha'],
8870 onComplete: function() {
8873 onComplete: function() {
8874 onComplete && onComplete.onComplete();
8881 // delete previous orientations (if any)
8882 delete rootNode.data.$orns;
8884 if(method == 'animate') {
8885 $setRoot.call(this);
8886 that.selectPath(clickedNode);
8887 } else if(method == 'replot') {
8888 $setRoot.call(this);
8889 this.select(this.root);
8899 subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8900 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.
8901 onComplete - (optional|object) An action to perform after the animation (if any).
8906 st.addSubtree(json, 'animate', {
8907 onComplete: function() {
8913 addSubtree: function(subtree, method, onComplete) {
8914 if(method == 'replot') {
8915 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8916 } else if (method == 'animate') {
8917 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8922 Method: removeSubtree
8927 id - (string) The _id_ of the subtree to be removed.
8928 removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8929 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.
8930 onComplete - (optional|object) An action to perform after the animation (if any).
8935 st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8936 onComplete: function() {
8943 removeSubtree: function(id, removeRoot, method, onComplete) {
8944 var node = this.graph.getNode(id), subids = [];
8945 node.eachLevel(+!removeRoot, false, function(n) {
8948 if(method == 'replot') {
8949 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8950 } else if (method == 'animate') {
8951 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8958 Selects a node in the <ST> without performing an animation. Useful when selecting
8959 nodes which are currently hidden or deep inside the tree.
8962 id - (string) The id of the node to select.
8963 onComplete - (optional|object) an onComplete callback.
8967 st.select('mynodeid', {
8968 onComplete: function() {
8974 select: function(id, onComplete) {
8975 var group = this.group, geom = this.geom;
8976 var node= this.graph.getNode(id), canvas = this.canvas;
8977 var root = this.graph.getNode(this.root);
8978 var complete = $.merge(this.controller, onComplete);
8981 complete.onBeforeCompute(node);
8982 this.selectPath(node);
8983 this.clickedNode= node;
8984 this.requestNodes(node, {
8985 onComplete: function(){
8986 group.hide(group.prepare(getNodesToHide.call(that)), complete);
8987 geom.setRightLevelToShow(node, canvas);
8988 that.compute("current");
8989 that.graph.eachNode(function(n) {
8990 var pos = n.pos.getc(true);
8991 n.startPos.setc(pos.x, pos.y);
8992 n.endPos.setc(pos.x, pos.y);
8995 var offset = { x: complete.offsetX, y: complete.offsetY };
8996 that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8997 group.show(getNodesToShow.call(that));
8999 complete.onAfterCompute(that.clickedNode);
9000 complete.onComplete();
9008 Animates the <ST> to center the node specified by *id*.
9012 id - (string) A node id.
9013 options - (optional|object) A group of options and callbacks described below.
9014 onComplete - (object) An object callback called when the animation finishes.
9015 Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
9020 st.onClick('mynodeid', {
9026 onComplete: function() {
9033 onClick: function (id, options) {
9034 var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
9035 var innerController = {
9038 offsetX: config.offsetX || 0,
9039 offsetY: config.offsetY || 0
9041 setRightLevelToShowConfig: false,
9042 onBeforeRequest: $.empty,
9043 onBeforeContract: $.empty,
9044 onBeforeMove: $.empty,
9045 onBeforeExpand: $.empty
9047 var complete = $.merge(this.controller, innerController, options);
9051 var node = this.graph.getNode(id);
9052 this.selectPath(node, this.clickedNode);
9053 this.clickedNode = node;
9054 complete.onBeforeCompute(node);
9055 complete.onBeforeRequest(node);
9056 this.requestNodes(node, {
9057 onComplete: function() {
9058 complete.onBeforeContract(node);
9060 onComplete: function() {
9061 Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
9062 complete.onBeforeMove(node);
9064 Move: complete.Move,
9065 onComplete: function() {
9066 complete.onBeforeExpand(node);
9068 onComplete: function() {
9070 complete.onAfterCompute(id);
9071 complete.onComplete();
9086 $jit.ST.$extend = true;
9091 Custom extension of <Graph.Op>.
9095 All <Graph.Op> methods
9102 $jit.ST.Op = new Class({
9104 Implements: Graph.Op
9110 Performs operations on group of nodes.
9113 $jit.ST.Group = new Class({
9115 initialize: function(viz) {
9117 this.canvas = viz.canvas;
9118 this.config = viz.config;
9119 this.animation = new Animation;
9125 Calls the request method on the controller to request a subtree for each node.
9127 requestNodes: function(nodes, controller) {
9128 var counter = 0, len = nodes.length, nodeSelected = {};
9129 var complete = function() { controller.onComplete(); };
9131 if(len == 0) complete();
9132 for(var i=0; i<len; i++) {
9133 nodeSelected[nodes[i].id] = nodes[i];
9134 controller.request(nodes[i].id, nodes[i]._level, {
9135 onComplete: function(nodeId, data) {
9136 if(data && data.children) {
9138 viz.op.sum(data, { type: 'nothing' });
9140 if(++counter == len) {
9141 viz.graph.computeLevels(viz.root, 0);
9151 Collapses group of nodes.
9153 contract: function(nodes, controller) {
9157 nodes = this.prepare(nodes);
9158 this.animation.setOptions($.merge(controller, {
9160 compute: function(delta) {
9161 if(delta == 1) delta = 0.99;
9162 that.plotStep(1 - delta, controller, this.$animating);
9163 this.$animating = 'contract';
9166 complete: function() {
9167 that.hide(nodes, controller);
9172 hide: function(nodes, controller) {
9174 for(var i=0; i<nodes.length; i++) {
9175 // TODO nodes are requested on demand, but not
9176 // deleted when hidden. Would that be a good feature?
9177 // Currently that feature is buggy, so I'll turn it off
9178 // Actually this feature is buggy because trimming should take
9179 // place onAfterCompute and not right after collapsing nodes.
9180 if (true || !controller || !controller.request) {
9181 nodes[i].eachLevel(1, false, function(elem){
9191 nodes[i].eachLevel(1, false, function(n) {
9194 viz.op.removeNode(ids, { 'type': 'nothing' });
9195 viz.labels.clearLabels();
9198 controller.onComplete();
9203 Expands group of nodes.
9205 expand: function(nodes, controller) {
9208 this.animation.setOptions($.merge(controller, {
9210 compute: function(delta) {
9211 that.plotStep(delta, controller, this.$animating);
9212 this.$animating = 'expand';
9215 complete: function() {
9216 that.plotStep(undefined, controller, false);
9217 controller.onComplete();
9223 show: function(nodes) {
9224 var config = this.config;
9225 this.prepare(nodes);
9226 $.each(nodes, function(n) {
9227 // check for root nodes if multitree
9228 if(config.multitree && !('$orn' in n.data)) {
9229 delete n.data.$orns;
9231 n.eachSubnode(function(ch) {
9232 if(('$orn' in ch.data)
9233 && orns.indexOf(ch.data.$orn) < 0
9234 && ch.exist && !ch.drawn) {
9235 orns += ch.data.$orn + ' ';
9238 n.data.$orns = orns;
9240 n.eachLevel(0, config.levelsToShow, function(n) {
9241 if(n.exist) n.drawn = true;
9246 prepare: function(nodes) {
9247 this.nodes = this.getNodesWithChildren(nodes);
9252 Filters an array of nodes leaving only nodes with children.
9254 getNodesWithChildren: function(nodes) {
9255 var ans = [], config = this.config, root = this.viz.root;
9256 nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9257 for(var i=0; i<nodes.length; i++) {
9258 if(nodes[i].anySubnode("exist")) {
9259 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9260 if(!config.multitree || '$orn' in nodes[j].data) {
9261 desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9264 if(!desc) ans.push(nodes[i]);
9270 plotStep: function(delta, controller, animating) {
9272 config = this.config,
9273 canvas = viz.canvas,
9274 ctx = canvas.getCtx(),
9277 // hide nodes that are meant to be collapsed/expanded
9279 for(i=0; i<nodes.length; i++) {
9282 var root = config.multitree && !('$orn' in node.data);
9283 var orns = root && node.data.$orns;
9284 node.eachSubgraph(function(n) {
9285 // TODO(nico): Cleanup
9286 // special check for root node subnodes when
9287 // multitree is checked.
9288 if(root && orns && orns.indexOf(n.data.$orn) > 0
9291 nds[node.id].push(n);
9292 } else if((!root || !orns) && n.drawn) {
9294 nds[node.id].push(n);
9299 // plot the whole (non-scaled) tree
9300 if(nodes.length > 0) viz.fx.plot();
9301 // show nodes that were previously hidden
9303 $.each(nds[i], function(n) { n.drawn = true; });
9305 // plot each scaled subtree
9306 for(i=0; i<nodes.length; i++) {
9309 viz.fx.plotSubtree(node, controller, delta, animating);
9314 getSiblings: function(nodes) {
9316 $.each(nodes, function(n) {
9317 var par = n.getParents();
9318 if (par.length == 0) {
9319 siblings[n.id] = [n];
9322 par[0].eachSubnode(function(sn) {
9325 siblings[n.id] = ans;
9335 Performs low level geometrical computations.
9339 This instance can be accessed with the _geom_ parameter of the st instance created.
9344 var st = new ST(canvas, config);
9345 st.geom.translate //or can also call any other <ST.Geom> method
9350 $jit.ST.Geom = new Class({
9351 Implements: Graph.Geom,
9353 Changes the tree current orientation to the one specified.
9355 You should usually use <ST.switchPosition> instead.
9357 switchOrientation: function(orn) {
9358 this.config.orientation = orn;
9362 Makes a value dispatch according to the current layout
9363 Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9365 dispatch: function() {
9366 // TODO(nico) should store Array.prototype.slice.call somewhere.
9367 var args = Array.prototype.slice.call(arguments);
9368 var s = args.shift(), len = args.length;
9369 var val = function(a) { return typeof a == 'function'? a() : a; };
9371 return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9372 } else if(len == 4) {
9374 case "top": return val(args[0]);
9375 case "right": return val(args[1]);
9376 case "bottom": return val(args[2]);
9377 case "left": return val(args[3]);
9384 Returns label height or with, depending on the tree current orientation.
9386 getSize: function(n, invert) {
9387 var data = n.data, config = this.config;
9388 var siblingOffset = config.siblingOffset;
9389 var s = (config.multitree
9391 && data.$orn) || config.orientation;
9392 var w = n.getData('width') + siblingOffset;
9393 var h = n.getData('height') + siblingOffset;
9395 return this.dispatch(s, h, w);
9397 return this.dispatch(s, w, h);
9401 Calculates a subtree base size. This is an utility function used by _getBaseSize_
9403 getTreeBaseSize: function(node, level, leaf) {
9404 var size = this.getSize(node, true), baseHeight = 0, that = this;
9405 if(leaf(level, node)) return size;
9406 if(level === 0) return 0;
9407 node.eachSubnode(function(elem) {
9408 baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9410 return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9417 Returns a Complex instance with the begin or end position of the edge to be plotted.
9421 node - A <Graph.Node> that is connected to this edge.
9422 type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9426 A <Complex> number specifying the begin or end position.
9428 getEdge: function(node, type, s) {
9429 var $C = function(a, b) {
9431 return node.pos.add(new Complex(a, b));
9434 var dim = this.node;
9435 var w = node.getData('width');
9436 var h = node.getData('height');
9438 if(type == 'begin') {
9439 if(dim.align == "center") {
9440 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9441 $C(0, -h/2),$C(w/2, 0));
9442 } else if(dim.align == "left") {
9443 return this.dispatch(s, $C(0, h), $C(0, 0),
9444 $C(0, 0), $C(w, 0));
9445 } else if(dim.align == "right") {
9446 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9447 $C(0, -h),$C(0, 0));
9448 } else throw "align: not implemented";
9451 } else if(type == 'end') {
9452 if(dim.align == "center") {
9453 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9454 $C(0, h/2), $C(-w/2, 0));
9455 } else if(dim.align == "left") {
9456 return this.dispatch(s, $C(0, 0), $C(w, 0),
9457 $C(0, h), $C(0, 0));
9458 } else if(dim.align == "right") {
9459 return this.dispatch(s, $C(0, -h),$C(0, 0),
9460 $C(0, 0), $C(-w, 0));
9461 } else throw "align: not implemented";
9466 Adjusts the tree position due to canvas scaling or translation.
9468 getScaledTreePosition: function(node, scale) {
9469 var dim = this.node;
9470 var w = node.getData('width');
9471 var h = node.getData('height');
9472 var s = (this.config.multitree
9473 && ('$orn' in node.data)
9474 && node.data.$orn) || this.config.orientation;
9476 var $C = function(a, b) {
9478 return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9481 if(dim.align == "left") {
9482 return this.dispatch(s, $C(0, h), $C(0, 0),
9483 $C(0, 0), $C(w, 0));
9484 } else if(dim.align == "center") {
9485 return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9486 $C(0, -h / 2),$C(w / 2, 0));
9487 } else if(dim.align == "right") {
9488 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9489 $C(0, -h),$C(0, 0));
9490 } else throw "align: not implemented";
9496 Returns a Boolean if the current subtree fits in canvas.
9500 node - A <Graph.Node> which is the current root of the subtree.
9501 canvas - The <Canvas> object.
9502 level - The depth of the subtree to be considered.
9504 treeFitsInCanvas: function(node, canvas, level) {
9505 var csize = canvas.getSize();
9506 var s = (this.config.multitree
9507 && ('$orn' in node.data)
9508 && node.data.$orn) || this.config.orientation;
9510 var size = this.dispatch(s, csize.width, csize.height);
9511 var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9512 return level === 0 || !node.anySubnode();
9514 return (baseSize < size);
9521 Custom extension of <Graph.Plot>.
9525 All <Graph.Plot> methods
9532 $jit.ST.Plot = new Class({
9534 Implements: Graph.Plot,
9537 Plots a subtree from the spacetree.
9539 plotSubtree: function(node, opt, scale, animating) {
9540 var viz = this.viz, canvas = viz.canvas, config = viz.config;
9541 scale = Math.min(Math.max(0.001, scale), 1);
9544 var ctx = canvas.getCtx();
9545 var diff = viz.geom.getScaledTreePosition(node, scale);
9546 ctx.translate(diff.x, diff.y);
9547 ctx.scale(scale, scale);
9549 this.plotTree(node, $.merge(opt, {
9551 'hideLabels': !!scale,
9552 'plotSubtree': function(n, ch) {
9553 var root = config.multitree && !('$orn' in node.data);
9554 var orns = root && node.getData('orns');
9555 return !root || orns.indexOf(node.getData('orn')) > -1;
9558 if(scale >= 0) node.drawn = true;
9562 Method: getAlignedPos
9564 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9568 pos - (object) A <Graph.Node> position.
9569 width - (number) The width of the node.
9570 height - (number) The height of the node.
9573 getAlignedPos: function(pos, width, height) {
9574 var nconfig = this.node;
9576 if(nconfig.align == "center") {
9578 x: pos.x - width / 2,
9579 y: pos.y - height / 2
9581 } else if (nconfig.align == "left") {
9582 orn = this.config.orientation;
9583 if(orn == "bottom" || orn == "top") {
9585 x: pos.x - width / 2,
9591 y: pos.y - height / 2
9594 } else if(nconfig.align == "right") {
9595 orn = this.config.orientation;
9596 if(orn == "bottom" || orn == "top") {
9598 x: pos.x - width / 2,
9604 y: pos.y - height / 2
9607 } else throw "align: not implemented";
9612 getOrientation: function(adj) {
9613 var config = this.config;
9614 var orn = config.orientation;
9616 if(config.multitree) {
9617 var nodeFrom = adj.nodeFrom;
9618 var nodeTo = adj.nodeTo;
9619 orn = (('$orn' in nodeFrom.data)
9620 && nodeFrom.data.$orn)
9621 || (('$orn' in nodeTo.data)
9622 && nodeTo.data.$orn);
9632 Custom extension of <Graph.Label>.
9633 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9637 All <Graph.Label> methods and subclasses.
9641 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9648 Custom extension of <Graph.Label.Native>.
9652 All <Graph.Label.Native> methods
9656 <Graph.Label.Native>
9658 $jit.ST.Label.Native = new Class({
9659 Implements: Graph.Label.Native,
9661 renderLabel: function(canvas, node, controller) {
9662 var ctx = canvas.getCtx(),
9663 coord = node.pos.getc(true),
9664 width = node.getData('width'),
9665 height = node.getData('height'),
9666 pos = this.viz.fx.getAlignedPos(coord, width, height);
9667 ctx.fillText(node.name, pos.x + width / 2, pos.y + height / 2);
9671 $jit.ST.Label.DOM = new Class({
9672 Implements: Graph.Label.DOM,
9677 Overrides abstract method placeLabel in <Graph.Plot>.
9681 tag - A DOM label element.
9682 node - A <Graph.Node>.
9683 controller - A configuration/controller object passed to the visualization.
9686 placeLabel: function(tag, node, controller) {
9687 var pos = node.pos.getc(true),
9688 config = this.viz.config,
9690 canvas = this.viz.canvas,
9691 w = node.getData('width'),
9692 h = node.getData('height'),
9693 radius = canvas.getSize(),
9696 var ox = canvas.translateOffsetX,
9697 oy = canvas.translateOffsetY,
9698 sx = canvas.scaleOffsetX,
9699 sy = canvas.scaleOffsetY,
9700 posx = pos.x * sx + ox,
9701 posy = pos.y * sy + oy;
9703 if(dim.align == "center") {
9705 x: Math.round(posx - w / 2 + radius.width/2),
9706 y: Math.round(posy - h / 2 + radius.height/2)
9708 } else if (dim.align == "left") {
9709 orn = config.orientation;
9710 if(orn == "bottom" || orn == "top") {
9712 x: Math.round(posx - w / 2 + radius.width/2),
9713 y: Math.round(posy + radius.height/2)
9717 x: Math.round(posx + radius.width/2),
9718 y: Math.round(posy - h / 2 + radius.height/2)
9721 } else if(dim.align == "right") {
9722 orn = config.orientation;
9723 if(orn == "bottom" || orn == "top") {
9725 x: Math.round(posx - w / 2 + radius.width/2),
9726 y: Math.round(posy - h + radius.height/2)
9730 x: Math.round(posx - w + radius.width/2),
9731 y: Math.round(posy - h / 2 + radius.height/2)
9734 } else throw "align: not implemented";
9736 var style = tag.style;
9737 style.left = labelPos.x + 'px';
9738 style.top = labelPos.y + 'px';
9739 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9740 controller.onPlaceLabel(tag, node);
9747 Custom extension of <Graph.Label.SVG>.
9751 All <Graph.Label.SVG> methods
9757 $jit.ST.Label.SVG = new Class({
9758 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9760 initialize: function(viz) {
9768 Custom extension of <Graph.Label.HTML>.
9772 All <Graph.Label.HTML> methods.
9779 $jit.ST.Label.HTML = new Class({
9780 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9782 initialize: function(viz) {
9789 Class: ST.Plot.NodeTypes
9791 This class contains a list of <Graph.Node> built-in types.
9792 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9794 You can add your custom node types, customizing your visualization to the extreme.
9799 ST.Plot.NodeTypes.implement({
9801 'render': function(node, canvas) {
9802 //print your custom node to canvas
9805 'contains': function(node, pos) {
9806 //return true if pos is inside the node or false otherwise
9813 $jit.ST.Plot.NodeTypes = new Class({
9816 'contains': $.lambda(false)
9819 'render': function(node, canvas) {
9820 var dim = node.getData('dim'),
9821 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9823 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9825 'contains': function(node, pos) {
9826 var dim = node.getData('dim'),
9827 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9829 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, pos, dim2);
9833 'render': function(node, canvas) {
9834 var dim = node.getData('dim'),
9836 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9837 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9839 'contains': function(node, pos) {
9840 var dim = node.getData('dim'),
9841 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9843 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, pos, dim2);
9847 'render': function(node, canvas) {
9848 var width = node.getData('width'),
9849 height = node.getData('height'),
9850 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9851 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9853 'contains': function(node, pos) {
9854 var width = node.getData('width'),
9855 height = node.getData('height'),
9856 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9857 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, pos, width, height);
9861 'render': function(node, canvas) {
9862 var width = node.getData('width'),
9863 height = node.getData('height'),
9864 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9865 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9867 'contains': function(node, pos) {
9868 var width = node.getData('width'),
9869 height = node.getData('height'),
9870 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9871 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, pos, width, height);
9877 Class: ST.Plot.EdgeTypes
9879 This class contains a list of <Graph.Adjacence> built-in types.
9880 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9882 You can add your custom edge types, customizing your visualization to the extreme.
9887 ST.Plot.EdgeTypes.implement({
9889 'render': function(adj, canvas) {
9890 //print your custom edge to canvas
9893 'contains': function(adj, pos) {
9894 //return true if pos is inside the arc or false otherwise
9901 $jit.ST.Plot.EdgeTypes = new Class({
9904 'render': function(adj, canvas) {
9905 var orn = this.getOrientation(adj),
9906 nodeFrom = adj.nodeFrom,
9907 nodeTo = adj.nodeTo,
9908 rel = nodeFrom._depth < nodeTo._depth,
9909 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9910 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9911 this.edgeHelper.line.render(from, to, canvas);
9913 'contains': function(adj, pos) {
9914 var orn = this.getOrientation(adj),
9915 nodeFrom = adj.nodeFrom,
9916 nodeTo = adj.nodeTo,
9917 rel = nodeFrom._depth < nodeTo._depth,
9918 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9919 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9920 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9924 'render': function(adj, canvas) {
9925 var orn = this.getOrientation(adj),
9926 node = adj.nodeFrom,
9928 dim = adj.getData('dim'),
9929 from = this.viz.geom.getEdge(node, 'begin', orn),
9930 to = this.viz.geom.getEdge(child, 'end', orn),
9931 direction = adj.data.$direction,
9932 inv = (direction && direction.length>1 && direction[0] != node.id);
9933 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9935 'contains': function(adj, pos) {
9936 var orn = this.getOrientation(adj),
9937 nodeFrom = adj.nodeFrom,
9938 nodeTo = adj.nodeTo,
9939 rel = nodeFrom._depth < nodeTo._depth,
9940 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9941 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9942 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9945 'quadratic:begin': {
9946 'render': function(adj, canvas) {
9947 var orn = this.getOrientation(adj);
9948 var nodeFrom = adj.nodeFrom,
9949 nodeTo = adj.nodeTo,
9950 rel = nodeFrom._depth < nodeTo._depth,
9951 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9952 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9953 dim = adj.getData('dim'),
9954 ctx = canvas.getCtx();
9956 ctx.moveTo(begin.x, begin.y);
9959 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9962 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9965 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9968 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9975 'render': function(adj, canvas) {
9976 var orn = this.getOrientation(adj);
9977 var nodeFrom = adj.nodeFrom,
9978 nodeTo = adj.nodeTo,
9979 rel = nodeFrom._depth < nodeTo._depth,
9980 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9981 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9982 dim = adj.getData('dim'),
9983 ctx = canvas.getCtx();
9985 ctx.moveTo(begin.x, begin.y);
9988 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9991 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9994 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9997 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
10004 'render': function(adj, canvas) {
10005 var orn = this.getOrientation(adj),
10006 nodeFrom = adj.nodeFrom,
10007 nodeTo = adj.nodeTo,
10008 rel = nodeFrom._depth < nodeTo._depth,
10009 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10010 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10011 dim = adj.getData('dim'),
10012 ctx = canvas.getCtx();
10014 ctx.moveTo(begin.x, begin.y);
10017 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
10020 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
10023 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
10026 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
10037 * File: AreaChart.js
10041 $jit.ST.Plot.NodeTypes.implement({
10042 'areachart-stacked' : {
10043 'render' : function(node, canvas) {
10044 var pos = node.pos.getc(true),
10045 width = node.getData('width'),
10046 height = node.getData('height'),
10047 algnPos = this.getAlignedPos(pos, width, height),
10048 x = algnPos.x, y = algnPos.y,
10049 stringArray = node.getData('stringArray'),
10050 dimArray = node.getData('dimArray'),
10051 valArray = node.getData('valueArray'),
10052 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10053 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10054 colorArray = node.getData('colorArray'),
10055 colorLength = colorArray.length,
10056 config = node.getData('config'),
10057 gradient = node.getData('gradient'),
10058 showLabels = config.showLabels,
10059 aggregates = config.showAggregates,
10060 label = config.Label,
10061 prev = node.getData('prev');
10063 var ctx = canvas.getCtx(), border = node.getData('border');
10064 if (colorArray && dimArray && stringArray) {
10065 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10066 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10068 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10069 var h1 = acumLeft + dimArray[i][0],
10070 h2 = acumRight + dimArray[i][1],
10071 alpha = Math.atan((h2 - h1) / width),
10073 var linear = ctx.createLinearGradient(x + width/2,
10075 x + width/2 + delta * Math.sin(alpha),
10076 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10077 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10078 function(v) { return (v * 0.85) >> 0; }));
10079 linear.addColorStop(0, colorArray[i % colorLength]);
10080 linear.addColorStop(1, color);
10081 ctx.fillStyle = linear;
10084 ctx.moveTo(x, y - acumLeft);
10085 ctx.lineTo(x + width, y - acumRight);
10086 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10087 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10088 ctx.lineTo(x, y - acumLeft);
10092 var strong = border.name == stringArray[i];
10093 var perc = strong? 0.7 : 0.8;
10094 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10095 function(v) { return (v * perc) >> 0; }));
10096 ctx.strokeStyle = color;
10097 ctx.lineWidth = strong? 4 : 1;
10100 if(border.index === 0) {
10101 ctx.moveTo(x, y - acumLeft);
10102 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10104 ctx.moveTo(x + width, y - acumRight);
10105 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10110 acumLeft += (dimArray[i][0] || 0);
10111 acumRight += (dimArray[i][1] || 0);
10113 if(dimArray[i][0] > 0)
10114 valAcum += (valArray[i][0] || 0);
10116 if(prev && label.type == 'Native') {
10119 ctx.fillStyle = ctx.strokeStyle = label.color;
10120 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10121 ctx.textAlign = 'center';
10122 ctx.textBaseline = 'middle';
10123 var aggValue = aggregates(node.name, valLeft, valRight, node, valAcum);
10124 if(aggValue !== false) {
10125 ctx.fillText(aggValue !== true? aggValue : valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10127 if(showLabels(node.name, valLeft, valRight, node)) {
10128 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10134 'contains': function(node, mpos) {
10135 var pos = node.pos.getc(true),
10136 width = node.getData('width'),
10137 height = node.getData('height'),
10138 algnPos = this.getAlignedPos(pos, width, height),
10139 x = algnPos.x, y = algnPos.y,
10140 dimArray = node.getData('dimArray'),
10142 //bounding box check
10143 if(mpos.x < x || mpos.x > x + width
10144 || mpos.y > y || mpos.y < y - height) {
10148 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10149 var dimi = dimArray[i];
10152 var intersec = lAcum + (rAcum - lAcum) * rx / width;
10153 if(mpos.y >= intersec) {
10154 var index = +(rx > width/2);
10156 'name': node.getData('stringArray')[i],
10157 'color': node.getData('colorArray')[i],
10158 'value': node.getData('valueArray')[i][index],
10171 A visualization that displays stacked area charts.
10173 Constructor Options:
10175 See <Options.AreaChart>.
10178 $jit.AreaChart = new Class({
10180 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10184 initialize: function(opt) {
10185 this.controller = this.config =
10186 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10187 Label: { type: 'Native' }
10189 //set functions for showLabels and showAggregates
10190 var showLabels = this.config.showLabels,
10191 typeLabels = $.type(showLabels),
10192 showAggregates = this.config.showAggregates,
10193 typeAggregates = $.type(showAggregates);
10194 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10195 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10197 this.initializeViz();
10200 initializeViz: function() {
10201 var config = this.config,
10203 nodeType = config.type.split(":")[0],
10206 var delegate = new $jit.ST({
10207 injectInto: config.injectInto,
10208 width: config.width,
10209 height: config.height,
10210 orientation: "bottom",
10214 withLabels: config.Label.type != 'Native',
10215 useCanvas: config.useCanvas,
10217 type: config.Label.type
10221 type: 'areachart-' + nodeType,
10230 enable: config.Tips.enable,
10233 onShow: function(tip, node, contains) {
10234 var elem = contains;
10235 config.Tips.onShow(tip, elem, node);
10241 onClick: function(node, eventInfo, evt) {
10242 if(!config.filterOnClick && !config.Events.enable) return;
10243 var elem = eventInfo.getContains();
10244 if(elem) config.filterOnClick && that.filter(elem.name);
10245 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10247 onRightClick: function(node, eventInfo, evt) {
10248 if(!config.restoreOnRightClick) return;
10251 onMouseMove: function(node, eventInfo, evt) {
10252 if(!config.selectOnHover) return;
10254 var elem = eventInfo.getContains();
10255 that.select(node.id, elem.name, elem.index);
10257 that.select(false, false, false);
10261 onCreateLabel: function(domElement, node) {
10262 var labelConf = config.Label,
10263 valueArray = node.getData('valueArray'),
10264 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10265 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10266 if(node.getData('prev')) {
10268 wrapper: document.createElement('div'),
10269 aggregate: document.createElement('div'),
10270 label: document.createElement('div')
10272 var wrapper = nlbs.wrapper,
10273 label = nlbs.label,
10274 aggregate = nlbs.aggregate,
10275 wrapperStyle = wrapper.style,
10276 labelStyle = label.style,
10277 aggregateStyle = aggregate.style;
10278 //store node labels
10279 nodeLabels[node.id] = nlbs;
10281 wrapper.appendChild(label);
10282 wrapper.appendChild(aggregate);
10283 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10284 label.style.display = 'none';
10286 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10287 aggregate.style.display = 'none';
10289 wrapperStyle.position = 'relative';
10290 wrapperStyle.overflow = 'visible';
10291 wrapperStyle.fontSize = labelConf.size + 'px';
10292 wrapperStyle.fontFamily = labelConf.family;
10293 wrapperStyle.color = labelConf.color;
10294 wrapperStyle.textAlign = 'center';
10295 aggregateStyle.position = labelStyle.position = 'absolute';
10297 domElement.style.width = node.getData('width') + 'px';
10298 domElement.style.height = node.getData('height') + 'px';
10299 label.innerHTML = node.name;
10301 domElement.appendChild(wrapper);
10304 onPlaceLabel: function(domElement, node) {
10305 if(!node.getData('prev')) return;
10306 var labels = nodeLabels[node.id],
10307 wrapperStyle = labels.wrapper.style,
10308 labelStyle = labels.label.style,
10309 aggregateStyle = labels.aggregate.style,
10310 width = node.getData('width'),
10311 height = node.getData('height'),
10312 dimArray = node.getData('dimArray'),
10313 valArray = node.getData('valueArray'),
10314 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10315 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10316 font = parseInt(wrapperStyle.fontSize, 10),
10317 domStyle = domElement.style;
10319 if(dimArray && valArray) {
10320 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10321 labelStyle.display = '';
10323 labelStyle.display = 'none';
10325 var aggValue = config.showAggregates(node.name, acumLeft, acumRight, node);
10326 if(aggValue !== false) {
10327 aggregateStyle.display = '';
10329 aggregateStyle.display = 'none';
10331 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10332 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10333 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10334 if(dimArray[i][0] > 0) {
10335 acum+= valArray[i][0];
10336 leftAcum+= dimArray[i][0];
10339 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10340 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10341 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10342 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10343 labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
10348 var size = delegate.canvas.getSize(),
10349 margin = config.Margin;
10350 delegate.config.offsetY = -size.height/2 + margin.bottom
10351 + (config.showLabels && (config.labelOffset + config.Label.size));
10352 delegate.config.offsetX = (margin.right - margin.left)/2;
10353 this.delegate = delegate;
10354 this.canvas = this.delegate.canvas;
10360 Loads JSON data into the visualization.
10364 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>.
10368 var areaChart = new $jit.AreaChart(options);
10369 areaChart.loadJSON(json);
10372 loadJSON: function(json) {
10373 var prefix = $.time(),
10375 delegate = this.delegate,
10376 name = $.splat(json.label),
10377 color = $.splat(json.color || this.colors),
10378 config = this.config,
10379 gradient = !!config.type.split(":")[1],
10380 animate = config.animate;
10382 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
10383 var val = values[i], prev = values[i-1], next = values[i+1];
10384 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
10385 var valArray = $.zip(valLeft, valRight);
10386 var acumLeft = 0, acumRight = 0;
10388 'id': prefix + val.label,
10392 '$valueArray': valArray,
10393 '$colorArray': color,
10394 '$stringArray': name,
10395 '$next': next.label,
10396 '$prev': prev? prev.label:false,
10398 '$gradient': gradient
10404 'id': prefix + '$root',
10413 delegate.loadJSON(root);
10415 this.normalizeDims();
10416 delegate.compute();
10417 delegate.select(delegate.root);
10419 delegate.fx.animate({
10420 modes: ['node-property:height:dimArray'],
10429 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.
10433 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10434 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10439 areaChart.updateJSON(json, {
10440 onComplete: function() {
10441 alert('update complete!');
10446 updateJSON: function(json, onComplete) {
10447 if(this.busy) return;
10450 var delegate = this.delegate,
10451 graph = delegate.graph,
10452 labels = json.label && $.splat(json.label),
10453 values = json.values,
10454 animate = this.config.animate,
10458 //convert the whole thing into a hash
10459 for (var i = 0, l = values.length; i < l; i++) {
10460 hashValues[values[i].label] = values[i];
10463 graph.eachNode(function(n) {
10464 var v = hashValues[n.name],
10465 stringArray = n.getData('stringArray'),
10466 valArray = n.getData('valueArray'),
10467 next = n.getData('next');
10470 v.values = $.splat(v.values);
10471 $.each(valArray, function(a, i) {
10472 a[0] = v.values[i];
10473 if(labels) stringArray[i] = labels[i];
10475 n.setData('valueArray', valArray);
10479 v = hashValues[next];
10481 $.each(valArray, function(a, i) {
10482 a[1] = v.values[i];
10487 this.normalizeDims();
10488 delegate.compute();
10489 delegate.select(delegate.root);
10491 delegate.fx.animate({
10492 modes: ['node-property:height:dimArray'],
10494 onComplete: function() {
10496 onComplete && onComplete.onComplete();
10505 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10509 filters - (array) An array of strings with the name of the stacks to be filtered.
10510 callback - (object) An object with an *onComplete* callback method.
10515 areaChart.filter(['label A', 'label C'], {
10516 onComplete: function() {
10517 console.log('done!');
10524 <AreaChart.restore>.
10526 filter: function(filters, callback) {
10527 if(this.busy) return;
10529 if(this.config.Tips.enable) this.delegate.tips.hide();
10530 this.select(false, false, false);
10531 var args = $.splat(filters);
10532 var rt = this.delegate.graph.getNode(this.delegate.root);
10534 this.normalizeDims();
10535 rt.eachAdjacency(function(adj) {
10536 var n = adj.nodeTo,
10537 dimArray = n.getData('dimArray', 'end'),
10538 stringArray = n.getData('stringArray');
10539 n.setData('dimArray', $.map(dimArray, function(d, i) {
10540 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10543 this.delegate.fx.animate({
10544 modes: ['node-property:dimArray'],
10546 onComplete: function() {
10548 callback && callback.onComplete();
10556 Sets all stacks that could have been filtered visible.
10561 areaChart.restore();
10566 <AreaChart.filter>.
10568 restore: function(callback) {
10569 if(this.busy) return;
10571 if(this.config.Tips.enable) this.delegate.tips.hide();
10572 this.select(false, false, false);
10573 this.normalizeDims();
10575 this.delegate.fx.animate({
10576 modes: ['node-property:height:dimArray'],
10578 onComplete: function() {
10580 callback && callback.onComplete();
10584 //adds the little brown bar when hovering the node
10585 select: function(id, name, index) {
10586 if(!this.config.selectOnHover) return;
10587 var s = this.selected;
10588 if(s.id != id || s.name != name
10589 || s.index != index) {
10593 this.delegate.graph.eachNode(function(n) {
10594 n.setData('border', false);
10597 var n = this.delegate.graph.getNode(id);
10598 n.setData('border', s);
10599 var link = index === 0? 'prev':'next';
10600 link = n.getData(link);
10602 n = this.delegate.graph.getByName(link);
10604 n.setData('border', {
10611 this.delegate.plot();
10618 Returns an object containing as keys the legend names and as values hex strings with color values.
10623 var legend = areaChart.getLegend();
10626 getLegend: function() {
10629 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
10632 var colors = n.getData('colorArray'),
10633 len = colors.length;
10634 $.each(n.getData('stringArray'), function(s, i) {
10635 legend[s] = colors[i % len];
10641 Method: getMaxValue
10643 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10648 var ans = areaChart.getMaxValue();
10651 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10656 //will return 100 for all AreaChart instances,
10657 //displaying all of them with the same scale
10658 $jit.AreaChart.implement({
10659 'getMaxValue': function() {
10666 getMaxValue: function() {
10668 this.delegate.graph.eachNode(function(n) {
10669 var valArray = n.getData('valueArray'),
10670 acumLeft = 0, acumRight = 0;
10671 $.each(valArray, function(v) {
10673 acumRight += +v[1];
10675 var acum = acumRight>acumLeft? acumRight:acumLeft;
10676 maxValue = maxValue>acum? maxValue:acum;
10681 normalizeDims: function() {
10682 //number of elements
10683 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
10684 root.eachAdjacency(function() {
10687 var maxValue = this.getMaxValue() || 1,
10688 size = this.delegate.canvas.getSize(),
10689 config = this.config,
10690 margin = config.Margin,
10691 labelOffset = config.labelOffset + config.Label.size,
10692 fixedDim = (size.width - (margin.left + margin.right)) / l,
10693 animate = config.animate,
10694 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10695 - (config.showLabels && labelOffset);
10696 this.delegate.graph.eachNode(function(n) {
10697 var acumLeft = 0, acumRight = 0, animateValue = [];
10698 $.each(n.getData('valueArray'), function(v) {
10700 acumRight += +v[1];
10701 animateValue.push([0, 0]);
10703 var acum = acumRight>acumLeft? acumRight:acumLeft;
10704 n.setData('width', fixedDim);
10706 n.setData('height', acum * height / maxValue, 'end');
10707 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10708 return [n[0] * height / maxValue, n[1] * height / maxValue];
10710 var dimArray = n.getData('dimArray');
10712 n.setData('dimArray', animateValue);
10715 n.setData('height', acum * height / maxValue);
10716 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10717 return [n[0] * height / maxValue, n[1] * height / maxValue];
10726 * File: Options.BarChart.js
10731 Object: Options.BarChart
10733 <BarChart> options.
10734 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
10740 Options.BarChart = {
10745 hoveredColor: '#9fd4ff',
10746 orientation: 'horizontal',
10747 showAggregates: true,
10757 var barChart = new $jit.BarChart({
10760 type: 'stacked:gradient'
10767 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
10768 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
10769 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
10770 barsOffset - (number) Default's *0*. Separation between bars.
10771 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
10772 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
10773 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
10774 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.
10775 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.
10779 Options.BarChart = {
10783 type: 'stacked', //stacked, grouped, : gradient
10784 labelOffset: 3, //label offset
10785 barsOffset: 0, //distance between bars
10786 hoveredColor: '#9fd4ff',
10787 orientation: 'horizontal',
10788 showAggregates: true,
10802 * File: BarChart.js
10806 $jit.ST.Plot.NodeTypes.implement({
10807 'barchart-stacked' : {
10808 'render' : function(node, canvas) {
10809 var pos = node.pos.getc(true),
10810 width = node.getData('width'),
10811 height = node.getData('height'),
10812 algnPos = this.getAlignedPos(pos, width, height),
10813 x = algnPos.x, y = algnPos.y,
10814 dimArray = node.getData('dimArray'),
10815 valueArray = node.getData('valueArray'),
10816 colorArray = node.getData('colorArray'),
10817 colorLength = colorArray.length,
10818 stringArray = node.getData('stringArray');
10820 var ctx = canvas.getCtx(),
10822 border = node.getData('border'),
10823 gradient = node.getData('gradient'),
10824 config = node.getData('config'),
10825 horz = config.orientation == 'horizontal',
10826 aggregates = config.showAggregates,
10827 showLabels = config.showLabels,
10828 label = config.Label;
10830 if (colorArray && dimArray && stringArray) {
10831 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
10832 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10836 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
10837 x + acum + dimArray[i]/2, y + height);
10839 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
10840 x + width, y - acum- dimArray[i]/2);
10842 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10843 function(v) { return (v * 0.5) >> 0; }));
10844 linear.addColorStop(0, color);
10845 linear.addColorStop(0.5, colorArray[i % colorLength]);
10846 linear.addColorStop(1, color);
10847 ctx.fillStyle = linear;
10850 ctx.fillRect(x + acum, y, dimArray[i], height);
10852 ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
10854 if(border && border.name == stringArray[i]) {
10856 opt.dimValue = dimArray[i];
10858 acum += (dimArray[i] || 0);
10859 valAcum += (valueArray[i] || 0);
10864 ctx.strokeStyle = border.color;
10866 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
10868 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
10872 if(label.type == 'Native') {
10874 ctx.fillStyle = ctx.strokeStyle = label.color;
10875 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10876 ctx.textBaseline = 'middle';
10877 var aggValue = aggregates(node.name, valAcum, node);
10878 if(aggValue !== false) {
10879 aggValue = aggValue !== true? aggValue : valAcum;
10881 ctx.textAlign = 'right';
10882 ctx.fillText(aggValue, x + acum - config.labelOffset, y + height/2);
10884 ctx.textAlign = 'center';
10885 ctx.fillText(aggValue, x + width/2, y - height - label.size/2 - config.labelOffset);
10888 if(showLabels(node.name, valAcum, node)) {
10890 ctx.textAlign = 'center';
10891 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
10892 ctx.rotate(Math.PI / 2);
10893 ctx.fillText(node.name, 0, 0);
10895 ctx.textAlign = 'center';
10896 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
10903 'contains': function(node, mpos) {
10904 var pos = node.pos.getc(true),
10905 width = node.getData('width'),
10906 height = node.getData('height'),
10907 algnPos = this.getAlignedPos(pos, width, height),
10908 x = algnPos.x, y = algnPos.y,
10909 dimArray = node.getData('dimArray'),
10910 config = node.getData('config'),
10912 horz = config.orientation == 'horizontal';
10913 //bounding box check
10915 if(mpos.x < x || mpos.x > x + width
10916 || mpos.y > y + height || mpos.y < y) {
10920 if(mpos.x < x || mpos.x > x + width
10921 || mpos.y > y || mpos.y < y - height) {
10926 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
10927 var dimi = dimArray[i];
10930 var intersec = acum;
10931 if(mpos.x <= intersec) {
10933 'name': node.getData('stringArray')[i],
10934 'color': node.getData('colorArray')[i],
10935 'value': node.getData('valueArray')[i],
10941 var intersec = acum;
10942 if(mpos.y >= intersec) {
10944 'name': node.getData('stringArray')[i],
10945 'color': node.getData('colorArray')[i],
10946 'value': node.getData('valueArray')[i],
10955 'barchart-grouped' : {
10956 'render' : function(node, canvas) {
10957 var pos = node.pos.getc(true),
10958 width = node.getData('width'),
10959 height = node.getData('height'),
10960 algnPos = this.getAlignedPos(pos, width, height),
10961 x = algnPos.x, y = algnPos.y,
10962 dimArray = node.getData('dimArray'),
10963 valueArray = node.getData('valueArray'),
10964 valueLength = valueArray.length,
10965 colorArray = node.getData('colorArray'),
10966 colorLength = colorArray.length,
10967 stringArray = node.getData('stringArray');
10969 var ctx = canvas.getCtx(),
10971 border = node.getData('border'),
10972 gradient = node.getData('gradient'),
10973 config = node.getData('config'),
10974 horz = config.orientation == 'horizontal',
10975 aggregates = config.showAggregates,
10976 showLabels = config.showLabels,
10977 label = config.Label,
10978 fixedDim = (horz? height : width) / valueLength;
10980 if (colorArray && dimArray && stringArray) {
10981 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
10982 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10986 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
10987 x + dimArray[i]/2, y + fixedDim * (i + 1));
10989 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
10990 x + fixedDim * (i + 1), y - dimArray[i]/2);
10992 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10993 function(v) { return (v * 0.5) >> 0; }));
10994 linear.addColorStop(0, color);
10995 linear.addColorStop(0.5, colorArray[i % colorLength]);
10996 linear.addColorStop(1, color);
10997 ctx.fillStyle = linear;
11000 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11002 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11004 if(border && border.name == stringArray[i]) {
11005 opt.acum = fixedDim * i;
11006 opt.dimValue = dimArray[i];
11008 acum += (dimArray[i] || 0);
11009 valAcum += (valueArray[i] || 0);
11014 ctx.strokeStyle = border.color;
11016 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
11018 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
11022 if(label.type == 'Native') {
11024 ctx.fillStyle = ctx.strokeStyle = label.color;
11025 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11026 ctx.textBaseline = 'middle';
11027 var aggValue = aggregates(node.name, valAcum, node);
11028 if(aggValue !== false) {
11029 aggValue = aggValue !== true? aggValue : valAcum;
11031 ctx.textAlign = 'right';
11032 ctx.fillText(aggValue, x + Math.max.apply(null, dimArray) - config.labelOffset, y + height/2);
11034 ctx.textAlign = 'center';
11035 ctx.fillText(aggValue, x + width/2, y - Math.max.apply(null, dimArray) - label.size/2 - config.labelOffset);
11038 if(showLabels(node.name, valAcum, node)) {
11040 ctx.textAlign = 'center';
11041 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
11042 ctx.rotate(Math.PI / 2);
11043 ctx.fillText(node.name, 0, 0);
11045 ctx.textAlign = 'center';
11046 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11053 'contains': function(node, mpos) {
11054 var pos = node.pos.getc(true),
11055 width = node.getData('width'),
11056 height = node.getData('height'),
11057 algnPos = this.getAlignedPos(pos, width, height),
11058 x = algnPos.x, y = algnPos.y,
11059 dimArray = node.getData('dimArray'),
11060 len = dimArray.length,
11061 config = node.getData('config'),
11063 horz = config.orientation == 'horizontal',
11064 fixedDim = (horz? height : width) / len;
11065 //bounding box check
11067 if(mpos.x < x || mpos.x > x + width
11068 || mpos.y > y + height || mpos.y < y) {
11072 if(mpos.x < x || mpos.x > x + width
11073 || mpos.y > y || mpos.y < y - height) {
11078 for(var i=0, l=dimArray.length; i<l; i++) {
11079 var dimi = dimArray[i];
11081 var limit = y + fixedDim * i;
11082 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
11084 'name': node.getData('stringArray')[i],
11085 'color': node.getData('colorArray')[i],
11086 'value': node.getData('valueArray')[i],
11091 var limit = x + fixedDim * i;
11092 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
11094 'name': node.getData('stringArray')[i],
11095 'color': node.getData('colorArray')[i],
11096 'value': node.getData('valueArray')[i],
11110 A visualization that displays stacked bar charts.
11112 Constructor Options:
11114 See <Options.BarChart>.
11117 $jit.BarChart = new Class({
11119 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
11123 initialize: function(opt) {
11124 this.controller = this.config =
11125 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
11126 Label: { type: 'Native' }
11128 //set functions for showLabels and showAggregates
11129 var showLabels = this.config.showLabels,
11130 typeLabels = $.type(showLabels),
11131 showAggregates = this.config.showAggregates,
11132 typeAggregates = $.type(showAggregates);
11133 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
11134 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
11136 this.initializeViz();
11139 initializeViz: function() {
11140 var config = this.config, that = this;
11141 var nodeType = config.type.split(":")[0],
11142 horz = config.orientation == 'horizontal',
11145 var delegate = new $jit.ST({
11146 injectInto: config.injectInto,
11147 width: config.width,
11148 height: config.height,
11149 orientation: horz? 'left' : 'bottom',
11151 siblingOffset: config.barsOffset,
11153 withLabels: config.Label.type != 'Native',
11154 useCanvas: config.useCanvas,
11156 type: config.Label.type
11160 type: 'barchart-' + nodeType,
11169 enable: config.Tips.enable,
11172 onShow: function(tip, node, contains) {
11173 var elem = contains;
11174 config.Tips.onShow(tip, elem, node);
11180 onClick: function(node, eventInfo, evt) {
11181 if(!config.Events.enable) return;
11182 var elem = eventInfo.getContains();
11183 config.Events.onClick(elem, eventInfo, evt);
11185 onMouseMove: function(node, eventInfo, evt) {
11186 if(!config.hoveredColor) return;
11188 var elem = eventInfo.getContains();
11189 that.select(node.id, elem.name, elem.index);
11191 that.select(false, false, false);
11195 onCreateLabel: function(domElement, node) {
11196 var labelConf = config.Label,
11197 valueArray = node.getData('valueArray'),
11198 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0);
11200 wrapper: document.createElement('div'),
11201 aggregate: document.createElement('div'),
11202 label: document.createElement('div')
11204 var wrapper = nlbs.wrapper,
11205 label = nlbs.label,
11206 aggregate = nlbs.aggregate,
11207 wrapperStyle = wrapper.style,
11208 labelStyle = label.style,
11209 aggregateStyle = aggregate.style;
11210 //store node labels
11211 nodeLabels[node.id] = nlbs;
11213 wrapper.appendChild(label);
11214 wrapper.appendChild(aggregate);
11215 if(!config.showLabels(node.name, acum, node)) {
11216 labelStyle.display = 'none';
11218 if(!config.showAggregates(node.name, acum, node)) {
11219 aggregateStyle.display = 'none';
11221 wrapperStyle.position = 'relative';
11222 wrapperStyle.overflow = 'visible';
11223 wrapperStyle.fontSize = labelConf.size + 'px';
11224 wrapperStyle.fontFamily = labelConf.family;
11225 wrapperStyle.color = labelConf.color;
11226 wrapperStyle.textAlign = 'center';
11227 aggregateStyle.position = labelStyle.position = 'absolute';
11229 domElement.style.width = node.getData('width') + 'px';
11230 domElement.style.height = node.getData('height') + 'px';
11231 aggregateStyle.left = labelStyle.left = '0px';
11233 label.innerHTML = node.name;
11235 domElement.appendChild(wrapper);
11237 onPlaceLabel: function(domElement, node) {
11238 if(!nodeLabels[node.id]) return;
11239 var labels = nodeLabels[node.id],
11240 wrapperStyle = labels.wrapper.style,
11241 labelStyle = labels.label.style,
11242 aggregateStyle = labels.aggregate.style,
11243 grouped = config.type.split(':')[0] == 'grouped',
11244 horz = config.orientation == 'horizontal',
11245 dimArray = node.getData('dimArray'),
11246 valArray = node.getData('valueArray'),
11247 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
11248 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
11249 font = parseInt(wrapperStyle.fontSize, 10),
11250 domStyle = domElement.style;
11253 if(dimArray && valArray) {
11254 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11255 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
11256 if(dimArray[i] > 0) {
11257 acum+= valArray[i];
11260 if(config.showLabels(node.name, acum, node)) {
11261 labelStyle.display = '';
11263 labelStyle.display = 'none';
11265 var aggValue = config.showAggregates(node.name, acum, node);
11266 if(aggValue !== false) {
11267 aggregateStyle.display = '';
11269 aggregateStyle.display = 'none';
11271 if(config.orientation == 'horizontal') {
11272 aggregateStyle.textAlign = 'right';
11273 labelStyle.textAlign = 'left';
11274 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
11275 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
11276 domElement.style.height = wrapperStyle.height = height + 'px';
11278 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11279 labelStyle.top = (config.labelOffset + height) + 'px';
11280 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
11281 domElement.style.height = wrapperStyle.height = height + 'px';
11283 labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
11288 var size = delegate.canvas.getSize(),
11289 margin = config.Margin;
11291 delegate.config.offsetX = size.width/2 - margin.left
11292 - (config.showLabels && (config.labelOffset + config.Label.size));
11293 delegate.config.offsetY = (margin.bottom - margin.top)/2;
11295 delegate.config.offsetY = -size.height/2 + margin.bottom
11296 + (config.showLabels && (config.labelOffset + config.Label.size));
11297 delegate.config.offsetX = (margin.right - margin.left)/2;
11299 this.delegate = delegate;
11300 this.canvas = this.delegate.canvas;
11306 Loads JSON data into the visualization.
11310 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>.
11314 var barChart = new $jit.BarChart(options);
11315 barChart.loadJSON(json);
11318 loadJSON: function(json) {
11319 if(this.busy) return;
11322 var prefix = $.time(),
11324 delegate = this.delegate,
11325 name = $.splat(json.label),
11326 color = $.splat(json.color || this.colors),
11327 config = this.config,
11328 gradient = !!config.type.split(":")[1],
11329 animate = config.animate,
11330 horz = config.orientation == 'horizontal',
11333 for(var i=0, values=json.values, l=values.length; i<l; i++) {
11334 var val = values[i]
11335 var valArray = $.splat(values[i].values);
11338 'id': prefix + val.label,
11342 '$valueArray': valArray,
11343 '$colorArray': color,
11344 '$stringArray': name,
11345 '$gradient': gradient,
11352 'id': prefix + '$root',
11361 delegate.loadJSON(root);
11363 this.normalizeDims();
11364 delegate.compute();
11365 delegate.select(delegate.root);
11368 delegate.fx.animate({
11369 modes: ['node-property:width:dimArray'],
11371 onComplete: function() {
11376 delegate.fx.animate({
11377 modes: ['node-property:height:dimArray'],
11379 onComplete: function() {
11392 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.
11396 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
11397 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11402 barChart.updateJSON(json, {
11403 onComplete: function() {
11404 alert('update complete!');
11409 updateJSON: function(json, onComplete) {
11410 if(this.busy) return;
11412 this.select(false, false, false);
11413 var delegate = this.delegate;
11414 var graph = delegate.graph;
11415 var values = json.values;
11416 var animate = this.config.animate;
11418 var horz = this.config.orientation == 'horizontal';
11419 $.each(values, function(v) {
11420 var n = graph.getByName(v.label);
11422 n.setData('valueArray', $.splat(v.values));
11424 n.setData('stringArray', $.splat(json.label));
11428 this.normalizeDims();
11429 delegate.compute();
11430 delegate.select(delegate.root);
11433 delegate.fx.animate({
11434 modes: ['node-property:width:dimArray'],
11436 onComplete: function() {
11438 onComplete && onComplete.onComplete();
11442 delegate.fx.animate({
11443 modes: ['node-property:height:dimArray'],
11445 onComplete: function() {
11447 onComplete && onComplete.onComplete();
11454 //adds the little brown bar when hovering the node
11455 select: function(id, name) {
11456 if(!this.config.hoveredColor) return;
11457 var s = this.selected;
11458 if(s.id != id || s.name != name) {
11461 s.color = this.config.hoveredColor;
11462 this.delegate.graph.eachNode(function(n) {
11464 n.setData('border', s);
11466 n.setData('border', false);
11469 this.delegate.plot();
11476 Returns an object containing as keys the legend names and as values hex strings with color values.
11481 var legend = barChart.getLegend();
11484 getLegend: function() {
11487 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
11490 var colors = n.getData('colorArray'),
11491 len = colors.length;
11492 $.each(n.getData('stringArray'), function(s, i) {
11493 legend[s] = colors[i % len];
11499 Method: getMaxValue
11501 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11506 var ans = barChart.getMaxValue();
11509 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
11514 //will return 100 for all BarChart instances,
11515 //displaying all of them with the same scale
11516 $jit.BarChart.implement({
11517 'getMaxValue': function() {
11524 getMaxValue: function() {
11525 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
11526 this.delegate.graph.eachNode(function(n) {
11527 var valArray = n.getData('valueArray'),
11529 if(!valArray) return;
11531 $.each(valArray, function(v) {
11535 acum = Math.max.apply(null, valArray);
11537 maxValue = maxValue>acum? maxValue:acum;
11542 setBarType: function(type) {
11543 this.config.type = type;
11544 this.delegate.config.Node.type = 'barchart-' + type.split(':')[0];
11547 normalizeDims: function() {
11548 //number of elements
11549 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
11550 root.eachAdjacency(function() {
11553 var maxValue = this.getMaxValue() || 1,
11554 size = this.delegate.canvas.getSize(),
11555 config = this.config,
11556 margin = config.Margin,
11557 marginWidth = margin.left + margin.right,
11558 marginHeight = margin.top + margin.bottom,
11559 horz = config.orientation == 'horizontal',
11560 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (l -1) * config.barsOffset) / l,
11561 animate = config.animate,
11562 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
11563 - (!horz && config.showAggregates && (config.Label.size + config.labelOffset))
11564 - (config.showLabels && (config.Label.size + config.labelOffset)),
11565 dim1 = horz? 'height':'width',
11566 dim2 = horz? 'width':'height';
11567 this.delegate.graph.eachNode(function(n) {
11568 var acum = 0, animateValue = [];
11569 $.each(n.getData('valueArray'), function(v) {
11571 animateValue.push(0);
11573 n.setData(dim1, fixedDim);
11575 n.setData(dim2, acum * height / maxValue, 'end');
11576 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11577 return n * height / maxValue;
11579 var dimArray = n.getData('dimArray');
11581 n.setData('dimArray', animateValue);
11584 n.setData(dim2, acum * height / maxValue);
11585 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11586 return n * height / maxValue;
11595 * File: Options.PieChart.js
11599 Object: Options.PieChart
11601 <PieChart> options.
11602 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
11608 Options.PieChart = {
11614 hoveredColor: '#9fd4ff',
11616 resizeLabels: false,
11617 updateHeights: false
11626 var pie = new $jit.PieChart({
11629 type: 'stacked:gradient'
11636 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
11637 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11638 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
11639 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11640 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
11641 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
11642 showLabels - (boolean) Default's *true*. Display the name of the slots.
11643 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.
11644 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.
11647 Options.PieChart = {
11651 offset: 25, // page offset
11653 labelOffset: 3, // label offset
11654 type: 'stacked', // gradient
11655 hoveredColor: '#9fd4ff',
11666 resizeLabels: false,
11668 //only valid for mono-valued datasets
11669 updateHeights: false
11673 * Class: Layouts.Radial
11675 * Implements a Radial Layout.
11679 * <RGraph>, <Hypertree>
11682 Layouts.Radial = new Class({
11687 * Computes nodes' positions.
11691 * property - _optional_ A <Graph.Node> position property to store the new
11692 * positions. Possible values are 'pos', 'end' or 'start'.
11695 compute : function(property) {
11696 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
11697 NodeDim.compute(this.graph, prop, this.config);
11698 this.graph.computeLevels(this.root, 0, "ignore");
11699 var lengthFunc = this.createLevelDistanceFunc();
11700 this.computeAngularWidths(prop);
11701 this.computePositions(prop, lengthFunc);
11707 * Performs the main algorithm for computing node positions.
11709 computePositions : function(property, getLength) {
11710 var propArray = property;
11711 var graph = this.graph;
11712 var root = graph.getNode(this.root);
11713 var parent = this.parent;
11714 var config = this.config;
11716 for ( var i=0, l=propArray.length; i < l; i++) {
11717 var pi = propArray[i];
11718 root.setPos($P(0, 0), pi);
11719 root.setData('span', Math.PI * 2, pi);
11727 graph.eachBFS(this.root, function(elem) {
11728 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
11729 var angleInit = elem.angleSpan.begin;
11730 var len = getLength(elem);
11731 //Calculate the sum of all angular widths
11732 var totalAngularWidths = 0, subnodes = [], maxDim = {};
11733 elem.eachSubnode(function(sib) {
11734 totalAngularWidths += sib._treeAngularWidth;
11736 for ( var i=0, l=propArray.length; i < l; i++) {
11737 var pi = propArray[i], dim = sib.getData('dim', pi);
11738 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
11740 subnodes.push(sib);
11742 //Maintain children order
11743 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
11744 if (parent && parent.id == elem.id && subnodes.length > 0
11745 && subnodes[0].dist) {
11746 subnodes.sort(function(a, b) {
11747 return (a.dist >= b.dist) - (a.dist <= b.dist);
11750 //Calculate nodes positions.
11751 for (var k = 0, ls=subnodes.length; k < ls; k++) {
11752 var child = subnodes[k];
11753 if (!child._flag) {
11754 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
11755 var theta = angleInit + angleProportion / 2;
11757 for ( var i=0, l=propArray.length; i < l; i++) {
11758 var pi = propArray[i];
11759 child.setPos($P(theta, len), pi);
11760 child.setData('span', angleProportion, pi);
11761 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
11764 child.angleSpan = {
11766 end : angleInit + angleProportion
11768 angleInit += angleProportion;
11775 * Method: setAngularWidthForNodes
11777 * Sets nodes angular widths.
11779 setAngularWidthForNodes : function(prop) {
11780 this.graph.eachBFS(this.root, function(elem, i) {
11781 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
11782 elem._angularWidth = diamValue / i;
11787 * Method: setSubtreesAngularWidth
11789 * Sets subtrees angular widths.
11791 setSubtreesAngularWidth : function() {
11793 this.graph.eachNode(function(elem) {
11794 that.setSubtreeAngularWidth(elem);
11799 * Method: setSubtreeAngularWidth
11801 * Sets the angular width for a subtree.
11803 setSubtreeAngularWidth : function(elem) {
11804 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
11805 elem.eachSubnode(function(child) {
11806 that.setSubtreeAngularWidth(child);
11807 sumAW += child._treeAngularWidth;
11809 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
11813 * Method: computeAngularWidths
11815 * Computes nodes and subtrees angular widths.
11817 computeAngularWidths : function(prop) {
11818 this.setAngularWidthForNodes(prop);
11819 this.setSubtreesAngularWidth();
11826 * File: Sunburst.js
11832 A radial space filling tree visualization.
11836 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
11840 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.
11844 All <Loader> methods
11846 Constructor Options:
11848 Inherits options from
11851 - <Options.Controller>
11857 - <Options.NodeStyles>
11858 - <Options.Navigation>
11860 Additionally, there are other parameters and some default values changed
11862 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
11863 levelDistance - (number) Default's *100*. The distance between levels of the tree.
11864 Node.type - Described in <Options.Node>. Default's to *multipie*.
11865 Node.height - Described in <Options.Node>. Default's *0*.
11866 Edge.type - Described in <Options.Edge>. Default's *none*.
11867 Label.textAlign - Described in <Options.Label>. Default's *start*.
11868 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
11870 Instance Properties:
11872 canvas - Access a <Canvas> instance.
11873 graph - Access a <Graph> instance.
11874 op - Access a <Sunburst.Op> instance.
11875 fx - Access a <Sunburst.Plot> instance.
11876 labels - Access a <Sunburst.Label> interface implementation.
11880 $jit.Sunburst = new Class({
11882 Implements: [ Loader, Extras, Layouts.Radial ],
11884 initialize: function(controller) {
11885 var $Sunburst = $jit.Sunburst;
11888 interpolation: 'linear',
11889 levelDistance: 100,
11891 'type': 'multipie',
11898 textAlign: 'start',
11899 textBaseline: 'middle'
11903 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
11904 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
11906 var canvasConfig = this.config;
11907 if(canvasConfig.useCanvas) {
11908 this.canvas = canvasConfig.useCanvas;
11909 this.config.labelContainer = this.canvas.id + '-label';
11911 if(canvasConfig.background) {
11912 canvasConfig.background = $.merge({
11914 }, canvasConfig.background);
11916 this.canvas = new Canvas(this, canvasConfig);
11917 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
11920 this.graphOptions = {
11928 this.graph = new Graph(this.graphOptions, this.config.Node,
11930 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
11931 this.fx = new $Sunburst.Plot(this, $Sunburst);
11932 this.op = new $Sunburst.Op(this);
11935 this.rotated = null;
11937 // initialize extras
11938 this.initializeExtras();
11943 createLevelDistanceFunc
11945 Returns the levelDistance function used for calculating a node distance
11946 to its origin. This function returns a function that is computed
11947 per level and not per node, such that all nodes with the same depth will have the
11948 same distance to the origin. The resulting function gets the
11949 parent node as parameter and returns a float.
11952 createLevelDistanceFunc: function() {
11953 var ld = this.config.levelDistance;
11954 return function(elem) {
11955 return (elem._depth + 1) * ld;
11962 Computes positions and plots the tree.
11965 refresh: function() {
11973 An alias for computing new positions to _endPos_
11980 reposition: function() {
11981 this.compute('end');
11987 Rotates the graph so that the selected node is horizontal on the right.
11991 node - (object) A <Graph.Node>.
11992 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
11993 opt - (object) Configuration options merged with this visualization configuration options.
11997 <Sunburst.rotateAngle>
12000 rotate: function(node, method, opt) {
12001 var theta = node.getPos(opt.property || 'current').getp(true).theta;
12002 this.rotated = node;
12003 this.rotateAngle(-theta, method, opt);
12007 Method: rotateAngle
12009 Rotates the graph of an angle theta.
12013 node - (object) A <Graph.Node>.
12014 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
12015 opt - (object) Configuration options merged with this visualization configuration options.
12022 rotateAngle: function(theta, method, opt) {
12024 var options = $.merge(this.config, opt || {}, {
12027 var prop = opt.property || (method === "animate" ? 'end' : 'current');
12028 if(method === 'animate') {
12029 this.fx.animation.pause();
12031 this.graph.eachNode(function(n) {
12032 var p = n.getPos(prop);
12035 p.theta += Math.PI * 2;
12038 if (method == 'animate') {
12039 this.fx.animate(options);
12040 } else if (method == 'replot') {
12049 Plots the Sunburst. This is a shortcut to *fx.plot*.
12056 $jit.Sunburst.$extend = true;
12058 (function(Sunburst) {
12063 Custom extension of <Graph.Op>.
12067 All <Graph.Op> methods
12074 Sunburst.Op = new Class( {
12076 Implements: Graph.Op
12081 Class: Sunburst.Plot
12083 Custom extension of <Graph.Plot>.
12087 All <Graph.Plot> methods
12094 Sunburst.Plot = new Class( {
12096 Implements: Graph.Plot
12101 Class: Sunburst.Label
12103 Custom extension of <Graph.Label>.
12104 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
12108 All <Graph.Label> methods and subclasses.
12112 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
12115 Sunburst.Label = {};
12118 Sunburst.Label.Native
12120 Custom extension of <Graph.Label.Native>.
12124 All <Graph.Label.Native> methods
12128 <Graph.Label.Native>
12130 Sunburst.Label.Native = new Class( {
12131 Implements: Graph.Label.Native,
12133 initialize: function(viz) {
12135 this.label = viz.config.Label;
12136 this.config = viz.config;
12139 renderLabel: function(canvas, node, controller) {
12140 var span = node.getData('span');
12141 if(span < Math.PI /2 && Math.tan(span) *
12142 this.config.levelDistance * node._depth < 10) {
12145 var ctx = canvas.getCtx();
12146 var measure = ctx.measureText(node.name);
12147 if (node.id == this.viz.root) {
12148 var x = -measure.width / 2, y = 0, thetap = 0;
12152 var ld = controller.levelDistance - indent;
12153 var clone = node.pos.clone();
12154 clone.rho += indent;
12155 var p = clone.getp(true);
12156 var ct = clone.getc(true);
12157 var x = ct.x, y = ct.y;
12158 // get angle in degrees
12160 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
12161 var thetap = cond ? p.theta + pi : p.theta;
12163 x -= Math.abs(Math.cos(p.theta) * measure.width);
12164 y += Math.sin(p.theta) * measure.width;
12165 } else if (node.id == this.viz.root) {
12166 x -= measure.width / 2;
12170 ctx.translate(x, y);
12171 ctx.rotate(thetap);
12172 ctx.fillText(node.name, 0, 0);
12180 Custom extension of <Graph.Label.SVG>.
12184 All <Graph.Label.SVG> methods
12191 Sunburst.Label.SVG = new Class( {
12192 Implements: Graph.Label.SVG,
12194 initialize: function(viz) {
12201 Overrides abstract method placeLabel in <Graph.Plot>.
12205 tag - A DOM label element.
12206 node - A <Graph.Node>.
12207 controller - A configuration/controller object passed to the visualization.
12210 placeLabel: function(tag, node, controller) {
12211 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
12212 var radius = canvas.getSize();
12214 x: Math.round(pos.x + radius.width / 2),
12215 y: Math.round(pos.y + radius.height / 2)
12217 tag.setAttribute('x', labelPos.x);
12218 tag.setAttribute('y', labelPos.y);
12220 var bb = tag.getBBox();
12222 // center the label
12223 var x = tag.getAttribute('x');
12224 var y = tag.getAttribute('y');
12225 // get polar coordinates
12226 var p = node.pos.getp(true);
12227 // get angle in degrees
12229 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
12231 tag.setAttribute('x', x - bb.width);
12232 tag.setAttribute('y', y - bb.height);
12233 } else if (node.id == viz.root) {
12234 tag.setAttribute('x', x - bb.width / 2);
12237 var thetap = cond ? p.theta + pi : p.theta;
12239 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
12243 controller.onPlaceLabel(tag, node);
12248 Sunburst.Label.HTML
12250 Custom extension of <Graph.Label.HTML>.
12254 All <Graph.Label.HTML> methods.
12261 Sunburst.Label.HTML = new Class( {
12262 Implements: Graph.Label.HTML,
12264 initialize: function(viz) {
12270 Overrides abstract method placeLabel in <Graph.Plot>.
12274 tag - A DOM label element.
12275 node - A <Graph.Node>.
12276 controller - A configuration/controller object passed to the visualization.
12279 placeLabel: function(tag, node, controller) {
12280 var pos = node.pos.clone(),
12281 canvas = this.viz.canvas,
12282 height = node.getData('height'),
12283 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
12284 radius = canvas.getSize();
12286 pos = pos.getc(true);
12289 x: Math.round(pos.x + radius.width / 2),
12290 y: Math.round(pos.y + radius.height / 2)
12293 var style = tag.style;
12294 style.left = labelPos.x + 'px';
12295 style.top = labelPos.y + 'px';
12296 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
12298 controller.onPlaceLabel(tag, node);
12303 Class: Sunburst.Plot.NodeTypes
12305 This class contains a list of <Graph.Node> built-in types.
12306 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
12308 You can add your custom node types, customizing your visualization to the extreme.
12313 Sunburst.Plot.NodeTypes.implement({
12315 'render': function(node, canvas) {
12316 //print your custom node to canvas
12319 'contains': function(node, pos) {
12320 //return true if pos is inside the node or false otherwise
12327 Sunburst.Plot.NodeTypes = new Class( {
12330 'contains': $.lambda(false),
12331 'anglecontains': function(node, pos) {
12332 var span = node.getData('span') / 2, theta = node.pos.theta;
12333 var begin = theta - span, end = theta + span;
12335 begin += Math.PI * 2;
12336 var atan = Math.atan2(pos.y, pos.x);
12338 atan += Math.PI * 2;
12340 return (atan > begin && atan <= Math.PI * 2) || atan < end;
12342 return atan > begin && atan < end;
12348 'render': function(node, canvas) {
12349 var span = node.getData('span') / 2, theta = node.pos.theta;
12350 var begin = theta - span, end = theta + span;
12351 var polarNode = node.pos.getp(true);
12352 var polar = new Polar(polarNode.rho, begin);
12353 var p1coord = polar.getc(true);
12355 var p2coord = polar.getc(true);
12357 var ctx = canvas.getCtx();
12360 ctx.lineTo(p1coord.x, p1coord.y);
12362 ctx.lineTo(p2coord.x, p2coord.y);
12364 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
12368 'contains': function(node, pos) {
12369 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12370 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12371 var ld = this.config.levelDistance, d = node._depth;
12372 return (rho <= ld * d);
12378 'render': function(node, canvas) {
12379 var height = node.getData('height');
12380 var ldist = height? height : this.config.levelDistance;
12381 var span = node.getData('span') / 2, theta = node.pos.theta;
12382 var begin = theta - span, end = theta + span;
12383 var polarNode = node.pos.getp(true);
12385 var polar = new Polar(polarNode.rho, begin);
12386 var p1coord = polar.getc(true);
12389 var p2coord = polar.getc(true);
12391 polar.rho += ldist;
12392 var p3coord = polar.getc(true);
12394 polar.theta = begin;
12395 var p4coord = polar.getc(true);
12397 var ctx = canvas.getCtx();
12400 ctx.arc(0, 0, polarNode.rho, begin, end, false);
12401 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
12402 ctx.moveTo(p1coord.x, p1coord.y);
12403 ctx.lineTo(p4coord.x, p4coord.y);
12404 ctx.moveTo(p2coord.x, p2coord.y);
12405 ctx.lineTo(p3coord.x, p3coord.y);
12408 if (node.collapsed) {
12413 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
12419 'contains': function(node, pos) {
12420 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12421 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12422 var height = node.getData('height');
12423 var ldist = height? height : this.config.levelDistance;
12424 var ld = this.config.levelDistance, d = node._depth;
12425 return (rho >= ld * d) && (rho <= (ld * d + ldist));
12431 'gradient-multipie': {
12432 'render': function(node, canvas) {
12433 var ctx = canvas.getCtx();
12434 var height = node.getData('height');
12435 var ldist = height? height : this.config.levelDistance;
12436 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
12437 0, 0, node.getPos().rho + ldist);
12439 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
12440 $.each(colorArray, function(i) {
12441 ans.push(parseInt(i * 0.5, 10));
12443 var endColor = $.rgbToHex(ans);
12444 radialGradient.addColorStop(0, endColor);
12445 radialGradient.addColorStop(1, node.getData('color'));
12446 ctx.fillStyle = radialGradient;
12447 this.nodeTypes['multipie'].render.call(this, node, canvas);
12449 'contains': function(node, pos) {
12450 return this.nodeTypes['multipie'].contains.call(this, node, pos);
12455 'render': function(node, canvas) {
12456 var ctx = canvas.getCtx();
12457 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
12460 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
12461 $.each(colorArray, function(i) {
12462 ans.push(parseInt(i * 0.5, 10));
12464 var endColor = $.rgbToHex(ans);
12465 radialGradient.addColorStop(1, endColor);
12466 radialGradient.addColorStop(0, node.getData('color'));
12467 ctx.fillStyle = radialGradient;
12468 this.nodeTypes['pie'].render.call(this, node, canvas);
12470 'contains': function(node, pos) {
12471 return this.nodeTypes['pie'].contains.call(this, node, pos);
12477 Class: Sunburst.Plot.EdgeTypes
12479 This class contains a list of <Graph.Adjacence> built-in types.
12480 Edge types implemented are 'none', 'line' and 'arrow'.
12482 You can add your custom edge types, customizing your visualization to the extreme.
12487 Sunburst.Plot.EdgeTypes.implement({
12489 'render': function(adj, canvas) {
12490 //print your custom edge to canvas
12493 'contains': function(adj, pos) {
12494 //return true if pos is inside the arc or false otherwise
12501 Sunburst.Plot.EdgeTypes = new Class({
12504 'render': function(adj, canvas) {
12505 var from = adj.nodeFrom.pos.getc(true),
12506 to = adj.nodeTo.pos.getc(true);
12507 this.edgeHelper.line.render(from, to, canvas);
12509 'contains': function(adj, pos) {
12510 var from = adj.nodeFrom.pos.getc(true),
12511 to = adj.nodeTo.pos.getc(true);
12512 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
12516 'render': function(adj, canvas) {
12517 var from = adj.nodeFrom.pos.getc(true),
12518 to = adj.nodeTo.pos.getc(true),
12519 dim = adj.getData('dim'),
12520 direction = adj.data.$direction,
12521 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
12522 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
12524 'contains': function(adj, pos) {
12525 var from = adj.nodeFrom.pos.getc(true),
12526 to = adj.nodeTo.pos.getc(true);
12527 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
12531 'render': function(adj, canvas) {
12532 var from = adj.nodeFrom.pos.getc(),
12533 to = adj.nodeTo.pos.getc(),
12534 dim = Math.max(from.norm(), to.norm());
12535 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
12537 'contains': $.lambda(false) //TODO(nico): Implement this!
12545 * File: PieChart.js
12549 $jit.Sunburst.Plot.NodeTypes.implement({
12550 'piechart-stacked' : {
12551 'render' : function(node, canvas) {
12552 var pos = node.pos.getp(true),
12553 dimArray = node.getData('dimArray'),
12554 valueArray = node.getData('valueArray'),
12555 colorArray = node.getData('colorArray'),
12556 colorLength = colorArray.length,
12557 stringArray = node.getData('stringArray'),
12558 span = node.getData('span') / 2,
12559 theta = node.pos.theta,
12560 begin = theta - span,
12561 end = theta + span,
12564 var ctx = canvas.getCtx(),
12566 gradient = node.getData('gradient'),
12567 border = node.getData('border'),
12568 config = node.getData('config'),
12569 showLabels = config.showLabels,
12570 resizeLabels = config.resizeLabels,
12571 label = config.Label;
12573 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
12574 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
12576 if (colorArray && dimArray && stringArray) {
12577 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
12578 var dimi = dimArray[i], colori = colorArray[i % colorLength];
12579 if(dimi <= 0) continue;
12580 ctx.fillStyle = ctx.strokeStyle = colori;
12581 if(gradient && dimi) {
12582 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
12583 xpos, ypos, acum + dimi + config.sliceOffset);
12584 var colorRgb = $.hexToRgb(colori),
12585 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
12586 endColor = $.rgbToHex(ans);
12588 radialGradient.addColorStop(0, colori);
12589 radialGradient.addColorStop(0.5, colori);
12590 radialGradient.addColorStop(1, endColor);
12591 ctx.fillStyle = radialGradient;
12594 polar.rho = acum + config.sliceOffset;
12595 polar.theta = begin;
12596 var p1coord = polar.getc(true);
12598 var p2coord = polar.getc(true);
12600 var p3coord = polar.getc(true);
12601 polar.theta = begin;
12602 var p4coord = polar.getc(true);
12605 //fixing FF arc method + fill
12606 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
12607 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
12609 if(border && border.name == stringArray[i]) {
12611 opt.dimValue = dimArray[i];
12615 acum += (dimi || 0);
12616 valAcum += (valueArray[i] || 0);
12620 ctx.globalCompositeOperation = "source-over";
12622 ctx.strokeStyle = border.color;
12623 var s = begin < end? 1 : -1;
12625 //fixing FF arc method + fill
12626 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
12627 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
12632 if(showLabels && label.type == 'Native') {
12634 ctx.fillStyle = ctx.strokeStyle = label.color;
12635 var scale = resizeLabels? node.getData('normalizedDim') : 1,
12636 fontSize = (label.size * scale) >> 0;
12637 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
12639 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
12640 ctx.textBaseline = 'middle';
12641 ctx.textAlign = 'center';
12643 polar.rho = acum + config.labelOffset + config.sliceOffset;
12644 polar.theta = node.pos.theta;
12645 var cart = polar.getc(true);
12647 ctx.fillText(node.name, cart.x, cart.y);
12652 'contains': function(node, pos) {
12653 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12654 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12655 var ld = this.config.levelDistance, d = node._depth;
12656 var config = node.getData('config');
12657 if(rho <=ld * d + config.sliceOffset) {
12658 var dimArray = node.getData('dimArray');
12659 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
12660 var dimi = dimArray[i];
12661 if(rho >= acum && rho <= acum + dimi) {
12663 name: node.getData('stringArray')[i],
12664 color: node.getData('colorArray')[i],
12665 value: node.getData('valueArray')[i],
12683 A visualization that displays stacked bar charts.
12685 Constructor Options:
12687 See <Options.PieChart>.
12690 $jit.PieChart = new Class({
12692 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
12696 initialize: function(opt) {
12697 this.controller = this.config =
12698 $.merge(Options("Canvas", "PieChart", "Label"), {
12699 Label: { type: 'Native' }
12701 this.initializeViz();
12704 initializeViz: function() {
12705 var config = this.config, that = this;
12706 var nodeType = config.type.split(":")[0];
12707 var delegate = new $jit.Sunburst({
12708 injectInto: config.injectInto,
12709 width: config.width,
12710 height: config.height,
12711 useCanvas: config.useCanvas,
12712 withLabels: config.Label.type != 'Native',
12714 type: config.Label.type
12718 type: 'piechart-' + nodeType,
12726 enable: config.Tips.enable,
12729 onShow: function(tip, node, contains) {
12730 var elem = contains;
12731 config.Tips.onShow(tip, elem, node);
12737 onClick: function(node, eventInfo, evt) {
12738 if(!config.Events.enable) return;
12739 var elem = eventInfo.getContains();
12740 config.Events.onClick(elem, eventInfo, evt);
12742 onMouseMove: function(node, eventInfo, evt) {
12743 if(!config.hoveredColor) return;
12745 var elem = eventInfo.getContains();
12746 that.select(node.id, elem.name, elem.index);
12748 that.select(false, false, false);
12752 onCreateLabel: function(domElement, node) {
12753 var labelConf = config.Label;
12754 if(config.showLabels) {
12755 var style = domElement.style;
12756 style.fontSize = labelConf.size + 'px';
12757 style.fontFamily = labelConf.family;
12758 style.color = labelConf.color;
12759 style.textAlign = 'center';
12760 domElement.innerHTML = node.name;
12763 onPlaceLabel: function(domElement, node) {
12764 if(!config.showLabels) return;
12765 var pos = node.pos.getp(true),
12766 dimArray = node.getData('dimArray'),
12767 span = node.getData('span') / 2,
12768 theta = node.pos.theta,
12769 begin = theta - span,
12770 end = theta + span,
12773 var showLabels = config.showLabels,
12774 resizeLabels = config.resizeLabels,
12775 label = config.Label;
12778 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
12779 acum += dimArray[i];
12781 var scale = resizeLabels? node.getData('normalizedDim') : 1,
12782 fontSize = (label.size * scale) >> 0;
12783 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
12784 domElement.style.fontSize = fontSize + 'px';
12785 polar.rho = acum + config.labelOffset + config.sliceOffset;
12786 polar.theta = (begin + end) / 2;
12787 var pos = polar.getc(true);
12788 var radius = that.canvas.getSize();
12790 x: Math.round(pos.x + radius.width / 2),
12791 y: Math.round(pos.y + radius.height / 2)
12793 domElement.style.left = labelPos.x + 'px';
12794 domElement.style.top = labelPos.y + 'px';
12799 var size = delegate.canvas.getSize(),
12801 delegate.config.levelDistance = min(size.width, size.height)/2
12802 - config.offset - config.sliceOffset;
12803 this.delegate = delegate;
12804 this.canvas = this.delegate.canvas;
12805 this.canvas.getCtx().globalCompositeOperation = 'lighter';
12811 Loads JSON data into the visualization.
12815 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>.
12819 var pieChart = new $jit.PieChart(options);
12820 pieChart.loadJSON(json);
12823 loadJSON: function(json) {
12824 var prefix = $.time(),
12826 delegate = this.delegate,
12827 name = $.splat(json.label),
12828 nameLength = name.length,
12829 color = $.splat(json.color || this.colors),
12830 colorLength = color.length,
12831 config = this.config,
12832 gradient = !!config.type.split(":")[1],
12833 animate = config.animate,
12834 mono = nameLength == 1;
12836 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12837 var val = values[i];
12838 var valArray = $.splat(val.values);
12840 'id': prefix + val.label,
12844 '$valueArray': valArray,
12845 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
12846 '$stringArray': name,
12847 '$gradient': gradient,
12849 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
12855 'id': prefix + '$root',
12864 delegate.loadJSON(root);
12866 this.normalizeDims();
12867 delegate.refresh();
12869 delegate.fx.animate({
12870 modes: ['node-property:dimArray'],
12879 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.
12883 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
12884 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
12889 pieChart.updateJSON(json, {
12890 onComplete: function() {
12891 alert('update complete!');
12896 updateJSON: function(json, onComplete) {
12897 if(this.busy) return;
12900 var delegate = this.delegate;
12901 var graph = delegate.graph;
12902 var values = json.values;
12903 var animate = this.config.animate;
12905 $.each(values, function(v) {
12906 var n = graph.getByName(v.label),
12907 vals = $.splat(v.values);
12909 n.setData('valueArray', vals);
12910 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
12912 n.setData('stringArray', $.splat(json.label));
12916 this.normalizeDims();
12918 delegate.compute('end');
12919 delegate.fx.animate({
12920 modes: ['node-property:dimArray:span', 'linear'],
12922 onComplete: function() {
12924 onComplete && onComplete.onComplete();
12928 delegate.refresh();
12932 //adds the little brown bar when hovering the node
12933 select: function(id, name) {
12934 if(!this.config.hoveredColor) return;
12935 var s = this.selected;
12936 if(s.id != id || s.name != name) {
12939 s.color = this.config.hoveredColor;
12940 this.delegate.graph.eachNode(function(n) {
12942 n.setData('border', s);
12944 n.setData('border', false);
12947 this.delegate.plot();
12954 Returns an object containing as keys the legend names and as values hex strings with color values.
12959 var legend = pieChart.getLegend();
12962 getLegend: function() {
12965 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
12968 var colors = n.getData('colorArray'),
12969 len = colors.length;
12970 $.each(n.getData('stringArray'), function(s, i) {
12971 legend[s] = colors[i % len];
12977 Method: getMaxValue
12979 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
12984 var ans = pieChart.getMaxValue();
12987 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
12992 //will return 100 for all PieChart instances,
12993 //displaying all of them with the same scale
12994 $jit.PieChart.implement({
12995 'getMaxValue': function() {
13002 getMaxValue: function() {
13004 this.delegate.graph.eachNode(function(n) {
13005 var valArray = n.getData('valueArray'),
13007 $.each(valArray, function(v) {
13010 maxValue = maxValue>acum? maxValue:acum;
13015 normalizeDims: function() {
13016 //number of elements
13017 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
13018 root.eachAdjacency(function() {
13021 var maxValue = this.getMaxValue() || 1,
13022 config = this.config,
13023 animate = config.animate,
13024 rho = this.delegate.config.levelDistance;
13025 this.delegate.graph.eachNode(function(n) {
13026 var acum = 0, animateValue = [];
13027 $.each(n.getData('valueArray'), function(v) {
13029 animateValue.push(1);
13031 var stat = (animateValue.length == 1) && !config.updateHeights;
13033 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13034 return stat? rho: (n * rho / maxValue);
13036 var dimArray = n.getData('dimArray');
13038 n.setData('dimArray', animateValue);
13041 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13042 return stat? rho : (n * rho / maxValue);
13045 n.setData('normalizedDim', acum / maxValue);
13052 * Class: Layouts.TM
13054 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
13063 Layouts.TM.SliceAndDice = new Class({
13064 compute: function(prop) {
13065 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
13066 this.controller.onBeforeCompute(root);
13067 var size = this.canvas.getSize(),
13068 config = this.config,
13069 width = size.width,
13070 height = size.height;
13071 this.graph.computeLevels(this.root, 0, "ignore");
13072 //set root position and dimensions
13073 root.getPos(prop).setc(-width/2, -height/2);
13074 root.setData('width', width, prop);
13075 root.setData('height', height + config.titleHeight, prop);
13076 this.computePositions(root, root, this.layout.orientation, prop);
13077 this.controller.onAfterCompute(root);
13080 computePositions: function(par, ch, orn, prop) {
13081 //compute children areas
13083 par.eachSubnode(function(n) {
13084 totalArea += n.getData('area', prop);
13087 var config = this.config,
13088 offst = config.offset,
13089 width = par.getData('width', prop),
13090 height = Math.max(par.getData('height', prop) - config.titleHeight, 0),
13091 fact = par == ch? 1 : (ch.getData('area', prop) / totalArea);
13093 var otherSize, size, dim, pos, pos2, posth, pos2th;
13094 var horizontal = (orn == "h");
13097 otherSize = height;
13098 size = width * fact;
13102 posth = config.titleHeight;
13106 otherSize = height * fact;
13112 pos2th = config.titleHeight;
13114 var cpos = ch.getPos(prop);
13115 ch.setData('width', size, prop);
13116 ch.setData('height', otherSize, prop);
13117 var offsetSize = 0, tm = this;
13118 ch.eachSubnode(function(n) {
13119 var p = n.getPos(prop);
13120 p[pos] = offsetSize + cpos[pos] + posth;
13121 p[pos2] = cpos[pos2] + pos2th;
13122 tm.computePositions(ch, n, orn, prop);
13123 offsetSize += n.getData(dim, prop);
13129 Layouts.TM.Area = {
13133 Called by loadJSON to calculate recursively all node positions and lay out the tree.
13137 json - A JSON tree. See also <Loader.loadJSON>.
13138 coord - A coordinates object specifying width, height, left and top style properties.
13140 compute: function(prop) {
13141 prop = prop || "current";
13142 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
13143 this.controller.onBeforeCompute(root);
13144 var config = this.config,
13145 size = this.canvas.getSize(),
13146 width = size.width,
13147 height = size.height,
13148 offst = config.offset,
13149 offwdth = width - offst,
13150 offhght = height - offst;
13151 this.graph.computeLevels(this.root, 0, "ignore");
13152 //set root position and dimensions
13153 root.getPos(prop).setc(-width/2, -height/2);
13154 root.setData('width', width, prop);
13155 root.setData('height', height, prop);
13156 //create a coordinates object
13158 'top': -height/2 + config.titleHeight,
13161 'height': offhght - config.titleHeight
13163 this.computePositions(root, coord, prop);
13164 this.controller.onAfterCompute(root);
13170 Computes dimensions and positions of a group of nodes
13171 according to a custom layout row condition.
13175 tail - An array of nodes.
13176 initElem - An array of nodes (containing the initial node to be laid).
13177 w - A fixed dimension where nodes will be layed out.
13178 coord - A coordinates object specifying width, height, left and top style properties.
13179 comp - A custom comparison function
13181 computeDim: function(tail, initElem, w, coord, comp, prop) {
13182 if(tail.length + initElem.length == 1) {
13183 var l = (tail.length == 1)? tail : initElem;
13184 this.layoutLast(l, w, coord, prop);
13187 if(tail.length >= 2 && initElem.length == 0) {
13188 initElem = [tail.shift()];
13190 if(tail.length == 0) {
13191 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
13195 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
13196 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
13198 var newCoords = this.layoutRow(initElem, w, coord, prop);
13199 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
13205 Method: worstAspectRatio
13207 Calculates the worst aspect ratio of a group of rectangles.
13211 <http://en.wikipedia.org/wiki/Aspect_ratio>
13215 ch - An array of nodes.
13216 w - The fixed dimension where rectangles are being laid out.
13220 The worst aspect ratio.
13224 worstAspectRatio: function(ch, w) {
13225 if(!ch || ch.length == 0) return Number.MAX_VALUE;
13226 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
13227 for(var i=0, l=ch.length; i<l; i++) {
13228 var area = ch[i]._area;
13230 minArea = minArea < area? minArea : area;
13231 maxArea = maxArea > area? maxArea : area;
13233 var sqw = w * w, sqAreaSum = areaSum * areaSum;
13234 return Math.max(sqw * maxArea / sqAreaSum,
13235 sqAreaSum / (sqw * minArea));
13239 Method: avgAspectRatio
13241 Calculates the average aspect ratio of a group of rectangles.
13245 <http://en.wikipedia.org/wiki/Aspect_ratio>
13249 ch - An array of nodes.
13250 w - The fixed dimension where rectangles are being laid out.
13254 The average aspect ratio.
13258 avgAspectRatio: function(ch, w) {
13259 if(!ch || ch.length == 0) return Number.MAX_VALUE;
13261 for(var i=0, l=ch.length; i<l; i++) {
13262 var area = ch[i]._area;
13264 arSum += w > h? w / h : h / w;
13272 Performs the layout of the last computed sibling.
13276 ch - An array of nodes.
13277 w - A fixed dimension where nodes will be layed out.
13278 coord - A coordinates object specifying width, height, left and top style properties.
13280 layoutLast: function(ch, w, coord, prop) {
13282 child.getPos(prop).setc(coord.left, coord.top);
13283 child.setData('width', coord.width, prop);
13284 child.setData('height', coord.height, prop);
13289 Layouts.TM.Squarified = new Class({
13290 Implements: Layouts.TM.Area,
13292 computePositions: function(node, coord, prop) {
13293 var config = this.config,
13296 if (coord.width >= coord.height)
13297 this.layout.orientation = 'h';
13299 this.layout.orientation = 'v';
13301 var ch = node.getSubnodes([1, 1], "ignore");
13302 if(ch.length > 0) {
13303 this.processChildrenLayout(node, ch, coord, prop);
13304 for(var i=0, l=ch.length; i<l; i++) {
13306 offst = config.offset,
13307 height = max(chi.getData('height', prop) - offst - config.titleHeight, 0),
13308 width = max(chi.getData('width', prop) - offst, 0),
13309 chipos = chi.getPos(prop);
13314 'top': chipos.y + config.titleHeight,
13317 this.computePositions(chi, coord, prop);
13323 Method: processChildrenLayout
13325 Computes children real areas and other useful parameters for performing the Squarified algorithm.
13329 par - The parent node of the json subtree.
13330 ch - An Array of nodes
13331 coord - A coordinates object specifying width, height, left and top style properties.
13333 processChildrenLayout: function(par, ch, coord, prop) {
13334 //compute children real areas
13335 var parentArea = coord.width * coord.height;
13336 var i, l=ch.length, totalChArea=0, chArea = [];
13337 for(i=0; i<l; i++) {
13338 chArea[i] = parseFloat(ch[i].getData('area', prop));
13339 totalChArea += chArea[i];
13341 for(i=0; i<l; i++) {
13342 ch[i]._area = parentArea * chArea[i] / totalChArea;
13344 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
13345 ch.sort(function(a, b) {
13346 var diff = b._area - a._area;
13347 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
13349 var initElem = [ch[0]];
13350 var tail = ch.slice(1);
13351 this.squarify(tail, initElem, minimumSideValue, coord, prop);
13357 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
13361 tail - An array of nodes.
13362 initElem - An array of nodes, containing the initial node to be laid out.
13363 w - A fixed dimension where nodes will be laid out.
13364 coord - A coordinates object specifying width, height, left and top style properties.
13366 squarify: function(tail, initElem, w, coord, prop) {
13367 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
13373 Performs the layout of an array of nodes.
13377 ch - An array of nodes.
13378 w - A fixed dimension where nodes will be laid out.
13379 coord - A coordinates object specifying width, height, left and top style properties.
13381 layoutRow: function(ch, w, coord, prop) {
13382 if(this.layout.horizontal()) {
13383 return this.layoutV(ch, w, coord, prop);
13385 return this.layoutH(ch, w, coord, prop);
13389 layoutV: function(ch, w, coord, prop) {
13390 var totalArea = 0, rnd = function(x) { return x; };
13391 $.each(ch, function(elem) { totalArea += elem._area; });
13392 var width = rnd(totalArea / w), top = 0;
13393 for(var i=0, l=ch.length; i<l; i++) {
13394 var h = rnd(ch[i]._area / width);
13396 chi.getPos(prop).setc(coord.left, coord.top + top);
13397 chi.setData('width', width, prop);
13398 chi.setData('height', h, prop);
13402 'height': coord.height,
13403 'width': coord.width - width,
13405 'left': coord.left + width
13407 //take minimum side value.
13408 ans.dim = Math.min(ans.width, ans.height);
13409 if(ans.dim != ans.height) this.layout.change();
13413 layoutH: function(ch, w, coord, prop) {
13415 $.each(ch, function(elem) { totalArea += elem._area; });
13416 var height = totalArea / w,
13420 for(var i=0, l=ch.length; i<l; i++) {
13422 var w = chi._area / height;
13423 chi.getPos(prop).setc(coord.left + left, top);
13424 chi.setData('width', w, prop);
13425 chi.setData('height', height, prop);
13429 'height': coord.height - height,
13430 'width': coord.width,
13431 'top': coord.top + height,
13434 ans.dim = Math.min(ans.width, ans.height);
13435 if(ans.dim != ans.width) this.layout.change();
13440 Layouts.TM.Strip = new Class({
13441 Implements: Layouts.TM.Area,
13446 Called by loadJSON to calculate recursively all node positions and lay out the tree.
13450 json - A JSON subtree. See also <Loader.loadJSON>.
13451 coord - A coordinates object specifying width, height, left and top style properties.
13453 computePositions: function(node, coord, prop) {
13454 var ch = node.getSubnodes([1, 1], "ignore"),
13455 config = this.config,
13457 if(ch.length > 0) {
13458 this.processChildrenLayout(node, ch, coord, prop);
13459 for(var i=0, l=ch.length; i<l; i++) {
13461 var offst = config.offset,
13462 height = max(chi.getData('height', prop) - offst - config.titleHeight, 0),
13463 width = max(chi.getData('width', prop) - offst, 0);
13464 var chipos = chi.getPos(prop);
13468 'top': chipos.y + config.titleHeight,
13471 this.computePositions(chi, coord, prop);
13477 Method: processChildrenLayout
13479 Computes children real areas and other useful parameters for performing the Strip algorithm.
13483 par - The parent node of the json subtree.
13484 ch - An Array of nodes
13485 coord - A coordinates object specifying width, height, left and top style properties.
13487 processChildrenLayout: function(par, ch, coord, prop) {
13488 //compute children real areas
13489 var parentArea = coord.width * coord.height;
13490 var i, l=ch.length, totalChArea=0, chArea = [];
13491 for(i=0; i<l; i++) {
13492 chArea[i] = +ch[i].getData('area', prop);
13493 totalChArea += chArea[i];
13495 for(i=0; i<l; i++) {
13496 ch[i]._area = parentArea * chArea[i] / totalChArea;
13498 var side = this.layout.horizontal()? coord.width : coord.height;
13499 var initElem = [ch[0]];
13500 var tail = ch.slice(1);
13501 this.stripify(tail, initElem, side, coord, prop);
13507 Performs an heuristic method to calculate div elements sizes in order to have
13508 a good compromise between aspect ratio and order.
13512 tail - An array of nodes.
13513 initElem - An array of nodes.
13514 w - A fixed dimension where nodes will be layed out.
13515 coord - A coordinates object specifying width, height, left and top style properties.
13517 stripify: function(tail, initElem, w, coord, prop) {
13518 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
13524 Performs the layout of an array of nodes.
13528 ch - An array of nodes.
13529 w - A fixed dimension where nodes will be laid out.
13530 coord - A coordinates object specifying width, height, left and top style properties.
13532 layoutRow: function(ch, w, coord, prop) {
13533 if(this.layout.horizontal()) {
13534 return this.layoutH(ch, w, coord, prop);
13536 return this.layoutV(ch, w, coord, prop);
13540 layoutV: function(ch, w, coord, prop) {
13542 $.each(ch, function(elem) { totalArea += elem._area; });
13543 var width = totalArea / w, top = 0;
13544 for(var i=0, l=ch.length; i<l; i++) {
13546 var h = chi._area / width;
13547 chi.getPos(prop).setc(coord.left,
13548 coord.top + (w - h - top));
13549 chi.setData('width', width, prop);
13550 chi.setData('height', h, prop);
13555 'height': coord.height,
13556 'width': coord.width - width,
13558 'left': coord.left + width,
13563 layoutH: function(ch, w, coord, prop) {
13565 $.each(ch, function(elem) { totalArea += elem._area; });
13566 var height = totalArea / w,
13567 top = coord.height - height,
13570 for(var i=0, l=ch.length; i<l; i++) {
13572 var s = chi._area / height;
13573 chi.getPos(prop).setc(coord.left + left, coord.top + top);
13574 chi.setData('width', s, prop);
13575 chi.setData('height', height, prop);
13579 'height': coord.height - height,
13580 'width': coord.width,
13582 'left': coord.left,
13590 * Class: Layouts.Icicle
13592 * Implements the icicle tree layout.
13600 Layouts.Icicle = new Class({
13604 * Called by loadJSON to calculate all node positions.
13608 * posType - The nodes' position to compute. Either "start", "end" or
13609 * "current". Defaults to "current".
13611 compute: function(posType) {
13612 posType = posType || "current";
13614 var root = this.graph.getNode(this.root),
13615 config = this.config,
13616 size = this.canvas.getSize(),
13617 width = size.width,
13618 height = size.height,
13619 offset = config.offset,
13620 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
13622 this.controller.onBeforeCompute(root);
13624 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
13628 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
13630 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
13631 var maxDepth = Math.min(treeDepth, levelsToShow-1);
13632 var initialDepth = startNode._depth;
13633 if(this.layout.horizontal()) {
13634 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
13636 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
13640 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
13641 root.getPos(posType).setc(x, y);
13642 root.setData('width', width, posType);
13643 root.setData('height', height, posType);
13645 var nodeLength, prevNodeLength = 0, totalDim = 0;
13646 var children = Graph.Util.getSubnodes(root, [1, 1], 'ignore'); // next level from this node
13648 if(!children.length)
13651 $.each(children, function(e) { totalDim += e.getData('dim'); });
13653 for(var i=0, l=children.length; i < l; i++) {
13654 if(this.layout.horizontal()) {
13655 nodeLength = height * children[i].getData('dim') / totalDim;
13656 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
13659 nodeLength = width * children[i].getData('dim') / totalDim;
13660 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
13677 Icicle space filling visualization.
13681 All <Loader> methods
13683 Constructor Options:
13685 Inherits options from
13688 - <Options.Controller>
13694 - <Options.NodeStyles>
13695 - <Options.Navigation>
13697 Additionally, there are other parameters and some default values changed
13699 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
13700 offset - (number) Default's *2*. Boxes offset.
13701 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
13702 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
13703 animate - (boolean) Default's *false*. Whether to animate transitions.
13704 Node.type - Described in <Options.Node>. Default's *rectangle*.
13705 Label.type - Described in <Options.Label>. Default's *Native*.
13706 duration - Described in <Options.Fx>. Default's *700*.
13707 fps - Described in <Options.Fx>. Default's *45*.
13709 Instance Properties:
13711 canvas - Access a <Canvas> instance.
13712 graph - Access a <Graph> instance.
13713 op - Access a <Icicle.Op> instance.
13714 fx - Access a <Icicle.Plot> instance.
13715 labels - Access a <Icicle.Label> interface implementation.
13719 $jit.Icicle = new Class({
13720 Implements: [ Loader, Extras, Layouts.Icicle ],
13724 vertical: function(){
13725 return this.orientation == "v";
13727 horizontal: function(){
13728 return this.orientation == "h";
13730 change: function(){
13731 this.orientation = this.vertical()? "h" : "v";
13735 initialize: function(controller) {
13740 levelsToShow: Number.MAX_VALUE,
13741 constrained: false,
13756 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
13757 "Events", "Navigation", "Controller", "Label");
13758 this.controller = this.config = $.merge(opts, config, controller);
13759 this.layout.orientation = this.config.orientation;
13761 var canvasConfig = this.config;
13762 if (canvasConfig.useCanvas) {
13763 this.canvas = canvasConfig.useCanvas;
13764 this.config.labelContainer = this.canvas.id + '-label';
13766 this.canvas = new Canvas(this, canvasConfig);
13767 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
13770 this.graphOptions = {
13779 this.graph = new Graph(
13780 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
13782 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
13783 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
13784 this.op = new $jit.Icicle.Op(this);
13785 this.group = new $jit.Icicle.Group(this);
13786 this.clickedNode = null;
13788 this.initializeExtras();
13794 Computes positions and plots the tree.
13796 refresh: function(){
13797 var labelType = this.config.Label.type;
13798 if(labelType != 'Native') {
13800 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
13809 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
13813 this.fx.plot(this.config);
13819 Sets the node as root.
13823 node - (object) A <Graph.Node>.
13826 enter: function (node) {
13832 config = this.config;
13835 onComplete: function() {
13836 //compute positions of newly inserted nodes
13840 if(config.animate) {
13841 that.graph.nodeList.setDataset(['current', 'end'], {
13842 'alpha': [1, 0] //fade nodes
13845 Graph.Util.eachSubgraph(node, function(n) {
13846 n.setData('alpha', 1, 'end');
13851 modes:['node-property:alpha'],
13852 onComplete: function() {
13853 that.clickedNode = node;
13854 that.compute('end');
13857 modes:['linear', 'node-property:width:height'],
13859 onComplete: function() {
13861 that.clickedNode = node;
13867 that.clickedNode = node;
13874 if(config.request) {
13875 this.requestNodes(clickedNode, callback);
13877 callback.onComplete();
13884 Sets the parent node of the current selected node as root.
13892 GUtil = Graph.Util,
13893 config = this.config,
13894 graph = this.graph,
13895 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
13896 parent = parents[0],
13897 clickedNode = parent,
13898 previousClickedNode = this.clickedNode;
13901 this.events.hoveredNode = false;
13908 //final plot callback
13910 onComplete: function() {
13911 that.clickedNode = parent;
13912 if(config.request) {
13913 that.requestNodes(parent, {
13914 onComplete: function() {
13928 //animate node positions
13929 if(config.animate) {
13930 this.clickedNode = clickedNode;
13931 this.compute('end');
13932 //animate the visible subtree only
13933 this.clickedNode = previousClickedNode;
13935 modes:['linear', 'node-property:width:height'],
13937 onComplete: function() {
13938 //animate the parent subtree
13939 that.clickedNode = clickedNode;
13940 //change nodes alpha
13941 graph.nodeList.setDataset(['current', 'end'], {
13944 GUtil.eachSubgraph(previousClickedNode, function(node) {
13945 node.setData('alpha', 1);
13949 modes:['node-property:alpha'],
13950 onComplete: function() {
13951 callback.onComplete();
13957 callback.onComplete();
13960 requestNodes: function(node, onComplete){
13961 var handler = $.merge(this.controller, onComplete),
13962 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
13964 if (handler.request) {
13965 var leaves = [], d = node._depth;
13966 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
13967 if (n.drawn && !Graph.Util.anySubnode(n)) {
13969 n._level = n._depth - d;
13970 if (this.config.constrained)
13971 n._level = levelsToShow - n._level;
13975 this.group.requestNodes(leaves, handler);
13977 handler.onComplete();
13985 Custom extension of <Graph.Op>.
13989 All <Graph.Op> methods
13996 $jit.Icicle.Op = new Class({
13998 Implements: Graph.Op
14003 * Performs operations on group of nodes.
14005 $jit.Icicle.Group = new Class({
14007 initialize: function(viz){
14009 this.canvas = viz.canvas;
14010 this.config = viz.config;
14014 * Calls the request method on the controller to request a subtree for each node.
14016 requestNodes: function(nodes, controller){
14017 var counter = 0, len = nodes.length, nodeSelected = {};
14018 var complete = function(){
14019 controller.onComplete();
14021 var viz = this.viz;
14024 for(var i = 0; i < len; i++) {
14025 nodeSelected[nodes[i].id] = nodes[i];
14026 controller.request(nodes[i].id, nodes[i]._level, {
14027 onComplete: function(nodeId, data){
14028 if (data && data.children) {
14034 if (++counter == len) {
14035 Graph.Util.computeLevels(viz.graph, viz.root, 0);
14047 Custom extension of <Graph.Plot>.
14051 All <Graph.Plot> methods
14058 $jit.Icicle.Plot = new Class({
14059 Implements: Graph.Plot,
14061 plot: function(opt, animating){
14062 opt = opt || this.viz.controller;
14063 var viz = this.viz,
14065 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
14066 initialDepth = root._depth;
14068 viz.canvas.clear();
14069 this.plotTree(root, $.merge(opt, {
14070 'withLabels': true,
14071 'hideLabels': false,
14072 'plotSubtree': function(root, node) {
14073 return !viz.config.constrained ||
14074 (node._depth - initialDepth < viz.config.levelsToShow);
14081 Class: Icicle.Label
14083 Custom extension of <Graph.Label>.
14084 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14088 All <Graph.Label> methods and subclasses.
14092 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14095 $jit.Icicle.Label = {};
14098 Icicle.Label.Native
14100 Custom extension of <Graph.Label.Native>.
14104 All <Graph.Label.Native> methods
14108 <Graph.Label.Native>
14111 $jit.Icicle.Label.Native = new Class({
14112 Implements: Graph.Label.Native,
14114 renderLabel: function(canvas, node, controller) {
14115 var ctx = canvas.getCtx(),
14116 width = node.getData('width'),
14117 height = node.getData('height'),
14118 size = node.getLabelData('size'),
14119 m = ctx.measureText(node.name);
14121 // Guess as much as possible if the label will fit in the node
14122 if(height < (size * 1.5) || width < m.width)
14125 var pos = node.pos.getc(true);
14126 ctx.fillText(node.name,
14128 pos.y + height / 2);
14135 Custom extension of <Graph.Label.SVG>.
14139 All <Graph.Label.SVG> methods
14145 $jit.Icicle.Label.SVG = new Class( {
14146 Implements: Graph.Label.SVG,
14148 initialize: function(viz){
14155 Overrides abstract method placeLabel in <Graph.Plot>.
14159 tag - A DOM label element.
14160 node - A <Graph.Node>.
14161 controller - A configuration/controller object passed to the visualization.
14163 placeLabel: function(tag, node, controller){
14164 var pos = node.pos.getc(true), canvas = this.viz.canvas;
14165 var radius = canvas.getSize();
14167 x: Math.round(pos.x + radius.width / 2),
14168 y: Math.round(pos.y + radius.height / 2)
14170 tag.setAttribute('x', labelPos.x);
14171 tag.setAttribute('y', labelPos.y);
14173 controller.onPlaceLabel(tag, node);
14180 Custom extension of <Graph.Label.HTML>.
14184 All <Graph.Label.HTML> methods.
14191 $jit.Icicle.Label.HTML = new Class( {
14192 Implements: Graph.Label.HTML,
14194 initialize: function(viz){
14201 Overrides abstract method placeLabel in <Graph.Plot>.
14205 tag - A DOM label element.
14206 node - A <Graph.Node>.
14207 controller - A configuration/controller object passed to the visualization.
14209 placeLabel: function(tag, node, controller){
14210 var pos = node.pos.getc(true), canvas = this.viz.canvas;
14211 var radius = canvas.getSize();
14213 x: Math.round(pos.x + radius.width / 2),
14214 y: Math.round(pos.y + radius.height / 2)
14217 var style = tag.style;
14218 style.left = labelPos.x + 'px';
14219 style.top = labelPos.y + 'px';
14220 style.display = '';
14222 controller.onPlaceLabel(tag, node);
14227 Class: Icicle.Plot.NodeTypes
14229 This class contains a list of <Graph.Node> built-in types.
14230 Node types implemented are 'none', 'rectangle'.
14232 You can add your custom node types, customizing your visualization to the extreme.
14237 Icicle.Plot.NodeTypes.implement({
14239 'render': function(node, canvas) {
14240 //print your custom node to canvas
14243 'contains': function(node, pos) {
14244 //return true if pos is inside the node or false otherwise
14251 $jit.Icicle.Plot.NodeTypes = new Class( {
14257 'render': function(node, canvas, animating) {
14258 var config = this.viz.config;
14259 var offset = config.offset;
14260 var width = node.getData('width');
14261 var height = node.getData('height');
14262 var border = node.getData('border');
14263 var pos = node.pos.getc(true);
14264 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
14265 var ctx = canvas.getCtx();
14267 if(width - offset < 2 || height - offset < 2) return;
14269 if(config.cushion) {
14270 var color = node.getData('color');
14271 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
14272 posy + (height - offset)/2, 1,
14273 posx + (width-offset)/2, posy + (height-offset)/2,
14274 width < height? height : width);
14275 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
14276 function(r) { return r * 0.3 >> 0; }));
14277 lg.addColorStop(0, color);
14278 lg.addColorStop(1, colorGrad);
14279 ctx.fillStyle = lg;
14283 ctx.strokeStyle = border;
14287 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
14288 border && ctx.strokeRect(pos.x, pos.y, width, height);
14291 'contains': function(node, pos) {
14292 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
14293 var npos = node.pos.getc(true),
14294 width = node.getData('width'),
14295 height = node.getData('height');
14296 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
14301 $jit.Icicle.Plot.EdgeTypes = new Class( {
14308 * File: Layouts.ForceDirected.js
14313 * Class: Layouts.ForceDirected
14315 * Implements a Force Directed Layout.
14323 * Marcus Cobden <http://marcuscobden.co.uk>
14326 Layouts.ForceDirected = new Class({
14328 getOptions: function(random) {
14329 var s = this.canvas.getSize();
14330 var w = s.width, h = s.height;
14333 this.graph.eachNode(function(n) {
14336 var k2 = w * h / count, k = Math.sqrt(k2);
14337 var l = this.config.levelDistance;
14343 nodef: function(x) { return k2 / (x || 1); },
14344 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
14348 compute: function(property, incremental) {
14349 var prop = $.splat(property || ['current', 'start', 'end']);
14350 var opt = this.getOptions();
14351 NodeDim.compute(this.graph, prop, this.config);
14352 this.graph.computeLevels(this.root, 0, "ignore");
14353 this.graph.eachNode(function(n) {
14354 $.each(prop, function(p) {
14355 var pos = n.getPos(p);
14356 if(pos.equals(Complex.KER)) {
14357 pos.x = opt.width/5 * (Math.random() - 0.5);
14358 pos.y = opt.height/5 * (Math.random() - 0.5);
14360 //initialize disp vector
14362 $.each(prop, function(p) {
14363 n.disp[p] = $C(0, 0);
14367 this.computePositions(prop, opt, incremental);
14370 computePositions: function(property, opt, incremental) {
14371 var times = this.config.iterations, i = 0, that = this;
14374 for(var total=incremental.iter, j=0; j<total; j++) {
14375 opt.t = opt.tstart;
14376 if(times) opt.t *= (1 - i++/(times -1));
14377 that.computePositionStep(property, opt);
14378 if(times && i >= times) {
14379 incremental.onComplete();
14383 incremental.onStep(Math.round(i / (times -1) * 100));
14384 setTimeout(iter, 1);
14387 for(; i < times; i++) {
14388 opt.t = opt.tstart * (1 - i/(times -1));
14389 this.computePositionStep(property, opt);
14394 computePositionStep: function(property, opt) {
14395 var graph = this.graph;
14396 var min = Math.min, max = Math.max;
14397 var dpos = $C(0, 0);
14398 //calculate repulsive forces
14399 graph.eachNode(function(v) {
14401 $.each(property, function(p) {
14402 v.disp[p].x = 0; v.disp[p].y = 0;
14404 graph.eachNode(function(u) {
14406 $.each(property, function(p) {
14407 var vp = v.getPos(p), up = u.getPos(p);
14408 dpos.x = vp.x - up.x;
14409 dpos.y = vp.y - up.y;
14410 var norm = dpos.norm() || 1;
14411 v.disp[p].$add(dpos
14412 .$scale(opt.nodef(norm) / norm));
14417 //calculate attractive forces
14418 var T = !!graph.getNode(this.root).visited;
14419 graph.eachNode(function(node) {
14420 node.eachAdjacency(function(adj) {
14421 var nodeTo = adj.nodeTo;
14422 if(!!nodeTo.visited === T) {
14423 $.each(property, function(p) {
14424 var vp = node.getPos(p), up = nodeTo.getPos(p);
14425 dpos.x = vp.x - up.x;
14426 dpos.y = vp.y - up.y;
14427 var norm = dpos.norm() || 1;
14428 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
14429 nodeTo.disp[p].$add(dpos.$scale(-1));
14435 //arrange positions to fit the canvas
14436 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
14437 graph.eachNode(function(u) {
14438 $.each(property, function(p) {
14439 var disp = u.disp[p];
14440 var norm = disp.norm() || 1;
14441 var p = u.getPos(p);
14442 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
14443 disp.y * min(Math.abs(disp.y), t) / norm));
14444 p.x = min(w2, max(-w2, p.x));
14445 p.y = min(h2, max(-h2, p.y));
14452 * File: ForceDirected.js
14456 Class: ForceDirected
14458 A visualization that lays graphs using a Force-Directed layout algorithm.
14462 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
14466 All <Loader> methods
14468 Constructor Options:
14470 Inherits options from
14473 - <Options.Controller>
14479 - <Options.NodeStyles>
14480 - <Options.Navigation>
14482 Additionally, there are two parameters
14484 levelDistance - (number) Default's *50*. The natural length desired for the edges.
14485 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*.
14487 Instance Properties:
14489 canvas - Access a <Canvas> instance.
14490 graph - Access a <Graph> instance.
14491 op - Access a <ForceDirected.Op> instance.
14492 fx - Access a <ForceDirected.Plot> instance.
14493 labels - Access a <ForceDirected.Label> interface implementation.
14497 $jit.ForceDirected = new Class( {
14499 Implements: [ Loader, Extras, Layouts.ForceDirected ],
14501 initialize: function(controller) {
14502 var $ForceDirected = $jit.ForceDirected;
14509 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14510 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14512 var canvasConfig = this.config;
14513 if(canvasConfig.useCanvas) {
14514 this.canvas = canvasConfig.useCanvas;
14515 this.config.labelContainer = this.canvas.id + '-label';
14517 if(canvasConfig.background) {
14518 canvasConfig.background = $.merge({
14520 }, canvasConfig.background);
14522 this.canvas = new Canvas(this, canvasConfig);
14523 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14526 this.graphOptions = {
14534 this.graph = new Graph(this.graphOptions, this.config.Node,
14536 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
14537 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
14538 this.op = new $ForceDirected.Op(this);
14541 // initialize extras
14542 this.initializeExtras();
14548 Computes positions and plots the tree.
14550 refresh: function() {
14555 reposition: function() {
14556 this.compute('end');
14560 Method: computeIncremental
14562 Performs the Force Directed algorithm incrementally.
14566 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
14567 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
14568 avoiding browser messages such as "This script is taking too long to complete".
14572 opt - (object) The object properties are described below
14574 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
14575 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
14577 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
14578 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
14579 computations for final animation positions then you can just choose 'end'.
14581 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
14582 parameter a percentage value.
14584 onComplete - A callback function called when the algorithm completed.
14588 In this example I calculate the end positions and then animate the graph to those positions
14591 var fd = new $jit.ForceDirected(...);
14592 fd.computeIncremental({
14595 onStep: function(perc) {
14596 Log.write("loading " + perc + "%");
14598 onComplete: function() {
14605 In this example I calculate all positions and (re)plot the graph
14608 var fd = new ForceDirected(...);
14609 fd.computeIncremental({
14611 property: ['end', 'start', 'current'],
14612 onStep: function(perc) {
14613 Log.write("loading " + perc + "%");
14615 onComplete: function() {
14623 computeIncremental: function(opt) {
14628 onComplete: $.empty
14631 this.config.onBeforeCompute(this.graph.getNode(this.root));
14632 this.compute(opt.property, opt);
14638 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
14647 Animates the graph from the current positions to the 'end' node positions.
14649 animate: function(opt) {
14650 this.fx.animate($.merge( {
14651 modes: [ 'linear' ]
14656 $jit.ForceDirected.$extend = true;
14658 (function(ForceDirected) {
14661 Class: ForceDirected.Op
14663 Custom extension of <Graph.Op>.
14667 All <Graph.Op> methods
14674 ForceDirected.Op = new Class( {
14676 Implements: Graph.Op
14681 Class: ForceDirected.Plot
14683 Custom extension of <Graph.Plot>.
14687 All <Graph.Plot> methods
14694 ForceDirected.Plot = new Class( {
14696 Implements: Graph.Plot
14701 Class: ForceDirected.Label
14703 Custom extension of <Graph.Label>.
14704 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14708 All <Graph.Label> methods and subclasses.
14712 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14715 ForceDirected.Label = {};
14718 ForceDirected.Label.Native
14720 Custom extension of <Graph.Label.Native>.
14724 All <Graph.Label.Native> methods
14728 <Graph.Label.Native>
14731 ForceDirected.Label.Native = new Class( {
14732 Implements: Graph.Label.Native
14736 ForceDirected.Label.SVG
14738 Custom extension of <Graph.Label.SVG>.
14742 All <Graph.Label.SVG> methods
14749 ForceDirected.Label.SVG = new Class( {
14750 Implements: Graph.Label.SVG,
14752 initialize: function(viz) {
14759 Overrides abstract method placeLabel in <Graph.Label>.
14763 tag - A DOM label element.
14764 node - A <Graph.Node>.
14765 controller - A configuration/controller object passed to the visualization.
14768 placeLabel: function(tag, node, controller) {
14769 var pos = node.pos.getc(true),
14770 canvas = this.viz.canvas,
14771 ox = canvas.translateOffsetX,
14772 oy = canvas.translateOffsetY,
14773 sx = canvas.scaleOffsetX,
14774 sy = canvas.scaleOffsetY,
14775 radius = canvas.getSize();
14777 x: Math.round(pos.x * sx + ox + radius.width / 2),
14778 y: Math.round(pos.y * sy + oy + radius.height / 2)
14780 tag.setAttribute('x', labelPos.x);
14781 tag.setAttribute('y', labelPos.y);
14783 controller.onPlaceLabel(tag, node);
14788 ForceDirected.Label.HTML
14790 Custom extension of <Graph.Label.HTML>.
14794 All <Graph.Label.HTML> methods.
14801 ForceDirected.Label.HTML = new Class( {
14802 Implements: Graph.Label.HTML,
14804 initialize: function(viz) {
14810 Overrides abstract method placeLabel in <Graph.Plot>.
14814 tag - A DOM label element.
14815 node - A <Graph.Node>.
14816 controller - A configuration/controller object passed to the visualization.
14819 placeLabel: function(tag, node, controller) {
14820 var pos = node.pos.getc(true),
14821 canvas = this.viz.canvas,
14822 ox = canvas.translateOffsetX,
14823 oy = canvas.translateOffsetY,
14824 sx = canvas.scaleOffsetX,
14825 sy = canvas.scaleOffsetY,
14826 radius = canvas.getSize();
14828 x: Math.round(pos.x * sx + ox + radius.width / 2),
14829 y: Math.round(pos.y * sy + oy + radius.height / 2)
14831 var style = tag.style;
14832 style.left = labelPos.x + 'px';
14833 style.top = labelPos.y + 'px';
14834 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14836 controller.onPlaceLabel(tag, node);
14841 Class: ForceDirected.Plot.NodeTypes
14843 This class contains a list of <Graph.Node> built-in types.
14844 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
14846 You can add your custom node types, customizing your visualization to the extreme.
14851 ForceDirected.Plot.NodeTypes.implement({
14853 'render': function(node, canvas) {
14854 //print your custom node to canvas
14857 'contains': function(node, pos) {
14858 //return true if pos is inside the node or false otherwise
14865 ForceDirected.Plot.NodeTypes = new Class({
14868 'contains': $.lambda(false)
14871 'render': function(node, canvas){
14872 var pos = node.pos.getc(true),
14873 dim = node.getData('dim');
14874 this.nodeHelper.circle.render('fill', pos, dim, canvas);
14876 'contains': function(node, pos){
14877 var npos = node.pos.getc(true),
14878 dim = node.getData('dim');
14879 return this.nodeHelper.circle.contains(npos, pos, dim);
14883 'render': function(node, canvas){
14884 var pos = node.pos.getc(true),
14885 width = node.getData('width'),
14886 height = node.getData('height');
14887 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
14889 'contains': function(node, pos){
14890 var npos = node.pos.getc(true),
14891 width = node.getData('width'),
14892 height = node.getData('height');
14893 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
14897 'render': function(node, canvas){
14898 var pos = node.pos.getc(true),
14899 dim = node.getData('dim');
14900 this.nodeHelper.square.render('fill', pos, dim, canvas);
14902 'contains': function(node, pos){
14903 var npos = node.pos.getc(true),
14904 dim = node.getData('dim');
14905 return this.nodeHelper.square.contains(npos, pos, dim);
14909 'render': function(node, canvas){
14910 var pos = node.pos.getc(true),
14911 width = node.getData('width'),
14912 height = node.getData('height');
14913 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
14915 'contains': function(node, pos){
14916 var npos = node.pos.getc(true),
14917 width = node.getData('width'),
14918 height = node.getData('height');
14919 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
14923 'render': function(node, canvas){
14924 var pos = node.pos.getc(true),
14925 dim = node.getData('dim');
14926 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
14928 'contains': function(node, pos) {
14929 var npos = node.pos.getc(true),
14930 dim = node.getData('dim');
14931 return this.nodeHelper.triangle.contains(npos, pos, dim);
14935 'render': function(node, canvas){
14936 var pos = node.pos.getc(true),
14937 dim = node.getData('dim');
14938 this.nodeHelper.star.render('fill', pos, dim, canvas);
14940 'contains': function(node, pos) {
14941 var npos = node.pos.getc(true),
14942 dim = node.getData('dim');
14943 return this.nodeHelper.star.contains(npos, pos, dim);
14949 Class: ForceDirected.Plot.EdgeTypes
14951 This class contains a list of <Graph.Adjacence> built-in types.
14952 Edge types implemented are 'none', 'line' and 'arrow'.
14954 You can add your custom edge types, customizing your visualization to the extreme.
14959 ForceDirected.Plot.EdgeTypes.implement({
14961 'render': function(adj, canvas) {
14962 //print your custom edge to canvas
14965 'contains': function(adj, pos) {
14966 //return true if pos is inside the arc or false otherwise
14973 ForceDirected.Plot.EdgeTypes = new Class({
14976 'render': function(adj, canvas) {
14977 var from = adj.nodeFrom.pos.getc(true),
14978 to = adj.nodeTo.pos.getc(true);
14979 this.edgeHelper.line.render(from, to, canvas);
14981 'contains': function(adj, pos) {
14982 var from = adj.nodeFrom.pos.getc(true),
14983 to = adj.nodeTo.pos.getc(true);
14984 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
14988 'render': function(adj, canvas) {
14989 var from = adj.nodeFrom.pos.getc(true),
14990 to = adj.nodeTo.pos.getc(true),
14991 dim = adj.getData('dim'),
14992 direction = adj.data.$direction,
14993 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
14994 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
14996 'contains': function(adj, pos) {
14997 var from = adj.nodeFrom.pos.getc(true),
14998 to = adj.nodeTo.pos.getc(true);
14999 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15004 })($jit.ForceDirected);
15016 $jit.TM.$extend = true;
15021 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
15025 All <Loader> methods
15027 Constructor Options:
15029 Inherits options from
15032 - <Options.Controller>
15038 - <Options.NodeStyles>
15039 - <Options.Navigation>
15041 Additionally, there are other parameters and some default values changed
15043 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
15044 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
15045 offset - (number) Default's *2*. Boxes offset.
15046 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
15047 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
15048 animate - (boolean) Default's *false*. Whether to animate transitions.
15049 Node.type - Described in <Options.Node>. Default's *rectangle*.
15050 duration - Described in <Options.Fx>. Default's *700*.
15051 fps - Described in <Options.Fx>. Default's *45*.
15053 Instance Properties:
15055 canvas - Access a <Canvas> instance.
15056 graph - Access a <Graph> instance.
15057 op - Access a <TM.Op> instance.
15058 fx - Access a <TM.Plot> instance.
15059 labels - Access a <TM.Label> interface implementation.
15063 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
15065 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
15069 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.
15075 vertical: function(){
15076 return this.orientation == "v";
15078 horizontal: function(){
15079 return this.orientation == "h";
15081 change: function(){
15082 this.orientation = this.vertical()? "h" : "v";
15086 initialize: function(controller){
15092 constrained: false,
15097 //we all know why this is not zero,
15104 textAlign: 'center',
15105 textBaseline: 'top'
15114 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
15115 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
15116 this.layout.orientation = this.config.orientation;
15118 var canvasConfig = this.config;
15119 if (canvasConfig.useCanvas) {
15120 this.canvas = canvasConfig.useCanvas;
15121 this.config.labelContainer = this.canvas.id + '-label';
15123 if(canvasConfig.background) {
15124 canvasConfig.background = $.merge({
15126 }, canvasConfig.background);
15128 this.canvas = new Canvas(this, canvasConfig);
15129 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
15132 this.graphOptions = {
15140 this.graph = new Graph(this.graphOptions, this.config.Node,
15142 this.labels = new TM.Label[canvasConfig.Label.type](this);
15143 this.fx = new TM.Plot(this);
15144 this.op = new TM.Op(this);
15145 this.group = new TM.Group(this);
15146 this.geom = new TM.Geom(this);
15147 this.clickedNode = null;
15149 // initialize extras
15150 this.initializeExtras();
15156 Computes positions and plots the tree.
15158 refresh: function(){
15159 if(this.busy) return;
15162 if(this.config.animate) {
15163 this.compute('end');
15164 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
15165 && this.clickedNode.id || this.root));
15166 this.fx.animate($.merge(this.config, {
15167 modes: ['linear', 'node-property:width:height'],
15168 onComplete: function() {
15173 var labelType = this.config.Label.type;
15174 if(labelType != 'Native') {
15176 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
15180 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
15181 && this.clickedNode.id || this.root));
15189 Plots the TreeMap. This is a shortcut to *fx.plot*.
15199 Returns whether the node is a leaf.
15203 n - (object) A <Graph.Node>.
15207 return n.getSubnodes([
15209 ], "ignore").length == 0;
15215 Sets the node as root.
15219 n - (object) A <Graph.Node>.
15222 enter: function(n){
15223 if(this.busy) return;
15227 config = this.config,
15228 graph = this.graph,
15230 previousClickedNode = this.clickedNode;
15233 onComplete: function() {
15234 //ensure that nodes are shown for that level
15235 if(config.levelsToShow > 0) {
15236 that.geom.setRightLevelToShow(n);
15238 //compute positions of newly inserted nodes
15239 if(config.levelsToShow > 0 || config.request) that.compute();
15240 if(config.animate) {
15242 graph.nodeList.setData('alpha', 0, 'end');
15243 n.eachSubgraph(function(n) {
15244 n.setData('alpha', 1, 'end');
15248 modes:['node-property:alpha'],
15249 onComplete: function() {
15250 //compute end positions
15251 that.clickedNode = clickedNode;
15252 that.compute('end');
15253 //animate positions
15254 //TODO(nico) commenting this line didn't seem to throw errors...
15255 that.clickedNode = previousClickedNode;
15257 modes:['linear', 'node-property:width:height'],
15259 onComplete: function() {
15261 //TODO(nico) check comment above
15262 that.clickedNode = clickedNode;
15269 that.clickedNode = n;
15274 if(config.request) {
15275 this.requestNodes(clickedNode, callback);
15277 callback.onComplete();
15284 Sets the parent node of the current selected node as root.
15288 if(this.busy) return;
15290 this.events.hoveredNode = false;
15292 config = this.config,
15293 graph = this.graph,
15294 parents = graph.getNode(this.clickedNode
15295 && this.clickedNode.id || this.root).getParents(),
15296 parent = parents[0],
15297 clickedNode = parent,
15298 previousClickedNode = this.clickedNode;
15300 //if no parents return
15305 //final plot callback
15307 onComplete: function() {
15308 that.clickedNode = parent;
15309 if(config.request) {
15310 that.requestNodes(parent, {
15311 onComplete: function() {
15325 if (config.levelsToShow > 0)
15326 this.geom.setRightLevelToShow(parent);
15327 //animate node positions
15328 if(config.animate) {
15329 this.clickedNode = clickedNode;
15330 this.compute('end');
15331 //animate the visible subtree only
15332 this.clickedNode = previousClickedNode;
15334 modes:['linear', 'node-property:width:height'],
15336 onComplete: function() {
15337 //animate the parent subtree
15338 that.clickedNode = clickedNode;
15339 //change nodes alpha
15340 graph.eachNode(function(n) {
15341 n.setDataset(['current', 'end'], {
15345 previousClickedNode.eachSubgraph(function(node) {
15346 node.setData('alpha', 1);
15350 modes:['node-property:alpha'],
15351 onComplete: function() {
15352 callback.onComplete();
15358 callback.onComplete();
15362 requestNodes: function(node, onComplete){
15363 var handler = $.merge(this.controller, onComplete),
15364 lev = this.config.levelsToShow;
15365 if (handler.request) {
15366 var leaves = [], d = node._depth;
15367 node.eachLevel(0, lev, function(n){
15368 var nodeLevel = lev - (n._depth - d);
15369 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
15371 n._level = nodeLevel;
15374 this.group.requestNodes(leaves, handler);
15376 handler.onComplete();
15380 reposition: function() {
15381 this.compute('end');
15388 Custom extension of <Graph.Op>.
15392 All <Graph.Op> methods
15399 TM.Op = new Class({
15400 Implements: Graph.Op,
15402 initialize: function(viz){
15407 //extend level methods of Graph.Geom
15408 TM.Geom = new Class({
15409 Implements: Graph.Geom,
15411 getRightLevelToShow: function() {
15412 return this.viz.config.levelsToShow;
15415 setRightLevelToShow: function(node) {
15416 var level = this.getRightLevelToShow(),
15417 fx = this.viz.labels;
15418 node.eachLevel(0, level+1, function(n) {
15419 var d = n._depth - node._depth;
15424 fx.hideLabel(n, false);
15432 delete node.ignore;
15438 Performs operations on group of nodes.
15441 TM.Group = new Class( {
15443 initialize: function(viz){
15445 this.canvas = viz.canvas;
15446 this.config = viz.config;
15451 Calls the request method on the controller to request a subtree for each node.
15453 requestNodes: function(nodes, controller){
15454 var counter = 0, len = nodes.length, nodeSelected = {};
15455 var complete = function(){
15456 controller.onComplete();
15458 var viz = this.viz;
15461 for ( var i = 0; i < len; i++) {
15462 nodeSelected[nodes[i].id] = nodes[i];
15463 controller.request(nodes[i].id, nodes[i]._level, {
15464 onComplete: function(nodeId, data){
15465 if (data && data.children) {
15471 if (++counter == len) {
15472 viz.graph.computeLevels(viz.root, 0);
15484 Custom extension of <Graph.Plot>.
15488 All <Graph.Plot> methods
15495 TM.Plot = new Class({
15497 Implements: Graph.Plot,
15499 initialize: function(viz){
15501 this.config = viz.config;
15502 this.node = this.config.Node;
15503 this.edge = this.config.Edge;
15504 this.animation = new Animation;
15505 this.nodeTypes = new TM.Plot.NodeTypes;
15506 this.edgeTypes = new TM.Plot.EdgeTypes;
15507 this.labels = viz.labels;
15510 plot: function(opt, animating){
15511 var viz = this.viz,
15513 viz.canvas.clear();
15514 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
15515 'withLabels': true,
15516 'hideLabels': false,
15517 'plotSubtree': function(n, ch){
15518 return n.anySubnode("exist");
15527 Custom extension of <Graph.Label>.
15528 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
15532 All <Graph.Label> methods and subclasses.
15536 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
15544 Custom extension of <Graph.Label.Native>.
15548 All <Graph.Label.Native> methods
15552 <Graph.Label.Native>
15554 TM.Label.Native = new Class({
15555 Implements: Graph.Label.Native,
15557 initialize: function(viz) {
15558 this.config = viz.config;
15559 this.leaf = viz.leaf;
15562 renderLabel: function(canvas, node, controller){
15563 if(!this.leaf(node) && !this.config.titleHeight) return;
15564 var pos = node.pos.getc(true),
15565 ctx = canvas.getCtx(),
15566 width = node.getData('width'),
15567 height = node.getData('height'),
15568 x = pos.x + width/2,
15571 ctx.fillText(node.name, x, y, width);
15578 Custom extension of <Graph.Label.SVG>.
15582 All <Graph.Label.SVG> methods
15588 TM.Label.SVG = new Class( {
15589 Implements: Graph.Label.SVG,
15591 initialize: function(viz){
15593 this.leaf = viz.leaf;
15594 this.config = viz.config;
15600 Overrides abstract method placeLabel in <Graph.Plot>.
15604 tag - A DOM label element.
15605 node - A <Graph.Node>.
15606 controller - A configuration/controller object passed to the visualization.
15609 placeLabel: function(tag, node, controller){
15610 var pos = node.pos.getc(true),
15611 canvas = this.viz.canvas,
15612 ox = canvas.translateOffsetX,
15613 oy = canvas.translateOffsetY,
15614 sx = canvas.scaleOffsetX,
15615 sy = canvas.scaleOffsetY,
15616 radius = canvas.getSize();
15618 x: Math.round(pos.x * sx + ox + radius.width / 2),
15619 y: Math.round(pos.y * sy + oy + radius.height / 2)
15621 tag.setAttribute('x', labelPos.x);
15622 tag.setAttribute('y', labelPos.y);
15624 if(!this.leaf(node) && !this.config.titleHeight) {
15625 tag.style.display = 'none';
15627 controller.onPlaceLabel(tag, node);
15634 Custom extension of <Graph.Label.HTML>.
15638 All <Graph.Label.HTML> methods.
15645 TM.Label.HTML = new Class( {
15646 Implements: Graph.Label.HTML,
15648 initialize: function(viz){
15650 this.leaf = viz.leaf;
15651 this.config = viz.config;
15657 Overrides abstract method placeLabel in <Graph.Plot>.
15661 tag - A DOM label element.
15662 node - A <Graph.Node>.
15663 controller - A configuration/controller object passed to the visualization.
15666 placeLabel: function(tag, node, controller){
15667 var pos = node.pos.getc(true),
15668 canvas = this.viz.canvas,
15669 ox = canvas.translateOffsetX,
15670 oy = canvas.translateOffsetY,
15671 sx = canvas.scaleOffsetX,
15672 sy = canvas.scaleOffsetY,
15673 radius = canvas.getSize();
15675 x: Math.round(pos.x * sx + ox + radius.width / 2),
15676 y: Math.round(pos.y * sy + oy + radius.height / 2)
15679 var style = tag.style;
15680 style.left = labelPos.x + 'px';
15681 style.top = labelPos.y + 'px';
15682 style.width = node.getData('width') * sx + 'px';
15683 style.height = node.getData('height') * sy + 'px';
15684 style.zIndex = node._depth * 100;
15685 style.display = '';
15687 if(!this.leaf(node) && !this.config.titleHeight) {
15688 tag.style.display = 'none';
15690 controller.onPlaceLabel(tag, node);
15695 Class: TM.Plot.NodeTypes
15697 This class contains a list of <Graph.Node> built-in types.
15698 Node types implemented are 'none', 'rectangle'.
15700 You can add your custom node types, customizing your visualization to the extreme.
15705 TM.Plot.NodeTypes.implement({
15707 'render': function(node, canvas) {
15708 //print your custom node to canvas
15711 'contains': function(node, pos) {
15712 //return true if pos is inside the node or false otherwise
15719 TM.Plot.NodeTypes = new Class( {
15725 'render': function(node, canvas, animating){
15726 var leaf = this.viz.leaf(node),
15727 config = this.config,
15728 offst = config.offset,
15729 titleHeight = config.titleHeight,
15730 pos = node.pos.getc(true),
15731 width = node.getData('width'),
15732 height = node.getData('height'),
15733 border = node.getData('border'),
15734 ctx = canvas.getCtx(),
15735 posx = pos.x + offst / 2,
15736 posy = pos.y + offst / 2;
15737 if(width <= offst || height <= offst) return;
15739 if(config.cushion) {
15740 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
15741 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
15742 var color = node.getData('color');
15743 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
15744 function(r) { return r * 0.2 >> 0; }));
15745 lg.addColorStop(0, color);
15746 lg.addColorStop(1, colorGrad);
15747 ctx.fillStyle = lg;
15749 ctx.fillRect(posx, posy, width - offst, height - offst);
15752 ctx.strokeStyle = border;
15753 ctx.strokeRect(posx, posy, width - offst, height - offst);
15756 } else if(titleHeight > 0){
15757 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
15758 titleHeight - offst);
15761 ctx.strokeStyle = border;
15762 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
15768 'contains': function(node, pos) {
15769 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
15770 var npos = node.pos.getc(true),
15771 width = node.getData('width'),
15772 leaf = this.viz.leaf(node),
15773 height = leaf? node.getData('height') : this.config.titleHeight;
15774 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
15779 TM.Plot.EdgeTypes = new Class( {
15784 Class: TM.SliceAndDice
15786 A slice and dice TreeMap visualization.
15790 All <TM.Base> methods and properties.
15792 TM.SliceAndDice = new Class( {
15794 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
15799 Class: TM.Squarified
15801 A squarified TreeMap visualization.
15805 All <TM.Base> methods and properties.
15807 TM.Squarified = new Class( {
15809 Loader, Extras, TM.Base, Layouts.TM.Squarified
15816 A strip TreeMap visualization.
15820 All <TM.Base> methods and properties.
15822 TM.Strip = new Class( {
15824 Loader, Extras, TM.Base, Layouts.TM.Strip
15837 A radial graph visualization with advanced animations.
15841 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>
15845 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.
15849 All <Loader> methods
15851 Constructor Options:
15853 Inherits options from
15856 - <Options.Controller>
15862 - <Options.NodeStyles>
15863 - <Options.Navigation>
15865 Additionally, there are other parameters and some default values changed
15867 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
15868 levelDistance - (number) Default's *100*. The distance between levels of the tree.
15870 Instance Properties:
15872 canvas - Access a <Canvas> instance.
15873 graph - Access a <Graph> instance.
15874 op - Access a <RGraph.Op> instance.
15875 fx - Access a <RGraph.Plot> instance.
15876 labels - Access a <RGraph.Label> interface implementation.
15879 $jit.RGraph = new Class( {
15882 Loader, Extras, Layouts.Radial
15885 initialize: function(controller){
15886 var $RGraph = $jit.RGraph;
15889 interpolation: 'linear',
15893 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
15894 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
15896 var canvasConfig = this.config;
15897 if(canvasConfig.useCanvas) {
15898 this.canvas = canvasConfig.useCanvas;
15899 this.config.labelContainer = this.canvas.id + '-label';
15901 if(canvasConfig.background) {
15902 canvasConfig.background = $.merge({
15904 }, canvasConfig.background);
15906 this.canvas = new Canvas(this, canvasConfig);
15907 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
15910 this.graphOptions = {
15918 this.graph = new Graph(this.graphOptions, this.config.Node,
15920 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
15921 this.fx = new $RGraph.Plot(this, $RGraph);
15922 this.op = new $RGraph.Op(this);
15926 this.parent = false;
15927 // initialize extras
15928 this.initializeExtras();
15933 createLevelDistanceFunc
15935 Returns the levelDistance function used for calculating a node distance
15936 to its origin. This function returns a function that is computed
15937 per level and not per node, such that all nodes with the same depth will have the
15938 same distance to the origin. The resulting function gets the
15939 parent node as parameter and returns a float.
15942 createLevelDistanceFunc: function(){
15943 var ld = this.config.levelDistance;
15944 return function(elem){
15945 return (elem._depth + 1) * ld;
15952 Computes positions and plots the tree.
15955 refresh: function(){
15960 reposition: function(){
15961 this.compute('end');
15967 Plots the RGraph. This is a shortcut to *fx.plot*.
15973 getNodeAndParentAngle
15975 Returns the _parent_ of the given node, also calculating its angle span.
15977 getNodeAndParentAngle: function(id){
15979 var n = this.graph.getNode(id);
15980 var ps = n.getParents();
15981 var p = (ps.length > 0)? ps[0] : false;
15983 var posParent = p.pos.getc(), posChild = n.pos.getc();
15984 var newPos = posParent.add(posChild.scale(-1));
15985 theta = Math.atan2(newPos.y, newPos.x);
15987 theta += 2 * Math.PI;
15997 Enumerates the children in order to maintain child ordering (second constraint of the paper).
15999 tagChildren: function(par, id){
16000 if (par.angleSpan) {
16002 par.eachAdjacency(function(elem){
16003 adjs.push(elem.nodeTo);
16005 var len = adjs.length;
16006 for ( var i = 0; i < len && id != adjs[i].id; i++)
16008 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
16009 adjs[j].dist = k++;
16016 Animates the <RGraph> to center the node specified by *id*.
16020 id - A <Graph.Node> id.
16021 opt - (optional|object) An object containing some extra properties described below
16022 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
16027 rgraph.onClick('someid');
16029 rgraph.onClick('someid', {
16035 onClick: function(id, opt){
16036 if (this.root != id && !this.busy) {
16040 this.controller.onBeforeCompute(this.graph.getNode(id));
16041 var obj = this.getNodeAndParentAngle(id);
16043 // second constraint
16044 this.tagChildren(obj.parent, id);
16045 this.parent = obj.parent;
16046 this.compute('end');
16048 // first constraint
16049 var thetaDiff = obj.theta - obj.parent.endPos.theta;
16050 this.graph.eachNode(function(elem){
16051 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
16054 var mode = this.config.interpolation;
16056 onComplete: $.empty
16059 this.fx.animate($.merge( {
16065 onComplete: function(){
16074 $jit.RGraph.$extend = true;
16081 Custom extension of <Graph.Op>.
16085 All <Graph.Op> methods
16092 RGraph.Op = new Class( {
16094 Implements: Graph.Op
16101 Custom extension of <Graph.Plot>.
16105 All <Graph.Plot> methods
16112 RGraph.Plot = new Class( {
16114 Implements: Graph.Plot
16119 Object: RGraph.Label
16121 Custom extension of <Graph.Label>.
16122 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
16126 All <Graph.Label> methods and subclasses.
16130 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
16136 RGraph.Label.Native
16138 Custom extension of <Graph.Label.Native>.
16142 All <Graph.Label.Native> methods
16146 <Graph.Label.Native>
16149 RGraph.Label.Native = new Class( {
16150 Implements: Graph.Label.Native
16156 Custom extension of <Graph.Label.SVG>.
16160 All <Graph.Label.SVG> methods
16167 RGraph.Label.SVG = new Class( {
16168 Implements: Graph.Label.SVG,
16170 initialize: function(viz){
16177 Overrides abstract method placeLabel in <Graph.Plot>.
16181 tag - A DOM label element.
16182 node - A <Graph.Node>.
16183 controller - A configuration/controller object passed to the visualization.
16186 placeLabel: function(tag, node, controller){
16187 var pos = node.pos.getc(true),
16188 canvas = this.viz.canvas,
16189 ox = canvas.translateOffsetX,
16190 oy = canvas.translateOffsetY,
16191 sx = canvas.scaleOffsetX,
16192 sy = canvas.scaleOffsetY,
16193 radius = canvas.getSize();
16195 x: Math.round(pos.x * sx + ox + radius.width / 2),
16196 y: Math.round(pos.y * sy + oy + radius.height / 2)
16198 tag.setAttribute('x', labelPos.x);
16199 tag.setAttribute('y', labelPos.y);
16201 controller.onPlaceLabel(tag, node);
16208 Custom extension of <Graph.Label.HTML>.
16212 All <Graph.Label.HTML> methods.
16219 RGraph.Label.HTML = new Class( {
16220 Implements: Graph.Label.HTML,
16222 initialize: function(viz){
16228 Overrides abstract method placeLabel in <Graph.Plot>.
16232 tag - A DOM label element.
16233 node - A <Graph.Node>.
16234 controller - A configuration/controller object passed to the visualization.
16237 placeLabel: function(tag, node, controller){
16238 var pos = node.pos.getc(true),
16239 canvas = this.viz.canvas,
16240 ox = canvas.translateOffsetX,
16241 oy = canvas.translateOffsetY,
16242 sx = canvas.scaleOffsetX,
16243 sy = canvas.scaleOffsetY,
16244 radius = canvas.getSize();
16246 x: Math.round(pos.x * sx + ox + radius.width / 2),
16247 y: Math.round(pos.y * sy + oy + radius.height / 2)
16250 var style = tag.style;
16251 style.left = labelPos.x + 'px';
16252 style.top = labelPos.y + 'px';
16253 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
16255 controller.onPlaceLabel(tag, node);
16260 Class: RGraph.Plot.NodeTypes
16262 This class contains a list of <Graph.Node> built-in types.
16263 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
16265 You can add your custom node types, customizing your visualization to the extreme.
16270 RGraph.Plot.NodeTypes.implement({
16272 'render': function(node, canvas) {
16273 //print your custom node to canvas
16276 'contains': function(node, pos) {
16277 //return true if pos is inside the node or false otherwise
16284 RGraph.Plot.NodeTypes = new Class({
16287 'contains': $.lambda(false)
16290 'render': function(node, canvas){
16291 var pos = node.pos.getc(true),
16292 dim = node.getData('dim');
16293 this.nodeHelper.circle.render('fill', pos, dim, canvas);
16295 'contains': function(node, pos){
16296 var npos = node.pos.getc(true),
16297 dim = node.getData('dim');
16298 return this.nodeHelper.circle.contains(npos, pos, dim);
16302 'render': function(node, canvas){
16303 var pos = node.pos.getc(true),
16304 width = node.getData('width'),
16305 height = node.getData('height');
16306 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
16308 'contains': function(node, pos){
16309 var npos = node.pos.getc(true),
16310 width = node.getData('width'),
16311 height = node.getData('height');
16312 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
16316 'render': function(node, canvas){
16317 var pos = node.pos.getc(true),
16318 dim = node.getData('dim');
16319 this.nodeHelper.square.render('fill', pos, dim, canvas);
16321 'contains': function(node, pos){
16322 var npos = node.pos.getc(true),
16323 dim = node.getData('dim');
16324 return this.nodeHelper.square.contains(npos, pos, dim);
16328 'render': function(node, canvas){
16329 var pos = node.pos.getc(true),
16330 width = node.getData('width'),
16331 height = node.getData('height');
16332 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
16334 'contains': function(node, pos){
16335 var npos = node.pos.getc(true),
16336 width = node.getData('width'),
16337 height = node.getData('height');
16338 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
16342 'render': function(node, canvas){
16343 var pos = node.pos.getc(true),
16344 dim = node.getData('dim');
16345 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
16347 'contains': function(node, pos) {
16348 var npos = node.pos.getc(true),
16349 dim = node.getData('dim');
16350 return this.nodeHelper.triangle.contains(npos, pos, dim);
16354 'render': function(node, canvas){
16355 var pos = node.pos.getc(true),
16356 dim = node.getData('dim');
16357 this.nodeHelper.star.render('fill', pos, dim, canvas);
16359 'contains': function(node, pos) {
16360 var npos = node.pos.getc(true),
16361 dim = node.getData('dim');
16362 return this.nodeHelper.star.contains(npos, pos, dim);
16368 Class: RGraph.Plot.EdgeTypes
16370 This class contains a list of <Graph.Adjacence> built-in types.
16371 Edge types implemented are 'none', 'line' and 'arrow'.
16373 You can add your custom edge types, customizing your visualization to the extreme.
16378 RGraph.Plot.EdgeTypes.implement({
16380 'render': function(adj, canvas) {
16381 //print your custom edge to canvas
16384 'contains': function(adj, pos) {
16385 //return true if pos is inside the arc or false otherwise
16392 RGraph.Plot.EdgeTypes = new Class({
16395 'render': function(adj, canvas) {
16396 var from = adj.nodeFrom.pos.getc(true),
16397 to = adj.nodeTo.pos.getc(true);
16398 this.edgeHelper.line.render(from, to, canvas);
16400 'contains': function(adj, pos) {
16401 var from = adj.nodeFrom.pos.getc(true),
16402 to = adj.nodeTo.pos.getc(true);
16403 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
16407 'render': function(adj, canvas) {
16408 var from = adj.nodeFrom.pos.getc(true),
16409 to = adj.nodeTo.pos.getc(true),
16410 dim = adj.getData('dim'),
16411 direction = adj.data.$direction,
16412 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
16413 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
16415 'contains': function(adj, pos) {
16416 var from = adj.nodeFrom.pos.getc(true),
16417 to = adj.nodeTo.pos.getc(true);
16418 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
16427 * File: Hypertree.js
16434 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
16438 moebiusTransformation
16440 Calculates a moebius transformation for this point / complex.
16441 For more information go to:
16442 http://en.wikipedia.org/wiki/Moebius_transformation.
16446 c - An initialized Complex instance representing a translation Vector.
16449 Complex.prototype.moebiusTransformation = function(c) {
16450 var num = this.add(c);
16451 var den = c.$conjugate().$prod(this);
16453 return num.$div(den);
16457 moebiusTransformation
16459 Calculates a moebius transformation for the hyperbolic tree.
16461 <http://en.wikipedia.org/wiki/Moebius_transformation>
16465 graph - A <Graph> instance.
16467 prop - A property array.
16468 theta - Rotation angle.
16469 startPos - _optional_ start position.
16471 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
16472 this.eachNode(graph, function(elem) {
16473 for ( var i = 0; i < prop.length; i++) {
16474 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
16475 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
16483 A Hyperbolic Tree/Graph visualization.
16487 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
16488 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
16492 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.
16496 All <Loader> methods
16498 Constructor Options:
16500 Inherits options from
16503 - <Options.Controller>
16509 - <Options.NodeStyles>
16510 - <Options.Navigation>
16512 Additionally, there are other parameters and some default values changed
16514 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*.
16515 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.
16516 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
16517 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
16518 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
16520 Instance Properties:
16522 canvas - Access a <Canvas> instance.
16523 graph - Access a <Graph> instance.
16524 op - Access a <Hypertree.Op> instance.
16525 fx - Access a <Hypertree.Plot> instance.
16526 labels - Access a <Hypertree.Label> interface implementation.
16530 $jit.Hypertree = new Class( {
16532 Implements: [ Loader, Extras, Layouts.Radial ],
16534 initialize: function(controller) {
16535 var $Hypertree = $jit.Hypertree;
16546 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
16547 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
16549 var canvasConfig = this.config;
16550 if(canvasConfig.useCanvas) {
16551 this.canvas = canvasConfig.useCanvas;
16552 this.config.labelContainer = this.canvas.id + '-label';
16554 if(canvasConfig.background) {
16555 canvasConfig.background = $.merge({
16557 }, canvasConfig.background);
16559 this.canvas = new Canvas(this, canvasConfig);
16560 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
16563 this.graphOptions = {
16571 this.graph = new Graph(this.graphOptions, this.config.Node,
16573 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
16574 this.fx = new $Hypertree.Plot(this, $Hypertree);
16575 this.op = new $Hypertree.Op(this);
16579 // initialize extras
16580 this.initializeExtras();
16585 createLevelDistanceFunc
16587 Returns the levelDistance function used for calculating a node distance
16588 to its origin. This function returns a function that is computed
16589 per level and not per node, such that all nodes with the same depth will have the
16590 same distance to the origin. The resulting function gets the
16591 parent node as parameter and returns a float.
16594 createLevelDistanceFunc: function() {
16595 // get max viz. length.
16596 var r = this.getRadius();
16598 var depth = 0, max = Math.max, config = this.config;
16599 this.graph.eachNode(function(node) {
16600 depth = max(node._depth, depth);
16603 // node distance generator
16604 var genDistFunc = function(a) {
16605 return function(node) {
16607 var d = node._depth + 1;
16608 var acum = 0, pow = Math.pow;
16610 acum += pow(a, d--);
16612 return acum - config.offset;
16615 // estimate better edge length.
16616 for ( var i = 0.51; i <= 1; i += 0.01) {
16617 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
16618 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
16620 return genDistFunc(0.75);
16626 Returns the current radius of the visualization. If *config.radius* is *auto* then it
16627 calculates the radius by taking the smaller size of the <Canvas> widget.
16634 getRadius: function() {
16635 var rad = this.config.radius;
16636 if (rad !== "auto") { return rad; }
16637 var s = this.canvas.getSize();
16638 return Math.min(s.width, s.height) / 2;
16644 Computes positions and plots the tree.
16648 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
16651 refresh: function(reposition) {
16654 this.graph.eachNode(function(node) {
16655 node.startPos.rho = node.pos.rho = node.endPos.rho;
16656 node.startPos.theta = node.pos.theta = node.endPos.theta;
16667 Computes nodes' positions and restores the tree to its previous position.
16669 For calculating nodes' positions the root must be placed on its origin. This method does this
16670 and then attemps to restore the hypertree to its previous position.
16673 reposition: function() {
16674 this.compute('end');
16675 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
16676 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
16678 this.graph.eachNode(function(node) {
16680 node.endPos.rho = node.pos.rho;
16681 node.endPos.theta = node.pos.theta;
16689 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
16699 Animates the <Hypertree> to center the node specified by *id*.
16703 id - A <Graph.Node> id.
16704 opt - (optional|object) An object containing some extra properties described below
16705 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
16710 ht.onClick('someid');
16712 ht.onClick('someid', {
16718 onClick: function(id, opt) {
16719 var pos = this.graph.getNode(id).pos.getc(true);
16720 this.move(pos, opt);
16726 Translates the tree to the given position.
16730 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
16731 opt - This object has been defined in <Hypertree.onClick>
16736 ht.move({ x: 0, y: 0.7 }, {
16742 move: function(pos, opt) {
16743 var versor = $C(pos.x, pos.y);
16744 if (this.busy === false && versor.norm() < 1) {
16746 var root = this.graph.getClosestNodeToPos(versor), that = this;
16747 this.graph.computeLevels(root.id, 0);
16748 this.controller.onBeforeCompute(root);
16750 onComplete: $.empty
16752 this.fx.animate($.merge( {
16753 modes: [ 'moebius' ],
16756 onComplete: function() {
16765 $jit.Hypertree.$extend = true;
16767 (function(Hypertree) {
16770 Class: Hypertree.Op
16772 Custom extension of <Graph.Op>.
16776 All <Graph.Op> methods
16783 Hypertree.Op = new Class( {
16785 Implements: Graph.Op
16790 Class: Hypertree.Plot
16792 Custom extension of <Graph.Plot>.
16796 All <Graph.Plot> methods
16803 Hypertree.Plot = new Class( {
16805 Implements: Graph.Plot
16810 Object: Hypertree.Label
16812 Custom extension of <Graph.Label>.
16813 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
16817 All <Graph.Label> methods and subclasses.
16821 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
16824 Hypertree.Label = {};
16827 Hypertree.Label.Native
16829 Custom extension of <Graph.Label.Native>.
16833 All <Graph.Label.Native> methods
16837 <Graph.Label.Native>
16840 Hypertree.Label.Native = new Class( {
16841 Implements: Graph.Label.Native,
16843 initialize: function(viz) {
16847 renderLabel: function(canvas, node, controller) {
16848 var ctx = canvas.getCtx();
16849 var coord = node.pos.getc(true);
16850 var s = this.viz.getRadius();
16851 ctx.fillText(node.name, coord.x * s, coord.y * s);
16856 Hypertree.Label.SVG
16858 Custom extension of <Graph.Label.SVG>.
16862 All <Graph.Label.SVG> methods
16869 Hypertree.Label.SVG = new Class( {
16870 Implements: Graph.Label.SVG,
16872 initialize: function(viz) {
16879 Overrides abstract method placeLabel in <Graph.Plot>.
16883 tag - A DOM label element.
16884 node - A <Graph.Node>.
16885 controller - A configuration/controller object passed to the visualization.
16888 placeLabel: function(tag, node, controller) {
16889 var pos = node.pos.getc(true),
16890 canvas = this.viz.canvas,
16891 ox = canvas.translateOffsetX,
16892 oy = canvas.translateOffsetY,
16893 sx = canvas.scaleOffsetX,
16894 sy = canvas.scaleOffsetY,
16895 radius = canvas.getSize(),
16896 r = this.viz.getRadius();
16898 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
16899 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
16901 tag.setAttribute('x', labelPos.x);
16902 tag.setAttribute('y', labelPos.y);
16903 controller.onPlaceLabel(tag, node);
16908 Hypertree.Label.HTML
16910 Custom extension of <Graph.Label.HTML>.
16914 All <Graph.Label.HTML> methods.
16921 Hypertree.Label.HTML = new Class( {
16922 Implements: Graph.Label.HTML,
16924 initialize: function(viz) {
16930 Overrides abstract method placeLabel in <Graph.Plot>.
16934 tag - A DOM label element.
16935 node - A <Graph.Node>.
16936 controller - A configuration/controller object passed to the visualization.
16939 placeLabel: function(tag, node, controller) {
16940 var pos = node.pos.getc(true),
16941 canvas = this.viz.canvas,
16942 ox = canvas.translateOffsetX,
16943 oy = canvas.translateOffsetY,
16944 sx = canvas.scaleOffsetX,
16945 sy = canvas.scaleOffsetY,
16946 radius = canvas.getSize(),
16947 r = this.viz.getRadius();
16949 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
16950 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
16952 var style = tag.style;
16953 style.left = labelPos.x + 'px';
16954 style.top = labelPos.y + 'px';
16955 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
16957 controller.onPlaceLabel(tag, node);
16962 Class: Hypertree.Plot.NodeTypes
16964 This class contains a list of <Graph.Node> built-in types.
16965 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
16967 You can add your custom node types, customizing your visualization to the extreme.
16972 Hypertree.Plot.NodeTypes.implement({
16974 'render': function(node, canvas) {
16975 //print your custom node to canvas
16978 'contains': function(node, pos) {
16979 //return true if pos is inside the node or false otherwise
16986 Hypertree.Plot.NodeTypes = new Class({
16989 'contains': $.lambda(false)
16992 'render': function(node, canvas) {
16993 var nconfig = this.node,
16994 dim = node.getData('dim'),
16995 p = node.pos.getc();
16996 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
16997 p.$scale(node.scale);
16999 this.nodeHelper.circle.render('fill', p, dim, canvas);
17002 'contains': function(node, pos) {
17003 var dim = node.getData('dim'),
17004 npos = node.pos.getc().$scale(node.scale);
17005 return this.nodeHelper.circle.contains(npos, pos, dim);
17009 'render': function(node, canvas) {
17010 var pos = node.pos.getc().$scale(node.scale),
17011 width = node.getData('width'),
17012 height = node.getData('height');
17013 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
17015 'contains': function(node, pos) {
17016 var width = node.getData('width'),
17017 height = node.getData('height'),
17018 npos = node.pos.getc().$scale(node.scale);
17019 return this.nodeHelper.circle.contains(npos, pos, width, height);
17023 'render': function(node, canvas) {
17024 var nconfig = this.node,
17025 dim = node.getData('dim'),
17026 p = node.pos.getc();
17027 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17028 p.$scale(node.scale);
17030 this.nodeHelper.square.render('fill', p, dim, canvas);
17033 'contains': function(node, pos) {
17034 var dim = node.getData('dim'),
17035 npos = node.pos.getc().$scale(node.scale);
17036 return this.nodeHelper.square.contains(npos, pos, dim);
17040 'render': function(node, canvas) {
17041 var nconfig = this.node,
17042 width = node.getData('width'),
17043 height = node.getData('height'),
17044 pos = node.pos.getc();
17045 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
17046 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
17047 pos.$scale(node.scale);
17048 if (width > 0.2 && height > 0.2) {
17049 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
17052 'contains': function(node, pos) {
17053 var width = node.getData('width'),
17054 height = node.getData('height'),
17055 npos = node.pos.getc().$scale(node.scale);
17056 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
17060 'render': function(node, canvas) {
17061 var nconfig = this.node,
17062 dim = node.getData('dim'),
17063 p = node.pos.getc();
17064 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17065 p.$scale(node.scale);
17067 this.nodeHelper.triangle.render('fill', p, dim, canvas);
17070 'contains': function(node, pos) {
17071 var dim = node.getData('dim'),
17072 npos = node.pos.getc().$scale(node.scale);
17073 return this.nodeHelper.triangle.contains(npos, pos, dim);
17077 'render': function(node, canvas) {
17078 var nconfig = this.node,
17079 dim = node.getData('dim'),
17080 p = node.pos.getc();
17081 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17082 p.$scale(node.scale);
17084 this.nodeHelper.star.render('fill', p, dim, canvas);
17087 'contains': function(node, pos) {
17088 var dim = node.getData('dim'),
17089 npos = node.pos.getc().$scale(node.scale);
17090 return this.nodeHelper.star.contains(npos, pos, dim);
17096 Class: Hypertree.Plot.EdgeTypes
17098 This class contains a list of <Graph.Adjacence> built-in types.
17099 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
17101 You can add your custom edge types, customizing your visualization to the extreme.
17106 Hypertree.Plot.EdgeTypes.implement({
17108 'render': function(adj, canvas) {
17109 //print your custom edge to canvas
17112 'contains': function(adj, pos) {
17113 //return true if pos is inside the arc or false otherwise
17120 Hypertree.Plot.EdgeTypes = new Class({
17123 'render': function(adj, canvas) {
17124 var from = adj.nodeFrom.pos.getc(true),
17125 to = adj.nodeTo.pos.getc(true),
17126 r = adj.nodeFrom.scale;
17127 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
17129 'contains': function(adj, pos) {
17130 var from = adj.nodeFrom.pos.getc(true),
17131 to = adj.nodeTo.pos.getc(true),
17132 r = adj.nodeFrom.scale;
17133 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
17137 'render': function(adj, canvas) {
17138 var from = adj.nodeFrom.pos.getc(true),
17139 to = adj.nodeTo.pos.getc(true),
17140 r = adj.nodeFrom.scale,
17141 dim = adj.getData('dim'),
17142 direction = adj.data.$direction,
17143 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
17144 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
17146 'contains': function(adj, pos) {
17147 var from = adj.nodeFrom.pos.getc(true),
17148 to = adj.nodeTo.pos.getc(true),
17149 r = adj.nodeFrom.scale;
17150 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
17154 'render': function(adj, canvas) {
17155 var from = adj.nodeFrom.pos.getc(),
17156 to = adj.nodeTo.pos.getc(),
17157 dim = this.viz.getRadius();
17158 this.edgeHelper.hyperline.render(from, to, dim, canvas);
17160 'contains': $.lambda(false)
17164 })($jit.Hypertree);