blob: 9bf6e9ef6344a8a65c63ff6ca1d01975807c6d1b [file] [log] [blame]
srikanth116e6e82014-08-19 07:22:37 -07001/*
2Copyright (c) 2011 Sencha Inc. - Author: Nicolas Garcia Belmonte (http://philogb.github.com/)
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"), to deal
6in the Software without restriction, including without limitation the rights
7to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8copies of the Software, and to permit persons to whom the Software is
9furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in
12all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20THE SOFTWARE.
21
22 */
23 (function () {
24
25/*
26 File: Core.js
27
28 */
29
30/*
31 Object: $jit
32
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.
36 */
37window.$jit = function(w) {
38 w = w || window;
39 for(var k in $jit) {
40 if($jit[k].$extend) {
41 w[k] = $jit[k];
42 }
43 }
44};
45
46$jit.version = '2.0.1';
47/*
48 Object: $jit.id
49
50 Works just like *document.getElementById*
51
52 Example:
53 (start code js)
54 var element = $jit.id('elementId');
55 (end code)
56
57*/
58
59/*
60 Object: $jit.util
61
62 Contains utility functions.
63
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>.
67
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.
71
72 */
73var $ = function(d) {
74 return document.getElementById(d);
75};
76
77$.empty = function() {
78};
79
80/*
81 Method: extend
82
83 Augment an object by appending another object's properties.
84
85 Parameters:
86
87 original - (object) The object to be extended.
88 extended - (object) An object which properties are going to be appended to the original object.
89
90 Example:
91 (start code js)
92 $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
93 (end code)
94*/
95$.extend = function(original, extended) {
96 for ( var key in (extended || {}))
97 original[key] = extended[key];
98 return original;
99};
100
101$.lambda = function(value) {
102 return (typeof value == 'function') ? value : function() {
103 return value;
104 };
105};
106
107$.time = Date.now || function() {
108 return +new Date;
109};
110
111/*
112 Method: splat
113
114 Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
115
116 Parameters:
117
118 obj - (mixed) The object to be wrapped in an array.
119
120 Example:
121 (start code js)
122 $jit.util.splat(3); //[3]
123 $jit.util.splat([3]); //[3]
124 (end code)
125*/
126$.splat = function(obj) {
127 var type = $.type(obj);
128 return type ? ((type != 'array') ? [ obj ] : obj) : [];
129};
130
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;
136};
137$.type.s = Object.prototype.toString;
138
139/*
140 Method: each
141
142 Iterates through an iterable applying *f*.
143
144 Parameters:
145
146 iterable - (array) The original array.
147 fn - (function) The function to apply to the array elements.
148
149 Example:
150 (start code js)
151 $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
152 (end code)
153*/
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);
159 } else {
160 for ( var i = 0, l = iterable.length; i < l; i++)
161 fn(iterable[i], i);
162 }
163};
164
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;
169 }
170 return -1;
171};
172
173/*
174 Method: map
175
176 Maps or collects an array by applying *f*.
177
178 Parameters:
179
180 array - (array) The original array.
181 f - (function) The function to apply to the array elements.
182
183 Example:
184 (start code js)
185 $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
186 (end code)
187*/
188$.map = function(array, f) {
189 var ans = [];
190 $.each(array, function(elem, i) {
191 ans.push(f(elem, i));
192 });
193 return ans;
194};
195
196/*
197 Method: reduce
198
199 Iteratively applies the binary function *f* storing the result in an accumulator.
200
201 Parameters:
202
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.
206
207 Example:
208 (start code js)
209 $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
210 (end code)
211*/
212$.reduce = function(array, f, opt) {
213 var l = array.length;
214 if(l==0) return opt;
215 var acum = arguments.length == 3? opt : array[--l];
216 while(l--) {
217 acum = f(acum, array[l]);
218 }
219 return acum;
220};
221
222/*
223 Method: merge
224
225 Merges n-objects and their sub-objects creating a new, fresh object.
226
227 Parameters:
228
229 An arbitrary number of objects.
230
231 Example:
232 (start code js)
233 $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
234 (end code)
235*/
236$.merge = function() {
237 var mix = {};
238 for ( var i = 0, l = arguments.length; i < l; i++) {
239 var object = arguments[i];
240 if ($.type(object) != 'object')
241 continue;
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);
246 }
247 }
248 return mix;
249};
250
251$.unlink = function(object) {
252 var unlinked;
253 switch ($.type(object)) {
254 case 'object':
255 unlinked = {};
256 for ( var p in object)
257 unlinked[p] = $.unlink(object[p]);
258 break;
259 case 'array':
260 unlinked = [];
261 for ( var i = 0, l = object.length; i < l; i++)
262 unlinked[i] = $.unlink(object[i]);
263 break;
264 default:
265 return object;
266 }
267 return unlinked;
268};
269
270$.zip = function() {
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]);
275 }
276 ans.push(row);
277 }
278 return ans;
279};
280
281/*
282 Method: rgbToHex
283
284 Converts an RGB array into a Hex string.
285
286 Parameters:
287
288 srcArray - (array) An array with R, G and B values
289
290 Example:
291 (start code js)
292 $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
293 (end code)
294*/
295$.rgbToHex = function(srcArray, array) {
296 if (srcArray.length < 3)
297 return null;
298 if (srcArray.length == 4 && srcArray[3] == 0 && !array)
299 return 'transparent';
300 var hex = [];
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);
304 }
305 return array ? hex : '#' + hex.join('');
306};
307
308/*
309 Method: hexToRgb
310
311 Converts an Hex color string into an RGB array.
312
313 Parameters:
314
315 hex - (string) A color hex string.
316
317 Example:
318 (start code js)
319 $jit.util.hexToRgb('#fff'); //[255, 255, 255]
320 (end code)
321*/
322$.hexToRgb = function(hex) {
323 if (hex.length != 7) {
324 hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
325 hex.shift();
326 if (hex.length != 3)
327 return null;
328 var rgb = [];
329 for ( var i = 0; i < 3; i++) {
330 var value = hex[i];
331 if (value.length == 1)
332 value += value;
333 rgb.push(parseInt(value, 16));
334 }
335 return rgb;
336 } else {
337 hex = parseInt(hex.slice(1), 16);
338 return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
339 }
340};
341
342$.destroy = function(elem) {
343 $.clean(elem);
344 if (elem.parentNode)
345 elem.parentNode.removeChild(elem);
346 if (elem.clearAttributes)
347 elem.clearAttributes();
348};
349
350$.clean = function(elem) {
351 for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
352 $.destroy(ch[i]);
353 }
354};
355
356/*
357 Method: addEvent
358
359 Cross-browser add event listener.
360
361 Parameters:
362
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.
366
367 Example:
368 (start code js)
369 $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
370 (end code)
371*/
372$.addEvent = function(obj, type, fn) {
373 if (obj.addEventListener)
374 obj.addEventListener(type, fn, false);
375 else
376 obj.attachEvent('on' + type, fn);
377};
378
379$.addEvents = function(obj, typeObj) {
380 for(var type in typeObj) {
381 $.addEvent(obj, type, typeObj[type]);
382 }
383};
384
385$.hasClass = function(obj, klass) {
386 return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
387};
388
389$.addClass = function(obj, klass) {
390 if (!$.hasClass(obj, klass))
391 obj.className = (obj.className + " " + klass);
392};
393
394$.removeClass = function(obj, klass) {
395 obj.className = obj.className.replace(new RegExp(
396 '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
397};
398
399$.getPos = function(elem) {
400 var offset = getOffsets(elem);
401 var scroll = getScrolls(elem);
402 return {
403 x: offset.x - scroll.x,
404 y: offset.y - scroll.y
405 };
406
407 function getOffsets(elem) {
408 var position = {
409 x: 0,
410 y: 0
411 };
412 while (elem && !isBody(elem)) {
413 position.x += elem.offsetLeft;
414 position.y += elem.offsetTop;
415 elem = elem.offsetParent;
416 }
417 return position;
418 }
419
420 function getScrolls(elem) {
421 var position = {
422 x: 0,
423 y: 0
424 };
425 while (elem && !isBody(elem)) {
426 position.x += elem.scrollLeft;
427 position.y += elem.scrollTop;
428 elem = elem.parentNode;
429 }
430 return position;
431 }
432
433 function isBody(element) {
434 return (/^(?:body|html)$/i).test(element.tagName);
435 }
436};
437
438$.event = {
439 get: function(e, win) {
440 win = win || window;
441 return e || win.event;
442 },
443 getWheel: function(e) {
444 return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
445 },
446 isRightClick: function(e) {
447 return (e.which == 3 || e.button == 2);
448 },
449 getPos: function(e, win) {
450 // get mouse position
451 win = win || window;
452 e = e || win.event;
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) {
457 e = e.touches[0];
458 }
459 var page = {
460 x: e.pageX || (e.clientX + doc.scrollLeft),
461 y: e.pageY || (e.clientY + doc.scrollTop)
462 };
463 return page;
464 },
465 stop: function(e) {
466 if (e.stopPropagation) e.stopPropagation();
467 e.cancelBubble = true;
468 if (e.preventDefault) e.preventDefault();
469 else e.returnValue = false;
470 }
471};
472
473$jit.util = $jit.id = $;
474
475var 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]);
481 }
482 this.constructor = klass;
483 if (Class.prototyping)
484 return this;
485 var instance = this.initialize ? this.initialize.apply(this, arguments)
486 : this;
487 //typize
488 this.$$family = 'class';
489 return instance;
490 };
491
492 for ( var mutator in Class.Mutators) {
493 if (!properties[mutator])
494 continue;
495 properties = Class.Mutators[mutator](properties, properties[mutator]);
496 delete properties[mutator];
497 }
498
499 $.extend(klass, this);
500 klass.constructor = Class;
501 klass.prototype = properties;
502 return klass;
503};
504
505Class.Mutators = {
506
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];
514 }
515 }
516 delete Class.prototyping;
517 });
518 return self;
519 }
520
521};
522
523$.extend(Class, {
524
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);
533 }
534 } else if (type == 'object') {
535 object[key] = $.merge(previous, override);
536 } else {
537 object[key] = override;
538 }
539 }
540 return object;
541 },
542
543 override: function(object, name, method) {
544 var parent = Class.prototyping;
545 if (parent && object[name] != parent[name])
546 parent = null;
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;
552 return value;
553 };
554 object[name] = override;
555 }
556
557});
558
559Class.prototype.implement = function() {
560 var proto = this.prototype;
561 $.each(Array.prototype.slice.call(arguments || []), function(properties) {
562 Class.inherit(proto, properties);
563 });
564 return this;
565};
566
567$jit.Class = Class;
568
569/*
570 Object: $jit.json
571
572 Provides JSON utility functions.
573
574 Most of these functions are JSON-tree traversal and manipulation functions.
575*/
576$jit.json = {
577 /*
578 Method: prune
579
580 Clears all tree nodes having depth greater than maxLevel.
581
582 Parameters:
583
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.
586
587 */
588 prune: function(tree, maxLevel) {
589 this.each(tree, function(elem, i) {
590 if (i == maxLevel && elem.children) {
591 delete elem.children;
592 elem.children = [];
593 }
594 });
595 },
596 /*
597 Method: getParent
598
599 Returns the parent node of the node having _id_ as id.
600
601 Parameters:
602
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.
605
606 Returns:
607
608 A tree JSON node if any, or false otherwise.
609
610 */
611 getParent: function(tree, id) {
612 if (tree.id == id)
613 return false;
614 var ch = tree.children;
615 if (ch && ch.length > 0) {
616 for ( var i = 0; i < ch.length; i++) {
617 if (ch[i].id == id)
618 return tree;
619 else {
620 var ans = this.getParent(ch[i], id);
621 if (ans)
622 return ans;
623 }
624 }
625 }
626 return false;
627 },
628 /*
629 Method: getSubtree
630
631 Returns the subtree that matches the given id.
632
633 Parameters:
634
635 tree - (object) A JSON tree object. See also <Loader.loadJSON>.
636 id - (string) A node *unique* identifier.
637
638 Returns:
639
640 A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
641
642 */
643 getSubtree: function(tree, id) {
644 if (tree.id == id)
645 return tree;
646 for ( var i = 0, ch = tree.children; ch && i < ch.length; i++) {
647 var t = this.getSubtree(ch[i], id);
648 if (t != null)
649 return t;
650 }
651 return null;
652 },
653 /*
654 Method: eachLevel
655
656 Iterates on tree nodes with relative depth less or equal than a specified level.
657
658 Parameters:
659
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.
664
665 Example:
666 (start code js)
667 $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
668 alert(node.name + ' ' + depth);
669 });
670 (end code)
671 */
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);
678 }
679 }
680 },
681 /*
682 Method: each
683
684 A JSON tree iterator.
685
686 Parameters:
687
688 tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
689 action - (function) A function that receives a node.
690
691 Example:
692 (start code js)
693 $jit.json.each(tree, function(node) {
694 alert(node.name);
695 });
696 (end code)
697
698 */
699 each: function(tree, action) {
700 this.eachLevel(tree, 0, Number.MAX_VALUE, action);
701 }
702};
703
704
705/*
706 An object containing multiple type of transformations.
707*/
708
709$jit.Trans = {
710 $extend: true,
711
712 linear: function(p){
713 return p;
714 }
715};
716
717var Trans = $jit.Trans;
718
719(function(){
720
721 var makeTrans = function(transition, params){
722 params = $.splat(params);
723 return $.extend(transition, {
724 easeIn: function(pos){
725 return transition(pos, params);
726 },
727 easeOut: function(pos){
728 return 1 - transition(1 - pos, params);
729 },
730 easeInOut: function(pos){
731 return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
732 2 * (1 - pos), params)) / 2;
733 }
734 });
735 };
736
737 var transitions = {
738
739 Pow: function(p, x){
740 return Math.pow(p, x[0] || 6);
741 },
742
743 Expo: function(p){
744 return Math.pow(2, 8 * (p - 1));
745 },
746
747 Circ: function(p){
748 return 1 - Math.sin(Math.acos(p));
749 },
750
751 Sine: function(p){
752 return 1 - Math.sin((1 - p) * Math.PI / 2);
753 },
754
755 Back: function(p, x){
756 x = x[0] || 1.618;
757 return Math.pow(p, 2) * ((x + 1) * p - x);
758 },
759
760 Bounce: function(p){
761 var value;
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);
765 break;
766 }
767 }
768 return value;
769 },
770
771 Elastic: function(p, x){
772 return Math.pow(2, 10 * --p)
773 * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
774 }
775
776 };
777
778 $.each(transitions, function(val, key){
779 Trans[key] = makeTrans(val);
780 });
781
782 $.each( [
783 'Quad', 'Cubic', 'Quart', 'Quint'
784 ], function(elem, i){
785 Trans[elem] = makeTrans(function(p){
786 return Math.pow(p, [
787 i + 2
788 ]);
789 });
790 });
791
792})();
793
794/*
795 A Class that can perform animations for generic objects.
796
797 If you are looking for animation transitions please take a look at the <Trans> object.
798
799 Used by:
800
801 <Graph.Plot>
802
803 Based on:
804
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>.
806
807*/
808
809var Animation = new Class( {
810
811 initialize: function(options){
812 this.setOptions(options);
813 },
814
815 setOptions: function(options){
816 var opt = {
817 duration: 2500,
818 fps: 40,
819 transition: Trans.Quart.easeInOut,
820 compute: $.empty,
821 complete: $.empty,
822 link: 'ignore'
823 };
824 this.opt = $.merge(opt, options || {});
825 return this;
826 },
827
828 step: function(){
829 var time = $.time(), opt = this.opt;
830 if (time < this.time + opt.duration) {
831 var delta = opt.transition((time - this.time) / opt.duration);
832 opt.compute(delta);
833 } else {
834 this.timer = clearInterval(this.timer);
835 opt.compute(1);
836 opt.complete();
837 }
838 },
839
840 start: function(){
841 if (!this.check())
842 return this;
843 this.time = 0;
844 this.startTimer();
845 return this;
846 },
847
848 startTimer: function(){
849 var that = this, fps = this.opt.fps;
850 if (this.timer)
851 return false;
852 this.time = $.time() - this.time;
853 this.timer = setInterval((function(){
854 that.step();
855 }), Math.round(1000 / fps));
856 return true;
857 },
858
859 pause: function(){
860 this.stopTimer();
861 return this;
862 },
863
864 resume: function(){
865 this.startTimer();
866 return this;
867 },
868
869 stopTimer: function(){
870 if (!this.timer)
871 return false;
872 this.time = $.time() - this.time;
873 this.timer = clearInterval(this.timer);
874 return true;
875 },
876
877 check: function(){
878 if (!this.timer)
879 return true;
880 if (this.opt.link == 'cancel') {
881 this.stopTimer();
882 return true;
883 }
884 return false;
885 }
886});
887
888
889var Options = function() {
890 var args = arguments;
891 for(var i=0, l=args.length, ans={}; i<l; i++) {
892 var opt = Options[args[i]];
893 if(opt.$extend) {
894 $.extend(ans, opt);
895 } else {
896 ans[args[i]] = opt;
897 }
898 }
899 return ans;
900};
901
902/*
903 * File: Options.AreaChart.js
904 *
905*/
906
907/*
908 Object: Options.AreaChart
909
910 <AreaChart> options.
911 Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
912
913 Syntax:
914
915 (start code js)
916
917 Options.AreaChart = {
918 animate: true,
919 labelOffset: 3,
920 type: 'stacked',
921 selectOnHover: true,
922 showAggregates: true,
923 showLabels: true,
924 filterOnClick: false,
925 restoreOnRightClick: false
926 };
927
928 (end code)
929
930 Example:
931
932 (start code js)
933
934 var areaChart = new $jit.AreaChart({
935 animate: true,
936 type: 'stacked:gradient',
937 selectOnHover: true,
938 filterOnClick: true,
939 restoreOnRightClick: true
940 });
941
942 (end code)
943
944 Parameters:
945
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.
954
955*/
956
957Options.AreaChart = {
958 $extend: true,
959
960 animate: true,
961 labelOffset: 3, // label offset
962 type: 'stacked', // gradient
963 Tips: {
964 enable: false,
965 onShow: $.empty,
966 onHide: $.empty
967 },
968 Events: {
969 enable: false,
970 onClick: $.empty
971 },
972 selectOnHover: true,
973 showAggregates: true,
974 showLabels: true,
975 filterOnClick: false,
976 restoreOnRightClick: false
977};
978
979/*
980 * File: Options.Margin.js
981 *
982*/
983
984/*
985 Object: Options.Margin
986
987 Canvas drawing margins.
988
989 Syntax:
990
991 (start code js)
992
993 Options.Margin = {
994 top: 0,
995 left: 0,
996 right: 0,
997 bottom: 0
998 };
999
1000 (end code)
1001
1002 Example:
1003
1004 (start code js)
1005
1006 var viz = new $jit.Viz({
1007 Margin: {
1008 right: 10,
1009 bottom: 20
1010 }
1011 });
1012
1013 (end code)
1014
1015 Parameters:
1016
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.
1021
1022*/
1023
1024Options.Margin = {
1025 $extend: false,
1026
1027 top: 0,
1028 left: 0,
1029 right: 0,
1030 bottom: 0
1031};
1032
1033/*
1034 * File: Options.Canvas.js
1035 *
1036*/
1037
1038/*
1039 Object: Options.Canvas
1040
1041 These are Canvas general options, like where to append it in the DOM, its dimensions, background,
1042 and other more advanced options.
1043
1044 Syntax:
1045
1046 (start code js)
1047
1048 Options.Canvas = {
1049 injectInto: 'id',
1050 type: '2D', //'3D'
1051 width: false,
1052 height: false,
1053 useCanvas: false,
1054 withLabels: true,
1055 background: false
1056 };
1057 (end code)
1058
1059 Example:
1060
1061 (start code js)
1062 var viz = new $jit.Viz({
1063 injectInto: 'someContainerId',
1064 width: 500,
1065 height: 700
1066 });
1067 (end code)
1068
1069 Parameters:
1070
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.
1078*/
1079
1080Options.Canvas = {
1081 $extend: true,
1082
1083 injectInto: 'id',
1084 type: '2D',
1085 width: false,
1086 height: false,
1087 useCanvas: false,
1088 withLabels: true,
1089 background: false,
1090
1091 Scene: {
1092 Lighting: {
1093 enable: false,
1094 ambient: [1, 1, 1],
1095 directional: {
1096 direction: { x: -100, y: -100, z: -100 },
1097 color: [0.5, 0.3, 0.1]
1098 }
1099 }
1100 }
1101};
1102
1103/*
1104 * File: Options.Tree.js
1105 *
1106*/
1107
1108/*
1109 Object: Options.Tree
1110
1111 Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1112
1113 Syntax:
1114
1115 (start code js)
1116 Options.Tree = {
1117 orientation: "left",
1118 subtreeOffset: 8,
1119 siblingOffset: 5,
1120 indent:10,
1121 multitree: false,
1122 align:"center"
1123 };
1124 (end code)
1125
1126 Example:
1127
1128 (start code js)
1129 var st = new $jit.ST({
1130 orientation: 'left',
1131 subtreeOffset: 1,
1132 siblingOFfset: 5,
1133 multitree: true
1134 });
1135 (end code)
1136
1137 Parameters:
1138
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.
1145
1146*/
1147Options.Tree = {
1148 $extend: true,
1149
1150 orientation: "left",
1151 subtreeOffset: 8,
1152 siblingOffset: 5,
1153 indent:10,
1154 multitree: false,
1155 align:"center"
1156};
1157
1158
1159/*
1160 * File: Options.Node.js
1161 *
1162*/
1163
1164/*
1165 Object: Options.Node
1166
1167 Provides Node rendering options for Tree and Graph based visualizations.
1168
1169 Syntax:
1170
1171 (start code js)
1172 Options.Node = {
1173 overridable: false,
1174 type: 'circle',
1175 color: '#ccb',
1176 alpha: 1,
1177 dim: 3,
1178 height: 20,
1179 width: 90,
1180 autoHeight: false,
1181 autoWidth: false,
1182 lineWidth: 1,
1183 transform: true,
1184 align: "center",
1185 angularWidth:1,
1186 span:1,
1187 CanvasStyles: {}
1188 };
1189 (end code)
1190
1191 Example:
1192
1193 (start code js)
1194 var viz = new $jit.Viz({
1195 Node: {
1196 overridable: true,
1197 width: 30,
1198 autoHeight: true,
1199 type: 'rectangle'
1200 }
1201 });
1202 (end code)
1203
1204 Parameters:
1205
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.
1221
1222*/
1223Options.Node = {
1224 $extend: false,
1225
1226 overridable: false,
1227 type: 'circle',
1228 color: '#ccb',
1229 alpha: 1,
1230 dim: 3,
1231 height: 20,
1232 width: 90,
1233 autoHeight: false,
1234 autoWidth: false,
1235 lineWidth: 1,
1236 transform: true,
1237 align: "center",
1238 angularWidth:1,
1239 span:1,
1240 //Raw canvas styles to be
1241 //applied to the context instance
1242 //before plotting a node
1243 CanvasStyles: {}
1244};
1245
1246
1247/*
1248 * File: Options.Edge.js
1249 *
1250*/
1251
1252/*
1253 Object: Options.Edge
1254
1255 Provides Edge rendering options for Tree and Graph based visualizations.
1256
1257 Syntax:
1258
1259 (start code js)
1260 Options.Edge = {
1261 overridable: false,
1262 type: 'line',
1263 color: '#ccb',
1264 lineWidth: 1,
1265 dim:15,
1266 alpha: 1,
1267 CanvasStyles: {}
1268 };
1269 (end code)
1270
1271 Example:
1272
1273 (start code js)
1274 var viz = new $jit.Viz({
1275 Edge: {
1276 overridable: true,
1277 type: 'line',
1278 color: '#fff',
1279 CanvasStyles: {
1280 shadowColor: '#ccc',
1281 shadowBlur: 10
1282 }
1283 }
1284 });
1285 (end code)
1286
1287 Parameters:
1288
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.
1297
1298 See also:
1299
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.
1301*/
1302Options.Edge = {
1303 $extend: false,
1304
1305 overridable: false,
1306 type: 'line',
1307 color: '#ccb',
1308 lineWidth: 1,
1309 dim:15,
1310 alpha: 1,
1311 epsilon: 7,
1312
1313 //Raw canvas styles to be
1314 //applied to the context instance
1315 //before plotting an edge
1316 CanvasStyles: {}
1317};
1318
1319
1320/*
1321 * File: Options.Fx.js
1322 *
1323*/
1324
1325/*
1326 Object: Options.Fx
1327
1328 Provides animation options like duration of the animations, frames per second and animation transitions.
1329
1330 Syntax:
1331
1332 (start code js)
1333 Options.Fx = {
1334 fps:40,
1335 duration: 2500,
1336 transition: $jit.Trans.Quart.easeInOut,
1337 clearCanvas: true
1338 };
1339 (end code)
1340
1341 Example:
1342
1343 (start code js)
1344 var viz = new $jit.Viz({
1345 duration: 1000,
1346 fps: 35,
1347 transition: $jit.Trans.linear
1348 });
1349 (end code)
1350
1351 Parameters:
1352
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.
1357
1358 Object: $jit.Trans
1359
1360 This object is used for specifying different animation transitions in all visualizations.
1361
1362 There are many different type of animation transitions.
1363
1364 linear:
1365
1366 Displays a linear transition
1367
1368 >Trans.linear
1369
1370 (see Linear.png)
1371
1372 Quad:
1373
1374 Displays a Quadratic transition.
1375
1376 >Trans.Quad.easeIn
1377 >Trans.Quad.easeOut
1378 >Trans.Quad.easeInOut
1379
1380 (see Quad.png)
1381
1382 Cubic:
1383
1384 Displays a Cubic transition.
1385
1386 >Trans.Cubic.easeIn
1387 >Trans.Cubic.easeOut
1388 >Trans.Cubic.easeInOut
1389
1390 (see Cubic.png)
1391
1392 Quart:
1393
1394 Displays a Quartetic transition.
1395
1396 >Trans.Quart.easeIn
1397 >Trans.Quart.easeOut
1398 >Trans.Quart.easeInOut
1399
1400 (see Quart.png)
1401
1402 Quint:
1403
1404 Displays a Quintic transition.
1405
1406 >Trans.Quint.easeIn
1407 >Trans.Quint.easeOut
1408 >Trans.Quint.easeInOut
1409
1410 (see Quint.png)
1411
1412 Expo:
1413
1414 Displays an Exponential transition.
1415
1416 >Trans.Expo.easeIn
1417 >Trans.Expo.easeOut
1418 >Trans.Expo.easeInOut
1419
1420 (see Expo.png)
1421
1422 Circ:
1423
1424 Displays a Circular transition.
1425
1426 >Trans.Circ.easeIn
1427 >Trans.Circ.easeOut
1428 >Trans.Circ.easeInOut
1429
1430 (see Circ.png)
1431
1432 Sine:
1433
1434 Displays a Sineousidal transition.
1435
1436 >Trans.Sine.easeIn
1437 >Trans.Sine.easeOut
1438 >Trans.Sine.easeInOut
1439
1440 (see Sine.png)
1441
1442 Back:
1443
1444 >Trans.Back.easeIn
1445 >Trans.Back.easeOut
1446 >Trans.Back.easeInOut
1447
1448 (see Back.png)
1449
1450 Bounce:
1451
1452 Bouncy transition.
1453
1454 >Trans.Bounce.easeIn
1455 >Trans.Bounce.easeOut
1456 >Trans.Bounce.easeInOut
1457
1458 (see Bounce.png)
1459
1460 Elastic:
1461
1462 Elastic curve.
1463
1464 >Trans.Elastic.easeIn
1465 >Trans.Elastic.easeOut
1466 >Trans.Elastic.easeInOut
1467
1468 (see Elastic.png)
1469
1470 Based on:
1471
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>.
1473
1474
1475*/
1476Options.Fx = {
1477 $extend: true,
1478
1479 fps:40,
1480 duration: 2500,
1481 transition: $jit.Trans.Quart.easeInOut,
1482 clearCanvas: true
1483};
1484
1485/*
1486 * File: Options.Label.js
1487 *
1488*/
1489/*
1490 Object: Options.Label
1491
1492 Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.
1493
1494 Syntax:
1495
1496 (start code js)
1497 Options.Label = {
1498 overridable: false,
1499 type: 'HTML', //'SVG', 'Native'
1500 style: ' ',
1501 size: 10,
1502 family: 'sans-serif',
1503 textAlign: 'center',
1504 textBaseline: 'alphabetic',
1505 color: '#fff'
1506 };
1507 (end code)
1508
1509 Example:
1510
1511 (start code js)
1512 var viz = new $jit.Viz({
1513 Label: {
1514 type: 'Native',
1515 size: 11,
1516 color: '#ccc'
1517 }
1518 });
1519 (end code)
1520
1521 Parameters:
1522
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.
1529*/
1530Options.Label = {
1531 $extend: false,
1532
1533 overridable: false,
1534 type: 'HTML', //'SVG', 'Native'
1535 style: ' ',
1536 size: 10,
1537 family: 'sans-serif',
1538 textAlign: 'center',
1539 textBaseline: 'alphabetic',
1540 color: '#fff'
1541};
1542
1543
1544/*
1545 * File: Options.Tips.js
1546 *
1547 */
1548
1549/*
1550 Object: Options.Tips
1551
1552 Tips options
1553
1554 Syntax:
1555
1556 (start code js)
1557 Options.Tips = {
1558 enable: false,
1559 type: 'auto',
1560 offsetX: 20,
1561 offsetY: 20,
1562 onShow: $.empty,
1563 onHide: $.empty
1564 };
1565 (end code)
1566
1567 Example:
1568
1569 (start code js)
1570 var viz = new $jit.Viz({
1571 Tips: {
1572 enable: true,
1573 type: 'Native',
1574 offsetX: 10,
1575 offsetY: 10,
1576 onShow: function(tip, node) {
1577 tip.innerHTML = node.name;
1578 }
1579 }
1580 });
1581 (end code)
1582
1583 Parameters:
1584
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.
1591
1592*/
1593Options.Tips = {
1594 $extend: false,
1595
1596 enable: false,
1597 type: 'auto',
1598 offsetX: 20,
1599 offsetY: 20,
1600 force: false,
1601 onShow: $.empty,
1602 onHide: $.empty
1603};
1604
1605
1606/*
1607 * File: Options.NodeStyles.js
1608 *
1609 */
1610
1611/*
1612 Object: Options.NodeStyles
1613
1614 Apply different styles when a node is hovered or selected.
1615
1616 Syntax:
1617
1618 (start code js)
1619 Options.NodeStyles = {
1620 enable: false,
1621 type: 'auto',
1622 stylesHover: false,
1623 stylesClick: false
1624 };
1625 (end code)
1626
1627 Example:
1628
1629 (start code js)
1630 var viz = new $jit.Viz({
1631 NodeStyles: {
1632 enable: true,
1633 type: 'Native',
1634 stylesHover: {
1635 dim: 30,
1636 color: '#fcc'
1637 },
1638 duration: 600
1639 }
1640 });
1641 (end code)
1642
1643 Parameters:
1644
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.
1649*/
1650
1651Options.NodeStyles = {
1652 $extend: false,
1653
1654 enable: false,
1655 type: 'auto',
1656 stylesHover: false,
1657 stylesClick: false
1658};
1659
1660
1661/*
1662 * File: Options.Events.js
1663 *
1664*/
1665
1666/*
1667 Object: Options.Events
1668
1669 Configuration for adding mouse/touch event handlers to Nodes.
1670
1671 Syntax:
1672
1673 (start code js)
1674 Options.Events = {
1675 enable: false,
1676 enableForEdges: false,
1677 type: 'auto',
1678 onClick: $.empty,
1679 onRightClick: $.empty,
1680 onMouseMove: $.empty,
1681 onMouseEnter: $.empty,
1682 onMouseLeave: $.empty,
1683 onDragStart: $.empty,
1684 onDragMove: $.empty,
1685 onDragCancel: $.empty,
1686 onDragEnd: $.empty,
1687 onTouchStart: $.empty,
1688 onTouchMove: $.empty,
1689 onTouchEnd: $.empty,
1690 onTouchCancel: $.empty,
1691 onMouseWheel: $.empty
1692 };
1693 (end code)
1694
1695 Example:
1696
1697 (start code js)
1698 var viz = new $jit.Viz({
1699 Events: {
1700 enable: true,
1701 onClick: function(node, eventInfo, e) {
1702 viz.doSomething();
1703 },
1704 onMouseEnter: function(node, eventInfo, e) {
1705 viz.canvas.getElement().style.cursor = 'pointer';
1706 },
1707 onMouseLeave: function(node, eventInfo, e) {
1708 viz.canvas.getElement().style.cursor = '';
1709 }
1710 }
1711 });
1712 (end code)
1713
1714 Parameters:
1715
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.
1733*/
1734
1735Options.Events = {
1736 $extend: false,
1737
1738 enable: false,
1739 enableForEdges: false,
1740 type: 'auto',
1741 onClick: $.empty,
1742 onRightClick: $.empty,
1743 onMouseMove: $.empty,
1744 onMouseEnter: $.empty,
1745 onMouseLeave: $.empty,
1746 onDragStart: $.empty,
1747 onDragMove: $.empty,
1748 onDragCancel: $.empty,
1749 onDragEnd: $.empty,
1750 onTouchStart: $.empty,
1751 onTouchMove: $.empty,
1752 onTouchEnd: $.empty,
1753 onMouseWheel: $.empty
1754};
1755
1756/*
1757 * File: Options.Navigation.js
1758 *
1759*/
1760
1761/*
1762 Object: Options.Navigation
1763
1764 Panning and zooming options for Graph/Tree based visualizations. These options are implemented
1765 by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1766
1767 Syntax:
1768
1769 (start code js)
1770
1771 Options.Navigation = {
1772 enable: false,
1773 type: 'auto',
1774 panning: false, //true, 'avoid nodes'
1775 zooming: false
1776 };
1777
1778 (end code)
1779
1780 Example:
1781
1782 (start code js)
1783 var viz = new $jit.Viz({
1784 Navigation: {
1785 enable: true,
1786 panning: 'avoid nodes',
1787 zooming: 20
1788 }
1789 });
1790 (end code)
1791
1792 Parameters:
1793
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.
1798
1799*/
1800
1801Options.Navigation = {
1802 $extend: false,
1803
1804 enable: false,
1805 type: 'auto',
1806 panning: false, //true | 'avoid nodes'
1807 zooming: false
1808};
1809
1810/*
1811 * File: Options.Controller.js
1812 *
1813*/
1814
1815/*
1816 Object: Options.Controller
1817
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.
1820
1821 Implemented by:
1822
1823 All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1824
1825 Syntax:
1826
1827 (start code js)
1828
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,
1839 request: false
1840 };
1841
1842 (end code)
1843
1844 Example:
1845
1846 (start code js)
1847 var viz = new $jit.Viz({
1848 onBeforePlotNode: function(node) {
1849 if(node.selected) {
1850 node.setData('color', '#ffc');
1851 } else {
1852 node.removeData('color');
1853 }
1854 },
1855 onBeforePlotLine: function(adj) {
1856 if(adj.nodeFrom.selected && adj.nodeTo.selected) {
1857 adj.setData('color', '#ffc');
1858 } else {
1859 adj.removeData('color');
1860 }
1861 },
1862 onAfterCompute: function() {
1863 alert("computed!");
1864 }
1865 });
1866 (end code)
1867
1868 Parameters:
1869
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>.
1878
1879 *Used in <ST>, <TM.Base> and <Icicle> visualizations*
1880
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.
1882
1883 */
1884Options.Controller = {
1885 $extend: true,
1886
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,
1896 request: false
1897};
1898
1899
1900/*
1901 * File: Extras.js
1902 *
1903 * Provides Extras such as Tips and Style Effects.
1904 *
1905 * Description:
1906 *
1907 * Provides the <Tips> and <NodeStyles> classes and functions.
1908 *
1909 */
1910
1911/*
1912 * Manager for mouse events (clicking and mouse moving).
1913 *
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.
1918 *
1919 */
1920var ExtrasInitializer = {
1921 initialize: function(className, viz) {
1922 this.viz = 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();
1930 },
1931 initializePost: $.empty,
1932 setAsProperty: $.lambda(false),
1933 isEnabled: function() {
1934 return this.config.enable;
1935 },
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;
1941 if(group) {
1942 return related && related == this.viz.canvas.getCtx().canvas
1943 && !!target && this.isDescendantOf(target, labelContainer);
1944 } else {
1945 return this.isDescendantOf(target, labelContainer);
1946 }
1947 },
1948 isDescendantOf: function(elem, par) {
1949 while(elem && elem.parentNode) {
1950 if(elem.parentNode == par)
1951 return elem;
1952 elem = elem.parentNode;
1953 }
1954 return false;
1955 }
1956};
1957
1958var EventsInterface = {
1959 onMouseUp: $.empty,
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
1969};
1970
1971var MouseEventsManager = new Class({
1972 initialize: function(viz) {
1973 this.viz = viz;
1974 this.canvas = viz.canvas;
1975 this.node = false;
1976 this.edge = false;
1977 this.registeredObjects = [];
1978 this.attachEvents();
1979 },
1980
1981 attachEvents: function() {
1982 var htmlCanvas = this.canvas.getElement(),
1983 that = this;
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));
1991 },
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));
1996 },
1997 'mousemove': function(e, win) {
1998 that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
1999 },
2000 'mouseover': function(e, win) {
2001 that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2002 },
2003 'mouseout': function(e, win) {
2004 that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2005 },
2006 'touchstart': function(e, win) {
2007 that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2008 },
2009 'touchmove': function(e, win) {
2010 that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2011 },
2012 'touchend': function(e, win) {
2013 that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2014 }
2015 });
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);
2021 };
2022 //TODO(nico): this is a horrible check for non-gecko browsers!
2023 if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2024 $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2025 } else {
2026 htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2027 }
2028 },
2029
2030 register: function(obj) {
2031 this.registeredObjects.push(obj);
2032 },
2033
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);
2039 }
2040 },
2041
2042 makeEventObject: function(e, win) {
2043 var that = this,
2044 graph = this.viz.graph,
2045 fx = this.viz.fx,
2046 ntypes = fx.nodeTypes,
2047 etypes = fx.edgeTypes;
2048 return {
2049 pos: false,
2050 node: false,
2051 edge: false,
2052 contains: false,
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);
2066 this.pos = {
2067 x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2068 y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2069 };
2070 return this.pos;
2071 },
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());
2079 if(contains) {
2080 this.contains = contains;
2081 return that.node = this.node = n;
2082 }
2083 }
2084 return that.node = this.node = false;
2085 },
2086 getEdge: function() {
2087 if(this.getEdgeCalled) return this.edge;
2088 this.getEdgeCalled = true;
2089 var hashset = {};
2090 for(var id in graph.edges) {
2091 var edgeFrom = graph.edges[id];
2092 hashset[id] = true;
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());
2098 if(contains) {
2099 this.contains = contains;
2100 return that.edge = this.edge = e;
2101 }
2102 }
2103 }
2104 return that.edge = this.edge = false;
2105 },
2106 getContains: function() {
2107 if(this.getNodeCalled) return this.contains;
2108 this.getNode();
2109 return this.contains;
2110 }
2111 };
2112 }
2113});
2114
2115/*
2116 * Provides the initialization function for <NodeStyles> and <Tips> implemented
2117 * by all main visualizations.
2118 *
2119 */
2120var Extras = {
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()) {
2126 mem.register(obj);
2127 }
2128 if(obj.setAsProperty()) {
2129 that[k.toLowerCase()] = obj;
2130 }
2131 });
2132 }
2133};
2134
2135Extras.Classes = {};
2136/*
2137 Class: Events
2138
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.
2141*/
2142
2143Extras.Classes.Events = new Class({
2144 Implements: [ExtrasInitializer, EventsInterface],
2145
2146 initializePost: function() {
2147 this.fx = this.viz.fx;
2148 this.ntypes = this.viz.fx.nodeTypes;
2149 this.etypes = this.viz.fx.edgeTypes;
2150
2151 this.hovered = false;
2152 this.pressed = false;
2153 this.touched = false;
2154
2155 this.touchMoved = false;
2156 this.moved = false;
2157
2158 },
2159
2160 setAsProperty: $.lambda(true),
2161
2162 onMouseUp: function(e, win, event, isRightClick) {
2163 var evt = $.event.get(e, win);
2164 if(!this.moved) {
2165 if(isRightClick) {
2166 this.config.onRightClick(this.hovered, event, evt);
2167 } else {
2168 this.config.onClick(this.pressed, event, evt);
2169 }
2170 }
2171 if(this.pressed) {
2172 if(this.moved) {
2173 this.config.onDragEnd(this.pressed, event, evt);
2174 } else {
2175 this.config.onDragCancel(this.pressed, event, evt);
2176 }
2177 this.pressed = this.moved = false;
2178 }
2179 },
2180
2181 onMouseOut: function(e, win, event) {
2182 //mouseout a label
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),
2186 event, evt);
2187 this.hovered = false;
2188 return;
2189 }
2190 //mouseout canvas
2191 var rt = evt.relatedTarget,
2192 canvasWidget = this.canvas.getElement();
2193 while(rt && rt.parentNode) {
2194 if(canvasWidget == rt.parentNode) return;
2195 rt = rt.parentNode;
2196 }
2197 if(this.hovered) {
2198 this.config.onMouseLeave(this.hovered,
2199 event, evt);
2200 this.hovered = false;
2201 }
2202 },
2203
2204 onMouseOver: function(e, win, event) {
2205 //mouseover a label
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,
2210 event, evt);
2211 }
2212 },
2213
2214 onMouseMove: function(e, win, event) {
2215 var label, evt = $.event.get(e, win);
2216 if(this.pressed) {
2217 this.moved = true;
2218 this.config.onDragMove(this.pressed, event, evt);
2219 return;
2220 }
2221 if(this.dom) {
2222 this.config.onMouseMove(this.hovered,
2223 event, evt);
2224 } else {
2225 if(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());
2230 if(contains) {
2231 this.config.onMouseMove(hn, event, evt);
2232 return;
2233 } else {
2234 this.config.onMouseLeave(hn, event, evt);
2235 this.hovered = false;
2236 }
2237 }
2238 if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2239 this.config.onMouseEnter(this.hovered, event, evt);
2240 } else {
2241 this.config.onMouseMove(false, event, evt);
2242 }
2243 }
2244 },
2245
2246 onMouseWheel: function(e, win, delta) {
2247 this.config.onMouseWheel(delta, $.event.get(e, win));
2248 },
2249
2250 onMouseDown: function(e, win, event) {
2251 var evt = $.event.get(e, win), label;
2252 if(this.dom) {
2253 if(label = this.isLabel(e, win)) {
2254 this.pressed = this.viz.graph.getNode(label.id);
2255 }
2256 } else {
2257 this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2258 }
2259 this.pressed && this.config.onDragStart(this.pressed, event, evt);
2260 },
2261
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);
2266 } else {
2267 this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2268 }
2269 this.touched && this.config.onTouchStart(this.touched, event, evt);
2270 },
2271
2272 onTouchMove: function(e, win, event) {
2273 var evt = $.event.get(e, win);
2274 if(this.touched) {
2275 this.touchMoved = true;
2276 this.config.onTouchMove(this.touched, event, evt);
2277 }
2278 },
2279
2280 onTouchEnd: function(e, win, event) {
2281 var evt = $.event.get(e, win);
2282 if(this.touched) {
2283 if(this.touchMoved) {
2284 this.config.onTouchEnd(this.touched, event, evt);
2285 } else {
2286 this.config.onTouchCancel(this.touched, event, evt);
2287 }
2288 this.touched = this.touchMoved = false;
2289 }
2290 }
2291});
2292
2293/*
2294 Class: Tips
2295
2296 A class containing tip related functions. This class is used internally.
2297
2298 Used by:
2299
2300 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2301
2302 See also:
2303
2304 <Options.Tips>
2305*/
2306
2307Extras.Classes.Tips = new Class({
2308 Implements: [ExtrasInitializer, EventsInterface],
2309
2310 initializePost: function() {
2311 //add DOM tooltip
2312 if(document.body) {
2313 var tip = $('_tooltip') || document.createElement('div');
2314 tip.id = '_tooltip';
2315 tip.className = 'tip';
2316 $.extend(tip.style, {
2317 position: 'absolute',
2318 display: 'none',
2319 zIndex: 13000
2320 });
2321 document.body.appendChild(tip);
2322 this.tip = tip;
2323 this.node = false;
2324 }
2325 },
2326
2327 setAsProperty: $.lambda(true),
2328
2329 onMouseOut: function(e, win) {
2330 //mouseout a label
2331 var evt = $.event.get(e, win);
2332 if(this.dom && this.isLabel(e, win, true)) {
2333 this.hide(true);
2334 return;
2335 }
2336 //mouseout canvas
2337 var rt = e.relatedTarget,
2338 canvasWidget = this.canvas.getElement();
2339 while(rt && rt.parentNode) {
2340 if(canvasWidget == rt.parentNode) return;
2341 rt = rt.parentNode;
2342 }
2343 this.hide(false);
2344 },
2345
2346 onMouseOver: function(e, win) {
2347 //mouseover a label
2348 var label;
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);
2352 }
2353 },
2354
2355 onMouseMove: function(e, win, opt) {
2356 if(this.dom && this.isLabel(e, win)) {
2357 this.setTooltipPosition($.event.getPos(e, win));
2358 }
2359 if(!this.dom) {
2360 var node = opt.getNode();
2361 if(!node) {
2362 this.hide(true);
2363 return;
2364 }
2365 if(this.config.force || !this.node || this.node.id != node.id) {
2366 this.node = node;
2367 this.config.onShow(this.tip, node, opt.getContains());
2368 }
2369 this.setTooltipPosition($.event.getPos(e, win));
2370 }
2371 },
2372
2373 setTooltipPosition: function(pos) {
2374 var tip = this.tip,
2375 style = tip.style,
2376 cont = this.config;
2377 style.display = '';
2378 //get window dimensions
2379 var win = {
2380 'height': document.body.clientHeight,
2381 'width': document.body.clientWidth
2382 };
2383 //get tooltip dimensions
2384 var obj = {
2385 'width': tip.offsetWidth,
2386 'height': tip.offsetHeight
2387 };
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';
2394 },
2395
2396 hide: function(triggerCallback) {
2397 this.tip.style.display = 'none';
2398 triggerCallback && this.config.onHide();
2399 }
2400});
2401
2402/*
2403 Class: NodeStyles
2404
2405 Change node styles when clicking or hovering a node. This class is used internally.
2406
2407 Used by:
2408
2409 <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2410
2411 See also:
2412
2413 <Options.NodeStyles>
2414*/
2415Extras.Classes.NodeStyles = new Class({
2416 Implements: [ExtrasInitializer, EventsInterface],
2417
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();
2426
2427 this.down = false;
2428 this.move = false;
2429 },
2430
2431 onMouseOut: function(e, win) {
2432 this.down = this.move = false;
2433 if(!this.hoveredNode) return;
2434 //mouseout a label
2435 if(this.dom && this.isLabel(e, win, true)) {
2436 this.toggleStylesOnHover(this.hoveredNode, false);
2437 }
2438 //mouseout canvas
2439 var rt = e.relatedTarget,
2440 canvasWidget = this.canvas.getElement();
2441 while(rt && rt.parentNode) {
2442 if(canvasWidget == rt.parentNode) return;
2443 rt = rt.parentNode;
2444 }
2445 this.toggleStylesOnHover(this.hoveredNode, false);
2446 this.hoveredNode = false;
2447 },
2448
2449 onMouseOver: function(e, win) {
2450 //mouseover a label
2451 var label;
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);
2457 }
2458 },
2459
2460 onMouseDown: function(e, win, event, isRightClick) {
2461 if(isRightClick) return;
2462 var label;
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();
2467 }
2468 this.move = false;
2469 },
2470
2471 onMouseUp: function(e, win, event, isRightClick) {
2472 if(isRightClick) return;
2473 if(!this.move) {
2474 this.onClick(event.getNode());
2475 }
2476 this.down = this.move = false;
2477 },
2478
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];
2484 }
2485 return restoredStyles;
2486 },
2487
2488 toggleStylesOnHover: function(node, set) {
2489 if(this.nodeStylesOnHover) {
2490 this.toggleStylesOn('Hover', node, set);
2491 }
2492 },
2493
2494 toggleStylesOnClick: function(node, set) {
2495 if(this.nodeStylesOnClick) {
2496 this.toggleStylesOn('Click', node, set);
2497 }
2498 },
2499
2500 toggleStylesOn: function(type, node, set) {
2501 var viz = this.viz;
2502 var nStyles = this.nStyles;
2503 if(set) {
2504 var that = this;
2505 if(!node.styles) {
2506 node.styles = $.merge(node.data, {});
2507 }
2508 for(var s in this['nodeStylesOn' + type]) {
2509 var $s = '$' + s;
2510 if(!($s in node.styles)) {
2511 node.styles[$s] = node.getData(s);
2512 }
2513 }
2514 viz.fx.nodeFx($.extend({
2515 'elements': {
2516 'id': node.id,
2517 'properties': that['nodeStylesOn' + type]
2518 },
2519 transition: Trans.Quart.easeOut,
2520 duration:300,
2521 fps:40
2522 }, this.config));
2523 } else {
2524 var restoredStyles = this.getRestoredStyles(node, type);
2525 viz.fx.nodeFx($.extend({
2526 'elements': {
2527 'id': node.id,
2528 'properties': restoredStyles
2529 },
2530 transition: Trans.Quart.easeOut,
2531 duration:300,
2532 fps:40
2533 }, this.config));
2534 }
2535 },
2536
2537 onClick: function(node) {
2538 if(!node) return;
2539 var nStyles = this.nodeStylesOnClick;
2540 if(!nStyles) return;
2541 //if the node is selected then unselect it
2542 if(node.selected) {
2543 this.toggleStylesOnClick(node, false);
2544 delete node.selected;
2545 } else {
2546 //unselect all selected nodes...
2547 this.viz.graph.eachNode(function(n) {
2548 if(n.selected) {
2549 for(var s in nStyles) {
2550 n.setData(s, n.styles['$' + s], 'end');
2551 }
2552 delete n.selected;
2553 }
2554 });
2555 //select clicked node
2556 this.toggleStylesOnClick(node, true);
2557 node.selected = true;
2558 delete node.hovered;
2559 this.hoveredNode = false;
2560 }
2561 },
2562
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;
2570
2571 if(!this.dom) {
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;
2577 }
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');
2592 }
2593 delete n.hovered;
2594 }
2595 });
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;
2607 }
2608 }
2609 }
2610});
2611
2612Extras.Classes.Navigation = new Class({
2613 Implements: [ExtrasInitializer, EventsInterface],
2614
2615 initializePost: function() {
2616 this.pos = false;
2617 this.pressed = false;
2618 },
2619
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);
2626 },
2627
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;
2638 this.pos.x *= sx;
2639 this.pos.x += ox;
2640 this.pos.y *= sy;
2641 this.pos.y += oy;
2642 },
2643
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;
2655 currentPos.x *= sx;
2656 currentPos.y *= sy;
2657 currentPos.x += ox;
2658 currentPos.y += oy;
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);
2663 },
2664
2665 onMouseUp: function(e, win, eventInfo, isRightClick) {
2666 if(!this.config.panning) return;
2667 this.pressed = false;
2668 }
2669});
2670
2671
2672/*
2673 * File: Canvas.js
2674 *
2675 */
2676
2677/*
2678 Class: Canvas
2679
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>.
2682
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.
2685
2686 Example:
2687
2688 Suppose we have this HTML
2689
2690 (start code xml)
2691 <div id="infovis"></div>
2692 (end code)
2693
2694 Now we create a new Visualization
2695
2696 (start code js)
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.
2702 'width': 900,
2703 'height':500
2704 });
2705 (end code)
2706
2707 The generated HTML will look like this
2708
2709 (start code xml)
2710 <div id="infovis">
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">
2716 </div>
2717 </div>
2718 </div>
2719 (end code)
2720
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*.
2723 */
2724
2725var Canvas;
2726(function() {
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]);
2736 } else {
2737 elem[p] = props[p];
2738 }
2739 }
2740 if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2741 elem = G_vmlCanvasManager.initElement(document.body.appendChild(elem));
2742 }
2743 return elem;
2744 }
2745 //canvas widget which we will call just Canvas
2746 $jit.Canvas = Canvas = new Class({
2747 canvases: [],
2748 pos: false,
2749 element: false,
2750 labelContainer: false,
2751 translateOffsetX: 0,
2752 translateOffsetY: 0,
2753 scaleOffsetX: 1,
2754 scaleOffsetY: 1,
2755
2756 initialize: function(viz, opt) {
2757 this.viz = viz;
2758 this.opt = this.config = opt;
2759 var id = $.type(opt.injectInto) == 'string'?
2760 opt.injectInto:opt.injectInto.id,
2761 type = opt.type,
2762 idLabel = id + "-label",
2763 wrapper = $(id),
2764 width = opt.width || wrapper.offsetWidth,
2765 height = opt.height || wrapper.offsetHeight;
2766 this.id = id;
2767 //canvas options
2768 var canvasOptions = {
2769 injectInto: id,
2770 width: width,
2771 height: height
2772 };
2773 //create main wrapper
2774 this.element = $E('div', {
2775 'id': id + '-canvaswidget',
2776 'style': {
2777 'position': 'relative',
2778 'width': width + 'px',
2779 'height': height + 'px'
2780 }
2781 });
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) {
2789 viz.fx.plot();
2790 },
2791 resize: function() {
2792 viz.refresh();
2793 }
2794 }));
2795 //create secondary canvas
2796 var back = opt.background;
2797 if(back) {
2798 var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2799 this.canvases.push(new Canvas.Base[type](backCanvas));
2800 }
2801 //insert canvases
2802 var len = this.canvases.length;
2803 while(len--) {
2804 this.element.appendChild(this.canvases[len].canvas);
2805 if(len > 0) {
2806 this.canvases[len].plot();
2807 }
2808 }
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
2817 }, 500);
2818 });
2819 },
2820 /*
2821 Method: getCtx
2822
2823 Returns the main canvas context object
2824
2825 Example:
2826
2827 (start code js)
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;
2832 (end code)
2833 */
2834 getCtx: function(i) {
2835 return this.canvases[i || 0].getCtx();
2836 },
2837 /*
2838 Method: getConfig
2839
2840 Returns the current Configuration for this Canvas Widget.
2841
2842 Example:
2843
2844 (start code js)
2845 var config = canvas.getConfig();
2846 (end code)
2847 */
2848 getConfig: function() {
2849 return this.opt;
2850 },
2851 /*
2852 Method: getElement
2853
2854 Returns the main Canvas DOM wrapper
2855
2856 Example:
2857
2858 (start code js)
2859 var wrapper = canvas.getElement();
2860 //Returns <div id="infovis-canvaswidget" ... >...</div> as element
2861 (end code)
2862 */
2863 getElement: function() {
2864 return this.element;
2865 },
2866 /*
2867 Method: getSize
2868
2869 Returns canvas dimensions.
2870
2871 Returns:
2872
2873 An object with *width* and *height* properties.
2874
2875 Example:
2876 (start code js)
2877 canvas.getSize(); //returns { width: 900, height: 500 }
2878 (end code)
2879 */
2880 getSize: function(i) {
2881 return this.canvases[i || 0].getSize();
2882 },
2883 /*
2884 Method: resize
2885
2886 Resizes the canvas.
2887
2888 Parameters:
2889
2890 width - New canvas width.
2891 height - New canvas height.
2892
2893 Example:
2894
2895 (start code js)
2896 canvas.resize(width, height);
2897 (end code)
2898
2899 */
2900 resize: function(width, height) {
2901 this.getPos(true);
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);
2906 }
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';
2912 },
2913 /*
2914 Method: translate
2915
2916 Applies a translation to the canvas.
2917
2918 Parameters:
2919
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.
2923
2924 Example:
2925
2926 (start code js)
2927 canvas.translate(30, 30);
2928 (end code)
2929
2930 */
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);
2936 }
2937 },
2938 /*
2939 Method: scale
2940
2941 Scales the canvas.
2942
2943 Parameters:
2944
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.
2948
2949 Example:
2950
2951 (start code js)
2952 canvas.scale(0.5, 0.5);
2953 (end code)
2954
2955 */
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);
2965 }
2966 this.translate(dx, dy, false);
2967 },
2968 /*
2969 Method: getPos
2970
2971 Returns the canvas position as an *x, y* object.
2972
2973 Parameters:
2974
2975 force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
2976
2977 Returns:
2978
2979 An object with *x* and *y* properties.
2980
2981 Example:
2982 (start code js)
2983 canvas.getPos(true); //returns { x: 900, y: 500 }
2984 (end code)
2985 */
2986 getPos: function(force){
2987 if(force || !this.pos) {
2988 return this.pos = $.getPos(this.getElement());
2989 }
2990 return this.pos;
2991 },
2992 /*
2993 Method: clear
2994
2995 Clears the canvas.
2996 */
2997 clear: function(i){
2998 this.canvases[i||0].clear();
2999 },
3000
3001 path: function(type, action){
3002 var ctx = this.canvases[0].getCtx();
3003 ctx.beginPath();
3004 action(ctx);
3005 ctx[type]();
3006 ctx.closePath();
3007 },
3008
3009 createLabelContainer: function(type, idLabel, dim) {
3010 var NS = 'http://www.w3.org/2000/svg';
3011 if(type == 'HTML' || type == 'Native') {
3012 return $E('div', {
3013 'id': idLabel,
3014 'style': {
3015 'overflow': 'visible',
3016 'position': 'absolute',
3017 'top': 0,
3018 'left': 0,
3019 'width': dim.width + 'px',
3020 'height': 0
3021 }
3022 });
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;
3038 }
3039 }
3040 });
3041 //base canvas wrapper
3042 Canvas.Base = {};
3043 Canvas.Base['2D'] = new Class({
3044 translateOffsetX: 0,
3045 translateOffsetY: 0,
3046 scaleOffsetX: 1,
3047 scaleOffsetY: 1,
3048
3049 initialize: function(viz) {
3050 this.viz = viz;
3051 this.opt = viz.config;
3052 this.size = false;
3053 this.createCanvas();
3054 this.translateToCenter();
3055 },
3056 createCanvas: function() {
3057 var opt = this.opt,
3058 width = opt.width,
3059 height = opt.height;
3060 this.canvas = $E('canvas', {
3061 'id': opt.injectInto + opt.idSuffix,
3062 'width': width,
3063 'height': height,
3064 'style': {
3065 'position': 'absolute',
3066 'top': 0,
3067 'left': 0,
3068 'width': width + 'px',
3069 'height': height + 'px'
3070 }
3071 });
3072 },
3073 getCtx: function() {
3074 if(!this.ctx)
3075 return this.ctx = this.canvas.getContext('2d');
3076 return this.ctx;
3077 },
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
3084 };
3085 },
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);
3093 },
3094 resize: function(width, height) {
3095 var size = this.getSize(),
3096 canvas = this.canvas,
3097 styles = canvas.style;
3098 this.size = false;
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);
3106 } else {
3107 this.translateToCenter();
3108 }
3109 this.translateOffsetX =
3110 this.translateOffsetY = 0;
3111 this.scaleOffsetX =
3112 this.scaleOffsetY = 1;
3113 this.clear();
3114 this.viz.resize(width, height, this);
3115 },
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();
3123 },
3124 scale: function(x, y, disablePlot) {
3125 this.scaleOffsetX *= x;
3126 this.scaleOffsetY *= y;
3127 this.getCtx().scale(x, y);
3128 !disablePlot && this.plot();
3129 },
3130 clear: function(){
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);
3139 },
3140 plot: function() {
3141 this.clear();
3142 this.viz.plot(this);
3143 }
3144 });
3145 //background canvases
3146 //TODO(nico): document this!
3147 Canvas.Background = {};
3148 Canvas.Background.Circles = new Class({
3149 initialize: function(viz, options) {
3150 this.viz = viz;
3151 this.config = $.merge({
3152 idSuffix: '-bkcanvas',
3153 levelDistance: 100,
3154 numberOfCircles: 6,
3155 CanvasStyles: {},
3156 offset: 0
3157 }, options);
3158 },
3159 resize: function(width, height, base) {
3160 this.plot(base);
3161 },
3162 plot: function(base) {
3163 var canvas = base.canvas,
3164 ctx = base.getCtx(),
3165 conf = this.config,
3166 styles = conf.CanvasStyles;
3167 //set canvas styles
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++) {
3172 ctx.beginPath();
3173 ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3174 ctx.stroke();
3175 ctx.closePath();
3176 }
3177 //TODO(nico): print labels too!
3178 }
3179 });
3180})();
3181
3182
3183/*
3184 * File: Polar.js
3185 *
3186 * Defines the <Polar> class.
3187 *
3188 * Description:
3189 *
3190 * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3191 *
3192 * See also:
3193 *
3194 * <http://en.wikipedia.org/wiki/Polar_coordinates>
3195 *
3196*/
3197
3198/*
3199 Class: Polar
3200
3201 A multi purpose polar representation.
3202
3203 Description:
3204
3205 The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3206
3207 See also:
3208
3209 <http://en.wikipedia.org/wiki/Polar_coordinates>
3210
3211 Parameters:
3212
3213 theta - An angle.
3214 rho - The norm.
3215*/
3216
3217var Polar = function(theta, rho) {
3218 this.theta = theta || 0;
3219 this.rho = rho || 0;
3220};
3221
3222$jit.Polar = Polar;
3223
3224Polar.prototype = {
3225 /*
3226 Method: getc
3227
3228 Returns a complex number.
3229
3230 Parameters:
3231
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*.
3233
3234 Returns:
3235
3236 A complex number.
3237 */
3238 getc: function(simple) {
3239 return this.toComplex(simple);
3240 },
3241
3242 /*
3243 Method: getp
3244
3245 Returns a <Polar> representation.
3246
3247 Returns:
3248
3249 A variable in polar coordinates.
3250 */
3251 getp: function() {
3252 return this;
3253 },
3254
3255
3256 /*
3257 Method: set
3258
3259 Sets a number.
3260
3261 Parameters:
3262
3263 v - A <Complex> or <Polar> instance.
3264
3265 */
3266 set: function(v) {
3267 v = v.getp();
3268 this.theta = v.theta; this.rho = v.rho;
3269 },
3270
3271 /*
3272 Method: setc
3273
3274 Sets a <Complex> number.
3275
3276 Parameters:
3277
3278 x - A <Complex> number real part.
3279 y - A <Complex> number imaginary part.
3280
3281 */
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;
3286 },
3287
3288 /*
3289 Method: setp
3290
3291 Sets a polar number.
3292
3293 Parameters:
3294
3295 theta - A <Polar> number angle property.
3296 rho - A <Polar> number rho property.
3297
3298 */
3299 setp: function(theta, rho) {
3300 this.theta = theta;
3301 this.rho = rho;
3302 },
3303
3304 /*
3305 Method: clone
3306
3307 Returns a copy of the current object.
3308
3309 Returns:
3310
3311 A copy of the real object.
3312 */
3313 clone: function() {
3314 return new Polar(this.theta, this.rho);
3315 },
3316
3317 /*
3318 Method: toComplex
3319
3320 Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3321
3322 Parameters:
3323
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*.
3325
3326 Returns:
3327
3328 A new <Complex> instance.
3329 */
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);
3335 },
3336
3337 /*
3338 Method: add
3339
3340 Adds two <Polar> instances.
3341
3342 Parameters:
3343
3344 polar - A <Polar> number.
3345
3346 Returns:
3347
3348 A new Polar instance.
3349 */
3350 add: function(polar) {
3351 return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3352 },
3353
3354 /*
3355 Method: scale
3356
3357 Scales a polar norm.
3358
3359 Parameters:
3360
3361 number - A scale factor.
3362
3363 Returns:
3364
3365 A new Polar instance.
3366 */
3367 scale: function(number) {
3368 return new Polar(this.theta, this.rho * number);
3369 },
3370
3371 /*
3372 Method: equals
3373
3374 Comparison method.
3375
3376 Returns *true* if the theta and rho properties are equal.
3377
3378 Parameters:
3379
3380 c - A <Polar> number.
3381
3382 Returns:
3383
3384 *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3385 */
3386 equals: function(c) {
3387 return this.theta == c.theta && this.rho == c.rho;
3388 },
3389
3390 /*
3391 Method: $add
3392
3393 Adds two <Polar> instances affecting the current object.
3394
3395 Paramters:
3396
3397 polar - A <Polar> instance.
3398
3399 Returns:
3400
3401 The changed object.
3402 */
3403 $add: function(polar) {
3404 this.theta = this.theta + polar.theta; this.rho += polar.rho;
3405 return this;
3406 },
3407
3408 /*
3409 Method: $madd
3410
3411 Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3412
3413 Parameters:
3414
3415 polar - A <Polar> instance.
3416
3417 Returns:
3418
3419 The changed object.
3420 */
3421 $madd: function(polar) {
3422 this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3423 return this;
3424 },
3425
3426
3427 /*
3428 Method: $scale
3429
3430 Scales a polar instance affecting the object.
3431
3432 Parameters:
3433
3434 number - A scaling factor.
3435
3436 Returns:
3437
3438 The changed object.
3439 */
3440 $scale: function(number) {
3441 this.rho *= number;
3442 return this;
3443 },
3444
3445 /*
3446 Method: isZero
3447
3448 Returns *true* if the number is zero.
3449
3450 */
3451 isZero: function () {
3452 var almostZero = 0.0001, abs = Math.abs;
3453 return abs(this.theta) < almostZero && abs(this.rho) < almostZero;
3454 },
3455
3456 /*
3457 Method: interpolate
3458
3459 Calculates a polar interpolation between two points at a given delta moment.
3460
3461 Parameters:
3462
3463 elem - A <Polar> instance.
3464 delta - A delta factor ranging [0, 1].
3465
3466 Returns:
3467
3468 A new <Polar> instance representing an interpolation between _this_ and _elem_
3469 */
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;
3474 return a;
3475 };
3476 var tt = this.theta, et = elem.theta;
3477 var sum, diff = Math.abs(tt - et);
3478 if(diff == pi) {
3479 if(tt > et) {
3480 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3481 } else {
3482 sum = ch((et - pi2 + (tt - (et)) * delta));
3483 }
3484 } else if(diff >= pi) {
3485 if(tt > et) {
3486 sum = ch((et + ((tt - pi2) - et) * delta)) ;
3487 } else {
3488 sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3489 }
3490 } else {
3491 sum = ch((et + (tt - et) * delta)) ;
3492 }
3493 var r = (this.rho - elem.rho) * delta + elem.rho;
3494 return {
3495 'theta': sum,
3496 'rho': r
3497 };
3498 }
3499};
3500
3501
3502var $P = function(a, b) { return new Polar(a, b); };
3503
3504Polar.KER = $P(0, 0);
3505
3506
3507
3508/*
3509 * File: Complex.js
3510 *
3511 * Defines the <Complex> class.
3512 *
3513 * Description:
3514 *
3515 * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3516 *
3517 * See also:
3518 *
3519 * <http://en.wikipedia.org/wiki/Complex_number>
3520 *
3521*/
3522
3523/*
3524 Class: Complex
3525
3526 A multi-purpose Complex Class with common methods.
3527
3528 Description:
3529
3530 The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3531
3532 See also:
3533
3534 <http://en.wikipedia.org/wiki/Complex_number>
3535
3536 Parameters:
3537
3538 x - _optional_ A Complex number real part.
3539 y - _optional_ A Complex number imaginary part.
3540
3541*/
3542
3543var Complex = function(x, y) {
3544 this.x = x || 0;
3545 this.y = y || 0;
3546};
3547
3548$jit.Complex = Complex;
3549
3550Complex.prototype = {
3551 /*
3552 Method: getc
3553
3554 Returns a complex number.
3555
3556 Returns:
3557
3558 A complex number.
3559 */
3560 getc: function() {
3561 return this;
3562 },
3563
3564 /*
3565 Method: getp
3566
3567 Returns a <Polar> representation of this number.
3568
3569 Parameters:
3570
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*.
3572
3573 Returns:
3574
3575 A variable in <Polar> coordinates.
3576 */
3577 getp: function(simple) {
3578 return this.toPolar(simple);
3579 },
3580
3581
3582 /*
3583 Method: set
3584
3585 Sets a number.
3586
3587 Parameters:
3588
3589 c - A <Complex> or <Polar> instance.
3590
3591 */
3592 set: function(c) {
3593 c = c.getc(true);
3594 this.x = c.x;
3595 this.y = c.y;
3596 },
3597
3598 /*
3599 Method: setc
3600
3601 Sets a complex number.
3602
3603 Parameters:
3604
3605 x - A <Complex> number Real part.
3606 y - A <Complex> number Imaginary part.
3607
3608 */
3609 setc: function(x, y) {
3610 this.x = x;
3611 this.y = y;
3612 },
3613
3614 /*
3615 Method: setp
3616
3617 Sets a polar number.
3618
3619 Parameters:
3620
3621 theta - A <Polar> number theta property.
3622 rho - A <Polar> number rho property.
3623
3624 */
3625 setp: function(theta, rho) {
3626 this.x = Math.cos(theta) * rho;
3627 this.y = Math.sin(theta) * rho;
3628 },
3629
3630 /*
3631 Method: clone
3632
3633 Returns a copy of the current object.
3634
3635 Returns:
3636
3637 A copy of the real object.
3638 */
3639 clone: function() {
3640 return new Complex(this.x, this.y);
3641 },
3642
3643 /*
3644 Method: toPolar
3645
3646 Transforms cartesian to polar coordinates.
3647
3648 Parameters:
3649
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*.
3651
3652 Returns:
3653
3654 A new <Polar> instance.
3655 */
3656
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);
3663 },
3664 /*
3665 Method: norm
3666
3667 Calculates a <Complex> number norm.
3668
3669 Returns:
3670
3671 A real number representing the complex norm.
3672 */
3673 norm: function () {
3674 return Math.sqrt(this.squaredNorm());
3675 },
3676
3677 /*
3678 Method: squaredNorm
3679
3680 Calculates a <Complex> number squared norm.
3681
3682 Returns:
3683
3684 A real number representing the complex squared norm.
3685 */
3686 squaredNorm: function () {
3687 return this.x*this.x + this.y*this.y;
3688 },
3689
3690 /*
3691 Method: add
3692
3693 Returns the result of adding two complex numbers.
3694
3695 Does not alter the original object.
3696
3697 Parameters:
3698
3699 pos - A <Complex> instance.
3700
3701 Returns:
3702
3703 The result of adding two complex numbers.
3704 */
3705 add: function(pos) {
3706 return new Complex(this.x + pos.x, this.y + pos.y);
3707 },
3708
3709 /*
3710 Method: prod
3711
3712 Returns the result of multiplying two <Complex> numbers.
3713
3714 Does not alter the original object.
3715
3716 Parameters:
3717
3718 pos - A <Complex> instance.
3719
3720 Returns:
3721
3722 The result of multiplying two complex numbers.
3723 */
3724 prod: function(pos) {
3725 return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3726 },
3727
3728 /*
3729 Method: conjugate
3730
3731 Returns the conjugate of this <Complex> number.
3732
3733 Does not alter the original object.
3734
3735 Returns:
3736
3737 The conjugate of this <Complex> number.
3738 */
3739 conjugate: function() {
3740 return new Complex(this.x, -this.y);
3741 },
3742
3743
3744 /*
3745 Method: scale
3746
3747 Returns the result of scaling a <Complex> instance.
3748
3749 Does not alter the original object.
3750
3751 Parameters:
3752
3753 factor - A scale factor.
3754
3755 Returns:
3756
3757 The result of scaling this complex to a factor.
3758 */
3759 scale: function(factor) {
3760 return new Complex(this.x * factor, this.y * factor);
3761 },
3762
3763 /*
3764 Method: equals
3765
3766 Comparison method.
3767
3768 Returns *true* if both real and imaginary parts are equal.
3769
3770 Parameters:
3771
3772 c - A <Complex> instance.
3773
3774 Returns:
3775
3776 A boolean instance indicating if both <Complex> numbers are equal.
3777 */
3778 equals: function(c) {
3779 return this.x == c.x && this.y == c.y;
3780 },
3781
3782 /*
3783 Method: $add
3784
3785 Returns the result of adding two <Complex> numbers.
3786
3787 Alters the original object.
3788
3789 Parameters:
3790
3791 pos - A <Complex> instance.
3792
3793 Returns:
3794
3795 The result of adding two complex numbers.
3796 */
3797 $add: function(pos) {
3798 this.x += pos.x; this.y += pos.y;
3799 return this;
3800 },
3801
3802 /*
3803 Method: $prod
3804
3805 Returns the result of multiplying two <Complex> numbers.
3806
3807 Alters the original object.
3808
3809 Parameters:
3810
3811 pos - A <Complex> instance.
3812
3813 Returns:
3814
3815 The result of multiplying two complex numbers.
3816 */
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;
3821 return this;
3822 },
3823
3824 /*
3825 Method: $conjugate
3826
3827 Returns the conjugate for this <Complex>.
3828
3829 Alters the original object.
3830
3831 Returns:
3832
3833 The conjugate for this complex.
3834 */
3835 $conjugate: function() {
3836 this.y = -this.y;
3837 return this;
3838 },
3839
3840 /*
3841 Method: $scale
3842
3843 Returns the result of scaling a <Complex> instance.
3844
3845 Alters the original object.
3846
3847 Parameters:
3848
3849 factor - A scale factor.
3850
3851 Returns:
3852
3853 The result of scaling this complex to a factor.
3854 */
3855 $scale: function(factor) {
3856 this.x *= factor; this.y *= factor;
3857 return this;
3858 },
3859
3860 /*
3861 Method: $div
3862
3863 Returns the division of two <Complex> numbers.
3864
3865 Alters the original object.
3866
3867 Parameters:
3868
3869 pos - A <Complex> number.
3870
3871 Returns:
3872
3873 The result of scaling this complex to a factor.
3874 */
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);
3880 },
3881
3882 /*
3883 Method: isZero
3884
3885 Returns *true* if the number is zero.
3886
3887 */
3888 isZero: function () {
3889 var almostZero = 0.0001, abs = Math.abs;
3890 return abs(this.x) < almostZero && abs(this.y) < almostZero;
3891 }
3892};
3893
3894var $C = function(a, b) { return new Complex(a, b); };
3895
3896Complex.KER = $C(0, 0);
3897
3898
3899
3900/*
3901 * File: Graph.js
3902 *
3903*/
3904
3905/*
3906 Class: Graph
3907
3908 A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
3909
3910 An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
3911
3912 Example:
3913
3914 (start code js)
3915 //create new visualization
3916 var viz = new $jit.Viz(options);
3917 //load JSON data
3918 viz.loadJSON(json);
3919 //access model
3920 viz.graph; //<Graph> instance
3921 (end code)
3922
3923 Implements:
3924
3925 The following <Graph.Util> methods are implemented in <Graph>
3926
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>
3934
3935*/
3936
3937$jit.Graph = new Class({
3938
3939 initialize: function(opt, Node, Edge, Label) {
3940 var innerOptions = {
3941 'klass': Complex,
3942 'Node': {}
3943 };
3944 this.Node = Node;
3945 this.Edge = Edge;
3946 this.Label = Label;
3947 this.opt = $.merge(innerOptions, opt || {});
3948 this.nodes = {};
3949 this.edges = {};
3950
3951 //add nodeList methods
3952 var that = this;
3953 this.nodeList = {};
3954 for(var p in Accessors) {
3955 that.nodeList[p] = (function(p) {
3956 return function() {
3957 var args = Array.prototype.slice.call(arguments);
3958 that.eachNode(function(n) {
3959 n[p].apply(n, args);
3960 });
3961 };
3962 })(p);
3963 }
3964
3965 },
3966
3967/*
3968 Method: getNode
3969
3970 Returns a <Graph.Node> by *id*.
3971
3972 Parameters:
3973
3974 id - (string) A <Graph.Node> id.
3975
3976 Example:
3977
3978 (start code js)
3979 var node = graph.getNode('nodeId');
3980 (end code)
3981*/
3982 getNode: function(id) {
3983 if(this.hasNode(id)) return this.nodes[id];
3984 return false;
3985 },
3986
3987 /*
3988 Method: get
3989
3990 An alias for <Graph.Util.getNode>. Returns a node by *id*.
3991
3992 Parameters:
3993
3994 id - (string) A <Graph.Node> id.
3995
3996 Example:
3997
3998 (start code js)
3999 var node = graph.get('nodeId');
4000 (end code)
4001*/
4002 get: function(id) {
4003 return this.getNode(id);
4004 },
4005
4006 /*
4007 Method: getByName
4008
4009 Returns a <Graph.Node> by *name*.
4010
4011 Parameters:
4012
4013 name - (string) A <Graph.Node> name.
4014
4015 Example:
4016
4017 (start code js)
4018 var node = graph.getByName('someName');
4019 (end code)
4020 */
4021 getByName: function(name) {
4022 for(var id in this.nodes) {
4023 var n = this.nodes[id];
4024 if(n.name == name) return n;
4025 }
4026 return false;
4027 },
4028
4029/*
4030 Method: getAdjacence
4031
4032 Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4033
4034 Parameters:
4035
4036 id - (string) A <Graph.Node> id.
4037 id2 - (string) A <Graph.Node> id.
4038*/
4039 getAdjacence: function (id, id2) {
4040 if(id in this.edges) {
4041 return this.edges[id][id2];
4042 }
4043 return false;
4044 },
4045
4046 /*
4047 Method: addNode
4048
4049 Adds a node.
4050
4051 Parameters:
4052
4053 obj - An object with the properties described below
4054
4055 id - (string) A node id
4056 name - (string) A node's name
4057 data - (object) A node's data hash
4058
4059 See also:
4060 <Graph.Node>
4061
4062 */
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({
4067 'id': obj.id,
4068 'name': obj.name,
4069 'data': $.merge(obj.data || {}, {}),
4070 'adjacencies': edges
4071 }, this.opt.Node),
4072 this.opt.klass,
4073 this.Node,
4074 this.Edge,
4075 this.Label);
4076 }
4077 return this.nodes[obj.id];
4078 },
4079
4080 /*
4081 Method: addAdjacence
4082
4083 Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4084
4085 Parameters:
4086
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.
4090
4091 See also:
4092
4093 <Graph.Node>, <Graph.Adjacence>
4094 */
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];
4104 }
4105 return this.edges[obj.id][obj2.id];
4106 },
4107
4108 /*
4109 Method: removeNode
4110
4111 Removes a <Graph.Node> matching the specified *id*.
4112
4113 Parameters:
4114
4115 id - (string) A node's id.
4116
4117 */
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];
4124 }
4125 delete this.edges[id];
4126 }
4127 },
4128
4129/*
4130 Method: removeAdjacence
4131
4132 Removes a <Graph.Adjacence> matching *id1* and *id2*.
4133
4134 Parameters:
4135
4136 id1 - (string) A <Graph.Node> id.
4137 id2 - (string) A <Graph.Node> id.
4138*/
4139 removeAdjacence: function(id1, id2) {
4140 delete this.edges[id1][id2];
4141 delete this.edges[id2][id1];
4142 },
4143
4144 /*
4145 Method: hasNode
4146
4147 Returns a boolean indicating if the node belongs to the <Graph> or not.
4148
4149 Parameters:
4150
4151 id - (string) Node id.
4152 */
4153 hasNode: function(id) {
4154 return id in this.nodes;
4155 },
4156
4157 /*
4158 Method: empty
4159
4160 Empties the Graph
4161
4162 */
4163 empty: function() { this.nodes = {}; this.edges = {};}
4164
4165});
4166
4167var Graph = $jit.Graph;
4168
4169/*
4170 Object: Accessors
4171
4172 Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4173
4174 */
4175var Accessors;
4176
4177(function () {
4178 var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4179 var data;
4180 type = type || 'current';
4181 prefix = "$" + (prefix ? prefix + "-" : "");
4182
4183 if(type == 'current') {
4184 data = this.data;
4185 } else if(type == 'start') {
4186 data = this.startData;
4187 } else if(type == 'end') {
4188 data = this.endData;
4189 }
4190
4191 var dollar = prefix + prop;
4192
4193 if(force) {
4194 return data[dollar];
4195 }
4196
4197 if(!this.Config.overridable)
4198 return prefixConfig[prop] || 0;
4199
4200 return (dollar in data) ?
4201 data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4202 }
4203
4204 var setDataInternal = function(prefix, prop, value, type) {
4205 type = type || 'current';
4206 prefix = '$' + (prefix ? prefix + '-' : '');
4207
4208 var data;
4209
4210 if(type == 'current') {
4211 data = this.data;
4212 } else if(type == 'start') {
4213 data = this.startData;
4214 } else if(type == 'end') {
4215 data = this.endData;
4216 }
4217
4218 data[prefix + prop] = value;
4219 }
4220
4221 var removeDataInternal = function(prefix, properties) {
4222 prefix = '$' + (prefix ? prefix + '-' : '');
4223 var that = this;
4224 $.each(properties, function(prop) {
4225 var pref = prefix + prop;
4226 delete that.data[pref];
4227 delete that.endData[pref];
4228 delete that.startData[pref];
4229 });
4230 }
4231
4232 Accessors = {
4233 /*
4234 Method: getData
4235
4236 Returns the specified data value property.
4237 This is useful for querying special/reserved <Graph.Node> data properties
4238 (i.e dollar prefixed properties).
4239
4240 Parameters:
4241
4242 prop - (string) The name of the property. The dollar sign is not needed. For
4243 example *getData(width)* will return *data.$width*.
4244 type - (string) The type of the data property queried. Default's "current". You can access *start* and *end*
4245 data properties also. These properties are used when making animations.
4246 force - (boolean) Whether to obtain the true value of the property (equivalent to
4247 *data.$prop*) or to check for *node.overridable = true* first.
4248
4249 Returns:
4250
4251 The value of the dollar prefixed property or the global Node/Edge property
4252 value if *overridable=false*
4253
4254 Example:
4255 (start code js)
4256 node.getData('width'); //will return node.data.$width if Node.overridable=true;
4257 (end code)
4258 */
4259 getData: function(prop, type, force) {
4260 return getDataInternal.call(this, "", prop, type, force, this.Config);
4261 },
4262
4263
4264 /*
4265 Method: setData
4266
4267 Sets the current data property with some specific value.
4268 This method is only useful for reserved (dollar prefixed) properties.
4269
4270 Parameters:
4271
4272 prop - (string) The name of the property. The dollar sign is not necessary. For
4273 example *setData(width)* will set *data.$width*.
4274 value - (mixed) The value to store.
4275 type - (string) The type of the data property to store. Default's "current" but
4276 can also be "start" or "end".
4277
4278 Example:
4279
4280 (start code js)
4281 node.setData('width', 30);
4282 (end code)
4283
4284 If we were to make an animation of a node/edge width then we could do
4285
4286 (start code js)
4287 var node = viz.getNode('nodeId');
4288 //set start and end values
4289 node.setData('width', 10, 'start');
4290 node.setData('width', 30, 'end');
4291 //will animate nodes width property
4292 viz.fx.animate({
4293 modes: ['node-property:width'],
4294 duration: 1000
4295 });
4296 (end code)
4297 */
4298 setData: function(prop, value, type) {
4299 setDataInternal.call(this, "", prop, value, type);
4300 },
4301
4302 /*
4303 Method: setDataset
4304
4305 Convenience method to set multiple data values at once.
4306
4307 Parameters:
4308
4309 types - (array|string) A set of 'current', 'end' or 'start' values.
4310 obj - (object) A hash containing the names and values of the properties to be altered.
4311
4312 Example:
4313 (start code js)
4314 node.setDataset(['current', 'end'], {
4315 'width': [100, 5],
4316 'color': ['#fff', '#ccc']
4317 });
4318 //...or also
4319 node.setDataset('end', {
4320 'width': 5,
4321 'color': '#ccc'
4322 });
4323 (end code)
4324
4325 See also:
4326
4327 <Accessors.setData>
4328
4329 */
4330 setDataset: function(types, obj) {
4331 types = $.splat(types);
4332 for(var attr in obj) {
4333 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4334 this.setData(attr, val[i], types[i]);
4335 }
4336 }
4337 },
4338
4339 /*
4340 Method: removeData
4341
4342 Remove data properties.
4343
4344 Parameters:
4345
4346 One or more property names as arguments. The dollar sign is not needed.
4347
4348 Example:
4349 (start code js)
4350 node.removeData('width'); //now the default width value is returned
4351 (end code)
4352 */
4353 removeData: function() {
4354 removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4355 },
4356
4357 /*
4358 Method: getCanvasStyle
4359
4360 Returns the specified canvas style data value property. This is useful for
4361 querying special/reserved <Graph.Node> canvas style data properties (i.e.
4362 dollar prefixed properties that match with $canvas-<name of canvas style>).
4363
4364 Parameters:
4365
4366 prop - (string) The name of the property. The dollar sign is not needed. For
4367 example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4368 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4369 data properties also.
4370
4371 Example:
4372 (start code js)
4373 node.getCanvasStyle('shadowBlur');
4374 (end code)
4375
4376 See also:
4377
4378 <Accessors.getData>
4379 */
4380 getCanvasStyle: function(prop, type, force) {
4381 return getDataInternal.call(
4382 this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4383 },
4384
4385 /*
4386 Method: setCanvasStyle
4387
4388 Sets the canvas style data property with some specific value.
4389 This method is only useful for reserved (dollar prefixed) properties.
4390
4391 Parameters:
4392
4393 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4394 value - (mixed) The value to set to the property.
4395 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4396
4397 Example:
4398
4399 (start code js)
4400 node.setCanvasStyle('shadowBlur', 30);
4401 (end code)
4402
4403 If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4404
4405 (start code js)
4406 var node = viz.getNode('nodeId');
4407 //set start and end values
4408 node.setCanvasStyle('shadowBlur', 10, 'start');
4409 node.setCanvasStyle('shadowBlur', 30, 'end');
4410 //will animate nodes canvas style property for nodes
4411 viz.fx.animate({
4412 modes: ['node-style:shadowBlur'],
4413 duration: 1000
4414 });
4415 (end code)
4416
4417 See also:
4418
4419 <Accessors.setData>.
4420 */
4421 setCanvasStyle: function(prop, value, type) {
4422 setDataInternal.call(this, 'canvas', prop, value, type);
4423 },
4424
4425 /*
4426 Method: setCanvasStyles
4427
4428 Convenience method to set multiple styles at once.
4429
4430 Parameters:
4431
4432 types - (array|string) A set of 'current', 'end' or 'start' values.
4433 obj - (object) A hash containing the names and values of the properties to be altered.
4434
4435 See also:
4436
4437 <Accessors.setDataset>.
4438 */
4439 setCanvasStyles: function(types, obj) {
4440 types = $.splat(types);
4441 for(var attr in obj) {
4442 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4443 this.setCanvasStyle(attr, val[i], types[i]);
4444 }
4445 }
4446 },
4447
4448 /*
4449 Method: removeCanvasStyle
4450
4451 Remove canvas style properties from data.
4452
4453 Parameters:
4454
4455 A variable number of canvas style strings.
4456
4457 See also:
4458
4459 <Accessors.removeData>.
4460 */
4461 removeCanvasStyle: function() {
4462 removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4463 },
4464
4465 /*
4466 Method: getLabelData
4467
4468 Returns the specified label data value property. This is useful for
4469 querying special/reserved <Graph.Node> label options (i.e.
4470 dollar prefixed properties that match with $label-<name of label style>).
4471
4472 Parameters:
4473
4474 prop - (string) The name of the property. The dollar sign prefix is not needed. For
4475 example *getLabelData(size)* will return *data[$label-size]*.
4476 type - (string) The type of the data property queried. Default's *current*. You can access *start* and *end*
4477 data properties also.
4478
4479 See also:
4480
4481 <Accessors.getData>.
4482 */
4483 getLabelData: function(prop, type, force) {
4484 return getDataInternal.call(
4485 this, 'label', prop, type, force, this.Label);
4486 },
4487
4488 /*
4489 Method: setLabelData
4490
4491 Sets the current label data with some specific value.
4492 This method is only useful for reserved (dollar prefixed) properties.
4493
4494 Parameters:
4495
4496 prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4497 value - (mixed) The value to set to the property.
4498 type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4499
4500 Example:
4501
4502 (start code js)
4503 node.setLabelData('size', 30);
4504 (end code)
4505
4506 If we were to make an animation of a node label size then we could do
4507
4508 (start code js)
4509 var node = viz.getNode('nodeId');
4510 //set start and end values
4511 node.setLabelData('size', 10, 'start');
4512 node.setLabelData('size', 30, 'end');
4513 //will animate nodes label size
4514 viz.fx.animate({
4515 modes: ['label-property:size'],
4516 duration: 1000
4517 });
4518 (end code)
4519
4520 See also:
4521
4522 <Accessors.setData>.
4523 */
4524 setLabelData: function(prop, value, type) {
4525 setDataInternal.call(this, 'label', prop, value, type);
4526 },
4527
4528 /*
4529 Method: setLabelDataset
4530
4531 Convenience function to set multiple label data at once.
4532
4533 Parameters:
4534
4535 types - (array|string) A set of 'current', 'end' or 'start' values.
4536 obj - (object) A hash containing the names and values of the properties to be altered.
4537
4538 See also:
4539
4540 <Accessors.setDataset>.
4541 */
4542 setLabelDataset: function(types, obj) {
4543 types = $.splat(types);
4544 for(var attr in obj) {
4545 for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4546 this.setLabelData(attr, val[i], types[i]);
4547 }
4548 }
4549 },
4550
4551 /*
4552 Method: removeLabelData
4553
4554 Remove label properties from data.
4555
4556 Parameters:
4557
4558 A variable number of label property strings.
4559
4560 See also:
4561
4562 <Accessors.removeData>.
4563 */
4564 removeLabelData: function() {
4565 removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4566 }
4567 };
4568})();
4569
4570/*
4571 Class: Graph.Node
4572
4573 A <Graph> node.
4574
4575 Implements:
4576
4577 <Accessors> methods.
4578
4579 The following <Graph.Util> methods are implemented by <Graph.Node>
4580
4581 - <Graph.Util.eachAdjacency>
4582 - <Graph.Util.eachLevel>
4583 - <Graph.Util.eachSubgraph>
4584 - <Graph.Util.eachSubnode>
4585 - <Graph.Util.anySubnode>
4586 - <Graph.Util.getSubnodes>
4587 - <Graph.Util.getParents>
4588 - <Graph.Util.isDescendantOf>
4589*/
4590Graph.Node = new Class({
4591
4592 initialize: function(opt, klass, Node, Edge, Label) {
4593 var innerOptions = {
4594 'id': '',
4595 'name': '',
4596 'data': {},
4597 'startData': {},
4598 'endData': {},
4599 'adjacencies': {},
4600
4601 'selected': false,
4602 'drawn': false,
4603 'exist': false,
4604
4605 'angleSpan': {
4606 'begin': 0,
4607 'end' : 0
4608 },
4609
4610 'pos': new klass,
4611 'startPos': new klass,
4612 'endPos': new klass
4613 };
4614
4615 $.extend(this, $.extend(innerOptions, opt));
4616 this.Config = this.Node = Node;
4617 this.Edge = Edge;
4618 this.Label = Label;
4619 },
4620
4621 /*
4622 Method: adjacentTo
4623
4624 Indicates if the node is adjacent to the node specified by id
4625
4626 Parameters:
4627
4628 id - (string) A node id.
4629
4630 Example:
4631 (start code js)
4632 node.adjacentTo('nodeId') == true;
4633 (end code)
4634 */
4635 adjacentTo: function(node) {
4636 return node.id in this.adjacencies;
4637 },
4638
4639 /*
4640 Method: getAdjacency
4641
4642 Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4643
4644 Parameters:
4645
4646 id - (string) A node id.
4647 */
4648 getAdjacency: function(id) {
4649 return this.adjacencies[id];
4650 },
4651
4652 /*
4653 Method: getPos
4654
4655 Returns the position of the node.
4656
4657 Parameters:
4658
4659 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4660
4661 Returns:
4662
4663 A <Complex> or <Polar> instance.
4664
4665 Example:
4666 (start code js)
4667 var pos = node.getPos('end');
4668 (end code)
4669 */
4670 getPos: function(type) {
4671 type = type || "current";
4672 if(type == "current") {
4673 return this.pos;
4674 } else if(type == "end") {
4675 return this.endPos;
4676 } else if(type == "start") {
4677 return this.startPos;
4678 }
4679 },
4680 /*
4681 Method: setPos
4682
4683 Sets the node's position.
4684
4685 Parameters:
4686
4687 value - (object) A <Complex> or <Polar> instance.
4688 type - (string) Default's *current*. Possible values are "start", "end" or "current".
4689
4690 Example:
4691 (start code js)
4692 node.setPos(new $jit.Complex(0, 0), 'end');
4693 (end code)
4694 */
4695 setPos: function(value, type) {
4696 type = type || "current";
4697 var pos;
4698 if(type == "current") {
4699 pos = this.pos;
4700 } else if(type == "end") {
4701 pos = this.endPos;
4702 } else if(type == "start") {
4703 pos = this.startPos;
4704 }
4705 pos.set(value);
4706 }
4707});
4708
4709Graph.Node.implement(Accessors);
4710
4711/*
4712 Class: Graph.Adjacence
4713
4714 A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4715
4716 Implements:
4717
4718 <Accessors> methods.
4719
4720 See also:
4721
4722 <Graph>, <Graph.Node>
4723
4724 Properties:
4725
4726 nodeFrom - A <Graph.Node> connected by this edge.
4727 nodeTo - Another <Graph.Node> connected by this edge.
4728 data - Node data property containing a hash (i.e {}) with custom options.
4729*/
4730Graph.Adjacence = new Class({
4731
4732 initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4733 this.nodeFrom = nodeFrom;
4734 this.nodeTo = nodeTo;
4735 this.data = data || {};
4736 this.startData = {};
4737 this.endData = {};
4738 this.Config = this.Edge = Edge;
4739 this.Label = Label;
4740 }
4741});
4742
4743Graph.Adjacence.implement(Accessors);
4744
4745/*
4746 Object: Graph.Util
4747
4748 <Graph> traversal and processing utility object.
4749
4750 Note:
4751
4752 For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4753*/
4754Graph.Util = {
4755 /*
4756 filter
4757
4758 For internal use only. Provides a filtering function based on flags.
4759 */
4760 filter: function(param) {
4761 if(!param || !($.type(param) == 'string')) return function() { return true; };
4762 var props = param.split(" ");
4763 return function(elem) {
4764 for(var i=0; i<props.length; i++) {
4765 if(elem[props[i]]) {
4766 return false;
4767 }
4768 }
4769 return true;
4770 };
4771 },
4772 /*
4773 Method: getNode
4774
4775 Returns a <Graph.Node> by *id*.
4776
4777 Also implemented by:
4778
4779 <Graph>
4780
4781 Parameters:
4782
4783 graph - (object) A <Graph> instance.
4784 id - (string) A <Graph.Node> id.
4785
4786 Example:
4787
4788 (start code js)
4789 $jit.Graph.Util.getNode(graph, 'nodeid');
4790 //or...
4791 graph.getNode('nodeid');
4792 (end code)
4793 */
4794 getNode: function(graph, id) {
4795 return graph.nodes[id];
4796 },
4797
4798 /*
4799 Method: eachNode
4800
4801 Iterates over <Graph> nodes performing an *action*.
4802
4803 Also implemented by:
4804
4805 <Graph>.
4806
4807 Parameters:
4808
4809 graph - (object) A <Graph> instance.
4810 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4811
4812 Example:
4813 (start code js)
4814 $jit.Graph.Util.eachNode(graph, function(node) {
4815 alert(node.name);
4816 });
4817 //or...
4818 graph.eachNode(function(node) {
4819 alert(node.name);
4820 });
4821 (end code)
4822 */
4823 eachNode: function(graph, action, flags) {
4824 var filter = this.filter(flags);
4825 for(var i in graph.nodes) {
4826 if(filter(graph.nodes[i])) action(graph.nodes[i]);
4827 }
4828 },
4829
4830 /*
4831 Method: each
4832
4833 Iterates over <Graph> nodes performing an *action*. It's an alias for <Graph.Util.eachNode>.
4834
4835 Also implemented by:
4836
4837 <Graph>.
4838
4839 Parameters:
4840
4841 graph - (object) A <Graph> instance.
4842 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4843
4844 Example:
4845 (start code js)
4846 $jit.Graph.Util.each(graph, function(node) {
4847 alert(node.name);
4848 });
4849 //or...
4850 graph.each(function(node) {
4851 alert(node.name);
4852 });
4853 (end code)
4854 */
4855 each: function(graph, action, flags) {
4856 this.eachNode(graph, action, flags);
4857 },
4858
4859 /*
4860 Method: eachAdjacency
4861
4862 Iterates over <Graph.Node> adjacencies applying the *action* function.
4863
4864 Also implemented by:
4865
4866 <Graph.Node>.
4867
4868 Parameters:
4869
4870 node - (object) A <Graph.Node>.
4871 action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4872
4873 Example:
4874 (start code js)
4875 $jit.Graph.Util.eachAdjacency(node, function(adj) {
4876 alert(adj.nodeTo.name);
4877 });
4878 //or...
4879 node.eachAdjacency(function(adj) {
4880 alert(adj.nodeTo.name);
4881 });
4882 (end code)
4883 */
4884 eachAdjacency: function(node, action, flags) {
4885 var adj = node.adjacencies, filter = this.filter(flags);
4886 for(var id in adj) {
4887 var a = adj[id];
4888 if(filter(a)) {
4889 if(a.nodeFrom != node) {
4890 var tmp = a.nodeFrom;
4891 a.nodeFrom = a.nodeTo;
4892 a.nodeTo = tmp;
4893 }
4894 action(a, id);
4895 }
4896 }
4897 },
4898
4899 /*
4900 Method: computeLevels
4901
4902 Performs a BFS traversal setting the correct depth for each node.
4903
4904 Also implemented by:
4905
4906 <Graph>.
4907
4908 Note:
4909
4910 The depth of each node can then be accessed by
4911 >node._depth
4912
4913 Parameters:
4914
4915 graph - (object) A <Graph>.
4916 id - (string) A starting node id for the BFS traversal.
4917 startDepth - (optional|number) A minimum depth value. Default's 0.
4918
4919 */
4920 computeLevels: function(graph, id, startDepth, flags) {
4921 startDepth = startDepth || 0;
4922 var filter = this.filter(flags);
4923 this.eachNode(graph, function(elem) {
4924 elem._flag = false;
4925 elem._depth = -1;
4926 }, flags);
4927 var root = graph.getNode(id);
4928 root._depth = startDepth;
4929 var queue = [root];
4930 while(queue.length != 0) {
4931 var node = queue.pop();
4932 node._flag = true;
4933 this.eachAdjacency(node, function(adj) {
4934 var n = adj.nodeTo;
4935 if(n._flag == false && filter(n)) {
4936 if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
4937 queue.unshift(n);
4938 }
4939 }, flags);
4940 }
4941 },
4942
4943 /*
4944 Method: eachBFS
4945
4946 Performs a BFS traversal applying *action* to each <Graph.Node>.
4947
4948 Also implemented by:
4949
4950 <Graph>.
4951
4952 Parameters:
4953
4954 graph - (object) A <Graph>.
4955 id - (string) A starting node id for the BFS traversal.
4956 action - (function) A callback function having a <Graph.Node> as first formal parameter.
4957
4958 Example:
4959 (start code js)
4960 $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
4961 alert(node.name);
4962 });
4963 //or...
4964 graph.eachBFS('mynodeid', function(node) {
4965 alert(node.name);
4966 });
4967 (end code)
4968 */
4969 eachBFS: function(graph, id, action, flags) {
4970 var filter = this.filter(flags);
4971 this.clean(graph);
4972 var queue = [graph.getNode(id)];
4973 while(queue.length != 0) {
4974 var node = queue.pop();
4975 node._flag = true;
4976 action(node, node._depth);
4977 this.eachAdjacency(node, function(adj) {
4978 var n = adj.nodeTo;
4979 if(n._flag == false && filter(n)) {
4980 n._flag = true;
4981 queue.unshift(n);
4982 }
4983 }, flags);
4984 }
4985 },
4986
4987 /*
4988 Method: eachLevel
4989
4990 Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
4991
4992 Also implemented by:
4993
4994 <Graph.Node>.
4995
4996 Parameters:
4997
4998 node - (object) A <Graph.Node>.
4999 levelBegin - (number) A relative level value.
5000 levelEnd - (number) A relative level value.
5001 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5002
5003 */
5004 eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5005 var d = node._depth, filter = this.filter(flags), that = this;
5006 levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5007 (function loopLevel(node, levelBegin, levelEnd) {
5008 var d = node._depth;
5009 if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5010 if(d < levelEnd) {
5011 that.eachAdjacency(node, function(adj) {
5012 var n = adj.nodeTo;
5013 if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5014 });
5015 }
5016 })(node, levelBegin + d, levelEnd + d);
5017 },
5018
5019 /*
5020 Method: eachSubgraph
5021
5022 Iterates over a node's children recursively.
5023
5024 Also implemented by:
5025
5026 <Graph.Node>.
5027
5028 Parameters:
5029 node - (object) A <Graph.Node>.
5030 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5031
5032 Example:
5033 (start code js)
5034 $jit.Graph.Util.eachSubgraph(node, function(node) {
5035 alert(node.name);
5036 });
5037 //or...
5038 node.eachSubgraph(function(node) {
5039 alert(node.name);
5040 });
5041 (end code)
5042 */
5043 eachSubgraph: function(node, action, flags) {
5044 this.eachLevel(node, 0, false, action, flags);
5045 },
5046
5047 /*
5048 Method: eachSubnode
5049
5050 Iterates over a node's children (without deeper recursion).
5051
5052 Also implemented by:
5053
5054 <Graph.Node>.
5055
5056 Parameters:
5057 node - (object) A <Graph.Node>.
5058 action - (function) A callback function having a <Graph.Node> as first formal parameter.
5059
5060 Example:
5061 (start code js)
5062 $jit.Graph.Util.eachSubnode(node, function(node) {
5063 alert(node.name);
5064 });
5065 //or...
5066 node.eachSubnode(function(node) {
5067 alert(node.name);
5068 });
5069 (end code)
5070 */
5071 eachSubnode: function(node, action, flags) {
5072 this.eachLevel(node, 1, 1, action, flags);
5073 },
5074
5075 /*
5076 Method: anySubnode
5077
5078 Returns *true* if any subnode matches the given condition.
5079
5080 Also implemented by:
5081
5082 <Graph.Node>.
5083
5084 Parameters:
5085 node - (object) A <Graph.Node>.
5086 cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5087
5088 Example:
5089 (start code js)
5090 $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5091 //or...
5092 node.anySubnode(function(node) { return node.name == 'mynodename'; });
5093 (end code)
5094 */
5095 anySubnode: function(node, cond, flags) {
5096 var flag = false;
5097 cond = cond || $.lambda(true);
5098 var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5099 this.eachSubnode(node, function(elem) {
5100 if(c(elem)) flag = true;
5101 }, flags);
5102 return flag;
5103 },
5104
5105 /*
5106 Method: getSubnodes
5107
5108 Collects all subnodes for a specified node.
5109 The *level* parameter filters nodes having relative depth of *level* from the root node.
5110
5111 Also implemented by:
5112
5113 <Graph.Node>.
5114
5115 Parameters:
5116 node - (object) A <Graph.Node>.
5117 level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5118
5119 Returns:
5120 An array of nodes.
5121
5122 */
5123 getSubnodes: function(node, level, flags) {
5124 var ans = [], that = this;
5125 level = level || 0;
5126 var levelStart, levelEnd;
5127 if($.type(level) == 'array') {
5128 levelStart = level[0];
5129 levelEnd = level[1];
5130 } else {
5131 levelStart = level;
5132 levelEnd = Number.MAX_VALUE - node._depth;
5133 }
5134 this.eachLevel(node, levelStart, levelEnd, function(n) {
5135 ans.push(n);
5136 }, flags);
5137 return ans;
5138 },
5139
5140
5141 /*
5142 Method: getParents
5143
5144 Returns an Array of <Graph.Nodes> which are parents of the given node.
5145
5146 Also implemented by:
5147
5148 <Graph.Node>.
5149
5150 Parameters:
5151 node - (object) A <Graph.Node>.
5152
5153 Returns:
5154 An Array of <Graph.Nodes>.
5155
5156 Example:
5157 (start code js)
5158 var pars = $jit.Graph.Util.getParents(node);
5159 //or...
5160 var pars = node.getParents();
5161
5162 if(pars.length > 0) {
5163 //do stuff with parents
5164 }
5165 (end code)
5166 */
5167 getParents: function(node) {
5168 var ans = [];
5169 this.eachAdjacency(node, function(adj) {
5170 var n = adj.nodeTo;
5171 if(n._depth < node._depth) ans.push(n);
5172 });
5173 return ans;
5174 },
5175
5176 /*
5177 Method: isDescendantOf
5178
5179 Returns a boolean indicating if some node is descendant of the node with the given id.
5180
5181 Also implemented by:
5182
5183 <Graph.Node>.
5184
5185
5186 Parameters:
5187 node - (object) A <Graph.Node>.
5188 id - (string) A <Graph.Node> id.
5189
5190 Example:
5191 (start code js)
5192 $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5193 //or...
5194 node.isDescendantOf('nodeid');//true|false
5195 (end code)
5196 */
5197 isDescendantOf: function(node, id) {
5198 if(node.id == id) return true;
5199 var pars = this.getParents(node), ans = false;
5200 for ( var i = 0; !ans && i < pars.length; i++) {
5201 ans = ans || this.isDescendantOf(pars[i], id);
5202 }
5203 return ans;
5204 },
5205
5206 /*
5207 Method: clean
5208
5209 Cleans flags from nodes.
5210
5211 Also implemented by:
5212
5213 <Graph>.
5214
5215 Parameters:
5216 graph - A <Graph> instance.
5217 */
5218 clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5219
5220 /*
5221 Method: getClosestNodeToOrigin
5222
5223 Returns the closest node to the center of canvas.
5224
5225 Also implemented by:
5226
5227 <Graph>.
5228
5229 Parameters:
5230
5231 graph - (object) A <Graph> instance.
5232 prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5233
5234 */
5235 getClosestNodeToOrigin: function(graph, prop, flags) {
5236 return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5237 },
5238
5239 /*
5240 Method: getClosestNodeToPos
5241
5242 Returns the closest node to the given position.
5243
5244 Also implemented by:
5245
5246 <Graph>.
5247
5248 Parameters:
5249
5250 graph - (object) A <Graph> instance.
5251 pos - (object) A <Complex> or <Polar> instance.
5252 prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5253
5254 */
5255 getClosestNodeToPos: function(graph, pos, prop, flags) {
5256 var node = null;
5257 prop = prop || 'current';
5258 pos = pos && pos.getc(true) || Complex.KER;
5259 var distance = function(a, b) {
5260 var d1 = a.x - b.x, d2 = a.y - b.y;
5261 return d1 * d1 + d2 * d2;
5262 };
5263 this.eachNode(graph, function(elem) {
5264 node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5265 node.getPos(prop).getc(true), pos)) ? elem : node;
5266 }, flags);
5267 return node;
5268 }
5269};
5270
5271//Append graph methods to <Graph>
5272$.each(['get', 'getNode', 'each', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5273 Graph.prototype[m] = function() {
5274 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5275 };
5276});
5277
5278//Append node methods to <Graph.Node>
5279$.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5280 Graph.Node.prototype[m] = function() {
5281 return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5282 };
5283});
5284
5285/*
5286 * File: Graph.Op.js
5287 *
5288*/
5289
5290/*
5291 Object: Graph.Op
5292
5293 Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>,
5294 morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5295
5296*/
5297Graph.Op = {
5298
5299 options: {
5300 type: 'nothing',
5301 duration: 2000,
5302 hideLabels: true,
5303 fps:30
5304 },
5305
5306 initialize: function(viz) {
5307 this.viz = viz;
5308 },
5309
5310 /*
5311 Method: removeNode
5312
5313 Removes one or more <Graph.Nodes> from the visualization.
5314 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5315
5316 Parameters:
5317
5318 node - (string|array) The node's id. Can also be an array having many ids.
5319 opt - (object) Animation options. It's an object with optional properties described below
5320 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5321 duration - Described in <Options.Fx>.
5322 fps - Described in <Options.Fx>.
5323 transition - Described in <Options.Fx>.
5324 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5325
5326 Example:
5327 (start code js)
5328 var viz = new $jit.Viz(options);
5329 viz.op.removeNode('nodeId', {
5330 type: 'fade:seq',
5331 duration: 1000,
5332 hideLabels: false,
5333 transition: $jit.Trans.Quart.easeOut
5334 });
5335 //or also
5336 viz.op.removeNode(['someId', 'otherId'], {
5337 type: 'fade:con',
5338 duration: 1500
5339 });
5340 (end code)
5341 */
5342
5343 removeNode: function(node, opt) {
5344 var viz = this.viz;
5345 var options = $.merge(this.options, viz.controller, opt);
5346 var n = $.splat(node);
5347 var i, that, nodeObj;
5348 switch(options.type) {
5349 case 'nothing':
5350 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5351 break;
5352
5353 case 'replot':
5354 this.removeNode(n, { type: 'nothing' });
5355 viz.labels.clearLabels();
5356 viz.refresh(true);
5357 break;
5358
5359 case 'fade:seq': case 'fade':
5360 that = this;
5361 //set alpha to 0 for nodes to remove.
5362 for(i=0; i<n.length; i++) {
5363 nodeObj = viz.graph.getNode(n[i]);
5364 nodeObj.setData('alpha', 0, 'end');
5365 }
5366 viz.fx.animate($.merge(options, {
5367 modes: ['node-property:alpha'],
5368 onComplete: function() {
5369 that.removeNode(n, { type: 'nothing' });
5370 viz.labels.clearLabels();
5371 viz.reposition();
5372 viz.fx.animate($.merge(options, {
5373 modes: ['linear']
5374 }));
5375 }
5376 }));
5377 break;
5378
5379 case 'fade:con':
5380 that = this;
5381 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5382 for(i=0; i<n.length; i++) {
5383 nodeObj = viz.graph.getNode(n[i]);
5384 nodeObj.setData('alpha', 0, 'end');
5385 nodeObj.ignore = true;
5386 }
5387 viz.reposition();
5388 viz.fx.animate($.merge(options, {
5389 modes: ['node-property:alpha', 'linear'],
5390 onComplete: function() {
5391 that.removeNode(n, { type: 'nothing' });
5392 options.onComplete && options.onComplete();
5393 }
5394 }));
5395 break;
5396
5397 case 'iter':
5398 that = this;
5399 viz.fx.sequence({
5400 condition: function() { return n.length != 0; },
5401 step: function() { that.removeNode(n.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5402 onComplete: function() { options.onComplete && options.onComplete(); },
5403 duration: Math.ceil(options.duration / n.length)
5404 });
5405 break;
5406
5407 default: this.doError();
5408 }
5409 },
5410
5411 /*
5412 Method: removeEdge
5413
5414 Removes one or more <Graph.Adjacences> from the visualization.
5415 It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5416
5417 Parameters:
5418
5419 vertex - (array) An array having two strings which are the ids of the nodes connected by this edge (i.e ['id1', 'id2']). Can also be a two dimensional array holding many edges (i.e [['id1', 'id2'], ['id3', 'id4'], ...]).
5420 opt - (object) Animation options. It's an object with optional properties described below
5421 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con" or "iter".
5422 duration - Described in <Options.Fx>.
5423 fps - Described in <Options.Fx>.
5424 transition - Described in <Options.Fx>.
5425 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5426
5427 Example:
5428 (start code js)
5429 var viz = new $jit.Viz(options);
5430 viz.op.removeEdge(['nodeId', 'otherId'], {
5431 type: 'fade:seq',
5432 duration: 1000,
5433 hideLabels: false,
5434 transition: $jit.Trans.Quart.easeOut
5435 });
5436 //or also
5437 viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5438 type: 'fade:con',
5439 duration: 1500
5440 });
5441 (end code)
5442
5443 */
5444 removeEdge: function(vertex, opt) {
5445 var viz = this.viz;
5446 var options = $.merge(this.options, viz.controller, opt);
5447 var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5448 var i, that, adj;
5449 switch(options.type) {
5450 case 'nothing':
5451 for(i=0; i<v.length; i++) viz.graph.removeAdjacence(v[i][0], v[i][1]);
5452 break;
5453
5454 case 'replot':
5455 this.removeEdge(v, { type: 'nothing' });
5456 viz.refresh(true);
5457 break;
5458
5459 case 'fade:seq': case 'fade':
5460 that = this;
5461 //set alpha to 0 for edges to remove.
5462 for(i=0; i<v.length; i++) {
5463 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5464 if(adj) {
5465 adj.setData('alpha', 0,'end');
5466 }
5467 }
5468 viz.fx.animate($.merge(options, {
5469 modes: ['edge-property:alpha'],
5470 onComplete: function() {
5471 that.removeEdge(v, { type: 'nothing' });
5472 viz.reposition();
5473 viz.fx.animate($.merge(options, {
5474 modes: ['linear']
5475 }));
5476 }
5477 }));
5478 break;
5479
5480 case 'fade:con':
5481 that = this;
5482 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5483 for(i=0; i<v.length; i++) {
5484 adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5485 if(adj) {
5486 adj.setData('alpha',0 ,'end');
5487 adj.ignore = true;
5488 }
5489 }
5490 viz.reposition();
5491 viz.fx.animate($.merge(options, {
5492 modes: ['edge-property:alpha', 'linear'],
5493 onComplete: function() {
5494 that.removeEdge(v, { type: 'nothing' });
5495 options.onComplete && options.onComplete();
5496 }
5497 }));
5498 break;
5499
5500 case 'iter':
5501 that = this;
5502 viz.fx.sequence({
5503 condition: function() { return v.length != 0; },
5504 step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5505 onComplete: function() { options.onComplete(); },
5506 duration: Math.ceil(options.duration / v.length)
5507 });
5508 break;
5509
5510 default: this.doError();
5511 }
5512 },
5513
5514 /*
5515 Method: sum
5516
5517 Adds a new graph to the visualization.
5518 The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization.
5519 The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5520
5521 Parameters:
5522
5523 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5524 opt - (object) Animation options. It's an object with optional properties described below
5525 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq", "fade:con".
5526 duration - Described in <Options.Fx>.
5527 fps - Described in <Options.Fx>.
5528 transition - Described in <Options.Fx>.
5529 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5530
5531 Example:
5532 (start code js)
5533 //...json contains a tree or graph structure...
5534
5535 var viz = new $jit.Viz(options);
5536 viz.op.sum(json, {
5537 type: 'fade:seq',
5538 duration: 1000,
5539 hideLabels: false,
5540 transition: $jit.Trans.Quart.easeOut
5541 });
5542 //or also
5543 viz.op.sum(json, {
5544 type: 'fade:con',
5545 duration: 1500
5546 });
5547 (end code)
5548
5549 */
5550 sum: function(json, opt) {
5551 var viz = this.viz;
5552 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5553 var graph;
5554 viz.root = opt.id || viz.root;
5555 switch(options.type) {
5556 case 'nothing':
5557 graph = viz.construct(json);
5558 graph.eachNode(function(elem) {
5559 elem.eachAdjacency(function(adj) {
5560 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5561 });
5562 });
5563 break;
5564
5565 case 'replot':
5566 viz.refresh(true);
5567 this.sum(json, { type: 'nothing' });
5568 viz.refresh(true);
5569 break;
5570
5571 case 'fade:seq': case 'fade': case 'fade:con':
5572 that = this;
5573 graph = viz.construct(json);
5574
5575 //set alpha to 0 for nodes to add.
5576 var fadeEdges = this.preprocessSum(graph);
5577 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5578 viz.reposition();
5579 if(options.type != 'fade:con') {
5580 viz.fx.animate($.merge(options, {
5581 modes: ['linear'],
5582 onComplete: function() {
5583 viz.fx.animate($.merge(options, {
5584 modes: modes,
5585 onComplete: function() {
5586 options.onComplete();
5587 }
5588 }));
5589 }
5590 }));
5591 } else {
5592 viz.graph.eachNode(function(elem) {
5593 if (elem.id != root && elem.pos.isZero()) {
5594 elem.pos.set(elem.endPos);
5595 elem.startPos.set(elem.endPos);
5596 }
5597 });
5598 viz.fx.animate($.merge(options, {
5599 modes: ['linear'].concat(modes)
5600 }));
5601 }
5602 break;
5603
5604 default: this.doError();
5605 }
5606 },
5607
5608 /*
5609 Method: morph
5610
5611 This method will transform the current visualized graph into the new JSON representation passed in the method.
5612 The JSON object must at least have the root node in common with the current visualized graph.
5613
5614 Parameters:
5615
5616 json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5617 opt - (object) Animation options. It's an object with optional properties described below
5618 type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5619 duration - Described in <Options.Fx>.
5620 fps - Described in <Options.Fx>.
5621 transition - Described in <Options.Fx>.
5622 hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5623 id - (string) The shared <Graph.Node> id between both graphs.
5624
5625 extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to
5626 *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation.
5627 For animating these extra-parameters you have to specify an object that has animation groups as keys and animation
5628 properties as values, just like specified in <Graph.Plot.animate>.
5629
5630 Example:
5631 (start code js)
5632 //...json contains a tree or graph structure...
5633
5634 var viz = new $jit.Viz(options);
5635 viz.op.morph(json, {
5636 type: 'fade',
5637 duration: 1000,
5638 hideLabels: false,
5639 transition: $jit.Trans.Quart.easeOut
5640 });
5641 //or also
5642 viz.op.morph(json, {
5643 type: 'fade',
5644 duration: 1500
5645 });
5646 //if the json data contains dollar prefixed params
5647 //like $width or $height these too can be animated
5648 viz.op.morph(json, {
5649 type: 'fade',
5650 duration: 1500
5651 }, {
5652 'node-property': ['width', 'height']
5653 });
5654 (end code)
5655
5656 */
5657 morph: function(json, opt, extraModes) {
5658 extraModes = extraModes || {};
5659 var viz = this.viz;
5660 var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5661 var graph;
5662 //TODO(nico) this hack makes morphing work with the Hypertree.
5663 //Need to check if it has been solved and this can be removed.
5664 viz.root = opt.id || viz.root;
5665 switch(options.type) {
5666 case 'nothing':
5667 graph = viz.construct(json);
5668 graph.eachNode(function(elem) {
5669 var nodeExists = viz.graph.hasNode(elem.id);
5670 elem.eachAdjacency(function(adj) {
5671 var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5672 viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5673 //Update data properties if the node existed
5674 if(adjExists) {
5675 var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5676 for(var prop in (adj.data || {})) {
5677 addedAdj.data[prop] = adj.data[prop];
5678 }
5679 }
5680 });
5681 //Update data properties if the node existed
5682 if(nodeExists) {
5683 var addedNode = viz.graph.getNode(elem.id);
5684 for(var prop in (elem.data || {})) {
5685 addedNode.data[prop] = elem.data[prop];
5686 }
5687 }
5688 });
5689 viz.graph.eachNode(function(elem) {
5690 elem.eachAdjacency(function(adj) {
5691 if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5692 viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5693 }
5694 });
5695 if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5696 });
5697
5698 break;
5699
5700 case 'replot':
5701 viz.labels.clearLabels(true);
5702 this.morph(json, { type: 'nothing' });
5703 viz.refresh(true);
5704 viz.refresh(true);
5705 break;
5706
5707 case 'fade:seq': case 'fade': case 'fade:con':
5708 that = this;
5709 graph = viz.construct(json);
5710 //preprocessing for nodes to delete.
5711 //get node property modes to interpolate
5712 var nodeModes = ('node-property' in extraModes)
5713 && $.map($.splat(extraModes['node-property']),
5714 function(n) { return '$' + n; });
5715 viz.graph.eachNode(function(elem) {
5716 var graphNode = graph.getNode(elem.id);
5717 if(!graphNode) {
5718 elem.setData('alpha', 1);
5719 elem.setData('alpha', 1, 'start');
5720 elem.setData('alpha', 0, 'end');
5721 elem.ignore = true;
5722 } else {
5723 //Update node data information
5724 var graphNodeData = graphNode.data;
5725 for(var prop in graphNodeData) {
5726 if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5727 elem.endData[prop] = graphNodeData[prop];
5728 } else {
5729 elem.data[prop] = graphNodeData[prop];
5730 }
5731 }
5732 }
5733 });
5734 viz.graph.eachNode(function(elem) {
5735 if(elem.ignore) return;
5736 elem.eachAdjacency(function(adj) {
5737 if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5738 var nodeFrom = graph.getNode(adj.nodeFrom.id);
5739 var nodeTo = graph.getNode(adj.nodeTo.id);
5740 if(!nodeFrom.adjacentTo(nodeTo)) {
5741 var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5742 fadeEdges = true;
5743 adj.setData('alpha', 1);
5744 adj.setData('alpha', 1, 'start');
5745 adj.setData('alpha', 0, 'end');
5746 }
5747 });
5748 });
5749 //preprocessing for adding nodes.
5750 var fadeEdges = this.preprocessSum(graph);
5751
5752 var modes = !fadeEdges? ['node-property:alpha'] :
5753 ['node-property:alpha',
5754 'edge-property:alpha'];
5755 //Append extra node-property animations (if any)
5756 modes[0] = modes[0] + (('node-property' in extraModes)?
5757 (':' + $.splat(extraModes['node-property']).join(':')) : '');
5758 //Append extra edge-property animations (if any)
5759 modes[1] = (modes[1] || 'edge-property:alpha') + (('edge-property' in extraModes)?
5760 (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5761 //Add label-property animations (if any)
5762 if('label-property' in extraModes) {
5763 modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5764 }
5765 //only use reposition if its implemented.
5766 if (viz.reposition) {
5767 viz.reposition();
5768 } else {
5769 viz.compute('end');
5770 }
5771 viz.graph.eachNode(function(elem) {
5772 if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5773 elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5774 }
5775 });
5776 viz.fx.animate($.merge(options, {
5777 modes: [extraModes.position || 'polar'].concat(modes),
5778 onComplete: function() {
5779 viz.graph.eachNode(function(elem) {
5780 if(elem.ignore) viz.graph.removeNode(elem.id);
5781 });
5782 viz.graph.eachNode(function(elem) {
5783 elem.eachAdjacency(function(adj) {
5784 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5785 });
5786 });
5787 options.onComplete();
5788 }
5789 }));
5790 break;
5791
5792 default:;
5793 }
5794 },
5795
5796
5797 /*
5798 Method: contract
5799
5800 Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5801
5802 Parameters:
5803
5804 node - (object) A <Graph.Node>.
5805 opt - (object) An object containing options described below
5806 type - (string) Whether to 'replot' or 'animate' the contraction.
5807
5808 There are also a number of Animation options. For more information see <Options.Fx>.
5809
5810 Example:
5811 (start code js)
5812 var viz = new $jit.Viz(options);
5813 viz.op.contract(node, {
5814 type: 'animate',
5815 duration: 1000,
5816 hideLabels: true,
5817 transition: $jit.Trans.Quart.easeOut
5818 });
5819 (end code)
5820
5821 */
5822 contract: function(node, opt) {
5823 var viz = this.viz;
5824 if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5825 opt = $.merge(this.options, viz.config, opt || {}, {
5826 'modes': ['node-property:alpha:span', 'linear']
5827 });
5828 node.collapsed = true;
5829 (function subn(n) {
5830 n.eachSubnode(function(ch) {
5831 ch.ignore = true;
5832 ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5833 subn(ch);
5834 });
5835 })(node);
5836 if(opt.type == 'animate') {
5837 viz.compute('end');
5838 if(viz.rotated) {
5839 viz.rotate(viz.rotated, 'none', {
5840 'property':'end'
5841 });
5842 }
5843 (function subn(n) {
5844 n.eachSubnode(function(ch) {
5845 ch.setPos(node.getPos('end'), 'end');
5846 subn(ch);
5847 });
5848 })(node);
5849 viz.fx.animate(opt);
5850 } else if(opt.type == 'replot'){
5851 viz.refresh();
5852 }
5853 },
5854
5855 /*
5856 Method: expand
5857
5858 Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5859
5860 Parameters:
5861
5862 node - (object) A <Graph.Node>.
5863 opt - (object) An object containing options described below
5864 type - (string) Whether to 'replot' or 'animate'.
5865
5866 There are also a number of Animation options. For more information see <Options.Fx>.
5867
5868 Example:
5869 (start code js)
5870 var viz = new $jit.Viz(options);
5871 viz.op.expand(node, {
5872 type: 'animate',
5873 duration: 1000,
5874 hideLabels: true,
5875 transition: $jit.Trans.Quart.easeOut
5876 });
5877 (end code)
5878
5879 */
5880 expand: function(node, opt) {
5881 if(!('collapsed' in node)) return;
5882 var viz = this.viz;
5883 opt = $.merge(this.options, viz.config, opt || {}, {
5884 'modes': ['node-property:alpha:span', 'linear']
5885 });
5886 delete node.collapsed;
5887 (function subn(n) {
5888 n.eachSubnode(function(ch) {
5889 delete ch.ignore;
5890 ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5891 subn(ch);
5892 });
5893 })(node);
5894 if(opt.type == 'animate') {
5895 viz.compute('end');
5896 if(viz.rotated) {
5897 viz.rotate(viz.rotated, 'none', {
5898 'property':'end'
5899 });
5900 }
5901 viz.fx.animate(opt);
5902 } else if(opt.type == 'replot'){
5903 viz.refresh();
5904 }
5905 },
5906
5907 preprocessSum: function(graph) {
5908 var viz = this.viz;
5909 graph.eachNode(function(elem) {
5910 if(!viz.graph.hasNode(elem.id)) {
5911 viz.graph.addNode(elem);
5912 var n = viz.graph.getNode(elem.id);
5913 n.setData('alpha', 0);
5914 n.setData('alpha', 0, 'start');
5915 n.setData('alpha', 1, 'end');
5916 }
5917 });
5918 var fadeEdges = false;
5919 graph.eachNode(function(elem) {
5920 elem.eachAdjacency(function(adj) {
5921 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
5922 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
5923 if(!nodeFrom.adjacentTo(nodeTo)) {
5924 var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
5925 if(nodeFrom.startAlpha == nodeFrom.endAlpha
5926 && nodeTo.startAlpha == nodeTo.endAlpha) {
5927 fadeEdges = true;
5928 adj.setData('alpha', 0);
5929 adj.setData('alpha', 0, 'start');
5930 adj.setData('alpha', 1, 'end');
5931 }
5932 }
5933 });
5934 });
5935 return fadeEdges;
5936 }
5937};
5938
5939
5940
5941/*
5942 File: Helpers.js
5943
5944 Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
5945 Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
5946 position is over the rendered shape.
5947
5948 Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and
5949 *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
5950
5951 Example:
5952 (start code js)
5953 //implement a new node type
5954 $jit.Viz.Plot.NodeTypes.implement({
5955 'customNodeType': {
5956 'render': function(node, canvas) {
5957 this.nodeHelper.circle.render ...
5958 },
5959 'contains': function(node, pos) {
5960 this.nodeHelper.circle.contains ...
5961 }
5962 }
5963 });
5964 //implement an edge type
5965 $jit.Viz.Plot.EdgeTypes.implement({
5966 'customNodeType': {
5967 'render': function(node, canvas) {
5968 this.edgeHelper.circle.render ...
5969 },
5970 //optional
5971 'contains': function(node, pos) {
5972 this.edgeHelper.circle.contains ...
5973 }
5974 }
5975 });
5976 (end code)
5977
5978*/
5979
5980/*
5981 Object: NodeHelper
5982
5983 Contains rendering and other type of primitives for simple shapes.
5984 */
5985var NodeHelper = {
5986 'none': {
5987 'render': $.empty,
5988 'contains': $.lambda(false)
5989 },
5990 /*
5991 Object: NodeHelper.circle
5992 */
5993 'circle': {
5994 /*
5995 Method: render
5996
5997 Renders a circle into the canvas.
5998
5999 Parameters:
6000
6001 type - (string) Possible options are 'fill' or 'stroke'.
6002 pos - (object) An *x*, *y* object with the position of the center of the circle.
6003 radius - (number) The radius of the circle to be rendered.
6004 canvas - (object) A <Canvas> instance.
6005
6006 Example:
6007 (start code js)
6008 NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6009 (end code)
6010 */
6011 'render': function(type, pos, radius, canvas){
6012 var ctx = canvas.getCtx();
6013 ctx.beginPath();
6014 ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6015 ctx.closePath();
6016 ctx[type]();
6017 },
6018 /*
6019 Method: contains
6020
6021 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6022
6023 Parameters:
6024
6025 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6026 pos - (object) An *x*, *y* object with the position to check.
6027 radius - (number) The radius of the rendered circle.
6028
6029 Example:
6030 (start code js)
6031 NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6032 (end code)
6033 */
6034 'contains': function(npos, pos, radius){
6035 var diffx = npos.x - pos.x,
6036 diffy = npos.y - pos.y,
6037 diff = diffx * diffx + diffy * diffy;
6038 return diff <= radius * radius;
6039 }
6040 },
6041 /*
6042 Object: NodeHelper.ellipse
6043 */
6044 'ellipse': {
6045 /*
6046 Method: render
6047
6048 Renders an ellipse into the canvas.
6049
6050 Parameters:
6051
6052 type - (string) Possible options are 'fill' or 'stroke'.
6053 pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6054 width - (number) The width of the ellipse.
6055 height - (number) The height of the ellipse.
6056 canvas - (object) A <Canvas> instance.
6057
6058 Example:
6059 (start code js)
6060 NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6061 (end code)
6062 */
6063 'render': function(type, pos, width, height, canvas){
6064 var ctx = canvas.getCtx(),
6065 scalex = 1,
6066 scaley = 1,
6067 scaleposx = 1,
6068 scaleposy = 1,
6069 radius = 0;
6070
6071 if (width > height) {
6072 radius = width / 2;
6073 scaley = height / width;
6074 scaleposy = width / height;
6075 } else {
6076 radius = height / 2;
6077 scalex = width / height;
6078 scaleposx = height / width;
6079 }
6080
6081 ctx.save();
6082 ctx.scale(scalex, scaley);
6083 ctx.beginPath();
6084 ctx.arc(pos.x * scaleposx, pos.y * scaleposy, radius, 0, Math.PI * 2, true);
6085 ctx.closePath();
6086 ctx[type]();
6087 ctx.restore();
6088 },
6089 /*
6090 Method: contains
6091
6092 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6093
6094 Parameters:
6095
6096 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6097 pos - (object) An *x*, *y* object with the position to check.
6098 width - (number) The width of the rendered ellipse.
6099 height - (number) The height of the rendered ellipse.
6100
6101 Example:
6102 (start code js)
6103 NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6104 (end code)
6105 */
6106 'contains': function(npos, pos, width, height){
6107 var radius = 0,
6108 scalex = 1,
6109 scaley = 1,
6110 diffx = 0,
6111 diffy = 0,
6112 diff = 0;
6113
6114 if (width > height) {
6115 radius = width / 2;
6116 scaley = height / width;
6117 } else {
6118 radius = height / 2;
6119 scalex = width / height;
6120 }
6121
6122 diffx = (npos.x - pos.x) * (1 / scalex);
6123 diffy = (npos.y - pos.y) * (1 / scaley);
6124 diff = diffx * diffx + diffy * diffy;
6125 return diff <= radius * radius;
6126 }
6127 },
6128 /*
6129 Object: NodeHelper.square
6130 */
6131 'square': {
6132 /*
6133 Method: render
6134
6135 Renders a square into the canvas.
6136
6137 Parameters:
6138
6139 type - (string) Possible options are 'fill' or 'stroke'.
6140 pos - (object) An *x*, *y* object with the position of the center of the square.
6141 dim - (number) The radius (or half-diameter) of the square.
6142 canvas - (object) A <Canvas> instance.
6143
6144 Example:
6145 (start code js)
6146 NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6147 (end code)
6148 */
6149 'render': function(type, pos, dim, canvas){
6150 canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6151 },
6152 /*
6153 Method: contains
6154
6155 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6156
6157 Parameters:
6158
6159 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6160 pos - (object) An *x*, *y* object with the position to check.
6161 dim - (number) The radius (or half-diameter) of the square.
6162
6163 Example:
6164 (start code js)
6165 NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6166 (end code)
6167 */
6168 'contains': function(npos, pos, dim){
6169 return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6170 }
6171 },
6172 /*
6173 Object: NodeHelper.rectangle
6174 */
6175 'rectangle': {
6176 /*
6177 Method: render
6178
6179 Renders a rectangle into the canvas.
6180
6181 Parameters:
6182
6183 type - (string) Possible options are 'fill' or 'stroke'.
6184 pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6185 width - (number) The width of the rectangle.
6186 height - (number) The height of the rectangle.
6187 canvas - (object) A <Canvas> instance.
6188
6189 Example:
6190 (start code js)
6191 NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6192 (end code)
6193 */
6194 'render': function(type, pos, width, height, canvas){
6195 canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2,
6196 width, height);
6197 },
6198 /*
6199 Method: contains
6200
6201 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6202
6203 Parameters:
6204
6205 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6206 pos - (object) An *x*, *y* object with the position to check.
6207 width - (number) The width of the rendered rectangle.
6208 height - (number) The height of the rendered rectangle.
6209
6210 Example:
6211 (start code js)
6212 NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6213 (end code)
6214 */
6215 'contains': function(npos, pos, width, height){
6216 return Math.abs(pos.x - npos.x) <= width / 2
6217 && Math.abs(pos.y - npos.y) <= height / 2;
6218 }
6219 },
6220 /*
6221 Object: NodeHelper.triangle
6222 */
6223 'triangle': {
6224 /*
6225 Method: render
6226
6227 Renders a triangle into the canvas.
6228
6229 Parameters:
6230
6231 type - (string) Possible options are 'fill' or 'stroke'.
6232 pos - (object) An *x*, *y* object with the position of the center of the triangle.
6233 dim - (number) Half the base and half the height of the triangle.
6234 canvas - (object) A <Canvas> instance.
6235
6236 Example:
6237 (start code js)
6238 NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6239 (end code)
6240 */
6241 'render': function(type, pos, dim, canvas){
6242 var ctx = canvas.getCtx(),
6243 c1x = pos.x,
6244 c1y = pos.y - dim,
6245 c2x = c1x - dim,
6246 c2y = pos.y + dim,
6247 c3x = c1x + dim,
6248 c3y = c2y;
6249 ctx.beginPath();
6250 ctx.moveTo(c1x, c1y);
6251 ctx.lineTo(c2x, c2y);
6252 ctx.lineTo(c3x, c3y);
6253 ctx.closePath();
6254 ctx[type]();
6255 },
6256 /*
6257 Method: contains
6258
6259 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6260
6261 Parameters:
6262
6263 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6264 pos - (object) An *x*, *y* object with the position to check.
6265 dim - (number) Half the base and half the height of the triangle.
6266
6267 Example:
6268 (start code js)
6269 NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6270 (end code)
6271 */
6272 'contains': function(npos, pos, dim) {
6273 return NodeHelper.circle.contains(npos, pos, dim);
6274 }
6275 },
6276 /*
6277 Object: NodeHelper.star
6278 */
6279 'star': {
6280 /*
6281 Method: render
6282
6283 Renders a star (concave decagon) into the canvas.
6284
6285 Parameters:
6286
6287 type - (string) Possible options are 'fill' or 'stroke'.
6288 pos - (object) An *x*, *y* object with the position of the center of the star.
6289 dim - (number) The length of a side of a concave decagon.
6290 canvas - (object) A <Canvas> instance.
6291
6292 Example:
6293 (start code js)
6294 NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6295 (end code)
6296 */
6297 'render': function(type, pos, dim, canvas){
6298 var ctx = canvas.getCtx(),
6299 pi5 = Math.PI / 5;
6300 ctx.save();
6301 ctx.translate(pos.x, pos.y);
6302 ctx.beginPath();
6303 ctx.moveTo(dim, 0);
6304 for (var i = 0; i < 9; i++) {
6305 ctx.rotate(pi5);
6306 if (i % 2 == 0) {
6307 ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6308 } else {
6309 ctx.lineTo(dim, 0);
6310 }
6311 }
6312 ctx.closePath();
6313 ctx[type]();
6314 ctx.restore();
6315 },
6316 /*
6317 Method: contains
6318
6319 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6320
6321 Parameters:
6322
6323 npos - (object) An *x*, *y* object with the <Graph.Node> position.
6324 pos - (object) An *x*, *y* object with the position to check.
6325 dim - (number) The length of a side of a concave decagon.
6326
6327 Example:
6328 (start code js)
6329 NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6330 (end code)
6331 */
6332 'contains': function(npos, pos, dim) {
6333 return NodeHelper.circle.contains(npos, pos, dim);
6334 }
6335 }
6336};
6337
6338/*
6339 Object: EdgeHelper
6340
6341 Contains rendering primitives for simple edge shapes.
6342*/
6343var EdgeHelper = {
6344 /*
6345 Object: EdgeHelper.line
6346 */
6347 'line': {
6348 /*
6349 Method: render
6350
6351 Renders a line into the canvas.
6352
6353 Parameters:
6354
6355 from - (object) An *x*, *y* object with the starting position of the line.
6356 to - (object) An *x*, *y* object with the ending position of the line.
6357 canvas - (object) A <Canvas> instance.
6358
6359 Example:
6360 (start code js)
6361 EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6362 (end code)
6363 */
6364 'render': function(from, to, canvas){
6365 var ctx = canvas.getCtx();
6366 ctx.beginPath();
6367 ctx.moveTo(from.x, from.y);
6368 ctx.lineTo(to.x, to.y);
6369 ctx.stroke();
6370 },
6371 /*
6372 Method: contains
6373
6374 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6375
6376 Parameters:
6377
6378 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6379 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6380 pos - (object) An *x*, *y* object with the position to check.
6381 epsilon - (number) The dimension of the shape.
6382
6383 Example:
6384 (start code js)
6385 EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6386 (end code)
6387 */
6388 'contains': function(posFrom, posTo, pos, epsilon) {
6389 var min = Math.min,
6390 max = Math.max,
6391 minPosX = min(posFrom.x, posTo.x),
6392 maxPosX = max(posFrom.x, posTo.x),
6393 minPosY = min(posFrom.y, posTo.y),
6394 maxPosY = max(posFrom.y, posTo.y);
6395
6396 if(pos.x >= minPosX && pos.x <= maxPosX
6397 && pos.y >= minPosY && pos.y <= maxPosY) {
6398 if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6399 return true;
6400 }
6401 var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6402 return Math.abs(dist - pos.y) <= epsilon;
6403 }
6404 return false;
6405 }
6406 },
6407 /*
6408 Object: EdgeHelper.arrow
6409 */
6410 'arrow': {
6411 /*
6412 Method: render
6413
6414 Renders an arrow into the canvas.
6415
6416 Parameters:
6417
6418 from - (object) An *x*, *y* object with the starting position of the arrow.
6419 to - (object) An *x*, *y* object with the ending position of the arrow.
6420 dim - (number) The dimension of the arrow.
6421 swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6422 canvas - (object) A <Canvas> instance.
6423
6424 Example:
6425 (start code js)
6426 EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6427 (end code)
6428 */
6429 'render': function(from, to, dim, swap, canvas){
6430 var ctx = canvas.getCtx();
6431 // invert edge direction
6432 if (swap) {
6433 var tmp = from;
6434 from = to;
6435 to = tmp;
6436 }
6437 var vect = new Complex(to.x - from.x, to.y - from.y);
6438 vect.$scale(dim / vect.norm());
6439 var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6440 normal = new Complex(-vect.y / 2, vect.x / 2),
6441 v1 = intermediatePoint.add(normal),
6442 v2 = intermediatePoint.$add(normal.$scale(-1));
6443
6444 ctx.beginPath();
6445 ctx.moveTo(from.x, from.y);
6446 ctx.lineTo(to.x, to.y);
6447 ctx.stroke();
6448 ctx.beginPath();
6449 ctx.moveTo(v1.x, v1.y);
6450 ctx.lineTo(v2.x, v2.y);
6451 ctx.lineTo(to.x, to.y);
6452 ctx.closePath();
6453 ctx.fill();
6454 },
6455 /*
6456 Method: contains
6457
6458 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6459
6460 Parameters:
6461
6462 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6463 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6464 pos - (object) An *x*, *y* object with the position to check.
6465 epsilon - (number) The dimension of the shape.
6466
6467 Example:
6468 (start code js)
6469 EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6470 (end code)
6471 */
6472 'contains': function(posFrom, posTo, pos, epsilon) {
6473 return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6474 }
6475 },
6476 /*
6477 Object: EdgeHelper.hyperline
6478 */
6479 'hyperline': {
6480 /*
6481 Method: render
6482
6483 Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6484
6485 Parameters:
6486
6487 from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6488 to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6489 r - (number) The scaling factor.
6490 canvas - (object) A <Canvas> instance.
6491
6492 Example:
6493 (start code js)
6494 EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6495 (end code)
6496 */
6497 'render': function(from, to, r, canvas){
6498 var ctx = canvas.getCtx();
6499 var centerOfCircle = computeArcThroughTwoPoints(from, to);
6500 if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6501 || centerOfCircle.ratio < 0) {
6502 ctx.beginPath();
6503 ctx.moveTo(from.x * r, from.y * r);
6504 ctx.lineTo(to.x * r, to.y * r);
6505 ctx.stroke();
6506 } else {
6507 var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6508 - centerOfCircle.x);
6509 var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6510 - centerOfCircle.x);
6511 var sense = sense(angleBegin, angleEnd);
6512 ctx.beginPath();
6513 ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6514 * r, angleBegin, angleEnd, sense);
6515 ctx.stroke();
6516 }
6517 /*
6518 Calculates the arc parameters through two points.
6519
6520 More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane>
6521
6522 Parameters:
6523
6524 p1 - A <Complex> instance.
6525 p2 - A <Complex> instance.
6526 scale - The Disk's diameter.
6527
6528 Returns:
6529
6530 An object containing some arc properties.
6531 */
6532 function computeArcThroughTwoPoints(p1, p2){
6533 var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6534 var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6535 // Fall back to a straight line
6536 if (aDen == 0)
6537 return {
6538 x: 0,
6539 y: 0,
6540 ratio: -1
6541 };
6542
6543 var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6544 var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6545 var x = -a / 2;
6546 var y = -b / 2;
6547 var squaredRatio = (a * a + b * b) / 4 - 1;
6548 // Fall back to a straight line
6549 if (squaredRatio < 0)
6550 return {
6551 x: 0,
6552 y: 0,
6553 ratio: -1
6554 };
6555 var ratio = Math.sqrt(squaredRatio);
6556 var out = {
6557 x: x,
6558 y: y,
6559 ratio: ratio > 1000? -1 : ratio,
6560 a: a,
6561 b: b
6562 };
6563
6564 return out;
6565 }
6566 /*
6567 Sets angle direction to clockwise (true) or counterclockwise (false).
6568
6569 Parameters:
6570
6571 angleBegin - Starting angle for drawing the arc.
6572 angleEnd - The HyperLine will be drawn from angleBegin to angleEnd.
6573
6574 Returns:
6575
6576 A Boolean instance describing the sense for drawing the HyperLine.
6577 */
6578 function sense(angleBegin, angleEnd){
6579 return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6580 : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6581 }
6582 },
6583 /*
6584 Method: contains
6585
6586 Not Implemented
6587
6588 Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6589
6590 Parameters:
6591
6592 posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6593 posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6594 pos - (object) An *x*, *y* object with the position to check.
6595 epsilon - (number) The dimension of the shape.
6596
6597 Example:
6598 (start code js)
6599 EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6600 (end code)
6601 */
6602 'contains': $.lambda(false)
6603 }
6604};
6605
6606
6607/*
6608 * File: Graph.Plot.js
6609 */
6610
6611/*
6612 Object: Graph.Plot
6613
6614 <Graph> rendering and animation methods.
6615
6616 Properties:
6617
6618 nodeHelper - <NodeHelper> object.
6619 edgeHelper - <EdgeHelper> object.
6620*/
6621Graph.Plot = {
6622 //Default initializer
6623 initialize: function(viz, klass){
6624 this.viz = viz;
6625 this.config = viz.config;
6626 this.node = viz.config.Node;
6627 this.edge = viz.config.Edge;
6628 this.animation = new Animation;
6629 this.nodeTypes = new klass.Plot.NodeTypes;
6630 this.edgeTypes = new klass.Plot.EdgeTypes;
6631 this.labels = viz.labels;
6632 },
6633
6634 //Add helpers
6635 nodeHelper: NodeHelper,
6636 edgeHelper: EdgeHelper,
6637
6638 Interpolator: {
6639 //node/edge property parsers
6640 'map': {
6641 'border': 'color',
6642 'color': 'color',
6643 'width': 'number',
6644 'height': 'number',
6645 'dim': 'number',
6646 'alpha': 'number',
6647 'lineWidth': 'number',
6648 'angularWidth':'number',
6649 'span':'number',
6650 'valueArray':'array-number',
6651 'dimArray':'array-number'
6652 //'colorArray':'array-color'
6653 },
6654
6655 //canvas specific parsers
6656 'canvas': {
6657 'globalAlpha': 'number',
6658 'fillStyle': 'color',
6659 'strokeStyle': 'color',
6660 'lineWidth': 'number',
6661 'shadowBlur': 'number',
6662 'shadowColor': 'color',
6663 'shadowOffsetX': 'number',
6664 'shadowOffsetY': 'number',
6665 'miterLimit': 'number'
6666 },
6667
6668 //label parsers
6669 'label': {
6670 'size': 'number',
6671 'color': 'color'
6672 },
6673
6674 //Number interpolator
6675 'compute': function(from, to, delta) {
6676 return from + (to - from) * delta;
6677 },
6678
6679 //Position interpolators
6680 'moebius': function(elem, props, delta, vector) {
6681 var v = vector.scale(-delta);
6682 if(v.norm() < 1) {
6683 var x = v.x, y = v.y;
6684 var ans = elem.startPos
6685 .getc().moebiusTransformation(v);
6686 elem.pos.setc(ans.x, ans.y);
6687 v.x = x; v.y = y;
6688 }
6689 },
6690
6691 'linear': function(elem, props, delta) {
6692 var from = elem.startPos.getc(true);
6693 var to = elem.endPos.getc(true);
6694 elem.pos.setc(this.compute(from.x, to.x, delta),
6695 this.compute(from.y, to.y, delta));
6696 },
6697
6698 'polar': function(elem, props, delta) {
6699 var from = elem.startPos.getp(true);
6700 var to = elem.endPos.getp();
6701 var ans = to.interpolate(from, delta);
6702 elem.pos.setp(ans.theta, ans.rho);
6703 },
6704
6705 //Graph's Node/Edge interpolators
6706 'number': function(elem, prop, delta, getter, setter) {
6707 var from = elem[getter](prop, 'start');
6708 var to = elem[getter](prop, 'end');
6709 elem[setter](prop, this.compute(from, to, delta));
6710 },
6711
6712 'color': function(elem, prop, delta, getter, setter) {
6713 var from = $.hexToRgb(elem[getter](prop, 'start'));
6714 var to = $.hexToRgb(elem[getter](prop, 'end'));
6715 var comp = this.compute;
6716 var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6717 parseInt(comp(from[1], to[1], delta)),
6718 parseInt(comp(from[2], to[2], delta))]);
6719
6720 elem[setter](prop, val);
6721 },
6722
6723 'array-number': function(elem, prop, delta, getter, setter) {
6724 var from = elem[getter](prop, 'start'),
6725 to = elem[getter](prop, 'end'),
6726 cur = [];
6727 for(var i=0, l=from.length; i<l; i++) {
6728 var fromi = from[i], toi = to[i];
6729 if(fromi.length) {
6730 for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6731 curi.push(this.compute(fromi[j], toi[j], delta));
6732 }
6733 cur.push(curi);
6734 } else {
6735 cur.push(this.compute(fromi, toi, delta));
6736 }
6737 }
6738 elem[setter](prop, cur);
6739 },
6740
6741 'node': function(elem, props, delta, map, getter, setter) {
6742 map = this[map];
6743 if(props) {
6744 var len = props.length;
6745 for(var i=0; i<len; i++) {
6746 var pi = props[i];
6747 this[map[pi]](elem, pi, delta, getter, setter);
6748 }
6749 } else {
6750 for(var pi in map) {
6751 this[map[pi]](elem, pi, delta, getter, setter);
6752 }
6753 }
6754 },
6755
6756 'edge': function(elem, props, delta, mapKey, getter, setter) {
6757 var adjs = elem.adjacencies;
6758 for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6759 },
6760
6761 'node-property': function(elem, props, delta) {
6762 this['node'](elem, props, delta, 'map', 'getData', 'setData');
6763 },
6764
6765 'edge-property': function(elem, props, delta) {
6766 this['edge'](elem, props, delta, 'map', 'getData', 'setData');
6767 },
6768
6769 'label-property': function(elem, props, delta) {
6770 this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6771 },
6772
6773 'node-style': function(elem, props, delta) {
6774 this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6775 },
6776
6777 'edge-style': function(elem, props, delta) {
6778 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6779 }
6780 },
6781
6782
6783 /*
6784 sequence
6785
6786 Iteratively performs an action while refreshing the state of the visualization.
6787
6788 Parameters:
6789
6790 options - (object) An object containing some sequence options described below
6791 condition - (function) A function returning a boolean instance in order to stop iterations.
6792 step - (function) A function to execute on each step of the iteration.
6793 onComplete - (function) A function to execute when the sequence finishes.
6794 duration - (number) Duration (in milliseconds) of each step.
6795
6796 Example:
6797 (start code js)
6798 var rg = new $jit.RGraph(options);
6799 var i = 0;
6800 rg.fx.sequence({
6801 condition: function() {
6802 return i == 10;
6803 },
6804 step: function() {
6805 alert(i++);
6806 },
6807 onComplete: function() {
6808 alert('done!');
6809 }
6810 });
6811 (end code)
6812
6813 */
6814 sequence: function(options) {
6815 var that = this;
6816 options = $.merge({
6817 condition: $.lambda(false),
6818 step: $.empty,
6819 onComplete: $.empty,
6820 duration: 200
6821 }, options || {});
6822
6823 var interval = setInterval(function() {
6824 if(options.condition()) {
6825 options.step();
6826 } else {
6827 clearInterval(interval);
6828 options.onComplete();
6829 }
6830 that.viz.refresh(true);
6831 }, options.duration);
6832 },
6833
6834 /*
6835 prepare
6836
6837 Prepare graph position and other attribute values before performing an Animation.
6838 This method is used internally by the Toolkit.
6839
6840 See also:
6841
6842 <Animation>, <Graph.Plot.animate>
6843
6844 */
6845 prepare: function(modes) {
6846 var graph = this.viz.graph,
6847 accessors = {
6848 'node-property': {
6849 'getter': 'getData',
6850 'setter': 'setData'
6851 },
6852 'edge-property': {
6853 'getter': 'getData',
6854 'setter': 'setData'
6855 },
6856 'node-style': {
6857 'getter': 'getCanvasStyle',
6858 'setter': 'setCanvasStyle'
6859 },
6860 'edge-style': {
6861 'getter': 'getCanvasStyle',
6862 'setter': 'setCanvasStyle'
6863 }
6864 };
6865
6866 //parse modes
6867 var m = {};
6868 if($.type(modes) == 'array') {
6869 for(var i=0, len=modes.length; i < len; i++) {
6870 var elems = modes[i].split(':');
6871 m[elems.shift()] = elems;
6872 }
6873 } else {
6874 for(var p in modes) {
6875 if(p == 'position') {
6876 m[modes.position] = [];
6877 } else {
6878 m[p] = $.splat(modes[p]);
6879 }
6880 }
6881 }
6882
6883 graph.eachNode(function(node) {
6884 node.startPos.set(node.pos);
6885 $.each(['node-property', 'node-style'], function(p) {
6886 if(p in m) {
6887 var prop = m[p];
6888 for(var i=0, l=prop.length; i < l; i++) {
6889 node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6890 }
6891 }
6892 });
6893 $.each(['edge-property', 'edge-style'], function(p) {
6894 if(p in m) {
6895 var prop = m[p];
6896 node.eachAdjacency(function(adj) {
6897 for(var i=0, l=prop.length; i < l; i++) {
6898 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6899 }
6900 });
6901 }
6902 });
6903 });
6904 return m;
6905 },
6906
6907 /*
6908 Method: animate
6909
6910 Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6911
6912 Parameters:
6913
6914 opt - (object) Animation options. The object properties are described below
6915 duration - (optional) Described in <Options.Fx>.
6916 fps - (optional) Described in <Options.Fx>.
6917 hideLabels - (optional|boolean) Whether to hide labels during the animation.
6918 modes - (required|object) An object with animation modes (described below).
6919
6920 Animation modes:
6921
6922 Animation modes are strings representing different node/edge and graph properties that you'd like to animate.
6923 They are represented by an object that has as keys main categories of properties to animate and as values a list
6924 of these specific properties. The properties are described below
6925
6926 position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6927 node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6928 edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6929 label-property - Describes which Label properties will be interpolated. These properties can be any of the ones defined in <Options.Label> like color or size.
6930 node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6931 edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6932
6933 Example:
6934 (start code js)
6935 var viz = new $jit.Viz(options);
6936 //...tweak some Data, CanvasStyles or LabelData properties...
6937 viz.fx.animate({
6938 modes: {
6939 'position': 'linear',
6940 'node-property': ['width', 'height'],
6941 'node-style': 'shadowColor',
6942 'label-property': 'size'
6943 },
6944 hideLabels: false
6945 });
6946 //...can also be written like this...
6947 viz.fx.animate({
6948 modes: ['linear',
6949 'node-property:width:height',
6950 'node-style:shadowColor',
6951 'label-property:size'],
6952 hideLabels: false
6953 });
6954 (end code)
6955 */
6956 animate: function(opt, versor) {
6957 opt = $.merge(this.viz.config, opt || {});
6958 var that = this,
6959 viz = this.viz,
6960 graph = viz.graph,
6961 interp = this.Interpolator,
6962 animation = opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
6963 //prepare graph values
6964 var m = this.prepare(opt.modes);
6965
6966 //animate
6967 if(opt.hideLabels) this.labels.hideLabels(true);
6968 animation.setOptions($.extend(opt, {
6969 $animating: false,
6970 compute: function(delta) {
6971 graph.eachNode(function(node) {
6972 for(var p in m) {
6973 interp[p](node, m[p], delta, versor);
6974 }
6975 });
6976 that.plot(opt, this.$animating, delta);
6977 this.$animating = true;
6978 },
6979 complete: function() {
6980 if(opt.hideLabels) that.labels.hideLabels(false);
6981 that.plot(opt);
6982 opt.onComplete();
6983 //TODO(nico): This shouldn't be here!
6984 //opt.onAfterCompute();
6985 }
6986 })).start();
6987 },
6988
6989 /*
6990 nodeFx
6991
6992 Apply animation to node properties like color, width, height, dim, etc.
6993
6994 Parameters:
6995
6996 options - Animation options. This object properties is described below
6997 elements - The Elements to be transformed. This is an object that has a properties
6998
6999 (start code js)
7000 'elements': {
7001 //can also be an array of ids
7002 'id': 'id-of-node-to-transform',
7003 //properties to be modified. All properties are optional.
7004 'properties': {
7005 'color': '#ccc', //some color
7006 'width': 10, //some width
7007 'height': 10, //some height
7008 'dim': 20, //some dim
7009 'lineWidth': 10 //some line width
7010 }
7011 }
7012 (end code)
7013
7014 - _reposition_ Whether to recalculate positions and add a motion animation.
7015 This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7016
7017 - _onComplete_ A method that is called when the animation completes.
7018
7019 ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7020
7021 Example:
7022 (start code js)
7023 var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7024 rg.fx.nodeFx({
7025 'elements': {
7026 'id':'mynodeid',
7027 'properties': {
7028 'color':'#ccf'
7029 },
7030 'transition': Trans.Quart.easeOut
7031 }
7032 });
7033 (end code)
7034 */
7035 nodeFx: function(opt) {
7036 var viz = this.viz,
7037 graph = viz.graph,
7038 animation = this.nodeFxAnimation,
7039 options = $.merge(this.viz.config, {
7040 'elements': {
7041 'id': false,
7042 'properties': {}
7043 },
7044 'reposition': false
7045 });
7046 opt = $.merge(options, opt || {}, {
7047 onBeforeCompute: $.empty,
7048 onAfterCompute: $.empty
7049 });
7050 //check if an animation is running
7051 animation.stopTimer();
7052 var props = opt.elements.properties;
7053 //set end values for nodes
7054 if(!opt.elements.id) {
7055 graph.eachNode(function(n) {
7056 for(var prop in props) {
7057 n.setData(prop, props[prop], 'end');
7058 }
7059 });
7060 } else {
7061 var ids = $.splat(opt.elements.id);
7062 $.each(ids, function(id) {
7063 var n = graph.getNode(id);
7064 if(n) {
7065 for(var prop in props) {
7066 n.setData(prop, props[prop], 'end');
7067 }
7068 }
7069 });
7070 }
7071 //get keys
7072 var propnames = [];
7073 for(var prop in props) propnames.push(prop);
7074 //add node properties modes
7075 var modes = ['node-property:' + propnames.join(':')];
7076 //set new node positions
7077 if(opt.reposition) {
7078 modes.push('linear');
7079 viz.compute('end');
7080 }
7081 //animate
7082 this.animate($.merge(opt, {
7083 modes: modes,
7084 type: 'nodefx'
7085 }));
7086 },
7087
7088
7089 /*
7090 Method: plot
7091
7092 Plots a <Graph>.
7093
7094 Parameters:
7095
7096 opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7097
7098 Example:
7099
7100 (start code js)
7101 var viz = new $jit.Viz(options);
7102 viz.fx.plot();
7103 (end code)
7104
7105 */
7106 plot: function(opt, animating) {
7107 var viz = this.viz,
7108 aGraph = viz.graph,
7109 canvas = viz.canvas,
7110 id = viz.root,
7111 that = this,
7112 ctx = canvas.getCtx(),
7113 min = Math.min,
7114 opt = opt || this.viz.controller;
7115
7116 opt.clearCanvas && canvas.clear();
7117
7118 var root = aGraph.getNode(id);
7119 if(!root) return;
7120
7121 var T = !!root.visited;
7122 aGraph.eachNode(function(node) {
7123 var nodeAlpha = node.getData('alpha');
7124 node.eachAdjacency(function(adj) {
7125 var nodeTo = adj.nodeTo;
7126 if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7127 !animating && opt.onBeforePlotLine(adj);
7128 that.plotLine(adj, canvas, animating);
7129 !animating && opt.onAfterPlotLine(adj);
7130 }
7131 });
7132 if(node.drawn) {
7133 !animating && opt.onBeforePlotNode(node);
7134 that.plotNode(node, canvas, animating);
7135 !animating && opt.onAfterPlotNode(node);
7136 }
7137 if(!that.labelsHidden && opt.withLabels) {
7138 if(node.drawn && nodeAlpha >= 0.95) {
7139 that.labels.plotLabel(canvas, node, opt);
7140 } else {
7141 that.labels.hideLabel(node, false);
7142 }
7143 }
7144 node.visited = !T;
7145 });
7146 },
7147
7148 /*
7149 Plots a Subtree.
7150 */
7151 plotTree: function(node, opt, animating) {
7152 var that = this,
7153 viz = this.viz,
7154 canvas = viz.canvas,
7155 config = this.config,
7156 ctx = canvas.getCtx();
7157 var nodeAlpha = node.getData('alpha');
7158 node.eachSubnode(function(elem) {
7159 if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7160 var adj = node.getAdjacency(elem.id);
7161 !animating && opt.onBeforePlotLine(adj);
7162 that.plotLine(adj, canvas, animating);
7163 !animating && opt.onAfterPlotLine(adj);
7164 that.plotTree(elem, opt, animating);
7165 }
7166 });
7167 if(node.drawn) {
7168 !animating && opt.onBeforePlotNode(node);
7169 this.plotNode(node, canvas, animating);
7170 !animating && opt.onAfterPlotNode(node);
7171 if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95)
7172 this.labels.plotLabel(canvas, node, opt);
7173 else
7174 this.labels.hideLabel(node, false);
7175 } else {
7176 this.labels.hideLabel(node, true);
7177 }
7178 },
7179
7180 /*
7181 Method: plotNode
7182
7183 Plots a <Graph.Node>.
7184
7185 Parameters:
7186
7187 node - (object) A <Graph.Node>.
7188 canvas - (object) A <Canvas> element.
7189
7190 */
7191 plotNode: function(node, canvas, animating) {
7192 var f = node.getData('type'),
7193 ctxObj = this.node.CanvasStyles;
7194 if(f != 'none') {
7195 var width = node.getData('lineWidth'),
7196 color = node.getData('color'),
7197 alpha = node.getData('alpha'),
7198 ctx = canvas.getCtx();
7199 ctx.save();
7200 ctx.lineWidth = width;
7201 ctx.fillStyle = ctx.strokeStyle = color;
7202 ctx.globalAlpha = alpha;
7203
7204 for(var s in ctxObj) {
7205 ctx[s] = node.getCanvasStyle(s);
7206 }
7207
7208 this.nodeTypes[f].render.call(this, node, canvas, animating);
7209 ctx.restore();
7210 }
7211 },
7212
7213 /*
7214 Method: plotLine
7215
7216 Plots a <Graph.Adjacence>.
7217
7218 Parameters:
7219
7220 adj - (object) A <Graph.Adjacence>.
7221 canvas - (object) A <Canvas> instance.
7222
7223 */
7224 plotLine: function(adj, canvas, animating) {
7225 var f = adj.getData('type'),
7226 ctxObj = this.edge.CanvasStyles;
7227 if(f != 'none') {
7228 var width = adj.getData('lineWidth'),
7229 color = adj.getData('color'),
7230 ctx = canvas.getCtx(),
7231 nodeFrom = adj.nodeFrom,
7232 nodeTo = adj.nodeTo;
7233
7234 ctx.save();
7235 ctx.lineWidth = width;
7236 ctx.fillStyle = ctx.strokeStyle = color;
7237 ctx.globalAlpha = Math.min(nodeFrom.getData('alpha'),
7238 nodeTo.getData('alpha'),
7239 adj.getData('alpha'));
7240
7241 for(var s in ctxObj) {
7242 ctx[s] = adj.getCanvasStyle(s);
7243 }
7244
7245 this.edgeTypes[f].render.call(this, adj, canvas, animating);
7246 ctx.restore();
7247 }
7248 }
7249
7250};
7251
7252/*
7253 Object: Graph.Plot3D
7254
7255 <Graph> 3D rendering and animation methods.
7256
7257 Properties:
7258
7259 nodeHelper - <NodeHelper> object.
7260 edgeHelper - <EdgeHelper> object.
7261
7262*/
7263Graph.Plot3D = $.merge(Graph.Plot, {
7264 Interpolator: {
7265 'linear': function(elem, props, delta) {
7266 var from = elem.startPos.getc(true);
7267 var to = elem.endPos.getc(true);
7268 elem.pos.setc(this.compute(from.x, to.x, delta),
7269 this.compute(from.y, to.y, delta),
7270 this.compute(from.z, to.z, delta));
7271 }
7272 },
7273
7274 plotNode: function(node, canvas) {
7275 if(node.getData('type') == 'none') return;
7276 this.plotElement(node, canvas, {
7277 getAlpha: function() {
7278 return node.getData('alpha');
7279 }
7280 });
7281 },
7282
7283 plotLine: function(adj, canvas) {
7284 if(adj.getData('type') == 'none') return;
7285 this.plotElement(adj, canvas, {
7286 getAlpha: function() {
7287 return Math.min(adj.nodeFrom.getData('alpha'),
7288 adj.nodeTo.getData('alpha'),
7289 adj.getData('alpha'));
7290 }
7291 });
7292 },
7293
7294 plotElement: function(elem, canvas, opt) {
7295 var gl = canvas.getCtx(),
7296 viewMatrix = new Matrix4,
7297 lighting = canvas.config.Scene.Lighting,
7298 wcanvas = canvas.canvases[0],
7299 program = wcanvas.program,
7300 camera = wcanvas.camera;
7301
7302 if(!elem.geometry) {
7303 elem.geometry = new O3D[elem.getData('type')];
7304 }
7305 elem.geometry.update(elem);
7306 if(!elem.webGLVertexBuffer) {
7307 var vertices = [],
7308 faces = [],
7309 normals = [],
7310 vertexIndex = 0,
7311 geom = elem.geometry;
7312
7313 for(var i=0, vs=geom.vertices, fs=geom.faces, fsl=fs.length; i<fsl; i++) {
7314 var face = fs[i],
7315 v1 = vs[face.a],
7316 v2 = vs[face.b],
7317 v3 = vs[face.c],
7318 v4 = face.d? vs[face.d] : false,
7319 n = face.normal;
7320
7321 vertices.push(v1.x, v1.y, v1.z);
7322 vertices.push(v2.x, v2.y, v2.z);
7323 vertices.push(v3.x, v3.y, v3.z);
7324 if(v4) vertices.push(v4.x, v4.y, v4.z);
7325
7326 normals.push(n.x, n.y, n.z);
7327 normals.push(n.x, n.y, n.z);
7328 normals.push(n.x, n.y, n.z);
7329 if(v4) normals.push(n.x, n.y, n.z);
7330
7331 faces.push(vertexIndex, vertexIndex +1, vertexIndex +2);
7332 if(v4) {
7333 faces.push(vertexIndex, vertexIndex +2, vertexIndex +3);
7334 vertexIndex += 4;
7335 } else {
7336 vertexIndex += 3;
7337 }
7338 }
7339 //create and store vertex data
7340 elem.webGLVertexBuffer = gl.createBuffer();
7341 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLVertexBuffer);
7342 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
7343 //create and store faces index data
7344 elem.webGLFaceBuffer = gl.createBuffer();
7345 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem.webGLFaceBuffer);
7346 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(faces), gl.STATIC_DRAW);
7347 elem.webGLFaceCount = faces.length;
7348 //calculate vertex normals and store them
7349 elem.webGLNormalBuffer = gl.createBuffer();
7350 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLNormalBuffer);
7351 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
7352 }
7353 viewMatrix.multiply(camera.matrix, elem.geometry.matrix);
7354 //send matrix data
7355 gl.uniformMatrix4fv(program.viewMatrix, false, viewMatrix.flatten());
7356 gl.uniformMatrix4fv(program.projectionMatrix, false, camera.projectionMatrix.flatten());
7357 //send normal matrix for lighting
7358 var normalMatrix = Matrix4.makeInvert(viewMatrix);
7359 normalMatrix.$transpose();
7360 gl.uniformMatrix4fv(program.normalMatrix, false, normalMatrix.flatten());
7361 //send color data
7362 var color = $.hexToRgb(elem.getData('color'));
7363 color.push(opt.getAlpha());
7364 gl.uniform4f(program.color, color[0] / 255, color[1] / 255, color[2] / 255, color[3]);
7365 //send lighting data
7366 gl.uniform1i(program.enableLighting, lighting.enable);
7367 if(lighting.enable) {
7368 //set ambient light color
7369 if(lighting.ambient) {
7370 var acolor = lighting.ambient;
7371 gl.uniform3f(program.ambientColor, acolor[0], acolor[1], acolor[2]);
7372 }
7373 //set directional light
7374 if(lighting.directional) {
7375 var dir = lighting.directional,
7376 color = dir.color,
7377 pos = dir.direction,
7378 vd = new Vector3(pos.x, pos.y, pos.z).normalize().$scale(-1);
7379 gl.uniform3f(program.lightingDirection, vd.x, vd.y, vd.z);
7380 gl.uniform3f(program.directionalColor, color[0], color[1], color[2]);
7381 }
7382 }
7383 //send vertices data
7384 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLVertexBuffer);
7385 gl.vertexAttribPointer(program.position, 3, gl.FLOAT, false, 0, 0);
7386 //send normals data
7387 gl.bindBuffer(gl.ARRAY_BUFFER, elem.webGLNormalBuffer);
7388 gl.vertexAttribPointer(program.normal, 3, gl.FLOAT, false, 0, 0);
7389 //draw!
7390 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem.webGLFaceBuffer );
7391 gl.drawElements(gl.TRIANGLES, elem.webGLFaceCount, gl.UNSIGNED_SHORT, 0);
7392 }
7393});
7394
7395
7396/*
7397 * File: Graph.Label.js
7398 *
7399*/
7400
7401/*
7402 Object: Graph.Label
7403
7404 An interface for plotting/hiding/showing labels.
7405
7406 Description:
7407
7408 This is a generic interface for plotting/hiding/showing labels.
7409 The <Graph.Label> interface is implemented in multiple ways to provide
7410 different label types.
7411
7412 For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7413 HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels.
7414 The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7415
7416 All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7417*/
7418
7419Graph.Label = {};
7420
7421/*
7422 Class: Graph.Label.Native
7423
7424 Implements labels natively, using the Canvas text API.
7425*/
7426Graph.Label.Native = new Class({
7427 initialize: function(viz) {
7428 this.viz = viz;
7429 },
7430
7431 /*
7432 Method: plotLabel
7433
7434 Plots a label for a given node.
7435
7436 Parameters:
7437
7438 canvas - (object) A <Canvas> instance.
7439 node - (object) A <Graph.Node>.
7440 controller - (object) A configuration object.
7441
7442 Example:
7443
7444 (start code js)
7445 var viz = new $jit.Viz(options);
7446 var node = viz.graph.getNode('nodeId');
7447 viz.labels.plotLabel(viz.canvas, node, viz.config);
7448 (end code)
7449 */
7450 plotLabel: function(canvas, node, controller) {
7451 var ctx = canvas.getCtx();
7452 var pos = node.pos.getc(true);
7453
7454 ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7455 ctx.textAlign = node.getLabelData('textAlign');
7456 ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7457 ctx.textBaseline = node.getLabelData('textBaseline');
7458
7459 this.renderLabel(canvas, node, controller);
7460 },
7461
7462 /*
7463 renderLabel
7464
7465 Does the actual rendering of the label in the canvas. The default
7466 implementation renders the label close to the position of the node, this
7467 method should be overriden to position the labels differently.
7468
7469 Parameters:
7470
7471 canvas - A <Canvas> instance.
7472 node - A <Graph.Node>.
7473 controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7474 */
7475 renderLabel: function(canvas, node, controller) {
7476 var ctx = canvas.getCtx();
7477 var pos = node.pos.getc(true);
7478 ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7479 },
7480
7481 hideLabel: $.empty,
7482 hideLabels: $.empty
7483});
7484
7485/*
7486 Class: Graph.Label.DOM
7487
7488 Abstract Class implementing some DOM label methods.
7489
7490 Implemented by:
7491
7492 <Graph.Label.HTML> and <Graph.Label.SVG>.
7493
7494*/
7495Graph.Label.DOM = new Class({
7496 //A flag value indicating if node labels are being displayed or not.
7497 labelsHidden: false,
7498 //Label container
7499 labelContainer: false,
7500 //Label elements hash.
7501 labels: {},
7502
7503 /*
7504 Method: getLabelContainer
7505
7506 Lazy fetcher for the label container.
7507
7508 Returns:
7509
7510 The label container DOM element.
7511
7512 Example:
7513
7514 (start code js)
7515 var viz = new $jit.Viz(options);
7516 var labelContainer = viz.labels.getLabelContainer();
7517 alert(labelContainer.innerHTML);
7518 (end code)
7519 */
7520 getLabelContainer: function() {
7521 return this.labelContainer ?
7522 this.labelContainer :
7523 this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7524 },
7525
7526 /*
7527 Method: getLabel
7528
7529 Lazy fetcher for the label element.
7530
7531 Parameters:
7532
7533 id - (string) The label id (which is also a <Graph.Node> id).
7534
7535 Returns:
7536
7537 The label element.
7538
7539 Example:
7540
7541 (start code js)
7542 var viz = new $jit.Viz(options);
7543 var label = viz.labels.getLabel('someid');
7544 alert(label.innerHTML);
7545 (end code)
7546
7547 */
7548 getLabel: function(id) {
7549 return (id in this.labels && this.labels[id] != null) ?
7550 this.labels[id] :
7551 this.labels[id] = document.getElementById(id);
7552 },
7553
7554 /*
7555 Method: hideLabels
7556
7557 Hides all labels (by hiding the label container).
7558
7559 Parameters:
7560
7561 hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7562
7563 Example:
7564 (start code js)
7565 var viz = new $jit.Viz(options);
7566 rg.labels.hideLabels(true);
7567 (end code)
7568
7569 */
7570 hideLabels: function (hide) {
7571 var container = this.getLabelContainer();
7572 if(hide)
7573 container.style.display = 'none';
7574 else
7575 container.style.display = '';
7576 this.labelsHidden = hide;
7577 },
7578
7579 /*
7580 Method: clearLabels
7581
7582 Clears the label container.
7583
7584 Useful when using a new visualization with the same canvas element/widget.
7585
7586 Parameters:
7587
7588 force - (boolean) Forces deletion of all labels.
7589
7590 Example:
7591 (start code js)
7592 var viz = new $jit.Viz(options);
7593 viz.labels.clearLabels();
7594 (end code)
7595 */
7596 clearLabels: function(force) {
7597 for(var id in this.labels) {
7598 if (force || !this.viz.graph.hasNode(id)) {
7599 this.disposeLabel(id);
7600 delete this.labels[id];
7601 }
7602 }
7603 },
7604
7605 /*
7606 Method: disposeLabel
7607
7608 Removes a label.
7609
7610 Parameters:
7611
7612 id - (string) A label id (which generally is also a <Graph.Node> id).
7613
7614 Example:
7615 (start code js)
7616 var viz = new $jit.Viz(options);
7617 viz.labels.disposeLabel('labelid');
7618 (end code)
7619 */
7620 disposeLabel: function(id) {
7621 var elem = this.getLabel(id);
7622 if(elem && elem.parentNode) {
7623 elem.parentNode.removeChild(elem);
7624 }
7625 },
7626
7627 /*
7628 Method: hideLabel
7629
7630 Hides the corresponding <Graph.Node> label.
7631
7632 Parameters:
7633
7634 node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7635 show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7636
7637 Example:
7638 (start code js)
7639 var rg = new $jit.Viz(options);
7640 viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7641 (end code)
7642 */
7643 hideLabel: function(node, show) {
7644 node = $.splat(node);
7645 var st = show ? "" : "none", lab, that = this;
7646 $.each(node, function(n) {
7647 var lab = that.getLabel(n.id);
7648 if (lab) {
7649 lab.style.display = st;
7650 }
7651 });
7652 },
7653
7654 /*
7655 fitsInCanvas
7656
7657 Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7658
7659 Parameters:
7660
7661 pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7662 canvas - A <Canvas> instance.
7663
7664 Returns:
7665
7666 A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7667
7668 */
7669 fitsInCanvas: function(pos, canvas) {
7670 var size = canvas.getSize();
7671 if(pos.x >= size.width || pos.x < 0
7672 || pos.y >= size.height || pos.y < 0) return false;
7673 return true;
7674 }
7675});
7676
7677/*
7678 Class: Graph.Label.HTML
7679
7680 Implements HTML labels.
7681
7682 Extends:
7683
7684 All <Graph.Label.DOM> methods.
7685
7686*/
7687Graph.Label.HTML = new Class({
7688 Implements: Graph.Label.DOM,
7689
7690 /*
7691 Method: plotLabel
7692
7693 Plots a label for a given node.
7694
7695 Parameters:
7696
7697 canvas - (object) A <Canvas> instance.
7698 node - (object) A <Graph.Node>.
7699 controller - (object) A configuration object.
7700
7701 Example:
7702
7703 (start code js)
7704 var viz = new $jit.Viz(options);
7705 var node = viz.graph.getNode('nodeId');
7706 viz.labels.plotLabel(viz.canvas, node, viz.config);
7707 (end code)
7708
7709
7710 */
7711 plotLabel: function(canvas, node, controller) {
7712 var id = node.id, tag = this.getLabel(id);
7713
7714 if(!tag && !(tag = document.getElementById(id))) {
7715 tag = document.createElement('div');
7716 var container = this.getLabelContainer();
7717 tag.id = id;
7718 tag.className = 'node';
7719 tag.style.position = 'absolute';
7720 controller.onCreateLabel(tag, node);
7721 container.appendChild(tag);
7722 this.labels[node.id] = tag;
7723 }
7724
7725 this.placeLabel(tag, node, controller);
7726 }
7727});
7728
7729/*
7730 Class: Graph.Label.SVG
7731
7732 Implements SVG labels.
7733
7734 Extends:
7735
7736 All <Graph.Label.DOM> methods.
7737*/
7738Graph.Label.SVG = new Class({
7739 Implements: Graph.Label.DOM,
7740
7741 /*
7742 Method: plotLabel
7743
7744 Plots a label for a given node.
7745
7746 Parameters:
7747
7748 canvas - (object) A <Canvas> instance.
7749 node - (object) A <Graph.Node>.
7750 controller - (object) A configuration object.
7751
7752 Example:
7753
7754 (start code js)
7755 var viz = new $jit.Viz(options);
7756 var node = viz.graph.getNode('nodeId');
7757 viz.labels.plotLabel(viz.canvas, node, viz.config);
7758 (end code)
7759
7760
7761 */
7762 plotLabel: function(canvas, node, controller) {
7763 var id = node.id, tag = this.getLabel(id);
7764 if(!tag && !(tag = document.getElementById(id))) {
7765 var ns = 'http://www.w3.org/2000/svg';
7766 tag = document.createElementNS(ns, 'svg:text');
7767 var tspan = document.createElementNS(ns, 'svg:tspan');
7768 tag.appendChild(tspan);
7769 var container = this.getLabelContainer();
7770 tag.setAttribute('id', id);
7771 tag.setAttribute('class', 'node');
7772 container.appendChild(tag);
7773 controller.onCreateLabel(tag, node);
7774 this.labels[node.id] = tag;
7775 }
7776 this.placeLabel(tag, node, controller);
7777 }
7778});
7779
7780
7781
7782Graph.Geom = new Class({
7783
7784 initialize: function(viz) {
7785 this.viz = viz;
7786 this.config = viz.config;
7787 this.node = viz.config.Node;
7788 this.edge = viz.config.Edge;
7789 },
7790 /*
7791 Applies a translation to the tree.
7792
7793 Parameters:
7794
7795 pos - A <Complex> number specifying translation vector.
7796 prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7797
7798 Example:
7799
7800 (start code js)
7801 st.geom.translate(new Complex(300, 100), 'end');
7802 (end code)
7803 */
7804 translate: function(pos, prop) {
7805 prop = $.splat(prop);
7806 this.viz.graph.eachNode(function(elem) {
7807 $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7808 });
7809 },
7810 /*
7811 Hides levels of the tree until it properly fits in canvas.
7812 */
7813 setRightLevelToShow: function(node, canvas, callback) {
7814 var level = this.getRightLevelToShow(node, canvas),
7815 fx = this.viz.labels,
7816 opt = $.merge({
7817 execShow:true,
7818 execHide:true,
7819 onHide: $.empty,
7820 onShow: $.empty
7821 }, callback || {});
7822 node.eachLevel(0, this.config.levelsToShow, function(n) {
7823 var d = n._depth - node._depth;
7824 if(d > level) {
7825 opt.onHide(n);
7826 if(opt.execHide) {
7827 n.drawn = false;
7828 n.exist = false;
7829 fx.hideLabel(n, false);
7830 }
7831 } else {
7832 opt.onShow(n);
7833 if(opt.execShow) {
7834 n.exist = true;
7835 }
7836 }
7837 });
7838 node.drawn= true;
7839 },
7840 /*
7841 Returns the right level to show for the current tree in order to fit in canvas.
7842 */
7843 getRightLevelToShow: function(node, canvas) {
7844 var config = this.config;
7845 var level = config.levelsToShow;
7846 var constrained = config.constrained;
7847 if(!constrained) return level;
7848 while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7849 return level;
7850 }
7851});
7852
7853/*
7854 * File: Loader.js
7855 *
7856 */
7857
7858/*
7859 Object: Loader
7860
7861 Provides methods for loading and serving JSON data.
7862*/
7863var Loader = {
7864 construct: function(json) {
7865 var isGraph = ($.type(json) == 'array');
7866 var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7867 if(!isGraph)
7868 //make tree
7869 (function (ans, json) {
7870 ans.addNode(json);
7871 if(json.children) {
7872 for(var i=0, ch = json.children; i<ch.length; i++) {
7873 ans.addAdjacence(json, ch[i]);
7874 arguments.callee(ans, ch[i]);
7875 }
7876 }
7877 })(ans, json);
7878 else
7879 //make graph
7880 (function (ans, json) {
7881 var getNode = function(id) {
7882 for(var i=0, l=json.length; i<l; i++) {
7883 if(json[i].id == id) {
7884 return json[i];
7885 }
7886 }
7887 // The node was not defined in the JSON
7888 // Let's create it
7889 var newNode = {
7890 "id" : id,
7891 "name" : id
7892 };
7893 return ans.addNode(newNode);
7894 };
7895
7896 for(var i=0, l=json.length; i<l; i++) {
7897 ans.addNode(json[i]);
7898 var adj = json[i].adjacencies;
7899 if (adj) {
7900 for(var j=0, lj=adj.length; j<lj; j++) {
7901 var node = adj[j], data = {};
7902 if(typeof adj[j] != 'string') {
7903 data = $.merge(node.data, {});
7904 node = node.nodeTo;
7905 }
7906 ans.addAdjacence(json[i], getNode(node), data);
7907 }
7908 }
7909 }
7910 })(ans, json);
7911
7912 return ans;
7913 },
7914
7915 /*
7916 Method: loadJSON
7917
7918 Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7919
7920 A JSON tree or graph structure consists of nodes, each having as properties
7921
7922 id - (string) A unique identifier for the node
7923 name - (string) A node's name
7924 data - (object) The data optional property contains a hash (i.e {})
7925 where you can store all the information you want about this node.
7926
7927 For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7928
7929 Example:
7930
7931 (start code js)
7932 var json = {
7933 "id": "aUniqueIdentifier",
7934 "name": "usually a nodes name",
7935 "data": {
7936 "some key": "some value",
7937 "some other key": "some other value"
7938 },
7939 "children": [ *other nodes or empty* ]
7940 };
7941 (end code)
7942
7943 JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected.
7944 For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7945
7946 There are two types of *Graph* structures, *simple* and *extended* graph structures.
7947
7948 For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the
7949 id of the node connected to the main node.
7950
7951 Example:
7952
7953 (start code js)
7954 var json = [
7955 {
7956 "id": "aUniqueIdentifier",
7957 "name": "usually a nodes name",
7958 "data": {
7959 "some key": "some value",
7960 "some other key": "some other value"
7961 },
7962 "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']
7963 },
7964
7965 'other nodes go here...'
7966 ];
7967 (end code)
7968
7969 For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7970
7971 nodeTo - (string) The other node connected by this adjacency.
7972 data - (object) A data property, where we can store custom key/value information.
7973
7974 Example:
7975
7976 (start code js)
7977 var json = [
7978 {
7979 "id": "aUniqueIdentifier",
7980 "name": "usually a nodes name",
7981 "data": {
7982 "some key": "some value",
7983 "some other key": "some other value"
7984 },
7985 "adjacencies": [
7986 {
7987 nodeTo:"aNodeId",
7988 data: {} //put whatever you want here
7989 },
7990 'other adjacencies go here...'
7991 },
7992
7993 'other nodes go here...'
7994 ];
7995 (end code)
7996
7997 About the data property:
7998
7999 As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*.
8000 You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and
8001 have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
8002
8003 For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in
8004 <Options.Node> will override the general value for that option with that particular value. For this to work
8005 however, you do have to set *overridable = true* in <Options.Node>.
8006
8007 The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge>
8008 if <Options.Edge> has *overridable = true*.
8009
8010 When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key,
8011 since this is the value which will be taken into account when creating the layout.
8012 The same thing goes for the *$color* parameter.
8013
8014 In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example,
8015 *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set
8016 canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer
8017 to the *shadowBlur* property.
8018
8019 These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences>
8020 by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
8021
8022 Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more
8023 information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
8024
8025 loadJSON Parameters:
8026
8027 json - A JSON Tree or Graph structure.
8028 i - For Graph structures only. Sets the indexed node as root for the visualization.
8029
8030 */
8031 loadJSON: function(json, i) {
8032 this.json = json;
8033 //if they're canvas labels erase them.
8034 if(this.labels && this.labels.clearLabels) {
8035 this.labels.clearLabels(true);
8036 }
8037 this.graph = this.construct(json);
8038 if($.type(json) != 'array'){
8039 this.root = json.id;
8040 } else {
8041 this.root = json[i? i : 0].id;
8042 }
8043 },
8044
8045 /*
8046 Method: toJSON
8047
8048 Returns a JSON tree/graph structure from the visualization's <Graph>.
8049 See <Loader.loadJSON> for the graph formats available.
8050
8051 See also:
8052
8053 <Loader.loadJSON>
8054
8055 Parameters:
8056
8057 type - (string) Default's "tree". The type of the JSON structure to be returned.
8058 Possible options are "tree" or "graph".
8059 */
8060 toJSON: function(type) {
8061 type = type || "tree";
8062 if(type == 'tree') {
8063 var ans = {};
8064 var rootNode = this.graph.getNode(this.root);
8065 var ans = (function recTree(node) {
8066 var ans = {};
8067 ans.id = node.id;
8068 ans.name = node.name;
8069 ans.data = node.data;
8070 var ch =[];
8071 node.eachSubnode(function(n) {
8072 ch.push(recTree(n));
8073 });
8074 ans.children = ch;
8075 return ans;
8076 })(rootNode);
8077 return ans;
8078 } else {
8079 var ans = [];
8080 var T = !!this.graph.getNode(this.root).visited;
8081 this.graph.eachNode(function(node) {
8082 var ansNode = {};
8083 ansNode.id = node.id;
8084 ansNode.name = node.name;
8085 ansNode.data = node.data;
8086 var adjs = [];
8087 node.eachAdjacency(function(adj) {
8088 var nodeTo = adj.nodeTo;
8089 if(!!nodeTo.visited === T) {
8090 var ansAdj = {};
8091 ansAdj.nodeTo = nodeTo.id;
8092 ansAdj.data = adj.data;
8093 adjs.push(ansAdj);
8094 }
8095 });
8096 ansNode.adjacencies = adjs;
8097 ans.push(ansNode);
8098 node.visited = !T;
8099 });
8100 return ans;
8101 }
8102 }
8103};
8104
8105
8106
8107/*
8108 * File: Layouts.js
8109 *
8110 * Implements base Tree and Graph layouts.
8111 *
8112 * Description:
8113 *
8114 * Implements base Tree and Graph layouts like Radial, Tree, etc.
8115 *
8116 */
8117
8118/*
8119 * Object: Layouts
8120 *
8121 * Parent object for common layouts.
8122 *
8123 */
8124var Layouts = $jit.Layouts = {};
8125
8126
8127//Some util shared layout functions are defined here.
8128var NodeDim = {
8129 label: null,
8130
8131 compute: function(graph, prop, opt) {
8132 this.initializeLabel(opt);
8133 var label = this.label, style = label.style;
8134 graph.eachNode(function(n) {
8135 var autoWidth = n.getData('autoWidth'),
8136 autoHeight = n.getData('autoHeight');
8137 if(autoWidth || autoHeight) {
8138 //delete dimensions since these are
8139 //going to be overridden now.
8140 delete n.data.$width;
8141 delete n.data.$height;
8142 delete n.data.$dim;
8143
8144 var width = n.getData('width'),
8145 height = n.getData('height');
8146 //reset label dimensions
8147 style.width = autoWidth? 'auto' : width + 'px';
8148 style.height = autoHeight? 'auto' : height + 'px';
8149
8150 //TODO(nico) should let the user choose what to insert here.
8151 label.innerHTML = n.name;
8152
8153 var offsetWidth = label.offsetWidth,
8154 offsetHeight = label.offsetHeight;
8155 var type = n.getData('type');
8156 if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8157 n.setData('width', offsetWidth);
8158 n.setData('height', offsetHeight);
8159 } else {
8160 var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8161 n.setData('width', dim);
8162 n.setData('height', dim);
8163 n.setData('dim', dim);
8164 }
8165 }
8166 });
8167 },
8168
8169 initializeLabel: function(opt) {
8170 if(!this.label) {
8171 this.label = document.createElement('div');
8172 document.body.appendChild(this.label);
8173 }
8174 this.setLabelStyles(opt);
8175 },
8176
8177 setLabelStyles: function(opt) {
8178 $.extend(this.label.style, {
8179 'visibility': 'hidden',
8180 'position': 'absolute',
8181 'width': 'auto',
8182 'height': 'auto'
8183 });
8184 this.label.className = 'jit-autoadjust-label';
8185 }
8186};
8187
8188
8189/*
8190 * Class: Layouts.Tree
8191 *
8192 * Implements a Tree Layout.
8193 *
8194 * Implemented By:
8195 *
8196 * <ST>
8197 *
8198 * Inspired by:
8199 *
8200 * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8201 *
8202 */
8203Layouts.Tree = (function() {
8204 //Layout functions
8205 var slice = Array.prototype.slice;
8206
8207 /*
8208 Calculates the max width and height nodes for a tree level
8209 */
8210 function getBoundaries(graph, config, level, orn, prop) {
8211 var dim = config.Node;
8212 var multitree = config.multitree;
8213 if (dim.overridable) {
8214 var w = -1, h = -1;
8215 graph.eachNode(function(n) {
8216 if (n._depth == level
8217 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8218 var dw = n.getData('width', prop);
8219 var dh = n.getData('height', prop);
8220 w = (w < dw) ? dw : w;
8221 h = (h < dh) ? dh : h;
8222 }
8223 });
8224 return {
8225 'width' : w < 0 ? dim.width : w,
8226 'height' : h < 0 ? dim.height : h
8227 };
8228 } else {
8229 return dim;
8230 }
8231 }
8232
8233
8234 function movetree(node, prop, val, orn) {
8235 var p = (orn == "left" || orn == "right") ? "y" : "x";
8236 node.getPos(prop)[p] += val;
8237 }
8238
8239
8240 function moveextent(extent, val) {
8241 var ans = [];
8242 $.each(extent, function(elem) {
8243 elem = slice.call(elem);
8244 elem[0] += val;
8245 elem[1] += val;
8246 ans.push(elem);
8247 });
8248 return ans;
8249 }
8250
8251
8252 function merge(ps, qs) {
8253 if (ps.length == 0)
8254 return qs;
8255 if (qs.length == 0)
8256 return ps;
8257 var p = ps.shift(), q = qs.shift();
8258 return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8259 }
8260
8261
8262 function mergelist(ls, def) {
8263 def = def || [];
8264 if (ls.length == 0)
8265 return def;
8266 var ps = ls.pop();
8267 return mergelist(ls, merge(ps, def));
8268 }
8269
8270
8271 function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8272 if (ext1.length <= i || ext2.length <= i)
8273 return 0;
8274
8275 var p = ext1[i][1], q = ext2[i][0];
8276 return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8277 + subtreeOffset, p - q + siblingOffset);
8278 }
8279
8280
8281 function fitlistl(es, subtreeOffset, siblingOffset) {
8282 function $fitlistl(acc, es, i) {
8283 if (es.length <= i)
8284 return [];
8285 var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8286 return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8287 }
8288 ;
8289 return $fitlistl( [], es, 0);
8290 }
8291
8292
8293 function fitlistr(es, subtreeOffset, siblingOffset) {
8294 function $fitlistr(acc, es, i) {
8295 if (es.length <= i)
8296 return [];
8297 var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8298 return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8299 }
8300 ;
8301 es = slice.call(es);
8302 var ans = $fitlistr( [], es.reverse(), 0);
8303 return ans.reverse();
8304 }
8305
8306
8307 function fitlist(es, subtreeOffset, siblingOffset, align) {
8308 var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8309 subtreeOffset, siblingOffset);
8310
8311 if (align == "left")
8312 esr = esl;
8313 else if (align == "right")
8314 esl = esr;
8315
8316 for ( var i = 0, ans = []; i < esl.length; i++) {
8317 ans[i] = (esl[i] + esr[i]) / 2;
8318 }
8319 return ans;
8320 }
8321
8322
8323 function design(graph, node, prop, config, orn) {
8324 var multitree = config.multitree;
8325 var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8326 var ind = +(orn == "left" || orn == "right");
8327 var p = auxp[ind], notp = auxp[1 - ind];
8328
8329 var cnode = config.Node;
8330 var s = auxs[ind], nots = auxs[1 - ind];
8331
8332 var siblingOffset = config.siblingOffset;
8333 var subtreeOffset = config.subtreeOffset;
8334 var align = config.align;
8335
8336 function $design(node, maxsize, acum) {
8337 var sval = node.getData(s, prop);
8338 var notsval = maxsize
8339 || (node.getData(nots, prop));
8340
8341 var trees = [], extents = [], chmaxsize = false;
8342 var chacum = notsval + config.levelDistance;
8343 node.eachSubnode(function(n) {
8344 if (n.exist
8345 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8346
8347 if (!chmaxsize)
8348 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8349
8350 var s = $design(n, chmaxsize[nots], acum + chacum);
8351 trees.push(s.tree);
8352 extents.push(s.extent);
8353 }
8354 });
8355 var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8356 for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8357 movetree(trees[i], prop, positions[i], orn);
8358 pextents.push(moveextent(extents[i], positions[i]));
8359 }
8360 var resultextent = [ [ -sval / 2, sval / 2 ] ]
8361 .concat(mergelist(pextents));
8362 node.getPos(prop)[p] = 0;
8363
8364 if (orn == "top" || orn == "left") {
8365 node.getPos(prop)[notp] = acum;
8366 } else {
8367 node.getPos(prop)[notp] = -acum;
8368 }
8369
8370 return {
8371 tree : node,
8372 extent : resultextent
8373 };
8374 }
8375
8376 $design(node, false, 0);
8377 }
8378
8379
8380 return new Class({
8381 /*
8382 Method: compute
8383
8384 Computes nodes' positions.
8385
8386 */
8387 compute : function(property, computeLevels) {
8388 var prop = property || 'start';
8389 var node = this.graph.getNode(this.root);
8390 $.extend(node, {
8391 'drawn' : true,
8392 'exist' : true,
8393 'selected' : true
8394 });
8395 NodeDim.compute(this.graph, prop, this.config);
8396 if (!!computeLevels || !("_depth" in node)) {
8397 this.graph.computeLevels(this.root, 0, "ignore");
8398 }
8399
8400 this.computePositions(node, prop);
8401 },
8402
8403 computePositions : function(node, prop) {
8404 var config = this.config;
8405 var multitree = config.multitree;
8406 var align = config.align;
8407 var indent = align !== 'center' && config.indent;
8408 var orn = config.orientation;
8409 var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8410 var that = this;
8411 $.each(orns, function(orn) {
8412 //calculate layout
8413 design(that.graph, node, prop, that.config, orn, prop);
8414 var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8415 //absolutize
8416 (function red(node) {
8417 node.eachSubnode(function(n) {
8418 if (n.exist
8419 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8420
8421 n.getPos(prop)[i] += node.getPos(prop)[i];
8422 if (indent) {
8423 n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8424 }
8425 red(n);
8426 }
8427 });
8428 })(node);
8429 });
8430 }
8431 });
8432
8433})();
8434
8435/*
8436 * File: Spacetree.js
8437 */
8438
8439/*
8440 Class: ST
8441
8442 A Tree layout with advanced contraction and expansion animations.
8443
8444 Inspired by:
8445
8446 SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson)
8447 <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8448
8449 Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8450
8451 Note:
8452
8453 This visualization was built and engineered from scratch, taking only the papers as inspiration, and only shares some features with the visualization described in those papers.
8454
8455 Implements:
8456
8457 All <Loader> methods
8458
8459 Constructor Options:
8460
8461 Inherits options from
8462
8463 - <Options.Canvas>
8464 - <Options.Controller>
8465 - <Options.Tree>
8466 - <Options.Node>
8467 - <Options.Edge>
8468 - <Options.Label>
8469 - <Options.Events>
8470 - <Options.Tips>
8471 - <Options.NodeStyles>
8472 - <Options.Navigation>
8473
8474 Additionally, there are other parameters and some default values changed
8475
8476 constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8477 levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8478 levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8479 Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8480 offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8481 offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8482 duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8483
8484 Instance Properties:
8485
8486 canvas - Access a <Canvas> instance.
8487 graph - Access a <Graph> instance.
8488 op - Access a <ST.Op> instance.
8489 fx - Access a <ST.Plot> instance.
8490 labels - Access a <ST.Label> interface implementation.
8491
8492 */
8493
8494$jit.ST= (function() {
8495 // Define some private methods first...
8496 // Nodes in path
8497 var nodesInPath = [];
8498 // Nodes to contract
8499 function getNodesToHide(node) {
8500 node = node || this.clickedNode;
8501 if(!this.config.constrained) {
8502 return [];
8503 }
8504 var Geom = this.geom;
8505 var graph = this.graph;
8506 var canvas = this.canvas;
8507 var level = node._depth, nodeArray = [];
8508 graph.eachNode(function(n) {
8509 if(n.exist && !n.selected) {
8510 if(n.isDescendantOf(node.id)) {
8511 if(n._depth <= level) nodeArray.push(n);
8512 } else {
8513 nodeArray.push(n);
8514 }
8515 }
8516 });
8517 var leafLevel = Geom.getRightLevelToShow(node, canvas);
8518 node.eachLevel(leafLevel, leafLevel, function(n) {
8519 if(n.exist && !n.selected) nodeArray.push(n);
8520 });
8521
8522 for (var i = 0; i < nodesInPath.length; i++) {
8523 var n = this.graph.getNode(nodesInPath[i]);
8524 if(!n.isDescendantOf(node.id)) {
8525 nodeArray.push(n);
8526 }
8527 }
8528 return nodeArray;
8529 };
8530 // Nodes to expand
8531 function getNodesToShow(node) {
8532 var nodeArray = [], config = this.config;
8533 node = node || this.clickedNode;
8534 this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8535 if(config.multitree && !('$orn' in n.data)
8536 && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8537 nodeArray.push(n);
8538 } else if(n.drawn && !n.anySubnode("drawn")) {
8539 nodeArray.push(n);
8540 }
8541 });
8542 return nodeArray;
8543 };
8544 // Now define the actual class.
8545 return new Class({
8546
8547 Implements: [Loader, Extras, Layouts.Tree],
8548
8549 initialize: function(controller) {
8550 var $ST = $jit.ST;
8551
8552 var config= {
8553 levelsToShow: 2,
8554 levelDistance: 30,
8555 constrained: true,
8556 Node: {
8557 type: 'rectangle'
8558 },
8559 duration: 700,
8560 offsetX: 0,
8561 offsetY: 0
8562 };
8563
8564 this.controller = this.config = $.merge(
8565 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller",
8566 "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8567
8568 var canvasConfig = this.config;
8569 if(canvasConfig.useCanvas) {
8570 this.canvas = canvasConfig.useCanvas;
8571 this.config.labelContainer = this.canvas.id + '-label';
8572 } else {
8573 if(canvasConfig.background) {
8574 canvasConfig.background = $.merge({
8575 type: 'Circles'
8576 }, canvasConfig.background);
8577 }
8578 this.canvas = new Canvas(this, canvasConfig);
8579 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8580 }
8581
8582 this.graphOptions = {
8583 'klass': Complex
8584 };
8585 this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8586 this.labels = new $ST.Label[canvasConfig.Label.type](this);
8587 this.fx = new $ST.Plot(this, $ST);
8588 this.op = new $ST.Op(this);
8589 this.group = new $ST.Group(this);
8590 this.geom = new $ST.Geom(this);
8591 this.clickedNode= null;
8592 // initialize extras
8593 this.initializeExtras();
8594 },
8595
8596 /*
8597 Method: plot
8598
8599 Plots the <ST>. This is a shortcut to *fx.plot*.
8600
8601 */
8602 plot: function() { this.fx.plot(this.controller); },
8603
8604
8605 /*
8606 Method: switchPosition
8607
8608 Switches the tree orientation.
8609
8610 Parameters:
8611
8612 pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8613 method - (string) Set this to "animate" if you want to animate the tree when switching its position. You can also set this parameter to "replot" to just replot the subtree.
8614 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8615
8616 Example:
8617
8618 (start code js)
8619 st.switchPosition("right", "animate", {
8620 onComplete: function() {
8621 alert('completed!');
8622 }
8623 });
8624 (end code)
8625 */
8626 switchPosition: function(pos, method, onComplete) {
8627 var Geom = this.geom, Plot = this.fx, that = this;
8628 if(!Plot.busy) {
8629 Plot.busy = true;
8630 this.contract({
8631 onComplete: function() {
8632 Geom.switchOrientation(pos);
8633 that.compute('end', false);
8634 Plot.busy = false;
8635 if(method == 'animate') {
8636 that.onClick(that.clickedNode.id, onComplete);
8637 } else if(method == 'replot') {
8638 that.select(that.clickedNode.id, onComplete);
8639 }
8640 }
8641 }, pos);
8642 }
8643 },
8644
8645 /*
8646 Method: switchAlignment
8647
8648 Switches the tree alignment.
8649
8650 Parameters:
8651
8652 align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8653 method - (string) Set this to "animate" if you want to animate the tree after aligning its position. You can also set this parameter to "replot" to just replot the subtree.
8654 onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8655
8656 Example:
8657
8658 (start code js)
8659 st.switchAlignment("right", "animate", {
8660 onComplete: function() {
8661 alert('completed!');
8662 }
8663 });
8664 (end code)
8665 */
8666 switchAlignment: function(align, method, onComplete) {
8667 this.config.align = align;
8668 if(method == 'animate') {
8669 this.select(this.clickedNode.id, onComplete);
8670 } else if(method == 'replot') {
8671 this.onClick(this.clickedNode.id, onComplete);
8672 }
8673 },
8674
8675 /*
8676 Method: addNodeInPath
8677
8678 Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8679
8680
8681 Parameters:
8682
8683 id - (string) A <Graph.Node> id.
8684
8685 Example:
8686
8687 (start code js)
8688 st.addNodeInPath("nodeId");
8689 (end code)
8690 */
8691 addNodeInPath: function(id) {
8692 nodesInPath.push(id);
8693 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8694 },
8695
8696 /*
8697 Method: clearNodesInPath
8698
8699 Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8700
8701 See also:
8702
8703 <ST.addNodeInPath>
8704
8705 Example:
8706
8707 (start code js)
8708 st.clearNodesInPath();
8709 (end code)
8710 */
8711 clearNodesInPath: function(id) {
8712 nodesInPath.length = 0;
8713 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8714 },
8715
8716 /*
8717 Method: refresh
8718
8719 Computes positions and plots the tree.
8720
8721 */
8722 refresh: function() {
8723 this.reposition();
8724 this.select((this.clickedNode && this.clickedNode.id) || this.root);
8725 },
8726
8727 reposition: function() {
8728 this.graph.computeLevels(this.root, 0, "ignore");
8729 this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8730 this.graph.eachNode(function(n) {
8731 if(n.exist) n.drawn = true;
8732 });
8733 this.compute('end');
8734 },
8735
8736 requestNodes: function(node, onComplete) {
8737 var handler = $.merge(this.controller, onComplete),
8738 lev = this.config.levelsToShow;
8739 if(handler.request) {
8740 var leaves = [], d = node._depth;
8741 node.eachLevel(0, lev, function(n) {
8742 if(n.drawn &&
8743 !n.anySubnode()) {
8744 leaves.push(n);
8745 n._level = lev - (n._depth - d);
8746 }
8747 });
8748 this.group.requestNodes(leaves, handler);
8749 }
8750 else
8751 handler.onComplete();
8752 },
8753
8754 contract: function(onComplete, switched) {
8755 var orn = this.config.orientation;
8756 var Geom = this.geom, Group = this.group;
8757 if(switched) Geom.switchOrientation(switched);
8758 var nodes = getNodesToHide.call(this);
8759 if(switched) Geom.switchOrientation(orn);
8760 Group.contract(nodes, $.merge(this.controller, onComplete));
8761 },
8762
8763 move: function(node, onComplete) {
8764 this.compute('end', false);
8765 var move = onComplete.Move, offset = {
8766 'x': move.offsetX,
8767 'y': move.offsetY
8768 };
8769 if(move.enable) {
8770 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8771 }
8772 this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8773 },
8774
8775 expand: function (node, onComplete) {
8776 var nodeArray = getNodesToShow.call(this, node);
8777 this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8778 },
8779
8780 selectPath: function(node) {
8781 var that = this;
8782 this.graph.eachNode(function(n) { n.selected = false; });
8783 function path(node) {
8784 if(node == null || node.selected) return;
8785 node.selected = true;
8786 $.each(that.group.getSiblings([node])[node.id],
8787 function(n) {
8788 n.exist = true;
8789 n.drawn = true;
8790 });
8791 var parents = node.getParents();
8792 parents = (parents.length > 0)? parents[0] : null;
8793 path(parents);
8794 };
8795 for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8796 path(this.graph.getNode(ns[i]));
8797 }
8798 },
8799
8800 /*
8801 Method: setRoot
8802
8803 Switches the current root node. Changes the topology of the Tree.
8804
8805 Parameters:
8806 id - (string) The id of the node to be set as root.
8807 method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8808 onComplete - (optional|object) An action to perform after the animation (if any).
8809
8810 Example:
8811
8812 (start code js)
8813 st.setRoot('nodeId', 'animate', {
8814 onComplete: function() {
8815 alert('complete!');
8816 }
8817 });
8818 (end code)
8819 */
8820 setRoot: function(id, method, onComplete) {
8821 if(this.busy) return;
8822 this.busy = true;
8823 var that = this, canvas = this.canvas;
8824 var rootNode = this.graph.getNode(this.root);
8825 var clickedNode = this.graph.getNode(id);
8826 function $setRoot() {
8827 if(this.config.multitree && clickedNode.data.$orn) {
8828 var orn = clickedNode.data.$orn;
8829 var opp = {
8830 'left': 'right',
8831 'right': 'left',
8832 'top': 'bottom',
8833 'bottom': 'top'
8834 }[orn];
8835 rootNode.data.$orn = opp;
8836 (function tag(rootNode) {
8837 rootNode.eachSubnode(function(n) {
8838 if(n.id != id) {
8839 n.data.$orn = opp;
8840 tag(n);
8841 }
8842 });
8843 })(rootNode);
8844 delete clickedNode.data.$orn;
8845 }
8846 this.root = id;
8847 this.clickedNode = clickedNode;
8848 this.graph.computeLevels(this.root, 0, "ignore");
8849 this.geom.setRightLevelToShow(clickedNode, canvas, {
8850 execHide: false,
8851 onShow: function(node) {
8852 if(!node.drawn) {
8853 node.drawn = true;
8854 node.setData('alpha', 1, 'end');
8855 node.setData('alpha', 0);
8856 node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8857 }
8858 }
8859 });
8860 this.compute('end');
8861 this.busy = true;
8862 this.fx.animate({
8863 modes: ['linear', 'node-property:alpha'],
8864 onComplete: function() {
8865 that.busy = false;
8866 that.onClick(id, {
8867 onComplete: function() {
8868 onComplete && onComplete.onComplete();
8869 }
8870 });
8871 }
8872 });
8873 }
8874
8875 // delete previous orientations (if any)
8876 delete rootNode.data.$orns;
8877
8878 if(method == 'animate') {
8879 $setRoot.call(this);
8880 that.selectPath(clickedNode);
8881 } else if(method == 'replot') {
8882 $setRoot.call(this);
8883 this.select(this.root);
8884 }
8885 },
8886
8887 /*
8888 Method: addSubtree
8889
8890 Adds a subtree.
8891
8892 Parameters:
8893 subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8894 method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8895 onComplete - (optional|object) An action to perform after the animation (if any).
8896
8897 Example:
8898
8899 (start code js)
8900 st.addSubtree(json, 'animate', {
8901 onComplete: function() {
8902 alert('complete!');
8903 }
8904 });
8905 (end code)
8906 */
8907 addSubtree: function(subtree, method, onComplete) {
8908 if(method == 'replot') {
8909 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8910 } else if (method == 'animate') {
8911 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8912 }
8913 },
8914
8915 /*
8916 Method: removeSubtree
8917
8918 Removes a subtree.
8919
8920 Parameters:
8921 id - (string) The _id_ of the subtree to be removed.
8922 removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8923 method - (string) Set this to "animate" if you want to animate the tree after removing the subtree. You can also set this parameter to "replot" to just replot the subtree.
8924 onComplete - (optional|object) An action to perform after the animation (if any).
8925
8926 Example:
8927
8928 (start code js)
8929 st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8930 onComplete: function() {
8931 alert('complete!');
8932 }
8933 });
8934 (end code)
8935
8936 */
8937 removeSubtree: function(id, removeRoot, method, onComplete) {
8938 var node = this.graph.getNode(id), subids = [];
8939 node.eachLevel(+!removeRoot, false, function(n) {
8940 subids.push(n.id);
8941 });
8942 if(method == 'replot') {
8943 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8944 } else if (method == 'animate') {
8945 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8946 }
8947 },
8948
8949 /*
8950 Method: select
8951
8952 Selects a node in the <ST> without performing an animation. Useful when selecting
8953 nodes which are currently hidden or deep inside the tree.
8954
8955 Parameters:
8956 id - (string) The id of the node to select.
8957 onComplete - (optional|object) an onComplete callback.
8958
8959 Example:
8960 (start code js)
8961 st.select('mynodeid', {
8962 onComplete: function() {
8963 alert('complete!');
8964 }
8965 });
8966 (end code)
8967 */
8968 select: function(id, onComplete) {
8969 var group = this.group, geom = this.geom;
8970 var node= this.graph.getNode(id), canvas = this.canvas;
8971 var root = this.graph.getNode(this.root);
8972 var complete = $.merge(this.controller, onComplete);
8973 var that = this;
8974
8975 complete.onBeforeCompute(node);
8976 this.selectPath(node);
8977 this.clickedNode= node;
8978 this.requestNodes(node, {
8979 onComplete: function(){
8980 group.hide(group.prepare(getNodesToHide.call(that)), complete);
8981 geom.setRightLevelToShow(node, canvas);
8982 that.compute("current");
8983 that.graph.eachNode(function(n) {
8984 var pos = n.pos.getc(true);
8985 n.startPos.setc(pos.x, pos.y);
8986 n.endPos.setc(pos.x, pos.y);
8987 n.visited = false;
8988 });
8989 var offset = { x: complete.offsetX, y: complete.offsetY };
8990 that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8991 group.show(getNodesToShow.call(that));
8992 that.plot();
8993 complete.onAfterCompute(that.clickedNode);
8994 complete.onComplete();
8995 }
8996 });
8997 },
8998
8999 /*
9000 Method: onClick
9001
9002 Animates the <ST> to center the node specified by *id*.
9003
9004 Parameters:
9005
9006 id - (string) A node id.
9007 options - (optional|object) A group of options and callbacks described below.
9008 onComplete - (object) An object callback called when the animation finishes.
9009 Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
9010
9011 Example:
9012
9013 (start code js)
9014 st.onClick('mynodeid', {
9015 Move: {
9016 enable: true,
9017 offsetX: 30,
9018 offsetY: 5
9019 },
9020 onComplete: function() {
9021 alert('yay!');
9022 }
9023 });
9024 (end code)
9025
9026 */
9027 onClick: function (id, options) {
9028 var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
9029 var innerController = {
9030 Move: {
9031 enable: true,
9032 offsetX: config.offsetX || 0,
9033 offsetY: config.offsetY || 0
9034 },
9035 setRightLevelToShowConfig: false,
9036 onBeforeRequest: $.empty,
9037 onBeforeContract: $.empty,
9038 onBeforeMove: $.empty,
9039 onBeforeExpand: $.empty
9040 };
9041 var complete = $.merge(this.controller, innerController, options);
9042
9043 if(!this.busy) {
9044 this.busy = true;
9045 var node = this.graph.getNode(id);
9046 this.selectPath(node, this.clickedNode);
9047 this.clickedNode = node;
9048 complete.onBeforeCompute(node);
9049 complete.onBeforeRequest(node);
9050 this.requestNodes(node, {
9051 onComplete: function() {
9052 complete.onBeforeContract(node);
9053 that.contract({
9054 onComplete: function() {
9055 Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
9056 complete.onBeforeMove(node);
9057 that.move(node, {
9058 Move: complete.Move,
9059 onComplete: function() {
9060 complete.onBeforeExpand(node);
9061 that.expand(node, {
9062 onComplete: function() {
9063 that.busy = false;
9064 complete.onAfterCompute(id);
9065 complete.onComplete();
9066 }
9067 }); // expand
9068 }
9069 }); // move
9070 }
9071 });// contract
9072 }
9073 });// request
9074 }
9075 }
9076 });
9077
9078})();
9079
9080$jit.ST.$extend = true;
9081
9082/*
9083 Class: ST.Op
9084
9085 Custom extension of <Graph.Op>.
9086
9087 Extends:
9088
9089 All <Graph.Op> methods
9090
9091 See also:
9092
9093 <Graph.Op>
9094
9095*/
9096$jit.ST.Op = new Class({
9097
9098 Implements: Graph.Op
9099
9100});
9101
9102/*
9103
9104 Performs operations on group of nodes.
9105
9106*/
9107$jit.ST.Group = new Class({
9108
9109 initialize: function(viz) {
9110 this.viz = viz;
9111 this.canvas = viz.canvas;
9112 this.config = viz.config;
9113 this.animation = new Animation;
9114 this.nodes = null;
9115 },
9116
9117 /*
9118
9119 Calls the request method on the controller to request a subtree for each node.
9120 */
9121 requestNodes: function(nodes, controller) {
9122 var counter = 0, len = nodes.length, nodeSelected = {};
9123 var complete = function() { controller.onComplete(); };
9124 var viz = this.viz;
9125 if(len == 0) complete();
9126 for(var i=0; i<len; i++) {
9127 nodeSelected[nodes[i].id] = nodes[i];
9128 controller.request(nodes[i].id, nodes[i]._level, {
9129 onComplete: function(nodeId, data) {
9130 if(data && data.children) {
9131 data.id = nodeId;
9132 viz.op.sum(data, { type: 'nothing' });
9133 }
9134 if(++counter == len) {
9135 viz.graph.computeLevels(viz.root, 0);
9136 complete();
9137 }
9138 }
9139 });
9140 }
9141 },
9142
9143 /*
9144
9145 Collapses group of nodes.
9146 */
9147 contract: function(nodes, controller) {
9148 var viz = this.viz;
9149 var that = this;
9150
9151 nodes = this.prepare(nodes);
9152 this.animation.setOptions($.merge(controller, {
9153 $animating: false,
9154 compute: function(delta) {
9155 if(delta == 1) delta = 0.99;
9156 that.plotStep(1 - delta, controller, this.$animating);
9157 this.$animating = 'contract';
9158 },
9159
9160 complete: function() {
9161 that.hide(nodes, controller);
9162 }
9163 })).start();
9164 },
9165
9166 hide: function(nodes, controller) {
9167 var viz = this.viz;
9168 for(var i=0; i<nodes.length; i++) {
9169 // TODO nodes are requested on demand, but not
9170 // deleted when hidden. Would that be a good feature?
9171 // Currently that feature is buggy, so I'll turn it off
9172 // Actually this feature is buggy because trimming should take
9173 // place onAfterCompute and not right after collapsing nodes.
9174 if (true || !controller || !controller.request) {
9175 nodes[i].eachLevel(1, false, function(elem){
9176 if (elem.exist) {
9177 $.extend(elem, {
9178 'drawn': false,
9179 'exist': false
9180 });
9181 }
9182 });
9183 } else {
9184 var ids = [];
9185 nodes[i].eachLevel(1, false, function(n) {
9186 ids.push(n.id);
9187 });
9188 viz.op.removeNode(ids, { 'type': 'nothing' });
9189 viz.labels.clearLabels();
9190 }
9191 }
9192 controller.onComplete();
9193 },
9194
9195
9196 /*
9197 Expands group of nodes.
9198 */
9199 expand: function(nodes, controller) {
9200 var that = this;
9201 this.show(nodes);
9202 this.animation.setOptions($.merge(controller, {
9203 $animating: false,
9204 compute: function(delta) {
9205 that.plotStep(delta, controller, this.$animating);
9206 this.$animating = 'expand';
9207 },
9208
9209 complete: function() {
9210 that.plotStep(undefined, controller, false);
9211 controller.onComplete();
9212 }
9213 })).start();
9214
9215 },
9216
9217 show: function(nodes) {
9218 var config = this.config;
9219 this.prepare(nodes);
9220 $.each(nodes, function(n) {
9221 // check for root nodes if multitree
9222 if(config.multitree && !('$orn' in n.data)) {
9223 delete n.data.$orns;
9224 var orns = ' ';
9225 n.eachSubnode(function(ch) {
9226 if(('$orn' in ch.data)
9227 && orns.indexOf(ch.data.$orn) < 0
9228 && ch.exist && !ch.drawn) {
9229 orns += ch.data.$orn + ' ';
9230 }
9231 });
9232 n.data.$orns = orns;
9233 }
9234 n.eachLevel(0, config.levelsToShow, function(n) {
9235 if(n.exist) n.drawn = true;
9236 });
9237 });
9238 },
9239
9240 prepare: function(nodes) {
9241 this.nodes = this.getNodesWithChildren(nodes);
9242 return this.nodes;
9243 },
9244
9245 /*
9246 Filters an array of nodes leaving only nodes with children.
9247 */
9248 getNodesWithChildren: function(nodes) {
9249 var ans = [], config = this.config, root = this.viz.root;
9250 nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9251 for(var i=0; i<nodes.length; i++) {
9252 if(nodes[i].anySubnode("exist")) {
9253 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9254 if(!config.multitree || '$orn' in nodes[j].data) {
9255 desc = desc || nodes[i].isDescendantOf(nodes[j].id);
9256 }
9257 }
9258 if(!desc) ans.push(nodes[i]);
9259 }
9260 }
9261 return ans;
9262 },
9263
9264 plotStep: function(delta, controller, animating) {
9265 var viz = this.viz,
9266 config = this.config,
9267 canvas = viz.canvas,
9268 ctx = canvas.getCtx(),
9269 nodes = this.nodes;
9270 var i, node;
9271 // hide nodes that are meant to be collapsed/expanded
9272 var nds = {};
9273 for(i=0; i<nodes.length; i++) {
9274 node = nodes[i];
9275 nds[node.id] = [];
9276 var root = config.multitree && !('$orn' in node.data);
9277 var orns = root && node.data.$orns;
9278 node.eachSubgraph(function(n) {
9279 // TODO(nico): Cleanup
9280 // special check for root node subnodes when
9281 // multitree is checked.
9282 if(root && orns && orns.indexOf(n.data.$orn) > 0
9283 && n.drawn) {
9284 n.drawn = false;
9285 nds[node.id].push(n);
9286 } else if((!root || !orns) && n.drawn) {
9287 n.drawn = false;
9288 nds[node.id].push(n);
9289 }
9290 });
9291 node.drawn = true;
9292 }
9293 // plot the whole (non-scaled) tree
9294 if(nodes.length > 0) viz.fx.plot();
9295 // show nodes that were previously hidden
9296 for(i in nds) {
9297 $.each(nds[i], function(n) { n.drawn = true; });
9298 }
9299 // plot each scaled subtree
9300 for(i=0; i<nodes.length; i++) {
9301 node = nodes[i];
9302 ctx.save();
9303 viz.fx.plotSubtree(node, controller, delta, animating);
9304 ctx.restore();
9305 }
9306 },
9307
9308 getSiblings: function(nodes) {
9309 var siblings = {};
9310 $.each(nodes, function(n) {
9311 var par = n.getParents();
9312 if (par.length == 0) {
9313 siblings[n.id] = [n];
9314 } else {
9315 var ans = [];
9316 par[0].eachSubnode(function(sn) {
9317 ans.push(sn);
9318 });
9319 siblings[n.id] = ans;
9320 }
9321 });
9322 return siblings;
9323 }
9324});
9325
9326/*
9327 ST.Geom
9328
9329 Performs low level geometrical computations.
9330
9331 Access:
9332
9333 This instance can be accessed with the _geom_ parameter of the st instance created.
9334
9335 Example:
9336
9337 (start code js)
9338 var st = new ST(canvas, config);
9339 st.geom.translate //or can also call any other <ST.Geom> method
9340 (end code)
9341
9342*/
9343
9344$jit.ST.Geom = new Class({
9345 Implements: Graph.Geom,
9346 /*
9347 Changes the tree current orientation to the one specified.
9348
9349 You should usually use <ST.switchPosition> instead.
9350 */
9351 switchOrientation: function(orn) {
9352 this.config.orientation = orn;
9353 },
9354
9355 /*
9356 Makes a value dispatch according to the current layout
9357 Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9358 */
9359 dispatch: function() {
9360 // TODO(nico) should store Array.prototype.slice.call somewhere.
9361 var args = Array.prototype.slice.call(arguments);
9362 var s = args.shift(), len = args.length;
9363 var val = function(a) { return typeof a == 'function'? a() : a; };
9364 if(len == 2) {
9365 return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9366 } else if(len == 4) {
9367 switch(s) {
9368 case "top": return val(args[0]);
9369 case "right": return val(args[1]);
9370 case "bottom": return val(args[2]);
9371 case "left": return val(args[3]);
9372 }
9373 }
9374 return undefined;
9375 },
9376
9377 /*
9378 Returns label height or with, depending on the tree current orientation.
9379 */
9380 getSize: function(n, invert) {
9381 var data = n.data, config = this.config;
9382 var siblingOffset = config.siblingOffset;
9383 var s = (config.multitree
9384 && ('$orn' in data)
9385 && data.$orn) || config.orientation;
9386 var w = n.getData('width') + siblingOffset;
9387 var h = n.getData('height') + siblingOffset;
9388 if(!invert)
9389 return this.dispatch(s, h, w);
9390 else
9391 return this.dispatch(s, w, h);
9392 },
9393
9394 /*
9395 Calculates a subtree base size. This is an utility function used by _getBaseSize_
9396 */
9397 getTreeBaseSize: function(node, level, leaf) {
9398 var size = this.getSize(node, true), baseHeight = 0, that = this;
9399 if(leaf(level, node)) return size;
9400 if(level === 0) return 0;
9401 node.eachSubnode(function(elem) {
9402 baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9403 });
9404 return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9405 },
9406
9407
9408 /*
9409 getEdge
9410
9411 Returns a Complex instance with the begin or end position of the edge to be plotted.
9412
9413 Parameters:
9414
9415 node - A <Graph.Node> that is connected to this edge.
9416 type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9417
9418 Returns:
9419
9420 A <Complex> number specifying the begin or end position.
9421 */
9422 getEdge: function(node, type, s) {
9423 var $C = function(a, b) {
9424 return function(){
9425 return node.pos.add(new Complex(a, b));
9426 };
9427 };
9428 var dim = this.node;
9429 var w = node.getData('width');
9430 var h = node.getData('height');
9431
9432 if(type == 'begin') {
9433 if(dim.align == "center") {
9434 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9435 $C(0, -h/2),$C(w/2, 0));
9436 } else if(dim.align == "left") {
9437 return this.dispatch(s, $C(0, h), $C(0, 0),
9438 $C(0, 0), $C(w, 0));
9439 } else if(dim.align == "right") {
9440 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9441 $C(0, -h),$C(0, 0));
9442 } else throw "align: not implemented";
9443
9444
9445 } else if(type == 'end') {
9446 if(dim.align == "center") {
9447 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9448 $C(0, h/2), $C(-w/2, 0));
9449 } else if(dim.align == "left") {
9450 return this.dispatch(s, $C(0, 0), $C(w, 0),
9451 $C(0, h), $C(0, 0));
9452 } else if(dim.align == "right") {
9453 return this.dispatch(s, $C(0, -h),$C(0, 0),
9454 $C(0, 0), $C(-w, 0));
9455 } else throw "align: not implemented";
9456 }
9457 },
9458
9459 /*
9460 Adjusts the tree position due to canvas scaling or translation.
9461 */
9462 getScaledTreePosition: function(node, scale) {
9463 var dim = this.node;
9464 var w = node.getData('width');
9465 var h = node.getData('height');
9466 var s = (this.config.multitree
9467 && ('$orn' in node.data)
9468 && node.data.$orn) || this.config.orientation;
9469
9470 var $C = function(a, b) {
9471 return function(){
9472 return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9473 };
9474 };
9475 if(dim.align == "left") {
9476 return this.dispatch(s, $C(0, h), $C(0, 0),
9477 $C(0, 0), $C(w, 0));
9478 } else if(dim.align == "center") {
9479 return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9480 $C(0, -h / 2),$C(w / 2, 0));
9481 } else if(dim.align == "right") {
9482 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9483 $C(0, -h),$C(0, 0));
9484 } else throw "align: not implemented";
9485 },
9486
9487 /*
9488 treeFitsInCanvas
9489
9490 Returns a Boolean if the current subtree fits in canvas.
9491
9492 Parameters:
9493
9494 node - A <Graph.Node> which is the current root of the subtree.
9495 canvas - The <Canvas> object.
9496 level - The depth of the subtree to be considered.
9497 */
9498 treeFitsInCanvas: function(node, canvas, level) {
9499 var csize = canvas.getSize();
9500 var s = (this.config.multitree
9501 && ('$orn' in node.data)
9502 && node.data.$orn) || this.config.orientation;
9503
9504 var size = this.dispatch(s, csize.width, csize.height);
9505 var baseSize = this.getTreeBaseSize(node, level, function(level, node) {
9506 return level === 0 || !node.anySubnode();
9507 });
9508 return (baseSize < size);
9509 }
9510});
9511
9512/*
9513 Class: ST.Plot
9514
9515 Custom extension of <Graph.Plot>.
9516
9517 Extends:
9518
9519 All <Graph.Plot> methods
9520
9521 See also:
9522
9523 <Graph.Plot>
9524
9525*/
9526$jit.ST.Plot = new Class({
9527
9528 Implements: Graph.Plot,
9529
9530 /*
9531 Plots a subtree from the spacetree.
9532 */
9533 plotSubtree: function(node, opt, scale, animating) {
9534 var viz = this.viz, canvas = viz.canvas, config = viz.config;
9535 scale = Math.min(Math.max(0.001, scale), 1);
9536 if(scale >= 0) {
9537 node.drawn = false;
9538 var ctx = canvas.getCtx();
9539 var diff = viz.geom.getScaledTreePosition(node, scale);
9540 ctx.translate(diff.x, diff.y);
9541 ctx.scale(scale, scale);
9542 }
9543 this.plotTree(node, $.merge(opt, {
9544 'withLabels': true,
9545 'hideLabels': !!scale,
9546 'plotSubtree': function(n, ch) {
9547 var root = config.multitree && !('$orn' in node.data);
9548 var orns = root && node.getData('orns');
9549 return !root || orns.indexOf(node.getData('orn')) > -1;
9550 }
9551 }), animating);
9552 if(scale >= 0) node.drawn = true;
9553 },
9554
9555 /*
9556 Method: getAlignedPos
9557
9558 Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9559
9560 Parameters:
9561
9562 pos - (object) A <Graph.Node> position.
9563 width - (number) The width of the node.
9564 height - (number) The height of the node.
9565
9566 */
9567 getAlignedPos: function(pos, width, height) {
9568 var nconfig = this.node;
9569 var square, orn;
9570 if(nconfig.align == "center") {
9571 square = {
9572 x: pos.x - width / 2,
9573 y: pos.y - height / 2
9574 };
9575 } else if (nconfig.align == "left") {
9576 orn = this.config.orientation;
9577 if(orn == "bottom" || orn == "top") {
9578 square = {
9579 x: pos.x - width / 2,
9580 y: pos.y
9581 };
9582 } else {
9583 square = {
9584 x: pos.x,
9585 y: pos.y - height / 2
9586 };
9587 }
9588 } else if(nconfig.align == "right") {
9589 orn = this.config.orientation;
9590 if(orn == "bottom" || orn == "top") {
9591 square = {
9592 x: pos.x - width / 2,
9593 y: pos.y - height
9594 };
9595 } else {
9596 square = {
9597 x: pos.x - width,
9598 y: pos.y - height / 2
9599 };
9600 }
9601 } else throw "align: not implemented";
9602
9603 return square;
9604 },
9605
9606 getOrientation: function(adj) {
9607 var config = this.config;
9608 var orn = config.orientation;
9609
9610 if(config.multitree) {
9611 var nodeFrom = adj.nodeFrom;
9612 var nodeTo = adj.nodeTo;
9613 orn = (('$orn' in nodeFrom.data)
9614 && nodeFrom.data.$orn)
9615 || (('$orn' in nodeTo.data)
9616 && nodeTo.data.$orn);
9617 }
9618
9619 return orn;
9620 }
9621});
9622
9623/*
9624 Class: ST.Label
9625
9626 Custom extension of <Graph.Label>.
9627 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9628
9629 Extends:
9630
9631 All <Graph.Label> methods and subclasses.
9632
9633 See also:
9634
9635 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9636 */
9637$jit.ST.Label = {};
9638
9639/*
9640 ST.Label.Native
9641
9642 Custom extension of <Graph.Label.Native>.
9643
9644 Extends:
9645
9646 All <Graph.Label.Native> methods
9647
9648 See also:
9649
9650 <Graph.Label.Native>
9651*/
9652$jit.ST.Label.Native = new Class({
9653 Implements: Graph.Label.Native,
9654
9655 renderLabel: function(canvas, node, controller) {
9656 var ctx = canvas.getCtx(),
9657 coord = node.pos.getc(true),
9658 width = node.getData('width'),
9659 height = node.getData('height'),
9660 pos = this.viz.fx.getAlignedPos(coord, width, height);
9661 ctx.fillText(node.name, pos.x + width / 2, pos.y + height / 2);
9662 }
9663});
9664
9665$jit.ST.Label.DOM = new Class({
9666 Implements: Graph.Label.DOM,
9667
9668 /*
9669 placeLabel
9670
9671 Overrides abstract method placeLabel in <Graph.Plot>.
9672
9673 Parameters:
9674
9675 tag - A DOM label element.
9676 node - A <Graph.Node>.
9677 controller - A configuration/controller object passed to the visualization.
9678
9679 */
9680 placeLabel: function(tag, node, controller) {
9681 var pos = node.pos.getc(true),
9682 config = this.viz.config,
9683 dim = config.Node,
9684 canvas = this.viz.canvas,
9685 w = node.getData('width'),
9686 h = node.getData('height'),
9687 radius = canvas.getSize(),
9688 labelPos, orn;
9689
9690 var ox = canvas.translateOffsetX,
9691 oy = canvas.translateOffsetY,
9692 sx = canvas.scaleOffsetX,
9693 sy = canvas.scaleOffsetY,
9694 posx = pos.x * sx + ox,
9695 posy = pos.y * sy + oy;
9696
9697 if(dim.align == "center") {
9698 labelPos= {
9699 x: Math.round(posx - w / 2 + radius.width/2),
9700 y: Math.round(posy - h / 2 + radius.height/2)
9701 };
9702 } else if (dim.align == "left") {
9703 orn = config.orientation;
9704 if(orn == "bottom" || orn == "top") {
9705 labelPos= {
9706 x: Math.round(posx - w / 2 + radius.width/2),
9707 y: Math.round(posy + radius.height/2)
9708 };
9709 } else {
9710 labelPos= {
9711 x: Math.round(posx + radius.width/2),
9712 y: Math.round(posy - h / 2 + radius.height/2)
9713 };
9714 }
9715 } else if(dim.align == "right") {
9716 orn = config.orientation;
9717 if(orn == "bottom" || orn == "top") {
9718 labelPos= {
9719 x: Math.round(posx - w / 2 + radius.width/2),
9720 y: Math.round(posy - h + radius.height/2)
9721 };
9722 } else {
9723 labelPos= {
9724 x: Math.round(posx - w + radius.width/2),
9725 y: Math.round(posy - h / 2 + radius.height/2)
9726 };
9727 }
9728 } else throw "align: not implemented";
9729
9730 var style = tag.style;
9731 style.left = labelPos.x + 'px';
9732 style.top = labelPos.y + 'px';
9733 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9734 controller.onPlaceLabel(tag, node);
9735 }
9736});
9737
9738/*
9739 ST.Label.SVG
9740
9741 Custom extension of <Graph.Label.SVG>.
9742
9743 Extends:
9744
9745 All <Graph.Label.SVG> methods
9746
9747 See also:
9748
9749 <Graph.Label.SVG>
9750*/
9751$jit.ST.Label.SVG = new Class({
9752 Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9753
9754 initialize: function(viz) {
9755 this.viz = viz;
9756 }
9757});
9758
9759/*
9760 ST.Label.HTML
9761
9762 Custom extension of <Graph.Label.HTML>.
9763
9764 Extends:
9765
9766 All <Graph.Label.HTML> methods.
9767
9768 See also:
9769
9770 <Graph.Label.HTML>
9771
9772*/
9773$jit.ST.Label.HTML = new Class({
9774 Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9775
9776 initialize: function(viz) {
9777 this.viz = viz;
9778 }
9779});
9780
9781
9782/*
9783 Class: ST.Plot.NodeTypes
9784
9785 This class contains a list of <Graph.Node> built-in types.
9786 Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9787
9788 You can add your custom node types, customizing your visualization to the extreme.
9789
9790 Example:
9791
9792 (start code js)
9793 ST.Plot.NodeTypes.implement({
9794 'mySpecialType': {
9795 'render': function(node, canvas) {
9796 //print your custom node to canvas
9797 },
9798 //optional
9799 'contains': function(node, pos) {
9800 //return true if pos is inside the node or false otherwise
9801 }
9802 }
9803 });
9804 (end code)
9805
9806*/
9807$jit.ST.Plot.NodeTypes = new Class({
9808 'none': {
9809 'render': $.empty,
9810 'contains': $.lambda(false)
9811 },
9812 'circle': {
9813 'render': function(node, canvas) {
9814 var dim = node.getData('dim'),
9815 pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9816 dim2 = dim/2;
9817 this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9818 },
9819 'contains': function(node, pos) {
9820 var dim = node.getData('dim'),
9821 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9822 dim2 = dim/2;
9823 this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, pos, dim2);
9824 }
9825 },
9826 'square': {
9827 'render': function(node, canvas) {
9828 var dim = node.getData('dim'),
9829 dim2 = dim/2,
9830 pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9831 this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9832 },
9833 'contains': function(node, pos) {
9834 var dim = node.getData('dim'),
9835 npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9836 dim2 = dim/2;
9837 this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, pos, dim2);
9838 }
9839 },
9840 'ellipse': {
9841 'render': function(node, canvas) {
9842 var width = node.getData('width'),
9843 height = node.getData('height'),
9844 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9845 this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9846 },
9847 'contains': function(node, pos) {
9848 var width = node.getData('width'),
9849 height = node.getData('height'),
9850 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9851 this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, pos, width, height);
9852 }
9853 },
9854 'rectangle': {
9855 'render': function(node, canvas) {
9856 var width = node.getData('width'),
9857 height = node.getData('height'),
9858 pos = this.getAlignedPos(node.pos.getc(true), width, height);
9859 this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9860 },
9861 'contains': function(node, pos) {
9862 var width = node.getData('width'),
9863 height = node.getData('height'),
9864 npos = this.getAlignedPos(node.pos.getc(true), width, height);
9865 this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, pos, width, height);
9866 }
9867 }
9868});
9869
9870/*
9871 Class: ST.Plot.EdgeTypes
9872
9873 This class contains a list of <Graph.Adjacence> built-in types.
9874 Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9875
9876 You can add your custom edge types, customizing your visualization to the extreme.
9877
9878 Example:
9879
9880 (start code js)
9881 ST.Plot.EdgeTypes.implement({
9882 'mySpecialType': {
9883 'render': function(adj, canvas) {
9884 //print your custom edge to canvas
9885 },
9886 //optional
9887 'contains': function(adj, pos) {
9888 //return true if pos is inside the arc or false otherwise
9889 }
9890 }
9891 });
9892 (end code)
9893
9894*/
9895$jit.ST.Plot.EdgeTypes = new Class({
9896 'none': $.empty,
9897 'line': {
9898 'render': function(adj, canvas) {
9899 var orn = this.getOrientation(adj),
9900 nodeFrom = adj.nodeFrom,
9901 nodeTo = adj.nodeTo,
9902 rel = nodeFrom._depth < nodeTo._depth,
9903 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9904 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9905 this.edgeHelper.line.render(from, to, canvas);
9906 },
9907 'contains': function(adj, pos) {
9908 var orn = this.getOrientation(adj),
9909 nodeFrom = adj.nodeFrom,
9910 nodeTo = adj.nodeTo,
9911 rel = nodeFrom._depth < nodeTo._depth,
9912 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9913 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9914 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9915 }
9916 },
9917 'arrow': {
9918 'render': function(adj, canvas) {
9919 var orn = this.getOrientation(adj),
9920 node = adj.nodeFrom,
9921 child = adj.nodeTo,
9922 dim = adj.getData('dim'),
9923 from = this.viz.geom.getEdge(node, 'begin', orn),
9924 to = this.viz.geom.getEdge(child, 'end', orn),
9925 direction = adj.data.$direction,
9926 inv = (direction && direction.length>1 && direction[0] != node.id);
9927 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9928 },
9929 'contains': function(adj, pos) {
9930 var orn = this.getOrientation(adj),
9931 nodeFrom = adj.nodeFrom,
9932 nodeTo = adj.nodeTo,
9933 rel = nodeFrom._depth < nodeTo._depth,
9934 from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9935 to = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9936 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9937 }
9938 },
9939 'quadratic:begin': {
9940 'render': function(adj, canvas) {
9941 var orn = this.getOrientation(adj);
9942 var nodeFrom = adj.nodeFrom,
9943 nodeTo = adj.nodeTo,
9944 rel = nodeFrom._depth < nodeTo._depth,
9945 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9946 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9947 dim = adj.getData('dim'),
9948 ctx = canvas.getCtx();
9949 ctx.beginPath();
9950 ctx.moveTo(begin.x, begin.y);
9951 switch(orn) {
9952 case "left":
9953 ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9954 break;
9955 case "right":
9956 ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9957 break;
9958 case "top":
9959 ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9960 break;
9961 case "bottom":
9962 ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9963 break;
9964 }
9965 ctx.stroke();
9966 }
9967 },
9968 'quadratic:end': {
9969 'render': function(adj, canvas) {
9970 var orn = this.getOrientation(adj);
9971 var nodeFrom = adj.nodeFrom,
9972 nodeTo = adj.nodeTo,
9973 rel = nodeFrom._depth < nodeTo._depth,
9974 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9975 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9976 dim = adj.getData('dim'),
9977 ctx = canvas.getCtx();
9978 ctx.beginPath();
9979 ctx.moveTo(begin.x, begin.y);
9980 switch(orn) {
9981 case "left":
9982 ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9983 break;
9984 case "right":
9985 ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9986 break;
9987 case "top":
9988 ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9989 break;
9990 case "bottom":
9991 ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9992 break;
9993 }
9994 ctx.stroke();
9995 }
9996 },
9997 'bezier': {
9998 'render': function(adj, canvas) {
9999 var orn = this.getOrientation(adj),
10000 nodeFrom = adj.nodeFrom,
10001 nodeTo = adj.nodeTo,
10002 rel = nodeFrom._depth < nodeTo._depth,
10003 begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10004 end = this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10005 dim = adj.getData('dim'),
10006 ctx = canvas.getCtx();
10007 ctx.beginPath();
10008 ctx.moveTo(begin.x, begin.y);
10009 switch(orn) {
10010 case "left":
10011 ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
10012 break;
10013 case "right":
10014 ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
10015 break;
10016 case "top":
10017 ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
10018 break;
10019 case "bottom":
10020 ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
10021 break;
10022 }
10023 ctx.stroke();
10024 }
10025 }
10026});
10027
10028
10029
10030/*
10031 * File: AreaChart.js
10032 *
10033*/
10034
10035$jit.ST.Plot.NodeTypes.implement({
10036 'areachart-stacked' : {
10037 'render' : function(node, canvas) {
10038 var pos = node.pos.getc(true),
10039 width = node.getData('width'),
10040 height = node.getData('height'),
10041 algnPos = this.getAlignedPos(pos, width, height),
10042 x = algnPos.x, y = algnPos.y,
10043 stringArray = node.getData('stringArray'),
10044 dimArray = node.getData('dimArray'),
10045 valArray = node.getData('valueArray'),
10046 valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10047 valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10048 colorArray = node.getData('colorArray'),
10049 colorLength = colorArray.length,
10050 config = node.getData('config'),
10051 gradient = node.getData('gradient'),
10052 showLabels = config.showLabels,
10053 aggregates = config.showAggregates,
10054 label = config.Label,
10055 prev = node.getData('prev');
10056
10057 var ctx = canvas.getCtx(), border = node.getData('border');
10058 if (colorArray && dimArray && stringArray) {
10059 for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10060 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10061 ctx.save();
10062 if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10063 var h1 = acumLeft + dimArray[i][0],
10064 h2 = acumRight + dimArray[i][1],
10065 alpha = Math.atan((h2 - h1) / width),
10066 delta = 55;
10067 var linear = ctx.createLinearGradient(x + width/2,
10068 y - (h1 + h2)/2,
10069 x + width/2 + delta * Math.sin(alpha),
10070 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10071 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10072 function(v) { return (v * 0.85) >> 0; }));
10073 linear.addColorStop(0, colorArray[i % colorLength]);
10074 linear.addColorStop(1, color);
10075 ctx.fillStyle = linear;
10076 }
10077 ctx.beginPath();
10078 ctx.moveTo(x, y - acumLeft);
10079 ctx.lineTo(x + width, y - acumRight);
10080 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10081 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10082 ctx.lineTo(x, y - acumLeft);
10083 ctx.fill();
10084 ctx.restore();
10085 if(border) {
10086 var strong = border.name == stringArray[i];
10087 var perc = strong? 0.7 : 0.8;
10088 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10089 function(v) { return (v * perc) >> 0; }));
10090 ctx.strokeStyle = color;
10091 ctx.lineWidth = strong? 4 : 1;
10092 ctx.save();
10093 ctx.beginPath();
10094 if(border.index === 0) {
10095 ctx.moveTo(x, y - acumLeft);
10096 ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10097 } else {
10098 ctx.moveTo(x + width, y - acumRight);
10099 ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10100 }
10101 ctx.stroke();
10102 ctx.restore();
10103 }
10104 acumLeft += (dimArray[i][0] || 0);
10105 acumRight += (dimArray[i][1] || 0);
10106
10107 if(dimArray[i][0] > 0)
10108 valAcum += (valArray[i][0] || 0);
10109 }
10110 if(prev && label.type == 'Native') {
10111 ctx.save();
10112 ctx.beginPath();
10113 ctx.fillStyle = ctx.strokeStyle = label.color;
10114 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10115 ctx.textAlign = 'center';
10116 ctx.textBaseline = 'middle';
10117 var aggValue = aggregates(node.name, valLeft, valRight, node, valAcum);
10118 if(aggValue !== false) {
10119 ctx.fillText(aggValue !== true? aggValue : valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10120 }
10121 if(showLabels(node.name, valLeft, valRight, node)) {
10122 ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10123 }
10124 ctx.restore();
10125 }
10126 }
10127 },
10128 'contains': function(node, mpos) {
10129 var pos = node.pos.getc(true),
10130 width = node.getData('width'),
10131 height = node.getData('height'),
10132 algnPos = this.getAlignedPos(pos, width, height),
10133 x = algnPos.x, y = algnPos.y,
10134 dimArray = node.getData('dimArray'),
10135 rx = mpos.x - x;
10136 //bounding box check
10137 if(mpos.x < x || mpos.x > x + width
10138 || mpos.y > y || mpos.y < y - height) {
10139 return false;
10140 }
10141 //deep check
10142 for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10143 var dimi = dimArray[i];
10144 lAcum -= dimi[0];
10145 rAcum -= dimi[1];
10146 var intersec = lAcum + (rAcum - lAcum) * rx / width;
10147 if(mpos.y >= intersec) {
10148 var index = +(rx > width/2);
10149 return {
10150 'name': node.getData('stringArray')[i],
10151 'color': node.getData('colorArray')[i],
10152 'value': node.getData('valueArray')[i][index],
10153 'index': index
10154 };
10155 }
10156 }
10157 return false;
10158 }
10159 }
10160});
10161
10162/*
10163 Class: AreaChart
10164
10165 A visualization that displays stacked area charts.
10166
10167 Constructor Options:
10168
10169 See <Options.AreaChart>.
10170
10171*/
10172$jit.AreaChart = new Class({
10173 st: null,
10174 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10175 selected: {},
10176 busy: false,
10177
10178 initialize: function(opt) {
10179 this.controller = this.config =
10180 $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10181 Label: { type: 'Native' }
10182 }, opt);
10183 //set functions for showLabels and showAggregates
10184 var showLabels = this.config.showLabels,
10185 typeLabels = $.type(showLabels),
10186 showAggregates = this.config.showAggregates,
10187 typeAggregates = $.type(showAggregates);
10188 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10189 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10190
10191 this.initializeViz();
10192 },
10193
10194 initializeViz: function() {
10195 var config = this.config,
10196 that = this,
10197 nodeType = config.type.split(":")[0],
10198 nodeLabels = {};
10199
10200 var delegate = new $jit.ST({
10201 injectInto: config.injectInto,
10202 width: config.width,
10203 height: config.height,
10204 orientation: "bottom",
10205 levelDistance: 0,
10206 siblingOffset: 0,
10207 subtreeOffset: 0,
10208 withLabels: config.Label.type != 'Native',
10209 useCanvas: config.useCanvas,
10210 Label: {
10211 type: config.Label.type
10212 },
10213 Node: {
10214 overridable: true,
10215 type: 'areachart-' + nodeType,
10216 align: 'left',
10217 width: 1,
10218 height: 1
10219 },
10220 Edge: {
10221 type: 'none'
10222 },
10223 Tips: {
10224 enable: config.Tips.enable,
10225 type: 'Native',
10226 force: true,
10227 onShow: function(tip, node, contains) {
10228 var elem = contains;
10229 config.Tips.onShow(tip, elem, node);
10230 }
10231 },
10232 Events: {
10233 enable: true,
10234 type: 'Native',
10235 onClick: function(node, eventInfo, evt) {
10236 if(!config.filterOnClick && !config.Events.enable) return;
10237 var elem = eventInfo.getContains();
10238 if(elem) config.filterOnClick && that.filter(elem.name);
10239 config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10240 },
10241 onRightClick: function(node, eventInfo, evt) {
10242 if(!config.restoreOnRightClick) return;
10243 that.restore();
10244 },
10245 onMouseMove: function(node, eventInfo, evt) {
10246 if(!config.selectOnHover) return;
10247 if(node) {
10248 var elem = eventInfo.getContains();
10249 that.select(node.id, elem.name, elem.index);
10250 } else {
10251 that.select(false, false, false);
10252 }
10253 }
10254 },
10255 onCreateLabel: function(domElement, node) {
10256 var labelConf = config.Label,
10257 valueArray = node.getData('valueArray'),
10258 acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10259 acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10260 if(node.getData('prev')) {
10261 var nlbs = {
10262 wrapper: document.createElement('div'),
10263 aggregate: document.createElement('div'),
10264 label: document.createElement('div')
10265 };
10266 var wrapper = nlbs.wrapper,
10267 label = nlbs.label,
10268 aggregate = nlbs.aggregate,
10269 wrapperStyle = wrapper.style,
10270 labelStyle = label.style,
10271 aggregateStyle = aggregate.style;
10272 //store node labels
10273 nodeLabels[node.id] = nlbs;
10274 //append labels
10275 wrapper.appendChild(label);
10276 wrapper.appendChild(aggregate);
10277 if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10278 label.style.display = 'none';
10279 }
10280 if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10281 aggregate.style.display = 'none';
10282 }
10283 wrapperStyle.position = 'relative';
10284 wrapperStyle.overflow = 'visible';
10285 wrapperStyle.fontSize = labelConf.size + 'px';
10286 wrapperStyle.fontFamily = labelConf.family;
10287 wrapperStyle.color = labelConf.color;
10288 wrapperStyle.textAlign = 'center';
10289 aggregateStyle.position = labelStyle.position = 'absolute';
10290
10291 domElement.style.width = node.getData('width') + 'px';
10292 domElement.style.height = node.getData('height') + 'px';
10293 label.innerHTML = node.name;
10294
10295 domElement.appendChild(wrapper);
10296 }
10297 },
10298 onPlaceLabel: function(domElement, node) {
10299 if(!node.getData('prev')) return;
10300 var labels = nodeLabels[node.id],
10301 wrapperStyle = labels.wrapper.style,
10302 labelStyle = labels.label.style,
10303 aggregateStyle = labels.aggregate.style,
10304 width = node.getData('width'),
10305 height = node.getData('height'),
10306 dimArray = node.getData('dimArray'),
10307 valArray = node.getData('valueArray'),
10308 acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10309 acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10310 font = parseInt(wrapperStyle.fontSize, 10),
10311 domStyle = domElement.style;
10312
10313 if(dimArray && valArray) {
10314 if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10315 labelStyle.display = '';
10316 } else {
10317 labelStyle.display = 'none';
10318 }
10319 var aggValue = config.showAggregates(node.name, acumLeft, acumRight, node);
10320 if(aggValue !== false) {
10321 aggregateStyle.display = '';
10322 } else {
10323 aggregateStyle.display = 'none';
10324 }
10325 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10326 aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10327 for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10328 if(dimArray[i][0] > 0) {
10329 acum+= valArray[i][0];
10330 leftAcum+= dimArray[i][0];
10331 }
10332 }
10333 aggregateStyle.top = (-font - config.labelOffset) + 'px';
10334 labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10335 domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10336 domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10337 labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
10338 }
10339 }
10340 });
10341
10342 var size = delegate.canvas.getSize(),
10343 margin = config.Margin;
10344 delegate.config.offsetY = -size.height/2 + margin.bottom
10345 + (config.showLabels && (config.labelOffset + config.Label.size));
10346 delegate.config.offsetX = (margin.right - margin.left)/2;
10347 this.delegate = delegate;
10348 this.canvas = this.delegate.canvas;
10349 },
10350
10351 /*
10352 Method: loadJSON
10353
10354 Loads JSON data into the visualization.
10355
10356 Parameters:
10357
10358 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
10359
10360 Example:
10361 (start code js)
10362 var areaChart = new $jit.AreaChart(options);
10363 areaChart.loadJSON(json);
10364 (end code)
10365 */
10366 loadJSON: function(json) {
10367 var prefix = $.time(),
10368 ch = [],
10369 delegate = this.delegate,
10370 name = $.splat(json.label),
10371 color = $.splat(json.color || this.colors),
10372 config = this.config,
10373 gradient = !!config.type.split(":")[1],
10374 animate = config.animate;
10375
10376 for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
10377 var val = values[i], prev = values[i-1], next = values[i+1];
10378 var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
10379 var valArray = $.zip(valLeft, valRight);
10380 var acumLeft = 0, acumRight = 0;
10381 ch.push({
10382 'id': prefix + val.label,
10383 'name': val.label,
10384 'data': {
10385 'value': valArray,
10386 '$valueArray': valArray,
10387 '$colorArray': color,
10388 '$stringArray': name,
10389 '$next': next.label,
10390 '$prev': prev? prev.label:false,
10391 '$config': config,
10392 '$gradient': gradient
10393 },
10394 'children': []
10395 });
10396 }
10397 var root = {
10398 'id': prefix + '$root',
10399 'name': '',
10400 'data': {
10401 '$type': 'none',
10402 '$width': 1,
10403 '$height': 1
10404 },
10405 'children': ch
10406 };
10407 delegate.loadJSON(root);
10408
10409 this.normalizeDims();
10410 delegate.compute();
10411 delegate.select(delegate.root);
10412 if(animate) {
10413 delegate.fx.animate({
10414 modes: ['node-property:height:dimArray'],
10415 duration:1500
10416 });
10417 }
10418 },
10419
10420 /*
10421 Method: updateJSON
10422
10423 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
10424
10425 Parameters:
10426
10427 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10428 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10429
10430 Example:
10431
10432 (start code js)
10433 areaChart.updateJSON(json, {
10434 onComplete: function() {
10435 alert('update complete!');
10436 }
10437 });
10438 (end code)
10439 */
10440 updateJSON: function(json, onComplete) {
10441 if(this.busy) return;
10442 this.busy = true;
10443
10444 var delegate = this.delegate,
10445 graph = delegate.graph,
10446 labels = json.label && $.splat(json.label),
10447 values = json.values,
10448 animate = this.config.animate,
10449 that = this,
10450 hashValues = {};
10451
10452 //convert the whole thing into a hash
10453 for (var i = 0, l = values.length; i < l; i++) {
10454 hashValues[values[i].label] = values[i];
10455 }
10456
10457 graph.eachNode(function(n) {
10458 var v = hashValues[n.name],
10459 stringArray = n.getData('stringArray'),
10460 valArray = n.getData('valueArray'),
10461 next = n.getData('next');
10462
10463 if (v) {
10464 v.values = $.splat(v.values);
10465 $.each(valArray, function(a, i) {
10466 a[0] = v.values[i];
10467 if(labels) stringArray[i] = labels[i];
10468 });
10469 n.setData('valueArray', valArray);
10470 }
10471
10472 if(next) {
10473 v = hashValues[next];
10474 if(v) {
10475 $.each(valArray, function(a, i) {
10476 a[1] = v.values[i];
10477 });
10478 }
10479 }
10480 });
10481 this.normalizeDims();
10482 delegate.compute();
10483 delegate.select(delegate.root);
10484 if(animate) {
10485 delegate.fx.animate({
10486 modes: ['node-property:height:dimArray'],
10487 duration:1500,
10488 onComplete: function() {
10489 that.busy = false;
10490 onComplete && onComplete.onComplete();
10491 }
10492 });
10493 }
10494 },
10495
10496/*
10497 Method: filter
10498
10499 Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10500
10501 Parameters:
10502
10503 filters - (array) An array of strings with the name of the stacks to be filtered.
10504 callback - (object) An object with an *onComplete* callback method.
10505
10506 Example:
10507
10508 (start code js)
10509 areaChart.filter(['label A', 'label C'], {
10510 onComplete: function() {
10511 console.log('done!');
10512 }
10513 });
10514 (end code)
10515
10516 See also:
10517
10518 <AreaChart.restore>.
10519 */
10520 filter: function(filters, callback) {
10521 if(this.busy) return;
10522 this.busy = true;
10523 if(this.config.Tips.enable) this.delegate.tips.hide();
10524 this.select(false, false, false);
10525 var args = $.splat(filters);
10526 var rt = this.delegate.graph.getNode(this.delegate.root);
10527 var that = this;
10528 this.normalizeDims();
10529 rt.eachAdjacency(function(adj) {
10530 var n = adj.nodeTo,
10531 dimArray = n.getData('dimArray', 'end'),
10532 stringArray = n.getData('stringArray');
10533 n.setData('dimArray', $.map(dimArray, function(d, i) {
10534 return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10535 }), 'end');
10536 });
10537 this.delegate.fx.animate({
10538 modes: ['node-property:dimArray'],
10539 duration:1500,
10540 onComplete: function() {
10541 that.busy = false;
10542 callback && callback.onComplete();
10543 }
10544 });
10545 },
10546
10547 /*
10548 Method: restore
10549
10550 Sets all stacks that could have been filtered visible.
10551
10552 Example:
10553
10554 (start code js)
10555 areaChart.restore();
10556 (end code)
10557
10558 See also:
10559
10560 <AreaChart.filter>.
10561 */
10562 restore: function(callback) {
10563 if(this.busy) return;
10564 this.busy = true;
10565 if(this.config.Tips.enable) this.delegate.tips.hide();
10566 this.select(false, false, false);
10567 this.normalizeDims();
10568 var that = this;
10569 this.delegate.fx.animate({
10570 modes: ['node-property:height:dimArray'],
10571 duration:1500,
10572 onComplete: function() {
10573 that.busy = false;
10574 callback && callback.onComplete();
10575 }
10576 });
10577 },
10578 //adds the little brown bar when hovering the node
10579 select: function(id, name, index) {
10580 if(!this.config.selectOnHover) return;
10581 var s = this.selected;
10582 if(s.id != id || s.name != name
10583 || s.index != index) {
10584 s.id = id;
10585 s.name = name;
10586 s.index = index;
10587 this.delegate.graph.eachNode(function(n) {
10588 n.setData('border', false);
10589 });
10590 if(id) {
10591 var n = this.delegate.graph.getNode(id);
10592 n.setData('border', s);
10593 var link = index === 0? 'prev':'next';
10594 link = n.getData(link);
10595 if(link) {
10596 n = this.delegate.graph.getByName(link);
10597 if(n) {
10598 n.setData('border', {
10599 name: name,
10600 index: 1-index
10601 });
10602 }
10603 }
10604 }
10605 this.delegate.plot();
10606 }
10607 },
10608
10609 /*
10610 Method: getLegend
10611
10612 Returns an object containing as keys the legend names and as values hex strings with color values.
10613
10614 Example:
10615
10616 (start code js)
10617 var legend = areaChart.getLegend();
10618 (end code)
10619 */
10620 getLegend: function() {
10621 var legend = {};
10622 var n;
10623 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
10624 n = adj.nodeTo;
10625 });
10626 var colors = n.getData('colorArray'),
10627 len = colors.length;
10628 $.each(n.getData('stringArray'), function(s, i) {
10629 legend[s] = colors[i % len];
10630 });
10631 return legend;
10632 },
10633
10634 /*
10635 Method: getMaxValue
10636
10637 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10638
10639 Example:
10640
10641 (start code js)
10642 var ans = areaChart.getMaxValue();
10643 (end code)
10644
10645 In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10646
10647 Example:
10648
10649 (start code js)
10650 //will return 100 for all AreaChart instances,
10651 //displaying all of them with the same scale
10652 $jit.AreaChart.implement({
10653 'getMaxValue': function() {
10654 return 100;
10655 }
10656 });
10657 (end code)
10658
10659*/
10660 getMaxValue: function() {
10661 var maxValue = 0;
10662 this.delegate.graph.eachNode(function(n) {
10663 var valArray = n.getData('valueArray'),
10664 acumLeft = 0, acumRight = 0;
10665 $.each(valArray, function(v) {
10666 acumLeft += +v[0];
10667 acumRight += +v[1];
10668 });
10669 var acum = acumRight>acumLeft? acumRight:acumLeft;
10670 maxValue = maxValue>acum? maxValue:acum;
10671 });
10672 return maxValue;
10673 },
10674
10675 normalizeDims: function() {
10676 //number of elements
10677 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
10678 root.eachAdjacency(function() {
10679 l++;
10680 });
10681 var maxValue = this.getMaxValue() || 1,
10682 size = this.delegate.canvas.getSize(),
10683 config = this.config,
10684 margin = config.Margin,
10685 labelOffset = config.labelOffset + config.Label.size,
10686 fixedDim = (size.width - (margin.left + margin.right)) / l,
10687 animate = config.animate,
10688 height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset)
10689 - (config.showLabels && labelOffset);
10690 this.delegate.graph.eachNode(function(n) {
10691 var acumLeft = 0, acumRight = 0, animateValue = [];
10692 $.each(n.getData('valueArray'), function(v) {
10693 acumLeft += +v[0];
10694 acumRight += +v[1];
10695 animateValue.push([0, 0]);
10696 });
10697 var acum = acumRight>acumLeft? acumRight:acumLeft;
10698 n.setData('width', fixedDim);
10699 if(animate) {
10700 n.setData('height', acum * height / maxValue, 'end');
10701 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10702 return [n[0] * height / maxValue, n[1] * height / maxValue];
10703 }), 'end');
10704 var dimArray = n.getData('dimArray');
10705 if(!dimArray) {
10706 n.setData('dimArray', animateValue);
10707 }
10708 } else {
10709 n.setData('height', acum * height / maxValue);
10710 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
10711 return [n[0] * height / maxValue, n[1] * height / maxValue];
10712 }));
10713 }
10714 });
10715 }
10716});
10717
10718
10719/*
10720 * File: Options.BarChart.js
10721 *
10722*/
10723
10724/*
10725 Object: Options.BarChart
10726
10727 <BarChart> options.
10728 Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
10729
10730 Syntax:
10731
10732 (start code js)
10733
10734 Options.BarChart = {
10735 animate: true,
10736 labelOffset: 3,
10737 barsOffset: 0,
10738 type: 'stacked',
10739 hoveredColor: '#9fd4ff',
10740 orientation: 'horizontal',
10741 showAggregates: true,
10742 showLabels: true
10743 };
10744
10745 (end code)
10746
10747 Example:
10748
10749 (start code js)
10750
10751 var barChart = new $jit.BarChart({
10752 animate: true,
10753 barsOffset: 10,
10754 type: 'stacked:gradient'
10755 });
10756
10757 (end code)
10758
10759 Parameters:
10760
10761 animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
10762 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
10763 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
10764 barsOffset - (number) Default's *0*. Separation between bars.
10765 type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
10766 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
10767 orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
10768 showAggregates - (boolean, function) Default's *true*. Display the sum the values of each bar. Can also be a function that returns *true* or *false* to display the value of the bar or not. That same function can also return a string with the formatted data to be added.
10769 showLabels - (boolean, function) Default's *true*. Display the name of the slots. Can also be a function that returns *true* or *false* for each bar to decide whether to show the label or not.
10770
10771*/
10772
10773Options.BarChart = {
10774 $extend: true,
10775
10776 animate: true,
10777 type: 'stacked', //stacked, grouped, : gradient
10778 labelOffset: 3, //label offset
10779 barsOffset: 0, //distance between bars
10780 hoveredColor: '#9fd4ff',
10781 orientation: 'horizontal',
10782 showAggregates: true,
10783 showLabels: true,
10784 Tips: {
10785 enable: false,
10786 onShow: $.empty,
10787 onHide: $.empty
10788 },
10789 Events: {
10790 enable: false,
10791 onClick: $.empty
10792 }
10793};
10794
10795/*
10796 * File: BarChart.js
10797 *
10798*/
10799
10800$jit.ST.Plot.NodeTypes.implement({
10801 'barchart-stacked' : {
10802 'render' : function(node, canvas) {
10803 var pos = node.pos.getc(true),
10804 width = node.getData('width'),
10805 height = node.getData('height'),
10806 algnPos = this.getAlignedPos(pos, width, height),
10807 x = algnPos.x, y = algnPos.y,
10808 dimArray = node.getData('dimArray'),
10809 valueArray = node.getData('valueArray'),
10810 colorArray = node.getData('colorArray'),
10811 colorLength = colorArray.length,
10812 stringArray = node.getData('stringArray');
10813
10814 var ctx = canvas.getCtx(),
10815 opt = {},
10816 border = node.getData('border'),
10817 gradient = node.getData('gradient'),
10818 config = node.getData('config'),
10819 horz = config.orientation == 'horizontal',
10820 aggregates = config.showAggregates,
10821 showLabels = config.showLabels,
10822 label = config.Label;
10823
10824 if (colorArray && dimArray && stringArray) {
10825 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
10826 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10827 if(gradient) {
10828 var linear;
10829 if(horz) {
10830 linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y,
10831 x + acum + dimArray[i]/2, y + height);
10832 } else {
10833 linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2,
10834 x + width, y - acum- dimArray[i]/2);
10835 }
10836 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10837 function(v) { return (v * 0.5) >> 0; }));
10838 linear.addColorStop(0, color);
10839 linear.addColorStop(0.5, colorArray[i % colorLength]);
10840 linear.addColorStop(1, color);
10841 ctx.fillStyle = linear;
10842 }
10843 if(horz) {
10844 ctx.fillRect(x + acum, y, dimArray[i], height);
10845 } else {
10846 ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
10847 }
10848 if(border && border.name == stringArray[i]) {
10849 opt.acum = acum;
10850 opt.dimValue = dimArray[i];
10851 }
10852 acum += (dimArray[i] || 0);
10853 valAcum += (valueArray[i] || 0);
10854 }
10855 if(border) {
10856 ctx.save();
10857 ctx.lineWidth = 2;
10858 ctx.strokeStyle = border.color;
10859 if(horz) {
10860 ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
10861 } else {
10862 ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
10863 }
10864 ctx.restore();
10865 }
10866 if(label.type == 'Native') {
10867 ctx.save();
10868 ctx.fillStyle = ctx.strokeStyle = label.color;
10869 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10870 ctx.textBaseline = 'middle';
10871 var aggValue = aggregates(node.name, valAcum, node);
10872 if(aggValue !== false) {
10873 aggValue = aggValue !== true? aggValue : valAcum;
10874 if(horz) {
10875 ctx.textAlign = 'right';
10876 ctx.fillText(aggValue, x + acum - config.labelOffset, y + height/2);
10877 } else {
10878 ctx.textAlign = 'center';
10879 ctx.fillText(aggValue, x + width/2, y - height - label.size/2 - config.labelOffset);
10880 }
10881 }
10882 if(showLabels(node.name, valAcum, node)) {
10883 if(horz) {
10884 ctx.textAlign = 'center';
10885 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
10886 ctx.rotate(Math.PI / 2);
10887 ctx.fillText(node.name, 0, 0);
10888 } else {
10889 ctx.textAlign = 'center';
10890 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
10891 }
10892 }
10893 ctx.restore();
10894 }
10895 }
10896 },
10897 'contains': function(node, mpos) {
10898 var pos = node.pos.getc(true),
10899 width = node.getData('width'),
10900 height = node.getData('height'),
10901 algnPos = this.getAlignedPos(pos, width, height),
10902 x = algnPos.x, y = algnPos.y,
10903 dimArray = node.getData('dimArray'),
10904 config = node.getData('config'),
10905 rx = mpos.x - x,
10906 horz = config.orientation == 'horizontal';
10907 //bounding box check
10908 if(horz) {
10909 if(mpos.x < x || mpos.x > x + width
10910 || mpos.y > y + height || mpos.y < y) {
10911 return false;
10912 }
10913 } else {
10914 if(mpos.x < x || mpos.x > x + width
10915 || mpos.y > y || mpos.y < y - height) {
10916 return false;
10917 }
10918 }
10919 //deep check
10920 for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
10921 var dimi = dimArray[i];
10922 if(horz) {
10923 acum += dimi;
10924 var intersec = acum;
10925 if(mpos.x <= intersec) {
10926 return {
10927 'name': node.getData('stringArray')[i],
10928 'color': node.getData('colorArray')[i],
10929 'value': node.getData('valueArray')[i],
10930 'label': node.name
10931 };
10932 }
10933 } else {
10934 acum -= dimi;
10935 var intersec = acum;
10936 if(mpos.y >= intersec) {
10937 return {
10938 'name': node.getData('stringArray')[i],
10939 'color': node.getData('colorArray')[i],
10940 'value': node.getData('valueArray')[i],
10941 'label': node.name
10942 };
10943 }
10944 }
10945 }
10946 return false;
10947 }
10948 },
10949 'barchart-grouped' : {
10950 'render' : function(node, canvas) {
10951 var pos = node.pos.getc(true),
10952 width = node.getData('width'),
10953 height = node.getData('height'),
10954 algnPos = this.getAlignedPos(pos, width, height),
10955 x = algnPos.x, y = algnPos.y,
10956 dimArray = node.getData('dimArray'),
10957 valueArray = node.getData('valueArray'),
10958 valueLength = valueArray.length,
10959 colorArray = node.getData('colorArray'),
10960 colorLength = colorArray.length,
10961 stringArray = node.getData('stringArray');
10962
10963 var ctx = canvas.getCtx(),
10964 opt = {},
10965 border = node.getData('border'),
10966 gradient = node.getData('gradient'),
10967 config = node.getData('config'),
10968 horz = config.orientation == 'horizontal',
10969 aggregates = config.showAggregates,
10970 showLabels = config.showLabels,
10971 label = config.Label,
10972 fixedDim = (horz? height : width) / valueLength;
10973
10974 if (colorArray && dimArray && stringArray) {
10975 for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
10976 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10977 if(gradient) {
10978 var linear;
10979 if(horz) {
10980 linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i,
10981 x + dimArray[i]/2, y + fixedDim * (i + 1));
10982 } else {
10983 linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2,
10984 x + fixedDim * (i + 1), y - dimArray[i]/2);
10985 }
10986 var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)),
10987 function(v) { return (v * 0.5) >> 0; }));
10988 linear.addColorStop(0, color);
10989 linear.addColorStop(0.5, colorArray[i % colorLength]);
10990 linear.addColorStop(1, color);
10991 ctx.fillStyle = linear;
10992 }
10993 if(horz) {
10994 ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
10995 } else {
10996 ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
10997 }
10998 if(border && border.name == stringArray[i]) {
10999 opt.acum = fixedDim * i;
11000 opt.dimValue = dimArray[i];
11001 }
11002 acum += (dimArray[i] || 0);
11003 valAcum += (valueArray[i] || 0);
11004 }
11005 if(border) {
11006 ctx.save();
11007 ctx.lineWidth = 2;
11008 ctx.strokeStyle = border.color;
11009 if(horz) {
11010 ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
11011 } else {
11012 ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
11013 }
11014 ctx.restore();
11015 }
11016 if(label.type == 'Native') {
11017 ctx.save();
11018 ctx.fillStyle = ctx.strokeStyle = label.color;
11019 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11020 ctx.textBaseline = 'middle';
11021 var aggValue = aggregates(node.name, valAcum, node);
11022 if(aggValue !== false) {
11023 aggValue = aggValue !== true? aggValue : valAcum;
11024 if(horz) {
11025 ctx.textAlign = 'right';
11026 ctx.fillText(aggValue, x + Math.max.apply(null, dimArray) - config.labelOffset, y + height/2);
11027 } else {
11028 ctx.textAlign = 'center';
11029 ctx.fillText(aggValue, x + width/2, y - Math.max.apply(null, dimArray) - label.size/2 - config.labelOffset);
11030 }
11031 }
11032 if(showLabels(node.name, valAcum, node)) {
11033 if(horz) {
11034 ctx.textAlign = 'center';
11035 ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
11036 ctx.rotate(Math.PI / 2);
11037 ctx.fillText(node.name, 0, 0);
11038 } else {
11039 ctx.textAlign = 'center';
11040 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11041 }
11042 }
11043 ctx.restore();
11044 }
11045 }
11046 },
11047 'contains': function(node, mpos) {
11048 var pos = node.pos.getc(true),
11049 width = node.getData('width'),
11050 height = node.getData('height'),
11051 algnPos = this.getAlignedPos(pos, width, height),
11052 x = algnPos.x, y = algnPos.y,
11053 dimArray = node.getData('dimArray'),
11054 len = dimArray.length,
11055 config = node.getData('config'),
11056 rx = mpos.x - x,
11057 horz = config.orientation == 'horizontal',
11058 fixedDim = (horz? height : width) / len;
11059 //bounding box check
11060 if(horz) {
11061 if(mpos.x < x || mpos.x > x + width
11062 || mpos.y > y + height || mpos.y < y) {
11063 return false;
11064 }
11065 } else {
11066 if(mpos.x < x || mpos.x > x + width
11067 || mpos.y > y || mpos.y < y - height) {
11068 return false;
11069 }
11070 }
11071 //deep check
11072 for(var i=0, l=dimArray.length; i<l; i++) {
11073 var dimi = dimArray[i];
11074 if(horz) {
11075 var limit = y + fixedDim * i;
11076 if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
11077 return {
11078 'name': node.getData('stringArray')[i],
11079 'color': node.getData('colorArray')[i],
11080 'value': node.getData('valueArray')[i],
11081 'label': node.name
11082 };
11083 }
11084 } else {
11085 var limit = x + fixedDim * i;
11086 if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
11087 return {
11088 'name': node.getData('stringArray')[i],
11089 'color': node.getData('colorArray')[i],
11090 'value': node.getData('valueArray')[i],
11091 'label': node.name
11092 };
11093 }
11094 }
11095 }
11096 return false;
11097 }
11098 }
11099});
11100
11101/*
11102 Class: BarChart
11103
11104 A visualization that displays stacked bar charts.
11105
11106 Constructor Options:
11107
11108 See <Options.BarChart>.
11109
11110*/
11111$jit.BarChart = new Class({
11112 st: null,
11113 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
11114 selected: {},
11115 busy: false,
11116
11117 initialize: function(opt) {
11118 this.controller = this.config =
11119 $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
11120 Label: { type: 'Native' }
11121 }, opt);
11122 //set functions for showLabels and showAggregates
11123 var showLabels = this.config.showLabels,
11124 typeLabels = $.type(showLabels),
11125 showAggregates = this.config.showAggregates,
11126 typeAggregates = $.type(showAggregates);
11127 this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
11128 this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
11129
11130 this.initializeViz();
11131 },
11132
11133 initializeViz: function() {
11134 var config = this.config, that = this;
11135 var nodeType = config.type.split(":")[0],
11136 horz = config.orientation == 'horizontal',
11137 nodeLabels = {};
11138
11139 var delegate = new $jit.ST({
11140 injectInto: config.injectInto,
11141 width: config.width,
11142 height: config.height,
11143 orientation: horz? 'left' : 'bottom',
11144 levelDistance: 0,
11145 siblingOffset: config.barsOffset,
11146 subtreeOffset: 0,
11147 withLabels: config.Label.type != 'Native',
11148 useCanvas: config.useCanvas,
11149 Label: {
11150 type: config.Label.type
11151 },
11152 Node: {
11153 overridable: true,
11154 type: 'barchart-' + nodeType,
11155 align: 'left',
11156 width: 1,
11157 height: 1
11158 },
11159 Edge: {
11160 type: 'none'
11161 },
11162 Tips: {
11163 enable: config.Tips.enable,
11164 type: 'Native',
11165 force: true,
11166 onShow: function(tip, node, contains) {
11167 var elem = contains;
11168 config.Tips.onShow(tip, elem, node);
11169 }
11170 },
11171 Events: {
11172 enable: true,
11173 type: 'Native',
11174 onClick: function(node, eventInfo, evt) {
11175 if(!config.Events.enable) return;
11176 var elem = eventInfo.getContains();
11177 config.Events.onClick(elem, eventInfo, evt);
11178 },
11179 onMouseMove: function(node, eventInfo, evt) {
11180 if(!config.hoveredColor) return;
11181 if(node) {
11182 var elem = eventInfo.getContains();
11183 that.select(node.id, elem.name, elem.index);
11184 } else {
11185 that.select(false, false, false);
11186 }
11187 }
11188 },
11189 onCreateLabel: function(domElement, node) {
11190 var labelConf = config.Label,
11191 valueArray = node.getData('valueArray'),
11192 acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0);
11193 var nlbs = {
11194 wrapper: document.createElement('div'),
11195 aggregate: document.createElement('div'),
11196 label: document.createElement('div')
11197 };
11198 var wrapper = nlbs.wrapper,
11199 label = nlbs.label,
11200 aggregate = nlbs.aggregate,
11201 wrapperStyle = wrapper.style,
11202 labelStyle = label.style,
11203 aggregateStyle = aggregate.style;
11204 //store node labels
11205 nodeLabels[node.id] = nlbs;
11206 //append labels
11207 wrapper.appendChild(label);
11208 wrapper.appendChild(aggregate);
11209 if(!config.showLabels(node.name, acum, node)) {
11210 labelStyle.display = 'none';
11211 }
11212 if(!config.showAggregates(node.name, acum, node)) {
11213 aggregateStyle.display = 'none';
11214 }
11215 wrapperStyle.position = 'relative';
11216 wrapperStyle.overflow = 'visible';
11217 wrapperStyle.fontSize = labelConf.size + 'px';
11218 wrapperStyle.fontFamily = labelConf.family;
11219 wrapperStyle.color = labelConf.color;
11220 wrapperStyle.textAlign = 'center';
11221 aggregateStyle.position = labelStyle.position = 'absolute';
11222
11223 domElement.style.width = node.getData('width') + 'px';
11224 domElement.style.height = node.getData('height') + 'px';
11225 aggregateStyle.left = labelStyle.left = '0px';
11226
11227 label.innerHTML = node.name;
11228
11229 domElement.appendChild(wrapper);
11230 },
11231 onPlaceLabel: function(domElement, node) {
11232 if(!nodeLabels[node.id]) return;
11233 var labels = nodeLabels[node.id],
11234 wrapperStyle = labels.wrapper.style,
11235 labelStyle = labels.label.style,
11236 aggregateStyle = labels.aggregate.style,
11237 grouped = config.type.split(':')[0] == 'grouped',
11238 horz = config.orientation == 'horizontal',
11239 dimArray = node.getData('dimArray'),
11240 valArray = node.getData('valueArray'),
11241 width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
11242 height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
11243 font = parseInt(wrapperStyle.fontSize, 10),
11244 domStyle = domElement.style;
11245
11246
11247 if(dimArray && valArray) {
11248 wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11249 for(var i=0, l=valArray.length, acum=0; i<l; i++) {
11250 if(dimArray[i] > 0) {
11251 acum+= valArray[i];
11252 }
11253 }
11254 if(config.showLabels(node.name, acum, node)) {
11255 labelStyle.display = '';
11256 } else {
11257 labelStyle.display = 'none';
11258 }
11259 var aggValue = config.showAggregates(node.name, acum, node);
11260 if(aggValue !== false) {
11261 aggregateStyle.display = '';
11262 } else {
11263 aggregateStyle.display = 'none';
11264 }
11265 if(config.orientation == 'horizontal') {
11266 aggregateStyle.textAlign = 'right';
11267 labelStyle.textAlign = 'left';
11268 labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
11269 aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
11270 domElement.style.height = wrapperStyle.height = height + 'px';
11271 } else {
11272 aggregateStyle.top = (-font - config.labelOffset) + 'px';
11273 labelStyle.top = (config.labelOffset + height) + 'px';
11274 domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
11275 domElement.style.height = wrapperStyle.height = height + 'px';
11276 }
11277 labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
11278 }
11279 }
11280 });
11281
11282 var size = delegate.canvas.getSize(),
11283 margin = config.Margin;
11284 if(horz) {
11285 delegate.config.offsetX = size.width/2 - margin.left
11286 - (config.showLabels && (config.labelOffset + config.Label.size));
11287 delegate.config.offsetY = (margin.bottom - margin.top)/2;
11288 } else {
11289 delegate.config.offsetY = -size.height/2 + margin.bottom
11290 + (config.showLabels && (config.labelOffset + config.Label.size));
11291 delegate.config.offsetX = (margin.right - margin.left)/2;
11292 }
11293 this.delegate = delegate;
11294 this.canvas = this.delegate.canvas;
11295 },
11296
11297 /*
11298 Method: loadJSON
11299
11300 Loads JSON data into the visualization.
11301
11302 Parameters:
11303
11304 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
11305
11306 Example:
11307 (start code js)
11308 var barChart = new $jit.BarChart(options);
11309 barChart.loadJSON(json);
11310 (end code)
11311 */
11312 loadJSON: function(json) {
11313 if(this.busy) return;
11314 this.busy = true;
11315
11316 var prefix = $.time(),
11317 ch = [],
11318 delegate = this.delegate,
11319 name = $.splat(json.label),
11320 color = $.splat(json.color || this.colors),
11321 config = this.config,
11322 gradient = !!config.type.split(":")[1],
11323 animate = config.animate,
11324 horz = config.orientation == 'horizontal',
11325 that = this;
11326
11327 for(var i=0, values=json.values, l=values.length; i<l; i++) {
11328 var val = values[i]
11329 var valArray = $.splat(values[i].values);
11330 var acum = 0;
11331 ch.push({
11332 'id': prefix + val.label,
11333 'name': val.label,
11334 'data': {
11335 'value': valArray,
11336 '$valueArray': valArray,
11337 '$colorArray': color,
11338 '$stringArray': name,
11339 '$gradient': gradient,
11340 '$config': config
11341 },
11342 'children': []
11343 });
11344 }
11345 var root = {
11346 'id': prefix + '$root',
11347 'name': '',
11348 'data': {
11349 '$type': 'none',
11350 '$width': 1,
11351 '$height': 1
11352 },
11353 'children': ch
11354 };
11355 delegate.loadJSON(root);
11356
11357 this.normalizeDims();
11358 delegate.compute();
11359 delegate.select(delegate.root);
11360 if(animate) {
11361 if(horz) {
11362 delegate.fx.animate({
11363 modes: ['node-property:width:dimArray'],
11364 duration:1500,
11365 onComplete: function() {
11366 that.busy = false;
11367 }
11368 });
11369 } else {
11370 delegate.fx.animate({
11371 modes: ['node-property:height:dimArray'],
11372 duration:1500,
11373 onComplete: function() {
11374 that.busy = false;
11375 }
11376 });
11377 }
11378 } else {
11379 this.busy = false;
11380 }
11381 },
11382
11383 /*
11384 Method: updateJSON
11385
11386 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
11387
11388 Parameters:
11389
11390 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
11391 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11392
11393 Example:
11394
11395 (start code js)
11396 barChart.updateJSON(json, {
11397 onComplete: function() {
11398 alert('update complete!');
11399 }
11400 });
11401 (end code)
11402 */
11403 updateJSON: function(json, onComplete) {
11404 if(this.busy) return;
11405 this.busy = true;
11406 this.select(false, false, false);
11407 var delegate = this.delegate;
11408 var graph = delegate.graph;
11409 var values = json.values;
11410 var animate = this.config.animate;
11411 var that = this;
11412 var horz = this.config.orientation == 'horizontal';
11413 $.each(values, function(v) {
11414 var n = graph.getByName(v.label);
11415 if(n) {
11416 n.setData('valueArray', $.splat(v.values));
11417 if(json.label) {
11418 n.setData('stringArray', $.splat(json.label));
11419 }
11420 }
11421 });
11422 this.normalizeDims();
11423 delegate.compute();
11424 delegate.select(delegate.root);
11425 if(animate) {
11426 if(horz) {
11427 delegate.fx.animate({
11428 modes: ['node-property:width:dimArray'],
11429 duration:1500,
11430 onComplete: function() {
11431 that.busy = false;
11432 onComplete && onComplete.onComplete();
11433 }
11434 });
11435 } else {
11436 delegate.fx.animate({
11437 modes: ['node-property:height:dimArray'],
11438 duration:1500,
11439 onComplete: function() {
11440 that.busy = false;
11441 onComplete && onComplete.onComplete();
11442 }
11443 });
11444 }
11445 }
11446 },
11447
11448 //adds the little brown bar when hovering the node
11449 select: function(id, name) {
11450 if(!this.config.hoveredColor) return;
11451 var s = this.selected;
11452 if(s.id != id || s.name != name) {
11453 s.id = id;
11454 s.name = name;
11455 s.color = this.config.hoveredColor;
11456 this.delegate.graph.eachNode(function(n) {
11457 if(id == n.id) {
11458 n.setData('border', s);
11459 } else {
11460 n.setData('border', false);
11461 }
11462 });
11463 this.delegate.plot();
11464 }
11465 },
11466
11467 /*
11468 Method: getLegend
11469
11470 Returns an object containing as keys the legend names and as values hex strings with color values.
11471
11472 Example:
11473
11474 (start code js)
11475 var legend = barChart.getLegend();
11476 (end code)
11477 */
11478 getLegend: function() {
11479 var legend = {};
11480 var n;
11481 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
11482 n = adj.nodeTo;
11483 });
11484 var colors = n.getData('colorArray'),
11485 len = colors.length;
11486 $.each(n.getData('stringArray'), function(s, i) {
11487 legend[s] = colors[i % len];
11488 });
11489 return legend;
11490 },
11491
11492 /*
11493 Method: getMaxValue
11494
11495 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11496
11497 Example:
11498
11499 (start code js)
11500 var ans = barChart.getMaxValue();
11501 (end code)
11502
11503 In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
11504
11505 Example:
11506
11507 (start code js)
11508 //will return 100 for all BarChart instances,
11509 //displaying all of them with the same scale
11510 $jit.BarChart.implement({
11511 'getMaxValue': function() {
11512 return 100;
11513 }
11514 });
11515 (end code)
11516
11517 */
11518 getMaxValue: function() {
11519 var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
11520 this.delegate.graph.eachNode(function(n) {
11521 var valArray = n.getData('valueArray'),
11522 acum = 0;
11523 if(!valArray) return;
11524 if(stacked) {
11525 $.each(valArray, function(v) {
11526 acum += +v;
11527 });
11528 } else {
11529 acum = Math.max.apply(null, valArray);
11530 }
11531 maxValue = maxValue>acum? maxValue:acum;
11532 });
11533 return maxValue;
11534 },
11535
11536 setBarType: function(type) {
11537 this.config.type = type;
11538 this.delegate.config.Node.type = 'barchart-' + type.split(':')[0];
11539 },
11540
11541 normalizeDims: function() {
11542 //number of elements
11543 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
11544 root.eachAdjacency(function() {
11545 l++;
11546 });
11547 var maxValue = this.getMaxValue() || 1,
11548 size = this.delegate.canvas.getSize(),
11549 config = this.config,
11550 margin = config.Margin,
11551 marginWidth = margin.left + margin.right,
11552 marginHeight = margin.top + margin.bottom,
11553 horz = config.orientation == 'horizontal',
11554 fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (l -1) * config.barsOffset) / l,
11555 animate = config.animate,
11556 height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight)
11557 - (!horz && config.showAggregates && (config.Label.size + config.labelOffset))
11558 - (config.showLabels && (config.Label.size + config.labelOffset)),
11559 dim1 = horz? 'height':'width',
11560 dim2 = horz? 'width':'height';
11561 this.delegate.graph.eachNode(function(n) {
11562 var acum = 0, animateValue = [];
11563 $.each(n.getData('valueArray'), function(v) {
11564 acum += +v;
11565 animateValue.push(0);
11566 });
11567 n.setData(dim1, fixedDim);
11568 if(animate) {
11569 n.setData(dim2, acum * height / maxValue, 'end');
11570 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11571 return n * height / maxValue;
11572 }), 'end');
11573 var dimArray = n.getData('dimArray');
11574 if(!dimArray) {
11575 n.setData('dimArray', animateValue);
11576 }
11577 } else {
11578 n.setData(dim2, acum * height / maxValue);
11579 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
11580 return n * height / maxValue;
11581 }));
11582 }
11583 });
11584 }
11585});
11586
11587
11588/*
11589 * File: Options.PieChart.js
11590 *
11591*/
11592/*
11593 Object: Options.PieChart
11594
11595 <PieChart> options.
11596 Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
11597
11598 Syntax:
11599
11600 (start code js)
11601
11602 Options.PieChart = {
11603 animate: true,
11604 offset: 25,
11605 sliceOffset:0,
11606 labelOffset: 3,
11607 type: 'stacked',
11608 hoveredColor: '#9fd4ff',
11609 showLabels: true,
11610 resizeLabels: false,
11611 updateHeights: false
11612 };
11613
11614 (end code)
11615
11616 Example:
11617
11618 (start code js)
11619
11620 var pie = new $jit.PieChart({
11621 animate: true,
11622 sliceOffset: 5,
11623 type: 'stacked:gradient'
11624 });
11625
11626 (end code)
11627
11628 Parameters:
11629
11630 animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
11631 offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11632 sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
11633 labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11634 type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
11635 hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
11636 showLabels - (boolean) Default's *true*. Display the name of the slots.
11637 resizeLabels - (boolean|number) Default's *false*. Resize the pie labels according to their stacked values. Set a number for *resizeLabels* to set a font size minimum.
11638 updateHeights - (boolean) Default's *false*. Only for mono-valued (most common) pie charts. Resize the height of the pie slices according to their current values.
11639
11640*/
11641Options.PieChart = {
11642 $extend: true,
11643
11644 animate: true,
11645 offset: 25, // page offset
11646 sliceOffset:0,
11647 labelOffset: 3, // label offset
11648 type: 'stacked', // gradient
11649 hoveredColor: '#9fd4ff',
11650 Events: {
11651 enable: false,
11652 onClick: $.empty
11653 },
11654 Tips: {
11655 enable: false,
11656 onShow: $.empty,
11657 onHide: $.empty
11658 },
11659 showLabels: true,
11660 resizeLabels: false,
11661
11662 //only valid for mono-valued datasets
11663 updateHeights: false
11664};
11665
11666/*
11667 * Class: Layouts.Radial
11668 *
11669 * Implements a Radial Layout.
11670 *
11671 * Implemented By:
11672 *
11673 * <RGraph>, <Hypertree>
11674 *
11675 */
11676Layouts.Radial = new Class({
11677
11678 /*
11679 * Method: compute
11680 *
11681 * Computes nodes' positions.
11682 *
11683 * Parameters:
11684 *
11685 * property - _optional_ A <Graph.Node> position property to store the new
11686 * positions. Possible values are 'pos', 'end' or 'start'.
11687 *
11688 */
11689 compute : function(property) {
11690 var prop = $.splat(property || [ 'current', 'start', 'end' ]);
11691 NodeDim.compute(this.graph, prop, this.config);
11692 this.graph.computeLevels(this.root, 0, "ignore");
11693 var lengthFunc = this.createLevelDistanceFunc();
11694 this.computeAngularWidths(prop);
11695 this.computePositions(prop, lengthFunc);
11696 },
11697
11698 /*
11699 * computePositions
11700 *
11701 * Performs the main algorithm for computing node positions.
11702 */
11703 computePositions : function(property, getLength) {
11704 var propArray = property;
11705 var graph = this.graph;
11706 var root = graph.getNode(this.root);
11707 var parent = this.parent;
11708 var config = this.config;
11709
11710 for ( var i=0, l=propArray.length; i < l; i++) {
11711 var pi = propArray[i];
11712 root.setPos($P(0, 0), pi);
11713 root.setData('span', Math.PI * 2, pi);
11714 }
11715
11716 root.angleSpan = {
11717 begin : 0,
11718 end : 2 * Math.PI
11719 };
11720
11721 graph.eachBFS(this.root, function(elem) {
11722 var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
11723 var angleInit = elem.angleSpan.begin;
11724 var len = getLength(elem);
11725 //Calculate the sum of all angular widths
11726 var totalAngularWidths = 0, subnodes = [], maxDim = {};
11727 elem.eachSubnode(function(sib) {
11728 totalAngularWidths += sib._treeAngularWidth;
11729 //get max dim
11730 for ( var i=0, l=propArray.length; i < l; i++) {
11731 var pi = propArray[i], dim = sib.getData('dim', pi);
11732 maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
11733 }
11734 subnodes.push(sib);
11735 }, "ignore");
11736 //Maintain children order
11737 //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
11738 if (parent && parent.id == elem.id && subnodes.length > 0
11739 && subnodes[0].dist) {
11740 subnodes.sort(function(a, b) {
11741 return (a.dist >= b.dist) - (a.dist <= b.dist);
11742 });
11743 }
11744 //Calculate nodes positions.
11745 for (var k = 0, ls=subnodes.length; k < ls; k++) {
11746 var child = subnodes[k];
11747 if (!child._flag) {
11748 var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
11749 var theta = angleInit + angleProportion / 2;
11750
11751 for ( var i=0, l=propArray.length; i < l; i++) {
11752 var pi = propArray[i];
11753 child.setPos($P(theta, len), pi);
11754 child.setData('span', angleProportion, pi);
11755 child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
11756 }
11757
11758 child.angleSpan = {
11759 begin : angleInit,
11760 end : angleInit + angleProportion
11761 };
11762 angleInit += angleProportion;
11763 }
11764 }
11765 }, "ignore");
11766 },
11767
11768 /*
11769 * Method: setAngularWidthForNodes
11770 *
11771 * Sets nodes angular widths.
11772 */
11773 setAngularWidthForNodes : function(prop) {
11774 this.graph.eachBFS(this.root, function(elem, i) {
11775 var diamValue = elem.getData('angularWidth', prop[0]) || 5;
11776 elem._angularWidth = diamValue / i;
11777 }, "ignore");
11778 },
11779
11780 /*
11781 * Method: setSubtreesAngularWidth
11782 *
11783 * Sets subtrees angular widths.
11784 */
11785 setSubtreesAngularWidth : function() {
11786 var that = this;
11787 this.graph.eachNode(function(elem) {
11788 that.setSubtreeAngularWidth(elem);
11789 }, "ignore");
11790 },
11791
11792 /*
11793 * Method: setSubtreeAngularWidth
11794 *
11795 * Sets the angular width for a subtree.
11796 */
11797 setSubtreeAngularWidth : function(elem) {
11798 var that = this, nodeAW = elem._angularWidth, sumAW = 0;
11799 elem.eachSubnode(function(child) {
11800 that.setSubtreeAngularWidth(child);
11801 sumAW += child._treeAngularWidth;
11802 }, "ignore");
11803 elem._treeAngularWidth = Math.max(nodeAW, sumAW);
11804 },
11805
11806 /*
11807 * Method: computeAngularWidths
11808 *
11809 * Computes nodes and subtrees angular widths.
11810 */
11811 computeAngularWidths : function(prop) {
11812 this.setAngularWidthForNodes(prop);
11813 this.setSubtreesAngularWidth();
11814 }
11815
11816});
11817
11818
11819/*
11820 * File: Sunburst.js
11821 */
11822
11823/*
11824 Class: Sunburst
11825
11826 A radial space filling tree visualization.
11827
11828 Inspired by:
11829
11830 Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
11831
11832 Note:
11833
11834 This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
11835
11836 Implements:
11837
11838 All <Loader> methods
11839
11840 Constructor Options:
11841
11842 Inherits options from
11843
11844 - <Options.Canvas>
11845 - <Options.Controller>
11846 - <Options.Node>
11847 - <Options.Edge>
11848 - <Options.Label>
11849 - <Options.Events>
11850 - <Options.Tips>
11851 - <Options.NodeStyles>
11852 - <Options.Navigation>
11853
11854 Additionally, there are other parameters and some default values changed
11855
11856 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
11857 levelDistance - (number) Default's *100*. The distance between levels of the tree.
11858 Node.type - Described in <Options.Node>. Default's to *multipie*.
11859 Node.height - Described in <Options.Node>. Default's *0*.
11860 Edge.type - Described in <Options.Edge>. Default's *none*.
11861 Label.textAlign - Described in <Options.Label>. Default's *start*.
11862 Label.textBaseline - Described in <Options.Label>. Default's *middle*.
11863
11864 Instance Properties:
11865
11866 canvas - Access a <Canvas> instance.
11867 graph - Access a <Graph> instance.
11868 op - Access a <Sunburst.Op> instance.
11869 fx - Access a <Sunburst.Plot> instance.
11870 labels - Access a <Sunburst.Label> interface implementation.
11871
11872*/
11873
11874$jit.Sunburst = new Class({
11875
11876 Implements: [ Loader, Extras, Layouts.Radial ],
11877
11878 initialize: function(controller) {
11879 var $Sunburst = $jit.Sunburst;
11880
11881 var config = {
11882 interpolation: 'linear',
11883 levelDistance: 100,
11884 Node: {
11885 'type': 'multipie',
11886 'height':0
11887 },
11888 Edge: {
11889 'type': 'none'
11890 },
11891 Label: {
11892 textAlign: 'start',
11893 textBaseline: 'middle'
11894 }
11895 };
11896
11897 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
11898 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
11899
11900 var canvasConfig = this.config;
11901 if(canvasConfig.useCanvas) {
11902 this.canvas = canvasConfig.useCanvas;
11903 this.config.labelContainer = this.canvas.id + '-label';
11904 } else {
11905 if(canvasConfig.background) {
11906 canvasConfig.background = $.merge({
11907 type: 'Circles'
11908 }, canvasConfig.background);
11909 }
11910 this.canvas = new Canvas(this, canvasConfig);
11911 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
11912 }
11913
11914 this.graphOptions = {
11915 'klass': Polar,
11916 'Node': {
11917 'selected': false,
11918 'exist': true,
11919 'drawn': true
11920 }
11921 };
11922 this.graph = new Graph(this.graphOptions, this.config.Node,
11923 this.config.Edge);
11924 this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
11925 this.fx = new $Sunburst.Plot(this, $Sunburst);
11926 this.op = new $Sunburst.Op(this);
11927 this.json = null;
11928 this.root = null;
11929 this.rotated = null;
11930 this.busy = false;
11931 // initialize extras
11932 this.initializeExtras();
11933 },
11934
11935 /*
11936
11937 createLevelDistanceFunc
11938
11939 Returns the levelDistance function used for calculating a node distance
11940 to its origin. This function returns a function that is computed
11941 per level and not per node, such that all nodes with the same depth will have the
11942 same distance to the origin. The resulting function gets the
11943 parent node as parameter and returns a float.
11944
11945 */
11946 createLevelDistanceFunc: function() {
11947 var ld = this.config.levelDistance;
11948 return function(elem) {
11949 return (elem._depth + 1) * ld;
11950 };
11951 },
11952
11953 /*
11954 Method: refresh
11955
11956 Computes positions and plots the tree.
11957
11958 */
11959 refresh: function() {
11960 this.compute();
11961 this.plot();
11962 },
11963
11964 /*
11965 reposition
11966
11967 An alias for computing new positions to _endPos_
11968
11969 See also:
11970
11971 <Sunburst.compute>
11972
11973 */
11974 reposition: function() {
11975 this.compute('end');
11976 },
11977
11978 /*
11979 Method: rotate
11980
11981 Rotates the graph so that the selected node is horizontal on the right.
11982
11983 Parameters:
11984
11985 node - (object) A <Graph.Node>.
11986 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
11987 opt - (object) Configuration options merged with this visualization configuration options.
11988
11989 See also:
11990
11991 <Sunburst.rotateAngle>
11992
11993 */
11994 rotate: function(node, method, opt) {
11995 var theta = node.getPos(opt.property || 'current').getp(true).theta;
11996 this.rotated = node;
11997 this.rotateAngle(-theta, method, opt);
11998 },
11999
12000 /*
12001 Method: rotateAngle
12002
12003 Rotates the graph of an angle theta.
12004
12005 Parameters:
12006
12007 node - (object) A <Graph.Node>.
12008 method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
12009 opt - (object) Configuration options merged with this visualization configuration options.
12010
12011 See also:
12012
12013 <Sunburst.rotate>
12014
12015 */
12016 rotateAngle: function(theta, method, opt) {
12017 var that = this;
12018 var options = $.merge(this.config, opt || {}, {
12019 modes: [ 'polar' ]
12020 });
12021 var prop = opt.property || (method === "animate" ? 'end' : 'current');
12022 if(method === 'animate') {
12023 this.fx.animation.pause();
12024 }
12025 this.graph.eachNode(function(n) {
12026 var p = n.getPos(prop);
12027 p.theta += theta;
12028 if (p.theta < 0) {
12029 p.theta += Math.PI * 2;
12030 }
12031 });
12032 if (method == 'animate') {
12033 this.fx.animate(options);
12034 } else if (method == 'replot') {
12035 this.fx.plot();
12036 this.busy = false;
12037 }
12038 },
12039
12040 /*
12041 Method: plot
12042
12043 Plots the Sunburst. This is a shortcut to *fx.plot*.
12044 */
12045 plot: function() {
12046 this.fx.plot();
12047 }
12048});
12049
12050$jit.Sunburst.$extend = true;
12051
12052(function(Sunburst) {
12053
12054 /*
12055 Class: Sunburst.Op
12056
12057 Custom extension of <Graph.Op>.
12058
12059 Extends:
12060
12061 All <Graph.Op> methods
12062
12063 See also:
12064
12065 <Graph.Op>
12066
12067 */
12068 Sunburst.Op = new Class( {
12069
12070 Implements: Graph.Op
12071
12072 });
12073
12074 /*
12075 Class: Sunburst.Plot
12076
12077 Custom extension of <Graph.Plot>.
12078
12079 Extends:
12080
12081 All <Graph.Plot> methods
12082
12083 See also:
12084
12085 <Graph.Plot>
12086
12087 */
12088 Sunburst.Plot = new Class( {
12089
12090 Implements: Graph.Plot
12091
12092 });
12093
12094 /*
12095 Class: Sunburst.Label
12096
12097 Custom extension of <Graph.Label>.
12098 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
12099
12100 Extends:
12101
12102 All <Graph.Label> methods and subclasses.
12103
12104 See also:
12105
12106 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
12107
12108 */
12109 Sunburst.Label = {};
12110
12111 /*
12112 Sunburst.Label.Native
12113
12114 Custom extension of <Graph.Label.Native>.
12115
12116 Extends:
12117
12118 All <Graph.Label.Native> methods
12119
12120 See also:
12121
12122 <Graph.Label.Native>
12123 */
12124 Sunburst.Label.Native = new Class( {
12125 Implements: Graph.Label.Native,
12126
12127 initialize: function(viz) {
12128 this.viz = viz;
12129 this.label = viz.config.Label;
12130 this.config = viz.config;
12131 },
12132
12133 renderLabel: function(canvas, node, controller) {
12134 var span = node.getData('span');
12135 if(span < Math.PI /2 && Math.tan(span) *
12136 this.config.levelDistance * node._depth < 10) {
12137 return;
12138 }
12139 var ctx = canvas.getCtx();
12140 var measure = ctx.measureText(node.name);
12141 if (node.id == this.viz.root) {
12142 var x = -measure.width / 2, y = 0, thetap = 0;
12143 var ld = 0;
12144 } else {
12145 var indent = 5;
12146 var ld = controller.levelDistance - indent;
12147 var clone = node.pos.clone();
12148 clone.rho += indent;
12149 var p = clone.getp(true);
12150 var ct = clone.getc(true);
12151 var x = ct.x, y = ct.y;
12152 // get angle in degrees
12153 var pi = Math.PI;
12154 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
12155 var thetap = cond ? p.theta + pi : p.theta;
12156 if (cond) {
12157 x -= Math.abs(Math.cos(p.theta) * measure.width);
12158 y += Math.sin(p.theta) * measure.width;
12159 } else if (node.id == this.viz.root) {
12160 x -= measure.width / 2;
12161 }
12162 }
12163 ctx.save();
12164 ctx.translate(x, y);
12165 ctx.rotate(thetap);
12166 ctx.fillText(node.name, 0, 0);
12167 ctx.restore();
12168 }
12169 });
12170
12171 /*
12172 Sunburst.Label.SVG
12173
12174 Custom extension of <Graph.Label.SVG>.
12175
12176 Extends:
12177
12178 All <Graph.Label.SVG> methods
12179
12180 See also:
12181
12182 <Graph.Label.SVG>
12183
12184 */
12185 Sunburst.Label.SVG = new Class( {
12186 Implements: Graph.Label.SVG,
12187
12188 initialize: function(viz) {
12189 this.viz = viz;
12190 },
12191
12192 /*
12193 placeLabel
12194
12195 Overrides abstract method placeLabel in <Graph.Plot>.
12196
12197 Parameters:
12198
12199 tag - A DOM label element.
12200 node - A <Graph.Node>.
12201 controller - A configuration/controller object passed to the visualization.
12202
12203 */
12204 placeLabel: function(tag, node, controller) {
12205 var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
12206 var radius = canvas.getSize();
12207 var labelPos = {
12208 x: Math.round(pos.x + radius.width / 2),
12209 y: Math.round(pos.y + radius.height / 2)
12210 };
12211 tag.setAttribute('x', labelPos.x);
12212 tag.setAttribute('y', labelPos.y);
12213
12214 var bb = tag.getBBox();
12215 if (bb) {
12216 // center the label
12217 var x = tag.getAttribute('x');
12218 var y = tag.getAttribute('y');
12219 // get polar coordinates
12220 var p = node.pos.getp(true);
12221 // get angle in degrees
12222 var pi = Math.PI;
12223 var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
12224 if (cond) {
12225 tag.setAttribute('x', x - bb.width);
12226 tag.setAttribute('y', y - bb.height);
12227 } else if (node.id == viz.root) {
12228 tag.setAttribute('x', x - bb.width / 2);
12229 }
12230
12231 var thetap = cond ? p.theta + pi : p.theta;
12232 if(node._depth)
12233 tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
12234 + ' ' + y + ')');
12235 }
12236
12237 controller.onPlaceLabel(tag, node);
12238}
12239 });
12240
12241 /*
12242 Sunburst.Label.HTML
12243
12244 Custom extension of <Graph.Label.HTML>.
12245
12246 Extends:
12247
12248 All <Graph.Label.HTML> methods.
12249
12250 See also:
12251
12252 <Graph.Label.HTML>
12253
12254 */
12255 Sunburst.Label.HTML = new Class( {
12256 Implements: Graph.Label.HTML,
12257
12258 initialize: function(viz) {
12259 this.viz = viz;
12260 },
12261 /*
12262 placeLabel
12263
12264 Overrides abstract method placeLabel in <Graph.Plot>.
12265
12266 Parameters:
12267
12268 tag - A DOM label element.
12269 node - A <Graph.Node>.
12270 controller - A configuration/controller object passed to the visualization.
12271
12272 */
12273 placeLabel: function(tag, node, controller) {
12274 var pos = node.pos.clone(),
12275 canvas = this.viz.canvas,
12276 height = node.getData('height'),
12277 ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
12278 radius = canvas.getSize();
12279 pos.rho += ldist;
12280 pos = pos.getc(true);
12281
12282 var labelPos = {
12283 x: Math.round(pos.x + radius.width / 2),
12284 y: Math.round(pos.y + radius.height / 2)
12285 };
12286
12287 var style = tag.style;
12288 style.left = labelPos.x + 'px';
12289 style.top = labelPos.y + 'px';
12290 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
12291
12292 controller.onPlaceLabel(tag, node);
12293 }
12294 });
12295
12296 /*
12297 Class: Sunburst.Plot.NodeTypes
12298
12299 This class contains a list of <Graph.Node> built-in types.
12300 Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
12301
12302 You can add your custom node types, customizing your visualization to the extreme.
12303
12304 Example:
12305
12306 (start code js)
12307 Sunburst.Plot.NodeTypes.implement({
12308 'mySpecialType': {
12309 'render': function(node, canvas) {
12310 //print your custom node to canvas
12311 },
12312 //optional
12313 'contains': function(node, pos) {
12314 //return true if pos is inside the node or false otherwise
12315 }
12316 }
12317 });
12318 (end code)
12319
12320 */
12321 Sunburst.Plot.NodeTypes = new Class( {
12322 'none': {
12323 'render': $.empty,
12324 'contains': $.lambda(false),
12325 'anglecontains': function(node, pos) {
12326 var span = node.getData('span') / 2, theta = node.pos.theta;
12327 var begin = theta - span, end = theta + span;
12328 if (begin < 0)
12329 begin += Math.PI * 2;
12330 var atan = Math.atan2(pos.y, pos.x);
12331 if (atan < 0)
12332 atan += Math.PI * 2;
12333 if (begin > end) {
12334 return (atan > begin && atan <= Math.PI * 2) || atan < end;
12335 } else {
12336 return atan > begin && atan < end;
12337 }
12338 }
12339 },
12340
12341 'pie': {
12342 'render': function(node, canvas) {
12343 var span = node.getData('span') / 2, theta = node.pos.theta;
12344 var begin = theta - span, end = theta + span;
12345 var polarNode = node.pos.getp(true);
12346 var polar = new Polar(polarNode.rho, begin);
12347 var p1coord = polar.getc(true);
12348 polar.theta = end;
12349 var p2coord = polar.getc(true);
12350
12351 var ctx = canvas.getCtx();
12352 ctx.beginPath();
12353 ctx.moveTo(0, 0);
12354 ctx.lineTo(p1coord.x, p1coord.y);
12355 ctx.moveTo(0, 0);
12356 ctx.lineTo(p2coord.x, p2coord.y);
12357 ctx.moveTo(0, 0);
12358 ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
12359 false);
12360 ctx.fill();
12361 },
12362 'contains': function(node, pos) {
12363 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12364 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12365 var ld = this.config.levelDistance, d = node._depth;
12366 return (rho <= ld * d);
12367 }
12368 return false;
12369 }
12370 },
12371 'multipie': {
12372 'render': function(node, canvas) {
12373 var height = node.getData('height');
12374 var ldist = height? height : this.config.levelDistance;
12375 var span = node.getData('span') / 2, theta = node.pos.theta;
12376 var begin = theta - span, end = theta + span;
12377 var polarNode = node.pos.getp(true);
12378
12379 var polar = new Polar(polarNode.rho, begin);
12380 var p1coord = polar.getc(true);
12381
12382 polar.theta = end;
12383 var p2coord = polar.getc(true);
12384
12385 polar.rho += ldist;
12386 var p3coord = polar.getc(true);
12387
12388 polar.theta = begin;
12389 var p4coord = polar.getc(true);
12390
12391 var ctx = canvas.getCtx();
12392 ctx.moveTo(0, 0);
12393 ctx.beginPath();
12394 ctx.arc(0, 0, polarNode.rho, begin, end, false);
12395 ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
12396 ctx.moveTo(p1coord.x, p1coord.y);
12397 ctx.lineTo(p4coord.x, p4coord.y);
12398 ctx.moveTo(p2coord.x, p2coord.y);
12399 ctx.lineTo(p3coord.x, p3coord.y);
12400 ctx.fill();
12401
12402 if (node.collapsed) {
12403 ctx.save();
12404 ctx.lineWidth = 2;
12405 ctx.moveTo(0, 0);
12406 ctx.beginPath();
12407 ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
12408 true);
12409 ctx.stroke();
12410 ctx.restore();
12411 }
12412 },
12413 'contains': function(node, pos) {
12414 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12415 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12416 var height = node.getData('height');
12417 var ldist = height? height : this.config.levelDistance;
12418 var ld = this.config.levelDistance, d = node._depth;
12419 return (rho >= ld * d) && (rho <= (ld * d + ldist));
12420 }
12421 return false;
12422 }
12423 },
12424
12425 'gradient-multipie': {
12426 'render': function(node, canvas) {
12427 var ctx = canvas.getCtx();
12428 var height = node.getData('height');
12429 var ldist = height? height : this.config.levelDistance;
12430 var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
12431 0, 0, node.getPos().rho + ldist);
12432
12433 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
12434 $.each(colorArray, function(i) {
12435 ans.push(parseInt(i * 0.5, 10));
12436 });
12437 var endColor = $.rgbToHex(ans);
12438 radialGradient.addColorStop(0, endColor);
12439 radialGradient.addColorStop(1, node.getData('color'));
12440 ctx.fillStyle = radialGradient;
12441 this.nodeTypes['multipie'].render.call(this, node, canvas);
12442 },
12443 'contains': function(node, pos) {
12444 return this.nodeTypes['multipie'].contains.call(this, node, pos);
12445 }
12446 },
12447
12448 'gradient-pie': {
12449 'render': function(node, canvas) {
12450 var ctx = canvas.getCtx();
12451 var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
12452 .getPos().rho);
12453
12454 var colorArray = $.hexToRgb(node.getData('color')), ans = [];
12455 $.each(colorArray, function(i) {
12456 ans.push(parseInt(i * 0.5, 10));
12457 });
12458 var endColor = $.rgbToHex(ans);
12459 radialGradient.addColorStop(1, endColor);
12460 radialGradient.addColorStop(0, node.getData('color'));
12461 ctx.fillStyle = radialGradient;
12462 this.nodeTypes['pie'].render.call(this, node, canvas);
12463 },
12464 'contains': function(node, pos) {
12465 return this.nodeTypes['pie'].contains.call(this, node, pos);
12466 }
12467 }
12468 });
12469
12470 /*
12471 Class: Sunburst.Plot.EdgeTypes
12472
12473 This class contains a list of <Graph.Adjacence> built-in types.
12474 Edge types implemented are 'none', 'line' and 'arrow'.
12475
12476 You can add your custom edge types, customizing your visualization to the extreme.
12477
12478 Example:
12479
12480 (start code js)
12481 Sunburst.Plot.EdgeTypes.implement({
12482 'mySpecialType': {
12483 'render': function(adj, canvas) {
12484 //print your custom edge to canvas
12485 },
12486 //optional
12487 'contains': function(adj, pos) {
12488 //return true if pos is inside the arc or false otherwise
12489 }
12490 }
12491 });
12492 (end code)
12493
12494 */
12495 Sunburst.Plot.EdgeTypes = new Class({
12496 'none': $.empty,
12497 'line': {
12498 'render': function(adj, canvas) {
12499 var from = adj.nodeFrom.pos.getc(true),
12500 to = adj.nodeTo.pos.getc(true);
12501 this.edgeHelper.line.render(from, to, canvas);
12502 },
12503 'contains': function(adj, pos) {
12504 var from = adj.nodeFrom.pos.getc(true),
12505 to = adj.nodeTo.pos.getc(true);
12506 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
12507 }
12508 },
12509 'arrow': {
12510 'render': function(adj, canvas) {
12511 var from = adj.nodeFrom.pos.getc(true),
12512 to = adj.nodeTo.pos.getc(true),
12513 dim = adj.getData('dim'),
12514 direction = adj.data.$direction,
12515 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
12516 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
12517 },
12518 'contains': function(adj, pos) {
12519 var from = adj.nodeFrom.pos.getc(true),
12520 to = adj.nodeTo.pos.getc(true);
12521 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
12522 }
12523 },
12524 'hyperline': {
12525 'render': function(adj, canvas) {
12526 var from = adj.nodeFrom.pos.getc(),
12527 to = adj.nodeTo.pos.getc(),
12528 dim = Math.max(from.norm(), to.norm());
12529 this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
12530 },
12531 'contains': $.lambda(false) //TODO(nico): Implement this!
12532 }
12533 });
12534
12535})($jit.Sunburst);
12536
12537
12538/*
12539 * File: PieChart.js
12540 *
12541*/
12542
12543$jit.Sunburst.Plot.NodeTypes.implement({
12544 'piechart-stacked' : {
12545 'render' : function(node, canvas) {
12546 var pos = node.pos.getp(true),
12547 dimArray = node.getData('dimArray'),
12548 valueArray = node.getData('valueArray'),
12549 colorArray = node.getData('colorArray'),
12550 colorLength = colorArray.length,
12551 stringArray = node.getData('stringArray'),
12552 span = node.getData('span') / 2,
12553 theta = node.pos.theta,
12554 begin = theta - span,
12555 end = theta + span,
12556 polar = new Polar;
12557
12558 var ctx = canvas.getCtx(),
12559 opt = {},
12560 gradient = node.getData('gradient'),
12561 border = node.getData('border'),
12562 config = node.getData('config'),
12563 showLabels = config.showLabels,
12564 resizeLabels = config.resizeLabels,
12565 label = config.Label;
12566
12567 var xpos = config.sliceOffset * Math.cos((begin + end) /2);
12568 var ypos = config.sliceOffset * Math.sin((begin + end) /2);
12569
12570 if (colorArray && dimArray && stringArray) {
12571 for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
12572 var dimi = dimArray[i], colori = colorArray[i % colorLength];
12573 if(dimi <= 0) continue;
12574 ctx.fillStyle = ctx.strokeStyle = colori;
12575 if(gradient && dimi) {
12576 var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
12577 xpos, ypos, acum + dimi + config.sliceOffset);
12578 var colorRgb = $.hexToRgb(colori),
12579 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
12580 endColor = $.rgbToHex(ans);
12581
12582 radialGradient.addColorStop(0, colori);
12583 radialGradient.addColorStop(0.5, colori);
12584 radialGradient.addColorStop(1, endColor);
12585 ctx.fillStyle = radialGradient;
12586 }
12587
12588 polar.rho = acum + config.sliceOffset;
12589 polar.theta = begin;
12590 var p1coord = polar.getc(true);
12591 polar.theta = end;
12592 var p2coord = polar.getc(true);
12593 polar.rho += dimi;
12594 var p3coord = polar.getc(true);
12595 polar.theta = begin;
12596 var p4coord = polar.getc(true);
12597
12598 ctx.beginPath();
12599 //fixing FF arc method + fill
12600 ctx.arc(xpos, ypos, acum + .01, begin, end, false);
12601 ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
12602 ctx.fill();
12603 if(border && border.name == stringArray[i]) {
12604 opt.acum = acum;
12605 opt.dimValue = dimArray[i];
12606 opt.begin = begin;
12607 opt.end = end;
12608 }
12609 acum += (dimi || 0);
12610 valAcum += (valueArray[i] || 0);
12611 }
12612 if(border) {
12613 ctx.save();
12614 ctx.globalCompositeOperation = "source-over";
12615 ctx.lineWidth = 2;
12616 ctx.strokeStyle = border.color;
12617 var s = begin < end? 1 : -1;
12618 ctx.beginPath();
12619 //fixing FF arc method + fill
12620 ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
12621 ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
12622 ctx.closePath();
12623 ctx.stroke();
12624 ctx.restore();
12625 }
12626 if(showLabels && label.type == 'Native') {
12627 ctx.save();
12628 ctx.fillStyle = ctx.strokeStyle = label.color;
12629 var scale = resizeLabels? node.getData('normalizedDim') : 1,
12630 fontSize = (label.size * scale) >> 0;
12631 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
12632
12633 ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
12634 ctx.textBaseline = 'middle';
12635 ctx.textAlign = 'center';
12636
12637 polar.rho = acum + config.labelOffset + config.sliceOffset;
12638 polar.theta = node.pos.theta;
12639 var cart = polar.getc(true);
12640
12641 ctx.fillText(node.name, cart.x, cart.y);
12642 ctx.restore();
12643 }
12644 }
12645 },
12646 'contains': function(node, pos) {
12647 if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
12648 var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
12649 var ld = this.config.levelDistance, d = node._depth;
12650 var config = node.getData('config');
12651 if(rho <=ld * d + config.sliceOffset) {
12652 var dimArray = node.getData('dimArray');
12653 for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
12654 var dimi = dimArray[i];
12655 if(rho >= acum && rho <= acum + dimi) {
12656 return {
12657 name: node.getData('stringArray')[i],
12658 color: node.getData('colorArray')[i],
12659 value: node.getData('valueArray')[i],
12660 label: node.name
12661 };
12662 }
12663 acum += dimi;
12664 }
12665 }
12666 return false;
12667
12668 }
12669 return false;
12670 }
12671 }
12672});
12673
12674/*
12675 Class: PieChart
12676
12677 A visualization that displays stacked bar charts.
12678
12679 Constructor Options:
12680
12681 See <Options.PieChart>.
12682
12683*/
12684$jit.PieChart = new Class({
12685 sb: null,
12686 colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
12687 selected: {},
12688 busy: false,
12689
12690 initialize: function(opt) {
12691 this.controller = this.config =
12692 $.merge(Options("Canvas", "PieChart", "Label"), {
12693 Label: { type: 'Native' }
12694 }, opt);
12695 this.initializeViz();
12696 },
12697
12698 initializeViz: function() {
12699 var config = this.config, that = this;
12700 var nodeType = config.type.split(":")[0];
12701 var delegate = new $jit.Sunburst({
12702 injectInto: config.injectInto,
12703 width: config.width,
12704 height: config.height,
12705 useCanvas: config.useCanvas,
12706 withLabels: config.Label.type != 'Native',
12707 Label: {
12708 type: config.Label.type
12709 },
12710 Node: {
12711 overridable: true,
12712 type: 'piechart-' + nodeType,
12713 width: 1,
12714 height: 1
12715 },
12716 Edge: {
12717 type: 'none'
12718 },
12719 Tips: {
12720 enable: config.Tips.enable,
12721 type: 'Native',
12722 force: true,
12723 onShow: function(tip, node, contains) {
12724 var elem = contains;
12725 config.Tips.onShow(tip, elem, node);
12726 }
12727 },
12728 Events: {
12729 enable: true,
12730 type: 'Native',
12731 onClick: function(node, eventInfo, evt) {
12732 if(!config.Events.enable) return;
12733 var elem = eventInfo.getContains();
12734 config.Events.onClick(elem, eventInfo, evt);
12735 },
12736 onMouseMove: function(node, eventInfo, evt) {
12737 if(!config.hoveredColor) return;
12738 if(node) {
12739 var elem = eventInfo.getContains();
12740 that.select(node.id, elem.name, elem.index);
12741 } else {
12742 that.select(false, false, false);
12743 }
12744 }
12745 },
12746 onCreateLabel: function(domElement, node) {
12747 var labelConf = config.Label;
12748 if(config.showLabels) {
12749 var style = domElement.style;
12750 style.fontSize = labelConf.size + 'px';
12751 style.fontFamily = labelConf.family;
12752 style.color = labelConf.color;
12753 style.textAlign = 'center';
12754 domElement.innerHTML = node.name;
12755 }
12756 },
12757 onPlaceLabel: function(domElement, node) {
12758 if(!config.showLabels) return;
12759 var pos = node.pos.getp(true),
12760 dimArray = node.getData('dimArray'),
12761 span = node.getData('span') / 2,
12762 theta = node.pos.theta,
12763 begin = theta - span,
12764 end = theta + span,
12765 polar = new Polar;
12766
12767 var showLabels = config.showLabels,
12768 resizeLabels = config.resizeLabels,
12769 label = config.Label;
12770
12771 if (dimArray) {
12772 for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
12773 acum += dimArray[i];
12774 }
12775 var scale = resizeLabels? node.getData('normalizedDim') : 1,
12776 fontSize = (label.size * scale) >> 0;
12777 fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
12778 domElement.style.fontSize = fontSize + 'px';
12779 polar.rho = acum + config.labelOffset + config.sliceOffset;
12780 polar.theta = (begin + end) / 2;
12781 var pos = polar.getc(true);
12782 var radius = that.canvas.getSize();
12783 var labelPos = {
12784 x: Math.round(pos.x + radius.width / 2),
12785 y: Math.round(pos.y + radius.height / 2)
12786 };
12787 domElement.style.left = labelPos.x + 'px';
12788 domElement.style.top = labelPos.y + 'px';
12789 }
12790 }
12791 });
12792
12793 var size = delegate.canvas.getSize(),
12794 min = Math.min;
12795 delegate.config.levelDistance = min(size.width, size.height)/2
12796 - config.offset - config.sliceOffset;
12797 this.delegate = delegate;
12798 this.canvas = this.delegate.canvas;
12799 this.canvas.getCtx().globalCompositeOperation = 'lighter';
12800 },
12801
12802 /*
12803 Method: loadJSON
12804
12805 Loads JSON data into the visualization.
12806
12807 Parameters:
12808
12809 json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
12810
12811 Example:
12812 (start code js)
12813 var pieChart = new $jit.PieChart(options);
12814 pieChart.loadJSON(json);
12815 (end code)
12816 */
12817 loadJSON: function(json) {
12818 var prefix = $.time(),
12819 ch = [],
12820 delegate = this.delegate,
12821 name = $.splat(json.label),
12822 nameLength = name.length,
12823 color = $.splat(json.color || this.colors),
12824 colorLength = color.length,
12825 config = this.config,
12826 gradient = !!config.type.split(":")[1],
12827 animate = config.animate,
12828 mono = nameLength == 1;
12829
12830 for(var i=0, values=json.values, l=values.length; i<l; i++) {
12831 var val = values[i];
12832 var valArray = $.splat(val.values);
12833 ch.push({
12834 'id': prefix + val.label,
12835 'name': val.label,
12836 'data': {
12837 'value': valArray,
12838 '$valueArray': valArray,
12839 '$colorArray': mono? $.splat(color[i % colorLength]) : color,
12840 '$stringArray': name,
12841 '$gradient': gradient,
12842 '$config': config,
12843 '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
12844 },
12845 'children': []
12846 });
12847 }
12848 var root = {
12849 'id': prefix + '$root',
12850 'name': '',
12851 'data': {
12852 '$type': 'none',
12853 '$width': 1,
12854 '$height': 1
12855 },
12856 'children': ch
12857 };
12858 delegate.loadJSON(root);
12859
12860 this.normalizeDims();
12861 delegate.refresh();
12862 if(animate) {
12863 delegate.fx.animate({
12864 modes: ['node-property:dimArray'],
12865 duration:1500
12866 });
12867 }
12868 },
12869
12870 /*
12871 Method: updateJSON
12872
12873 Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
12874
12875 Parameters:
12876
12877 json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
12878 onComplete - (object) A callback object to be called when the animation transition when updating the data end.
12879
12880 Example:
12881
12882 (start code js)
12883 pieChart.updateJSON(json, {
12884 onComplete: function() {
12885 alert('update complete!');
12886 }
12887 });
12888 (end code)
12889 */
12890 updateJSON: function(json, onComplete) {
12891 if(this.busy) return;
12892 this.busy = true;
12893
12894 var delegate = this.delegate;
12895 var graph = delegate.graph;
12896 var values = json.values;
12897 var animate = this.config.animate;
12898 var that = this;
12899 $.each(values, function(v) {
12900 var n = graph.getByName(v.label),
12901 vals = $.splat(v.values);
12902 if(n) {
12903 n.setData('valueArray', vals);
12904 n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
12905 if(json.label) {
12906 n.setData('stringArray', $.splat(json.label));
12907 }
12908 }
12909 });
12910 this.normalizeDims();
12911 if(animate) {
12912 delegate.compute('end');
12913 delegate.fx.animate({
12914 modes: ['node-property:dimArray:span', 'linear'],
12915 duration:1500,
12916 onComplete: function() {
12917 that.busy = false;
12918 onComplete && onComplete.onComplete();
12919 }
12920 });
12921 } else {
12922 delegate.refresh();
12923 }
12924 },
12925
12926 //adds the little brown bar when hovering the node
12927 select: function(id, name) {
12928 if(!this.config.hoveredColor) return;
12929 var s = this.selected;
12930 if(s.id != id || s.name != name) {
12931 s.id = id;
12932 s.name = name;
12933 s.color = this.config.hoveredColor;
12934 this.delegate.graph.eachNode(function(n) {
12935 if(id == n.id) {
12936 n.setData('border', s);
12937 } else {
12938 n.setData('border', false);
12939 }
12940 });
12941 this.delegate.plot();
12942 }
12943 },
12944
12945 /*
12946 Method: getLegend
12947
12948 Returns an object containing as keys the legend names and as values hex strings with color values.
12949
12950 Example:
12951
12952 (start code js)
12953 var legend = pieChart.getLegend();
12954 (end code)
12955 */
12956 getLegend: function() {
12957 var legend = {};
12958 var n;
12959 this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
12960 n = adj.nodeTo;
12961 });
12962 var colors = n.getData('colorArray'),
12963 len = colors.length;
12964 $.each(n.getData('stringArray'), function(s, i) {
12965 legend[s] = colors[i % len];
12966 });
12967 return legend;
12968 },
12969
12970 /*
12971 Method: getMaxValue
12972
12973 Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
12974
12975 Example:
12976
12977 (start code js)
12978 var ans = pieChart.getMaxValue();
12979 (end code)
12980
12981 In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
12982
12983 Example:
12984
12985 (start code js)
12986 //will return 100 for all PieChart instances,
12987 //displaying all of them with the same scale
12988 $jit.PieChart.implement({
12989 'getMaxValue': function() {
12990 return 100;
12991 }
12992 });
12993 (end code)
12994
12995 */
12996 getMaxValue: function() {
12997 var maxValue = 0;
12998 this.delegate.graph.eachNode(function(n) {
12999 var valArray = n.getData('valueArray'),
13000 acum = 0;
13001 $.each(valArray, function(v) {
13002 acum += +v;
13003 });
13004 maxValue = maxValue>acum? maxValue:acum;
13005 });
13006 return maxValue;
13007 },
13008
13009 normalizeDims: function() {
13010 //number of elements
13011 var root = this.delegate.graph.getNode(this.delegate.root), l=0;
13012 root.eachAdjacency(function() {
13013 l++;
13014 });
13015 var maxValue = this.getMaxValue() || 1,
13016 config = this.config,
13017 animate = config.animate,
13018 rho = this.delegate.config.levelDistance;
13019 this.delegate.graph.eachNode(function(n) {
13020 var acum = 0, animateValue = [];
13021 $.each(n.getData('valueArray'), function(v) {
13022 acum += +v;
13023 animateValue.push(1);
13024 });
13025 var stat = (animateValue.length == 1) && !config.updateHeights;
13026 if(animate) {
13027 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13028 return stat? rho: (n * rho / maxValue);
13029 }), 'end');
13030 var dimArray = n.getData('dimArray');
13031 if(!dimArray) {
13032 n.setData('dimArray', animateValue);
13033 }
13034 } else {
13035 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) {
13036 return stat? rho : (n * rho / maxValue);
13037 }));
13038 }
13039 n.setData('normalizedDim', acum / maxValue);
13040 });
13041 }
13042});
13043
13044
13045/*
13046 * Class: Layouts.TM
13047 *
13048 * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
13049 *
13050 * Implemented By:
13051 *
13052 * <TM>
13053 *
13054 */
13055Layouts.TM = {};
13056
13057Layouts.TM.SliceAndDice = new Class({
13058 compute: function(prop) {
13059 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
13060 this.controller.onBeforeCompute(root);
13061 var size = this.canvas.getSize(),
13062 config = this.config,
13063 width = size.width,
13064 height = size.height;
13065 this.graph.computeLevels(this.root, 0, "ignore");
13066 //set root position and dimensions
13067 root.getPos(prop).setc(-width/2, -height/2);
13068 root.setData('width', width, prop);
13069 root.setData('height', height + config.titleHeight, prop);
13070 this.computePositions(root, root, this.layout.orientation, prop);
13071 this.controller.onAfterCompute(root);
13072 },
13073
13074 computePositions: function(par, ch, orn, prop) {
13075 //compute children areas
13076 var totalArea = 0;
13077 par.eachSubnode(function(n) {
13078 totalArea += n.getData('area', prop);
13079 });
13080
13081 var config = this.config,
13082 offst = config.offset,
13083 width = par.getData('width', prop),
13084 height = Math.max(par.getData('height', prop) - config.titleHeight, 0),
13085 fact = par == ch? 1 : (ch.getData('area', prop) / totalArea);
13086
13087 var otherSize, size, dim, pos, pos2, posth, pos2th;
13088 var horizontal = (orn == "h");
13089 if(horizontal) {
13090 orn = 'v';
13091 otherSize = height;
13092 size = width * fact;
13093 dim = 'height';
13094 pos = 'y';
13095 pos2 = 'x';
13096 posth = config.titleHeight;
13097 pos2th = 0;
13098 } else {
13099 orn = 'h';
13100 otherSize = height * fact;
13101 size = width;
13102 dim = 'width';
13103 pos = 'x';
13104 pos2 = 'y';
13105 posth = 0;
13106 pos2th = config.titleHeight;
13107 }
13108 var cpos = ch.getPos(prop);
13109 ch.setData('width', size, prop);
13110 ch.setData('height', otherSize, prop);
13111 var offsetSize = 0, tm = this;
13112 ch.eachSubnode(function(n) {
13113 var p = n.getPos(prop);
13114 p[pos] = offsetSize + cpos[pos] + posth;
13115 p[pos2] = cpos[pos2] + pos2th;
13116 tm.computePositions(ch, n, orn, prop);
13117 offsetSize += n.getData(dim, prop);
13118 });
13119 }
13120
13121});
13122
13123Layouts.TM.Area = {
13124 /*
13125 Method: compute
13126
13127 Called by loadJSON to calculate recursively all node positions and lay out the tree.
13128
13129 Parameters:
13130
13131 json - A JSON tree. See also <Loader.loadJSON>.
13132 coord - A coordinates object specifying width, height, left and top style properties.
13133 */
13134 compute: function(prop) {
13135 prop = prop || "current";
13136 var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
13137 this.controller.onBeforeCompute(root);
13138 var config = this.config,
13139 size = this.canvas.getSize(),
13140 width = size.width,
13141 height = size.height,
13142 offst = config.offset,
13143 offwdth = width - offst,
13144 offhght = height - offst;
13145 this.graph.computeLevels(this.root, 0, "ignore");
13146 //set root position and dimensions
13147 root.getPos(prop).setc(-width/2, -height/2);
13148 root.setData('width', width, prop);
13149 root.setData('height', height, prop);
13150 //create a coordinates object
13151 var coord = {
13152 'top': -height/2 + config.titleHeight,
13153 'left': -width/2,
13154 'width': offwdth,
13155 'height': offhght - config.titleHeight
13156 };
13157 this.computePositions(root, coord, prop);
13158 this.controller.onAfterCompute(root);
13159 },
13160
13161 /*
13162 Method: computeDim
13163
13164 Computes dimensions and positions of a group of nodes
13165 according to a custom layout row condition.
13166
13167 Parameters:
13168
13169 tail - An array of nodes.
13170 initElem - An array of nodes (containing the initial node to be laid).
13171 w - A fixed dimension where nodes will be layed out.
13172 coord - A coordinates object specifying width, height, left and top style properties.
13173 comp - A custom comparison function
13174 */
13175 computeDim: function(tail, initElem, w, coord, comp, prop) {
13176 if(tail.length + initElem.length == 1) {
13177 var l = (tail.length == 1)? tail : initElem;
13178 this.layoutLast(l, w, coord, prop);
13179 return;
13180 }
13181 if(tail.length >= 2 && initElem.length == 0) {
13182 initElem = [tail.shift()];
13183 }
13184 if(tail.length == 0) {
13185 if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
13186 return;
13187 }
13188 var c = tail[0];
13189 if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
13190 this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
13191 } else {
13192 var newCoords = this.layoutRow(initElem, w, coord, prop);
13193 this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
13194 }
13195 },
13196
13197
13198 /*
13199 Method: worstAspectRatio
13200
13201 Calculates the worst aspect ratio of a group of rectangles.
13202
13203 See also:
13204
13205 <http://en.wikipedia.org/wiki/Aspect_ratio>
13206
13207 Parameters:
13208
13209 ch - An array of nodes.
13210 w - The fixed dimension where rectangles are being laid out.
13211
13212 Returns:
13213
13214 The worst aspect ratio.
13215
13216
13217 */
13218 worstAspectRatio: function(ch, w) {
13219 if(!ch || ch.length == 0) return Number.MAX_VALUE;
13220 var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
13221 for(var i=0, l=ch.length; i<l; i++) {
13222 var area = ch[i]._area;
13223 areaSum += area;
13224 minArea = minArea < area? minArea : area;
13225 maxArea = maxArea > area? maxArea : area;
13226 }
13227 var sqw = w * w, sqAreaSum = areaSum * areaSum;
13228 return Math.max(sqw * maxArea / sqAreaSum,
13229 sqAreaSum / (sqw * minArea));
13230 },
13231
13232 /*
13233 Method: avgAspectRatio
13234
13235 Calculates the average aspect ratio of a group of rectangles.
13236
13237 See also:
13238
13239 <http://en.wikipedia.org/wiki/Aspect_ratio>
13240
13241 Parameters:
13242
13243 ch - An array of nodes.
13244 w - The fixed dimension where rectangles are being laid out.
13245
13246 Returns:
13247
13248 The average aspect ratio.
13249
13250
13251 */
13252 avgAspectRatio: function(ch, w) {
13253 if(!ch || ch.length == 0) return Number.MAX_VALUE;
13254 var arSum = 0;
13255 for(var i=0, l=ch.length; i<l; i++) {
13256 var area = ch[i]._area;
13257 var h = area / w;
13258 arSum += w > h? w / h : h / w;
13259 }
13260 return arSum / l;
13261 },
13262
13263 /*
13264 layoutLast
13265
13266 Performs the layout of the last computed sibling.
13267
13268 Parameters:
13269
13270 ch - An array of nodes.
13271 w - A fixed dimension where nodes will be layed out.
13272 coord - A coordinates object specifying width, height, left and top style properties.
13273 */
13274 layoutLast: function(ch, w, coord, prop) {
13275 var child = ch[0];
13276 child.getPos(prop).setc(coord.left, coord.top);
13277 child.setData('width', coord.width, prop);
13278 child.setData('height', coord.height, prop);
13279 }
13280};
13281
13282
13283Layouts.TM.Squarified = new Class({
13284 Implements: Layouts.TM.Area,
13285
13286 computePositions: function(node, coord, prop) {
13287 var config = this.config,
13288 max = Math.max;
13289
13290 if (coord.width >= coord.height)
13291 this.layout.orientation = 'h';
13292 else
13293 this.layout.orientation = 'v';
13294
13295 var ch = node.getSubnodes([1, 1], "ignore");
13296 if(ch.length > 0) {
13297 this.processChildrenLayout(node, ch, coord, prop);
13298 for(var i=0, l=ch.length; i<l; i++) {
13299 var chi = ch[i],
13300 offst = config.offset,
13301 height = max(chi.getData('height', prop) - offst - config.titleHeight, 0),
13302 width = max(chi.getData('width', prop) - offst, 0),
13303 chipos = chi.getPos(prop);
13304
13305 coord = {
13306 'width': width,
13307 'height': height,
13308 'top': chipos.y + config.titleHeight,
13309 'left': chipos.x
13310 };
13311 this.computePositions(chi, coord, prop);
13312 }
13313 }
13314 },
13315
13316 /*
13317 Method: processChildrenLayout
13318
13319 Computes children real areas and other useful parameters for performing the Squarified algorithm.
13320
13321 Parameters:
13322
13323 par - The parent node of the json subtree.
13324 ch - An Array of nodes
13325 coord - A coordinates object specifying width, height, left and top style properties.
13326 */
13327 processChildrenLayout: function(par, ch, coord, prop) {
13328 //compute children real areas
13329 var parentArea = coord.width * coord.height;
13330 var i, l=ch.length, totalChArea=0, chArea = [];
13331 for(i=0; i<l; i++) {
13332 chArea[i] = parseFloat(ch[i].getData('area', prop));
13333 totalChArea += chArea[i];
13334 }
13335 for(i=0; i<l; i++) {
13336 ch[i]._area = parentArea * chArea[i] / totalChArea;
13337 }
13338 var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
13339 ch.sort(function(a, b) {
13340 var diff = b._area - a._area;
13341 return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1));
13342 });
13343 var initElem = [ch[0]];
13344 var tail = ch.slice(1);
13345 this.squarify(tail, initElem, minimumSideValue, coord, prop);
13346 },
13347
13348 /*
13349 Method: squarify
13350
13351 Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
13352
13353 Parameters:
13354
13355 tail - An array of nodes.
13356 initElem - An array of nodes, containing the initial node to be laid out.
13357 w - A fixed dimension where nodes will be laid out.
13358 coord - A coordinates object specifying width, height, left and top style properties.
13359 */
13360 squarify: function(tail, initElem, w, coord, prop) {
13361 this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
13362 },
13363
13364 /*
13365 Method: layoutRow
13366
13367 Performs the layout of an array of nodes.
13368
13369 Parameters:
13370
13371 ch - An array of nodes.
13372 w - A fixed dimension where nodes will be laid out.
13373 coord - A coordinates object specifying width, height, left and top style properties.
13374 */
13375 layoutRow: function(ch, w, coord, prop) {
13376 if(this.layout.horizontal()) {
13377 return this.layoutV(ch, w, coord, prop);
13378 } else {
13379 return this.layoutH(ch, w, coord, prop);
13380 }
13381 },
13382
13383 layoutV: function(ch, w, coord, prop) {
13384 var totalArea = 0, rnd = function(x) { return x; };
13385 $.each(ch, function(elem) { totalArea += elem._area; });
13386 var width = rnd(totalArea / w), top = 0;
13387 for(var i=0, l=ch.length; i<l; i++) {
13388 var h = rnd(ch[i]._area / width);
13389 var chi = ch[i];
13390 chi.getPos(prop).setc(coord.left, coord.top + top);
13391 chi.setData('width', width, prop);
13392 chi.setData('height', h, prop);
13393 top += h;
13394 }
13395 var ans = {
13396 'height': coord.height,
13397 'width': coord.width - width,
13398 'top': coord.top,
13399 'left': coord.left + width
13400 };
13401 //take minimum side value.
13402 ans.dim = Math.min(ans.width, ans.height);
13403 if(ans.dim != ans.height) this.layout.change();
13404 return ans;
13405 },
13406
13407 layoutH: function(ch, w, coord, prop) {
13408 var totalArea = 0;
13409 $.each(ch, function(elem) { totalArea += elem._area; });
13410 var height = totalArea / w,
13411 top = coord.top,
13412 left = 0;
13413
13414 for(var i=0, l=ch.length; i<l; i++) {
13415 var chi = ch[i];
13416 var w = chi._area / height;
13417 chi.getPos(prop).setc(coord.left + left, top);
13418 chi.setData('width', w, prop);
13419 chi.setData('height', height, prop);
13420 left += w;
13421 }
13422 var ans = {
13423 'height': coord.height - height,
13424 'width': coord.width,
13425 'top': coord.top + height,
13426 'left': coord.left
13427 };
13428 ans.dim = Math.min(ans.width, ans.height);
13429 if(ans.dim != ans.width) this.layout.change();
13430 return ans;
13431 }
13432});
13433
13434Layouts.TM.Strip = new Class({
13435 Implements: Layouts.TM.Area,
13436
13437 /*
13438 Method: compute
13439
13440 Called by loadJSON to calculate recursively all node positions and lay out the tree.
13441
13442 Parameters:
13443
13444 json - A JSON subtree. See also <Loader.loadJSON>.
13445 coord - A coordinates object specifying width, height, left and top style properties.
13446 */
13447 computePositions: function(node, coord, prop) {
13448 var ch = node.getSubnodes([1, 1], "ignore"),
13449 config = this.config,
13450 max = Math.max;
13451 if(ch.length > 0) {
13452 this.processChildrenLayout(node, ch, coord, prop);
13453 for(var i=0, l=ch.length; i<l; i++) {
13454 var chi = ch[i];
13455 var offst = config.offset,
13456 height = max(chi.getData('height', prop) - offst - config.titleHeight, 0),
13457 width = max(chi.getData('width', prop) - offst, 0);
13458 var chipos = chi.getPos(prop);
13459 coord = {
13460 'width': width,
13461 'height': height,
13462 'top': chipos.y + config.titleHeight,
13463 'left': chipos.x
13464 };
13465 this.computePositions(chi, coord, prop);
13466 }
13467 }
13468 },
13469
13470 /*
13471 Method: processChildrenLayout
13472
13473 Computes children real areas and other useful parameters for performing the Strip algorithm.
13474
13475 Parameters:
13476
13477 par - The parent node of the json subtree.
13478 ch - An Array of nodes
13479 coord - A coordinates object specifying width, height, left and top style properties.
13480 */
13481 processChildrenLayout: function(par, ch, coord, prop) {
13482 //compute children real areas
13483 var parentArea = coord.width * coord.height;
13484 var i, l=ch.length, totalChArea=0, chArea = [];
13485 for(i=0; i<l; i++) {
13486 chArea[i] = +ch[i].getData('area', prop);
13487 totalChArea += chArea[i];
13488 }
13489 for(i=0; i<l; i++) {
13490 ch[i]._area = parentArea * chArea[i] / totalChArea;
13491 }
13492 var side = this.layout.horizontal()? coord.width : coord.height;
13493 var initElem = [ch[0]];
13494 var tail = ch.slice(1);
13495 this.stripify(tail, initElem, side, coord, prop);
13496 },
13497
13498 /*
13499 Method: stripify
13500
13501 Performs an heuristic method to calculate div elements sizes in order to have
13502 a good compromise between aspect ratio and order.
13503
13504 Parameters:
13505
13506 tail - An array of nodes.
13507 initElem - An array of nodes.
13508 w - A fixed dimension where nodes will be layed out.
13509 coord - A coordinates object specifying width, height, left and top style properties.
13510 */
13511 stripify: function(tail, initElem, w, coord, prop) {
13512 this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
13513 },
13514
13515 /*
13516 Method: layoutRow
13517
13518 Performs the layout of an array of nodes.
13519
13520 Parameters:
13521
13522 ch - An array of nodes.
13523 w - A fixed dimension where nodes will be laid out.
13524 coord - A coordinates object specifying width, height, left and top style properties.
13525 */
13526 layoutRow: function(ch, w, coord, prop) {
13527 if(this.layout.horizontal()) {
13528 return this.layoutH(ch, w, coord, prop);
13529 } else {
13530 return this.layoutV(ch, w, coord, prop);
13531 }
13532 },
13533
13534 layoutV: function(ch, w, coord, prop) {
13535 var totalArea = 0;
13536 $.each(ch, function(elem) { totalArea += elem._area; });
13537 var width = totalArea / w, top = 0;
13538 for(var i=0, l=ch.length; i<l; i++) {
13539 var chi = ch[i];
13540 var h = chi._area / width;
13541 chi.getPos(prop).setc(coord.left,
13542 coord.top + (w - h - top));
13543 chi.setData('width', width, prop);
13544 chi.setData('height', h, prop);
13545 top += h;
13546 }
13547
13548 return {
13549 'height': coord.height,
13550 'width': coord.width - width,
13551 'top': coord.top,
13552 'left': coord.left + width,
13553 'dim': w
13554 };
13555 },
13556
13557 layoutH: function(ch, w, coord, prop) {
13558 var totalArea = 0;
13559 $.each(ch, function(elem) { totalArea += elem._area; });
13560 var height = totalArea / w,
13561 top = coord.height - height,
13562 left = 0;
13563
13564 for(var i=0, l=ch.length; i<l; i++) {
13565 var chi = ch[i];
13566 var s = chi._area / height;
13567 chi.getPos(prop).setc(coord.left + left, coord.top + top);
13568 chi.setData('width', s, prop);
13569 chi.setData('height', height, prop);
13570 left += s;
13571 }
13572 return {
13573 'height': coord.height - height,
13574 'width': coord.width,
13575 'top': coord.top,
13576 'left': coord.left,
13577 'dim': w
13578 };
13579 }
13580 });
13581
13582
13583/*
13584 * Class: Layouts.Icicle
13585 *
13586 * Implements the icicle tree layout.
13587 *
13588 * Implemented By:
13589 *
13590 * <Icicle>
13591 *
13592 */
13593
13594Layouts.Icicle = new Class({
13595 /*
13596 * Method: compute
13597 *
13598 * Called by loadJSON to calculate all node positions.
13599 *
13600 * Parameters:
13601 *
13602 * posType - The nodes' position to compute. Either "start", "end" or
13603 * "current". Defaults to "current".
13604 */
13605 compute: function(posType) {
13606 posType = posType || "current";
13607
13608 var root = this.graph.getNode(this.root),
13609 config = this.config,
13610 size = this.canvas.getSize(),
13611 width = size.width,
13612 height = size.height,
13613 offset = config.offset,
13614 levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
13615
13616 this.controller.onBeforeCompute(root);
13617
13618 Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
13619
13620 var treeDepth = 0;
13621
13622 Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
13623
13624 var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
13625 var maxDepth = Math.min(treeDepth, levelsToShow-1);
13626 var initialDepth = startNode._depth;
13627 if(this.layout.horizontal()) {
13628 this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
13629 } else {
13630 this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
13631 }
13632 },
13633
13634 computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
13635 root.getPos(posType).setc(x, y);
13636 root.setData('width', width, posType);
13637 root.setData('height', height, posType);
13638
13639 var nodeLength, prevNodeLength = 0, totalDim = 0;
13640 var children = Graph.Util.getSubnodes(root, [1, 1], 'ignore'); // next level from this node
13641
13642 if(!children.length)
13643 return;
13644
13645 $.each(children, function(e) { totalDim += e.getData('dim'); });
13646
13647 for(var i=0, l=children.length; i < l; i++) {
13648 if(this.layout.horizontal()) {
13649 nodeLength = height * children[i].getData('dim') / totalDim;
13650 this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
13651 y += nodeLength;
13652 } else {
13653 nodeLength = width * children[i].getData('dim') / totalDim;
13654 this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
13655 x += nodeLength;
13656 }
13657 }
13658 }
13659});
13660
13661
13662
13663/*
13664 * File: Icicle.js
13665 *
13666*/
13667
13668/*
13669 Class: Icicle
13670
13671 Icicle space filling visualization.
13672
13673 Implements:
13674
13675 All <Loader> methods
13676
13677 Constructor Options:
13678
13679 Inherits options from
13680
13681 - <Options.Canvas>
13682 - <Options.Controller>
13683 - <Options.Node>
13684 - <Options.Edge>
13685 - <Options.Label>
13686 - <Options.Events>
13687 - <Options.Tips>
13688 - <Options.NodeStyles>
13689 - <Options.Navigation>
13690
13691 Additionally, there are other parameters and some default values changed
13692
13693 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
13694 offset - (number) Default's *2*. Boxes offset.
13695 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
13696 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
13697 animate - (boolean) Default's *false*. Whether to animate transitions.
13698 Node.type - Described in <Options.Node>. Default's *rectangle*.
13699 Label.type - Described in <Options.Label>. Default's *Native*.
13700 duration - Described in <Options.Fx>. Default's *700*.
13701 fps - Described in <Options.Fx>. Default's *45*.
13702
13703 Instance Properties:
13704
13705 canvas - Access a <Canvas> instance.
13706 graph - Access a <Graph> instance.
13707 op - Access a <Icicle.Op> instance.
13708 fx - Access a <Icicle.Plot> instance.
13709 labels - Access a <Icicle.Label> interface implementation.
13710
13711*/
13712
13713$jit.Icicle = new Class({
13714 Implements: [ Loader, Extras, Layouts.Icicle ],
13715
13716 layout: {
13717 orientation: "h",
13718 vertical: function(){
13719 return this.orientation == "v";
13720 },
13721 horizontal: function(){
13722 return this.orientation == "h";
13723 },
13724 change: function(){
13725 this.orientation = this.vertical()? "h" : "v";
13726 }
13727 },
13728
13729 initialize: function(controller) {
13730 var config = {
13731 animate: false,
13732 orientation: "h",
13733 offset: 2,
13734 levelsToShow: Number.MAX_VALUE,
13735 constrained: false,
13736 Node: {
13737 type: 'rectangle',
13738 overridable: true
13739 },
13740 Edge: {
13741 type: 'none'
13742 },
13743 Label: {
13744 type: 'Native'
13745 },
13746 duration: 700,
13747 fps: 45
13748 };
13749
13750 var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
13751 "Events", "Navigation", "Controller", "Label");
13752 this.controller = this.config = $.merge(opts, config, controller);
13753 this.layout.orientation = this.config.orientation;
13754
13755 var canvasConfig = this.config;
13756 if (canvasConfig.useCanvas) {
13757 this.canvas = canvasConfig.useCanvas;
13758 this.config.labelContainer = this.canvas.id + '-label';
13759 } else {
13760 this.canvas = new Canvas(this, canvasConfig);
13761 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
13762 }
13763
13764 this.graphOptions = {
13765 'klass': Complex,
13766 'Node': {
13767 'selected': false,
13768 'exist': true,
13769 'drawn': true
13770 }
13771 };
13772
13773 this.graph = new Graph(
13774 this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
13775
13776 this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
13777 this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
13778 this.op = new $jit.Icicle.Op(this);
13779 this.group = new $jit.Icicle.Group(this);
13780 this.clickedNode = null;
13781
13782 this.initializeExtras();
13783 },
13784
13785 /*
13786 Method: refresh
13787
13788 Computes positions and plots the tree.
13789 */
13790 refresh: function(){
13791 var labelType = this.config.Label.type;
13792 if(labelType != 'Native') {
13793 var that = this;
13794 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
13795 }
13796 this.compute();
13797 this.plot();
13798 },
13799
13800 /*
13801 Method: plot
13802
13803 Plots the Icicle visualization. This is a shortcut to *fx.plot*.
13804
13805 */
13806 plot: function(){
13807 this.fx.plot(this.config);
13808 },
13809
13810 /*
13811 Method: enter
13812
13813 Sets the node as root.
13814
13815 Parameters:
13816
13817 node - (object) A <Graph.Node>.
13818
13819 */
13820 enter: function (node) {
13821 if (this.busy)
13822 return;
13823 this.busy = true;
13824
13825 var that = this,
13826 config = this.config;
13827
13828 var callback = {
13829 onComplete: function() {
13830 //compute positions of newly inserted nodes
13831 if(config.request)
13832 that.compute();
13833
13834 if(config.animate) {
13835 that.graph.nodeList.setDataset(['current', 'end'], {
13836 'alpha': [1, 0] //fade nodes
13837 });
13838
13839 Graph.Util.eachSubgraph(node, function(n) {
13840 n.setData('alpha', 1, 'end');
13841 }, "ignore");
13842
13843 that.fx.animate({
13844 duration: 500,
13845 modes:['node-property:alpha'],
13846 onComplete: function() {
13847 that.clickedNode = node;
13848 that.compute('end');
13849
13850 that.fx.animate({
13851 modes:['linear', 'node-property:width:height'],
13852 duration: 1000,
13853 onComplete: function() {
13854 that.busy = false;
13855 that.clickedNode = node;
13856 }
13857 });
13858 }
13859 });
13860 } else {
13861 that.clickedNode = node;
13862 that.busy = false;
13863 that.refresh();
13864 }
13865 }
13866 };
13867
13868 if(config.request) {
13869 this.requestNodes(clickedNode, callback);
13870 } else {
13871 callback.onComplete();
13872 }
13873 },
13874
13875 /*
13876 Method: out
13877
13878 Sets the parent node of the current selected node as root.
13879
13880 */
13881 out: function(){
13882 if(this.busy)
13883 return;
13884
13885 var that = this,
13886 GUtil = Graph.Util,
13887 config = this.config,
13888 graph = this.graph,
13889 parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
13890 parent = parents[0],
13891 clickedNode = parent,
13892 previousClickedNode = this.clickedNode;
13893
13894 this.busy = true;
13895 this.events.hoveredNode = false;
13896
13897 if(!parent) {
13898 this.busy = false;
13899 return;
13900 }
13901
13902 //final plot callback
13903 callback = {
13904 onComplete: function() {
13905 that.clickedNode = parent;
13906 if(config.request) {
13907 that.requestNodes(parent, {
13908 onComplete: function() {
13909 that.compute();
13910 that.plot();
13911 that.busy = false;
13912 }
13913 });
13914 } else {
13915 that.compute();
13916 that.plot();
13917 that.busy = false;
13918 }
13919 }
13920 };
13921
13922 //animate node positions
13923 if(config.animate) {
13924 this.clickedNode = clickedNode;
13925 this.compute('end');
13926 //animate the visible subtree only
13927 this.clickedNode = previousClickedNode;
13928 this.fx.animate({
13929 modes:['linear', 'node-property:width:height'],
13930 duration: 1000,
13931 onComplete: function() {
13932 //animate the parent subtree
13933 that.clickedNode = clickedNode;
13934 //change nodes alpha
13935 graph.nodeList.setDataset(['current', 'end'], {
13936 'alpha': [0, 1]
13937 });
13938 GUtil.eachSubgraph(previousClickedNode, function(node) {
13939 node.setData('alpha', 1);
13940 }, "ignore");
13941 that.fx.animate({
13942 duration: 500,
13943 modes:['node-property:alpha'],
13944 onComplete: function() {
13945 callback.onComplete();
13946 }
13947 });
13948 }
13949 });
13950 } else {
13951 callback.onComplete();
13952 }
13953 },
13954 requestNodes: function(node, onComplete){
13955 var handler = $.merge(this.controller, onComplete),
13956 levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
13957
13958 if (handler.request) {
13959 var leaves = [], d = node._depth;
13960 Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
13961 if (n.drawn && !Graph.Util.anySubnode(n)) {
13962 leaves.push(n);
13963 n._level = n._depth - d;
13964 if (this.config.constrained)
13965 n._level = levelsToShow - n._level;
13966
13967 }
13968 });
13969 this.group.requestNodes(leaves, handler);
13970 } else {
13971 handler.onComplete();
13972 }
13973 }
13974});
13975
13976/*
13977 Class: Icicle.Op
13978
13979 Custom extension of <Graph.Op>.
13980
13981 Extends:
13982
13983 All <Graph.Op> methods
13984
13985 See also:
13986
13987 <Graph.Op>
13988
13989 */
13990$jit.Icicle.Op = new Class({
13991
13992 Implements: Graph.Op
13993
13994});
13995
13996/*
13997 * Performs operations on group of nodes.
13998 */
13999$jit.Icicle.Group = new Class({
14000
14001 initialize: function(viz){
14002 this.viz = viz;
14003 this.canvas = viz.canvas;
14004 this.config = viz.config;
14005 },
14006
14007 /*
14008 * Calls the request method on the controller to request a subtree for each node.
14009 */
14010 requestNodes: function(nodes, controller){
14011 var counter = 0, len = nodes.length, nodeSelected = {};
14012 var complete = function(){
14013 controller.onComplete();
14014 };
14015 var viz = this.viz;
14016 if (len == 0)
14017 complete();
14018 for(var i = 0; i < len; i++) {
14019 nodeSelected[nodes[i].id] = nodes[i];
14020 controller.request(nodes[i].id, nodes[i]._level, {
14021 onComplete: function(nodeId, data){
14022 if (data && data.children) {
14023 data.id = nodeId;
14024 viz.op.sum(data, {
14025 type: 'nothing'
14026 });
14027 }
14028 if (++counter == len) {
14029 Graph.Util.computeLevels(viz.graph, viz.root, 0);
14030 complete();
14031 }
14032 }
14033 });
14034 }
14035 }
14036});
14037
14038/*
14039 Class: Icicle.Plot
14040
14041 Custom extension of <Graph.Plot>.
14042
14043 Extends:
14044
14045 All <Graph.Plot> methods
14046
14047 See also:
14048
14049 <Graph.Plot>
14050
14051 */
14052$jit.Icicle.Plot = new Class({
14053 Implements: Graph.Plot,
14054
14055 plot: function(opt, animating){
14056 opt = opt || this.viz.controller;
14057 var viz = this.viz,
14058 graph = viz.graph,
14059 root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
14060 initialDepth = root._depth;
14061
14062 viz.canvas.clear();
14063 this.plotTree(root, $.merge(opt, {
14064 'withLabels': true,
14065 'hideLabels': false,
14066 'plotSubtree': function(root, node) {
14067 return !viz.config.constrained ||
14068 (node._depth - initialDepth < viz.config.levelsToShow);
14069 }
14070 }), animating);
14071 }
14072});
14073
14074/*
14075 Class: Icicle.Label
14076
14077 Custom extension of <Graph.Label>.
14078 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14079
14080 Extends:
14081
14082 All <Graph.Label> methods and subclasses.
14083
14084 See also:
14085
14086 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14087
14088 */
14089$jit.Icicle.Label = {};
14090
14091/*
14092 Icicle.Label.Native
14093
14094 Custom extension of <Graph.Label.Native>.
14095
14096 Extends:
14097
14098 All <Graph.Label.Native> methods
14099
14100 See also:
14101
14102 <Graph.Label.Native>
14103
14104 */
14105$jit.Icicle.Label.Native = new Class({
14106 Implements: Graph.Label.Native,
14107
14108 renderLabel: function(canvas, node, controller) {
14109 var ctx = canvas.getCtx(),
14110 width = node.getData('width'),
14111 height = node.getData('height'),
14112 size = node.getLabelData('size'),
14113 m = ctx.measureText(node.name);
14114
14115 // Guess as much as possible if the label will fit in the node
14116 if(height < (size * 1.5) || width < m.width)
14117 return;
14118
14119 var pos = node.pos.getc(true);
14120 ctx.fillText(node.name,
14121 pos.x + width / 2,
14122 pos.y + height / 2);
14123 }
14124});
14125
14126/*
14127 Icicle.Label.SVG
14128
14129 Custom extension of <Graph.Label.SVG>.
14130
14131 Extends:
14132
14133 All <Graph.Label.SVG> methods
14134
14135 See also:
14136
14137 <Graph.Label.SVG>
14138*/
14139$jit.Icicle.Label.SVG = new Class( {
14140 Implements: Graph.Label.SVG,
14141
14142 initialize: function(viz){
14143 this.viz = viz;
14144 },
14145
14146 /*
14147 placeLabel
14148
14149 Overrides abstract method placeLabel in <Graph.Plot>.
14150
14151 Parameters:
14152
14153 tag - A DOM label element.
14154 node - A <Graph.Node>.
14155 controller - A configuration/controller object passed to the visualization.
14156 */
14157 placeLabel: function(tag, node, controller){
14158 var pos = node.pos.getc(true), canvas = this.viz.canvas;
14159 var radius = canvas.getSize();
14160 var labelPos = {
14161 x: Math.round(pos.x + radius.width / 2),
14162 y: Math.round(pos.y + radius.height / 2)
14163 };
14164 tag.setAttribute('x', labelPos.x);
14165 tag.setAttribute('y', labelPos.y);
14166
14167 controller.onPlaceLabel(tag, node);
14168 }
14169});
14170
14171/*
14172 Icicle.Label.HTML
14173
14174 Custom extension of <Graph.Label.HTML>.
14175
14176 Extends:
14177
14178 All <Graph.Label.HTML> methods.
14179
14180 See also:
14181
14182 <Graph.Label.HTML>
14183
14184 */
14185$jit.Icicle.Label.HTML = new Class( {
14186 Implements: Graph.Label.HTML,
14187
14188 initialize: function(viz){
14189 this.viz = viz;
14190 },
14191
14192 /*
14193 placeLabel
14194
14195 Overrides abstract method placeLabel in <Graph.Plot>.
14196
14197 Parameters:
14198
14199 tag - A DOM label element.
14200 node - A <Graph.Node>.
14201 controller - A configuration/controller object passed to the visualization.
14202 */
14203 placeLabel: function(tag, node, controller){
14204 var pos = node.pos.getc(true), canvas = this.viz.canvas;
14205 var radius = canvas.getSize();
14206 var labelPos = {
14207 x: Math.round(pos.x + radius.width / 2),
14208 y: Math.round(pos.y + radius.height / 2)
14209 };
14210
14211 var style = tag.style;
14212 style.left = labelPos.x + 'px';
14213 style.top = labelPos.y + 'px';
14214 style.display = '';
14215
14216 controller.onPlaceLabel(tag, node);
14217 }
14218});
14219
14220/*
14221 Class: Icicle.Plot.NodeTypes
14222
14223 This class contains a list of <Graph.Node> built-in types.
14224 Node types implemented are 'none', 'rectangle'.
14225
14226 You can add your custom node types, customizing your visualization to the extreme.
14227
14228 Example:
14229
14230 (start code js)
14231 Icicle.Plot.NodeTypes.implement({
14232 'mySpecialType': {
14233 'render': function(node, canvas) {
14234 //print your custom node to canvas
14235 },
14236 //optional
14237 'contains': function(node, pos) {
14238 //return true if pos is inside the node or false otherwise
14239 }
14240 }
14241 });
14242 (end code)
14243
14244 */
14245$jit.Icicle.Plot.NodeTypes = new Class( {
14246 'none': {
14247 'render': $.empty
14248 },
14249
14250 'rectangle': {
14251 'render': function(node, canvas, animating) {
14252 var config = this.viz.config;
14253 var offset = config.offset;
14254 var width = node.getData('width');
14255 var height = node.getData('height');
14256 var border = node.getData('border');
14257 var pos = node.pos.getc(true);
14258 var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
14259 var ctx = canvas.getCtx();
14260
14261 if(width - offset < 2 || height - offset < 2) return;
14262
14263 if(config.cushion) {
14264 var color = node.getData('color');
14265 var lg = ctx.createRadialGradient(posx + (width - offset)/2,
14266 posy + (height - offset)/2, 1,
14267 posx + (width-offset)/2, posy + (height-offset)/2,
14268 width < height? height : width);
14269 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
14270 function(r) { return r * 0.3 >> 0; }));
14271 lg.addColorStop(0, color);
14272 lg.addColorStop(1, colorGrad);
14273 ctx.fillStyle = lg;
14274 }
14275
14276 if (border) {
14277 ctx.strokeStyle = border;
14278 ctx.lineWidth = 3;
14279 }
14280
14281 ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
14282 border && ctx.strokeRect(pos.x, pos.y, width, height);
14283 },
14284
14285 'contains': function(node, pos) {
14286 if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
14287 var npos = node.pos.getc(true),
14288 width = node.getData('width'),
14289 height = node.getData('height');
14290 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
14291 }
14292 }
14293});
14294
14295$jit.Icicle.Plot.EdgeTypes = new Class( {
14296 'none': $.empty
14297});
14298
14299
14300
14301/*
14302 * File: Layouts.ForceDirected.js
14303 *
14304*/
14305
14306/*
14307 * Class: Layouts.ForceDirected
14308 *
14309 * Implements a Force Directed Layout.
14310 *
14311 * Implemented By:
14312 *
14313 * <ForceDirected>
14314 *
14315 * Credits:
14316 *
14317 * Marcus Cobden <http://marcuscobden.co.uk>
14318 *
14319 */
14320Layouts.ForceDirected = new Class({
14321
14322 getOptions: function(random) {
14323 var s = this.canvas.getSize();
14324 var w = s.width, h = s.height;
14325 //count nodes
14326 var count = 0;
14327 this.graph.eachNode(function(n) {
14328 count++;
14329 });
14330 var k2 = w * h / count, k = Math.sqrt(k2);
14331 var l = this.config.levelDistance;
14332
14333 return {
14334 width: w,
14335 height: h,
14336 tstart: w * 0.1,
14337 nodef: function(x) { return k2 / (x || 1); },
14338 edgef: function(x) { return /* x * x / k; */ k * (x - l); }
14339 };
14340 },
14341
14342 compute: function(property, incremental) {
14343 var prop = $.splat(property || ['current', 'start', 'end']);
14344 var opt = this.getOptions();
14345 NodeDim.compute(this.graph, prop, this.config);
14346 this.graph.computeLevels(this.root, 0, "ignore");
14347 this.graph.eachNode(function(n) {
14348 $.each(prop, function(p) {
14349 var pos = n.getPos(p);
14350 if(pos.equals(Complex.KER)) {
14351 pos.x = opt.width/5 * (Math.random() - 0.5);
14352 pos.y = opt.height/5 * (Math.random() - 0.5);
14353 }
14354 //initialize disp vector
14355 n.disp = {};
14356 $.each(prop, function(p) {
14357 n.disp[p] = $C(0, 0);
14358 });
14359 });
14360 });
14361 this.computePositions(prop, opt, incremental);
14362 },
14363
14364 computePositions: function(property, opt, incremental) {
14365 var times = this.config.iterations, i = 0, that = this;
14366 if(incremental) {
14367 (function iter() {
14368 for(var total=incremental.iter, j=0; j<total; j++) {
14369 opt.t = opt.tstart;
14370 if(times) opt.t *= (1 - i++/(times -1));
14371 that.computePositionStep(property, opt);
14372 if(times && i >= times) {
14373 incremental.onComplete();
14374 return;
14375 }
14376 }
14377 incremental.onStep(Math.round(i / (times -1) * 100));
14378 setTimeout(iter, 1);
14379 })();
14380 } else {
14381 for(; i < times; i++) {
14382 opt.t = opt.tstart * (1 - i/(times -1));
14383 this.computePositionStep(property, opt);
14384 }
14385 }
14386 },
14387
14388 computePositionStep: function(property, opt) {
14389 var graph = this.graph;
14390 var min = Math.min, max = Math.max;
14391 var dpos = $C(0, 0);
14392 //calculate repulsive forces
14393 graph.eachNode(function(v) {
14394 //initialize disp
14395 $.each(property, function(p) {
14396 v.disp[p].x = 0; v.disp[p].y = 0;
14397 });
14398 graph.eachNode(function(u) {
14399 if(u.id != v.id) {
14400 $.each(property, function(p) {
14401 var vp = v.getPos(p), up = u.getPos(p);
14402 dpos.x = vp.x - up.x;
14403 dpos.y = vp.y - up.y;
14404 var norm = dpos.norm() || 1;
14405 v.disp[p].$add(dpos
14406 .$scale(opt.nodef(norm) / norm));
14407 });
14408 }
14409 });
14410 });
14411 //calculate attractive forces
14412 var T = !!graph.getNode(this.root).visited;
14413 graph.eachNode(function(node) {
14414 node.eachAdjacency(function(adj) {
14415 var nodeTo = adj.nodeTo;
14416 if(!!nodeTo.visited === T) {
14417 $.each(property, function(p) {
14418 var vp = node.getPos(p), up = nodeTo.getPos(p);
14419 dpos.x = vp.x - up.x;
14420 dpos.y = vp.y - up.y;
14421 var norm = dpos.norm() || 1;
14422 node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
14423 nodeTo.disp[p].$add(dpos.$scale(-1));
14424 });
14425 }
14426 });
14427 node.visited = !T;
14428 });
14429 //arrange positions to fit the canvas
14430 var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
14431 graph.eachNode(function(u) {
14432 $.each(property, function(p) {
14433 var disp = u.disp[p];
14434 var norm = disp.norm() || 1;
14435 var p = u.getPos(p);
14436 p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
14437 disp.y * min(Math.abs(disp.y), t) / norm));
14438 p.x = min(w2, max(-w2, p.x));
14439 p.y = min(h2, max(-h2, p.y));
14440 });
14441 });
14442 }
14443});
14444
14445/*
14446 * File: ForceDirected.js
14447 */
14448
14449/*
14450 Class: ForceDirected
14451
14452 A visualization that lays graphs using a Force-Directed layout algorithm.
14453
14454 Inspired by:
14455
14456 Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
14457
14458 Implements:
14459
14460 All <Loader> methods
14461
14462 Constructor Options:
14463
14464 Inherits options from
14465
14466 - <Options.Canvas>
14467 - <Options.Controller>
14468 - <Options.Node>
14469 - <Options.Edge>
14470 - <Options.Label>
14471 - <Options.Events>
14472 - <Options.Tips>
14473 - <Options.NodeStyles>
14474 - <Options.Navigation>
14475
14476 Additionally, there are two parameters
14477
14478 levelDistance - (number) Default's *50*. The natural length desired for the edges.
14479 iterations - (number) Default's *50*. The number of iterations for the spring layout simulation. Depending on the browser's speed you could set this to a more 'interesting' number, like *200*.
14480
14481 Instance Properties:
14482
14483 canvas - Access a <Canvas> instance.
14484 graph - Access a <Graph> instance.
14485 op - Access a <ForceDirected.Op> instance.
14486 fx - Access a <ForceDirected.Plot> instance.
14487 labels - Access a <ForceDirected.Label> interface implementation.
14488
14489*/
14490
14491$jit.ForceDirected = new Class( {
14492
14493 Implements: [ Loader, Extras, Layouts.ForceDirected ],
14494
14495 initialize: function(controller) {
14496 var $ForceDirected = $jit.ForceDirected;
14497
14498 var config = {
14499 iterations: 50,
14500 levelDistance: 50
14501 };
14502
14503 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14504 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14505
14506 var canvasConfig = this.config;
14507 if(canvasConfig.useCanvas) {
14508 this.canvas = canvasConfig.useCanvas;
14509 this.config.labelContainer = this.canvas.id + '-label';
14510 } else {
14511 if(canvasConfig.background) {
14512 canvasConfig.background = $.merge({
14513 type: 'Circles'
14514 }, canvasConfig.background);
14515 }
14516 this.canvas = new Canvas(this, canvasConfig);
14517 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14518 }
14519
14520 this.graphOptions = {
14521 'klass': Complex,
14522 'Node': {
14523 'selected': false,
14524 'exist': true,
14525 'drawn': true
14526 }
14527 };
14528 this.graph = new Graph(this.graphOptions, this.config.Node,
14529 this.config.Edge);
14530 this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
14531 this.fx = new $ForceDirected.Plot(this, $ForceDirected);
14532 this.op = new $ForceDirected.Op(this);
14533 this.json = null;
14534 this.busy = false;
14535 // initialize extras
14536 this.initializeExtras();
14537 },
14538
14539 /*
14540 Method: refresh
14541
14542 Computes positions and plots the tree.
14543 */
14544 refresh: function() {
14545 this.compute();
14546 this.plot();
14547 },
14548
14549 reposition: function() {
14550 this.compute('end');
14551 },
14552
14553/*
14554 Method: computeIncremental
14555
14556 Performs the Force Directed algorithm incrementally.
14557
14558 Description:
14559
14560 ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
14561 This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
14562 avoiding browser messages such as "This script is taking too long to complete".
14563
14564 Parameters:
14565
14566 opt - (object) The object properties are described below
14567
14568 iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
14569 of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
14570
14571 property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
14572 You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
14573 computations for final animation positions then you can just choose 'end'.
14574
14575 onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
14576 parameter a percentage value.
14577
14578 onComplete - A callback function called when the algorithm completed.
14579
14580 Example:
14581
14582 In this example I calculate the end positions and then animate the graph to those positions
14583
14584 (start code js)
14585 var fd = new $jit.ForceDirected(...);
14586 fd.computeIncremental({
14587 iter: 20,
14588 property: 'end',
14589 onStep: function(perc) {
14590 Log.write("loading " + perc + "%");
14591 },
14592 onComplete: function() {
14593 Log.write("done");
14594 fd.animate();
14595 }
14596 });
14597 (end code)
14598
14599 In this example I calculate all positions and (re)plot the graph
14600
14601 (start code js)
14602 var fd = new ForceDirected(...);
14603 fd.computeIncremental({
14604 iter: 20,
14605 property: ['end', 'start', 'current'],
14606 onStep: function(perc) {
14607 Log.write("loading " + perc + "%");
14608 },
14609 onComplete: function() {
14610 Log.write("done");
14611 fd.plot();
14612 }
14613 });
14614 (end code)
14615
14616 */
14617 computeIncremental: function(opt) {
14618 opt = $.merge( {
14619 iter: 20,
14620 property: 'end',
14621 onStep: $.empty,
14622 onComplete: $.empty
14623 }, opt || {});
14624
14625 this.config.onBeforeCompute(this.graph.getNode(this.root));
14626 this.compute(opt.property, opt);
14627 },
14628
14629 /*
14630 Method: plot
14631
14632 Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
14633 */
14634 plot: function() {
14635 this.fx.plot();
14636 },
14637
14638 /*
14639 Method: animate
14640
14641 Animates the graph from the current positions to the 'end' node positions.
14642 */
14643 animate: function(opt) {
14644 this.fx.animate($.merge( {
14645 modes: [ 'linear' ]
14646 }, opt || {}));
14647 }
14648});
14649
14650$jit.ForceDirected.$extend = true;
14651
14652(function(ForceDirected) {
14653
14654 /*
14655 Class: ForceDirected.Op
14656
14657 Custom extension of <Graph.Op>.
14658
14659 Extends:
14660
14661 All <Graph.Op> methods
14662
14663 See also:
14664
14665 <Graph.Op>
14666
14667 */
14668 ForceDirected.Op = new Class( {
14669
14670 Implements: Graph.Op
14671
14672 });
14673
14674 /*
14675 Class: ForceDirected.Plot
14676
14677 Custom extension of <Graph.Plot>.
14678
14679 Extends:
14680
14681 All <Graph.Plot> methods
14682
14683 See also:
14684
14685 <Graph.Plot>
14686
14687 */
14688 ForceDirected.Plot = new Class( {
14689
14690 Implements: Graph.Plot
14691
14692 });
14693
14694 /*
14695 Class: ForceDirected.Label
14696
14697 Custom extension of <Graph.Label>.
14698 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14699
14700 Extends:
14701
14702 All <Graph.Label> methods and subclasses.
14703
14704 See also:
14705
14706 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14707
14708 */
14709 ForceDirected.Label = {};
14710
14711 /*
14712 ForceDirected.Label.Native
14713
14714 Custom extension of <Graph.Label.Native>.
14715
14716 Extends:
14717
14718 All <Graph.Label.Native> methods
14719
14720 See also:
14721
14722 <Graph.Label.Native>
14723
14724 */
14725 ForceDirected.Label.Native = new Class( {
14726 Implements: Graph.Label.Native
14727 });
14728
14729 /*
14730 ForceDirected.Label.SVG
14731
14732 Custom extension of <Graph.Label.SVG>.
14733
14734 Extends:
14735
14736 All <Graph.Label.SVG> methods
14737
14738 See also:
14739
14740 <Graph.Label.SVG>
14741
14742 */
14743 ForceDirected.Label.SVG = new Class( {
14744 Implements: Graph.Label.SVG,
14745
14746 initialize: function(viz) {
14747 this.viz = viz;
14748 },
14749
14750 /*
14751 placeLabel
14752
14753 Overrides abstract method placeLabel in <Graph.Label>.
14754
14755 Parameters:
14756
14757 tag - A DOM label element.
14758 node - A <Graph.Node>.
14759 controller - A configuration/controller object passed to the visualization.
14760
14761 */
14762 placeLabel: function(tag, node, controller) {
14763 var pos = node.pos.getc(true),
14764 canvas = this.viz.canvas,
14765 ox = canvas.translateOffsetX,
14766 oy = canvas.translateOffsetY,
14767 sx = canvas.scaleOffsetX,
14768 sy = canvas.scaleOffsetY,
14769 radius = canvas.getSize();
14770 var labelPos = {
14771 x: Math.round(pos.x * sx + ox + radius.width / 2),
14772 y: Math.round(pos.y * sy + oy + radius.height / 2)
14773 };
14774 tag.setAttribute('x', labelPos.x);
14775 tag.setAttribute('y', labelPos.y);
14776
14777 controller.onPlaceLabel(tag, node);
14778 }
14779 });
14780
14781 /*
14782 ForceDirected.Label.HTML
14783
14784 Custom extension of <Graph.Label.HTML>.
14785
14786 Extends:
14787
14788 All <Graph.Label.HTML> methods.
14789
14790 See also:
14791
14792 <Graph.Label.HTML>
14793
14794 */
14795 ForceDirected.Label.HTML = new Class( {
14796 Implements: Graph.Label.HTML,
14797
14798 initialize: function(viz) {
14799 this.viz = viz;
14800 },
14801 /*
14802 placeLabel
14803
14804 Overrides abstract method placeLabel in <Graph.Plot>.
14805
14806 Parameters:
14807
14808 tag - A DOM label element.
14809 node - A <Graph.Node>.
14810 controller - A configuration/controller object passed to the visualization.
14811
14812 */
14813 placeLabel: function(tag, node, controller) {
14814 var pos = node.pos.getc(true),
14815 canvas = this.viz.canvas,
14816 ox = canvas.translateOffsetX,
14817 oy = canvas.translateOffsetY,
14818 sx = canvas.scaleOffsetX,
14819 sy = canvas.scaleOffsetY,
14820 radius = canvas.getSize();
14821 var labelPos = {
14822 x: Math.round(pos.x * sx + ox + radius.width / 2),
14823 y: Math.round(pos.y * sy + oy + radius.height / 2)
14824 };
14825 var style = tag.style;
14826 style.left = labelPos.x + 'px';
14827 style.top = labelPos.y + 'px';
14828 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14829
14830 controller.onPlaceLabel(tag, node);
14831 }
14832 });
14833
14834 /*
14835 Class: ForceDirected.Plot.NodeTypes
14836
14837 This class contains a list of <Graph.Node> built-in types.
14838 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
14839
14840 You can add your custom node types, customizing your visualization to the extreme.
14841
14842 Example:
14843
14844 (start code js)
14845 ForceDirected.Plot.NodeTypes.implement({
14846 'mySpecialType': {
14847 'render': function(node, canvas) {
14848 //print your custom node to canvas
14849 },
14850 //optional
14851 'contains': function(node, pos) {
14852 //return true if pos is inside the node or false otherwise
14853 }
14854 }
14855 });
14856 (end code)
14857
14858 */
14859 ForceDirected.Plot.NodeTypes = new Class({
14860 'none': {
14861 'render': $.empty,
14862 'contains': $.lambda(false)
14863 },
14864 'circle': {
14865 'render': function(node, canvas){
14866 var pos = node.pos.getc(true),
14867 dim = node.getData('dim');
14868 this.nodeHelper.circle.render('fill', pos, dim, canvas);
14869 },
14870 'contains': function(node, pos){
14871 var npos = node.pos.getc(true),
14872 dim = node.getData('dim');
14873 return this.nodeHelper.circle.contains(npos, pos, dim);
14874 }
14875 },
14876 'ellipse': {
14877 'render': function(node, canvas){
14878 var pos = node.pos.getc(true),
14879 width = node.getData('width'),
14880 height = node.getData('height');
14881 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
14882 },
14883 'contains': function(node, pos){
14884 var npos = node.pos.getc(true),
14885 width = node.getData('width'),
14886 height = node.getData('height');
14887 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
14888 }
14889 },
14890 'square': {
14891 'render': function(node, canvas){
14892 var pos = node.pos.getc(true),
14893 dim = node.getData('dim');
14894 this.nodeHelper.square.render('fill', pos, dim, canvas);
14895 },
14896 'contains': function(node, pos){
14897 var npos = node.pos.getc(true),
14898 dim = node.getData('dim');
14899 return this.nodeHelper.square.contains(npos, pos, dim);
14900 }
14901 },
14902 'rectangle': {
14903 'render': function(node, canvas){
14904 var pos = node.pos.getc(true),
14905 width = node.getData('width'),
14906 height = node.getData('height');
14907 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
14908 },
14909 'contains': function(node, pos){
14910 var npos = node.pos.getc(true),
14911 width = node.getData('width'),
14912 height = node.getData('height');
14913 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
14914 }
14915 },
14916 'triangle': {
14917 'render': function(node, canvas){
14918 var pos = node.pos.getc(true),
14919 dim = node.getData('dim');
14920 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
14921 },
14922 'contains': function(node, pos) {
14923 var npos = node.pos.getc(true),
14924 dim = node.getData('dim');
14925 return this.nodeHelper.triangle.contains(npos, pos, dim);
14926 }
14927 },
14928 'star': {
14929 'render': function(node, canvas){
14930 var pos = node.pos.getc(true),
14931 dim = node.getData('dim');
14932 this.nodeHelper.star.render('fill', pos, dim, canvas);
14933 },
14934 'contains': function(node, pos) {
14935 var npos = node.pos.getc(true),
14936 dim = node.getData('dim');
14937 return this.nodeHelper.star.contains(npos, pos, dim);
14938 }
14939 }
14940 });
14941
14942 /*
14943 Class: ForceDirected.Plot.EdgeTypes
14944
14945 This class contains a list of <Graph.Adjacence> built-in types.
14946 Edge types implemented are 'none', 'line' and 'arrow'.
14947
14948 You can add your custom edge types, customizing your visualization to the extreme.
14949
14950 Example:
14951
14952 (start code js)
14953 ForceDirected.Plot.EdgeTypes.implement({
14954 'mySpecialType': {
14955 'render': function(adj, canvas) {
14956 //print your custom edge to canvas
14957 },
14958 //optional
14959 'contains': function(adj, pos) {
14960 //return true if pos is inside the arc or false otherwise
14961 }
14962 }
14963 });
14964 (end code)
14965
14966 */
14967 ForceDirected.Plot.EdgeTypes = new Class({
14968 'none': $.empty,
14969 'line': {
14970 'render': function(adj, canvas) {
14971 var from = adj.nodeFrom.pos.getc(true),
14972 to = adj.nodeTo.pos.getc(true);
14973 this.edgeHelper.line.render(from, to, canvas);
14974 },
14975 'contains': function(adj, pos) {
14976 var from = adj.nodeFrom.pos.getc(true),
14977 to = adj.nodeTo.pos.getc(true);
14978 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
14979 }
14980 },
14981 'arrow': {
14982 'render': function(adj, canvas) {
14983 var from = adj.nodeFrom.pos.getc(true),
14984 to = adj.nodeTo.pos.getc(true),
14985 dim = adj.getData('dim'),
14986 direction = adj.data.$direction,
14987 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
14988 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
14989 },
14990 'contains': function(adj, pos) {
14991 var from = adj.nodeFrom.pos.getc(true),
14992 to = adj.nodeTo.pos.getc(true);
14993 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
14994 }
14995 }
14996 });
14997
14998})($jit.ForceDirected);
14999
15000
15001/*
15002 * File: Treemap.js
15003 *
15004*/
15005
15006$jit.TM = {};
15007
15008var TM = $jit.TM;
15009
15010$jit.TM.$extend = true;
15011
15012/*
15013 Class: TM.Base
15014
15015 Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
15016
15017 Implements:
15018
15019 All <Loader> methods
15020
15021 Constructor Options:
15022
15023 Inherits options from
15024
15025 - <Options.Canvas>
15026 - <Options.Controller>
15027 - <Options.Node>
15028 - <Options.Edge>
15029 - <Options.Label>
15030 - <Options.Events>
15031 - <Options.Tips>
15032 - <Options.NodeStyles>
15033 - <Options.Navigation>
15034
15035 Additionally, there are other parameters and some default values changed
15036
15037 orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
15038 titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
15039 offset - (number) Default's *2*. Boxes offset.
15040 constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
15041 levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
15042 animate - (boolean) Default's *false*. Whether to animate transitions.
15043 Node.type - Described in <Options.Node>. Default's *rectangle*.
15044 duration - Described in <Options.Fx>. Default's *700*.
15045 fps - Described in <Options.Fx>. Default's *45*.
15046
15047 Instance Properties:
15048
15049 canvas - Access a <Canvas> instance.
15050 graph - Access a <Graph> instance.
15051 op - Access a <TM.Op> instance.
15052 fx - Access a <TM.Plot> instance.
15053 labels - Access a <TM.Label> interface implementation.
15054
15055 Inspired by:
15056
15057 Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
15058
15059 Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
15060
15061 Note:
15062
15063 This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
15064
15065*/
15066TM.Base = {
15067 layout: {
15068 orientation: "h",
15069 vertical: function(){
15070 return this.orientation == "v";
15071 },
15072 horizontal: function(){
15073 return this.orientation == "h";
15074 },
15075 change: function(){
15076 this.orientation = this.vertical()? "h" : "v";
15077 }
15078 },
15079
15080 initialize: function(controller){
15081 var config = {
15082 orientation: "h",
15083 titleHeight: 13,
15084 offset: 2,
15085 levelsToShow: 0,
15086 constrained: false,
15087 animate: false,
15088 Node: {
15089 type: 'rectangle',
15090 overridable: true,
15091 //we all know why this is not zero,
15092 //right, Firefox?
15093 width: 3,
15094 height: 3,
15095 color: '#444'
15096 },
15097 Label: {
15098 textAlign: 'center',
15099 textBaseline: 'top'
15100 },
15101 Edge: {
15102 type: 'none'
15103 },
15104 duration: 700,
15105 fps: 45
15106 };
15107
15108 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
15109 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
15110 this.layout.orientation = this.config.orientation;
15111
15112 var canvasConfig = this.config;
15113 if (canvasConfig.useCanvas) {
15114 this.canvas = canvasConfig.useCanvas;
15115 this.config.labelContainer = this.canvas.id + '-label';
15116 } else {
15117 if(canvasConfig.background) {
15118 canvasConfig.background = $.merge({
15119 type: 'Circles'
15120 }, canvasConfig.background);
15121 }
15122 this.canvas = new Canvas(this, canvasConfig);
15123 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
15124 }
15125
15126 this.graphOptions = {
15127 'klass': Complex,
15128 'Node': {
15129 'selected': false,
15130 'exist': true,
15131 'drawn': true
15132 }
15133 };
15134 this.graph = new Graph(this.graphOptions, this.config.Node,
15135 this.config.Edge);
15136 this.labels = new TM.Label[canvasConfig.Label.type](this);
15137 this.fx = new TM.Plot(this);
15138 this.op = new TM.Op(this);
15139 this.group = new TM.Group(this);
15140 this.geom = new TM.Geom(this);
15141 this.clickedNode = null;
15142 this.busy = false;
15143 // initialize extras
15144 this.initializeExtras();
15145 },
15146
15147 /*
15148 Method: refresh
15149
15150 Computes positions and plots the tree.
15151 */
15152 refresh: function(){
15153 if(this.busy) return;
15154 this.busy = true;
15155 var that = this;
15156 if(this.config.animate) {
15157 this.compute('end');
15158 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
15159 && this.clickedNode.id || this.root));
15160 this.fx.animate($.merge(this.config, {
15161 modes: ['linear', 'node-property:width:height'],
15162 onComplete: function() {
15163 that.busy = false;
15164 }
15165 }));
15166 } else {
15167 var labelType = this.config.Label.type;
15168 if(labelType != 'Native') {
15169 var that = this;
15170 this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
15171 }
15172 this.busy = false;
15173 this.compute();
15174 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
15175 && this.clickedNode.id || this.root));
15176 this.plot();
15177 }
15178 },
15179
15180 /*
15181 Method: plot
15182
15183 Plots the TreeMap. This is a shortcut to *fx.plot*.
15184
15185 */
15186 plot: function(){
15187 this.fx.plot();
15188 },
15189
15190 /*
15191 Method: leaf
15192
15193 Returns whether the node is a leaf.
15194
15195 Parameters:
15196
15197 n - (object) A <Graph.Node>.
15198
15199 */
15200 leaf: function(n){
15201 return n.getSubnodes([
15202 1, 1
15203 ], "ignore").length == 0;
15204 },
15205
15206 /*
15207 Method: enter
15208
15209 Sets the node as root.
15210
15211 Parameters:
15212
15213 n - (object) A <Graph.Node>.
15214
15215 */
15216 enter: function(n){
15217 if(this.busy) return;
15218 this.busy = true;
15219
15220 var that = this,
15221 config = this.config,
15222 graph = this.graph,
15223 clickedNode = n,
15224 previousClickedNode = this.clickedNode;
15225
15226 var callback = {
15227 onComplete: function() {
15228 //ensure that nodes are shown for that level
15229 if(config.levelsToShow > 0) {
15230 that.geom.setRightLevelToShow(n);
15231 }
15232 //compute positions of newly inserted nodes
15233 if(config.levelsToShow > 0 || config.request) that.compute();
15234 if(config.animate) {
15235 //fade nodes
15236 graph.nodeList.setData('alpha', 0, 'end');
15237 n.eachSubgraph(function(n) {
15238 n.setData('alpha', 1, 'end');
15239 }, "ignore");
15240 that.fx.animate({
15241 duration: 500,
15242 modes:['node-property:alpha'],
15243 onComplete: function() {
15244 //compute end positions
15245 that.clickedNode = clickedNode;
15246 that.compute('end');
15247 //animate positions
15248 //TODO(nico) commenting this line didn't seem to throw errors...
15249 that.clickedNode = previousClickedNode;
15250 that.fx.animate({
15251 modes:['linear', 'node-property:width:height'],
15252 duration: 1000,
15253 onComplete: function() {
15254 that.busy = false;
15255 //TODO(nico) check comment above
15256 that.clickedNode = clickedNode;
15257 }
15258 });
15259 }
15260 });
15261 } else {
15262 that.busy = false;
15263 that.clickedNode = n;
15264 that.refresh();
15265 }
15266 }
15267 };
15268 if(config.request) {
15269 this.requestNodes(clickedNode, callback);
15270 } else {
15271 callback.onComplete();
15272 }
15273 },
15274
15275 /*
15276 Method: out
15277
15278 Sets the parent node of the current selected node as root.
15279
15280 */
15281 out: function(){
15282 if(this.busy) return;
15283 this.busy = true;
15284 this.events.hoveredNode = false;
15285 var that = this,
15286 config = this.config,
15287 graph = this.graph,
15288 parents = graph.getNode(this.clickedNode
15289 && this.clickedNode.id || this.root).getParents(),
15290 parent = parents[0],
15291 clickedNode = parent,
15292 previousClickedNode = this.clickedNode;
15293
15294 //if no parents return
15295 if(!parent) {
15296 this.busy = false;
15297 return;
15298 }
15299 //final plot callback
15300 callback = {
15301 onComplete: function() {
15302 that.clickedNode = parent;
15303 if(config.request) {
15304 that.requestNodes(parent, {
15305 onComplete: function() {
15306 that.compute();
15307 that.plot();
15308 that.busy = false;
15309 }
15310 });
15311 } else {
15312 that.compute();
15313 that.plot();
15314 that.busy = false;
15315 }
15316 }
15317 };
15318 //prune tree
15319 if (config.levelsToShow > 0)
15320 this.geom.setRightLevelToShow(parent);
15321 //animate node positions
15322 if(config.animate) {
15323 this.clickedNode = clickedNode;
15324 this.compute('end');
15325 //animate the visible subtree only
15326 this.clickedNode = previousClickedNode;
15327 this.fx.animate({
15328 modes:['linear', 'node-property:width:height'],
15329 duration: 1000,
15330 onComplete: function() {
15331 //animate the parent subtree
15332 that.clickedNode = clickedNode;
15333 //change nodes alpha
15334 graph.eachNode(function(n) {
15335 n.setDataset(['current', 'end'], {
15336 'alpha': [0, 1]
15337 });
15338 }, "ignore");
15339 previousClickedNode.eachSubgraph(function(node) {
15340 node.setData('alpha', 1);
15341 }, "ignore");
15342 that.fx.animate({
15343 duration: 500,
15344 modes:['node-property:alpha'],
15345 onComplete: function() {
15346 callback.onComplete();
15347 }
15348 });
15349 }
15350 });
15351 } else {
15352 callback.onComplete();
15353 }
15354 },
15355
15356 requestNodes: function(node, onComplete){
15357 var handler = $.merge(this.controller, onComplete),
15358 lev = this.config.levelsToShow;
15359 if (handler.request) {
15360 var leaves = [], d = node._depth;
15361 node.eachLevel(0, lev, function(n){
15362 var nodeLevel = lev - (n._depth - d);
15363 if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
15364 leaves.push(n);
15365 n._level = nodeLevel;
15366 }
15367 });
15368 this.group.requestNodes(leaves, handler);
15369 } else {
15370 handler.onComplete();
15371 }
15372 },
15373
15374 reposition: function() {
15375 this.compute('end');
15376 }
15377};
15378
15379/*
15380 Class: TM.Op
15381
15382 Custom extension of <Graph.Op>.
15383
15384 Extends:
15385
15386 All <Graph.Op> methods
15387
15388 See also:
15389
15390 <Graph.Op>
15391
15392 */
15393TM.Op = new Class({
15394 Implements: Graph.Op,
15395
15396 initialize: function(viz){
15397 this.viz = viz;
15398 }
15399});
15400
15401//extend level methods of Graph.Geom
15402TM.Geom = new Class({
15403 Implements: Graph.Geom,
15404
15405 getRightLevelToShow: function() {
15406 return this.viz.config.levelsToShow;
15407 },
15408
15409 setRightLevelToShow: function(node) {
15410 var level = this.getRightLevelToShow(),
15411 fx = this.viz.labels;
15412 node.eachLevel(0, level+1, function(n) {
15413 var d = n._depth - node._depth;
15414 if(d > level) {
15415 n.drawn = false;
15416 n.exist = false;
15417 n.ignore = true;
15418 fx.hideLabel(n, false);
15419 } else {
15420 n.drawn = true;
15421 n.exist = true;
15422 delete n.ignore;
15423 }
15424 });
15425 node.drawn = true;
15426 delete node.ignore;
15427 }
15428});
15429
15430/*
15431
15432Performs operations on group of nodes.
15433
15434*/
15435TM.Group = new Class( {
15436
15437 initialize: function(viz){
15438 this.viz = viz;
15439 this.canvas = viz.canvas;
15440 this.config = viz.config;
15441 },
15442
15443 /*
15444
15445 Calls the request method on the controller to request a subtree for each node.
15446 */
15447 requestNodes: function(nodes, controller){
15448 var counter = 0, len = nodes.length, nodeSelected = {};
15449 var complete = function(){
15450 controller.onComplete();
15451 };
15452 var viz = this.viz;
15453 if (len == 0)
15454 complete();
15455 for ( var i = 0; i < len; i++) {
15456 nodeSelected[nodes[i].id] = nodes[i];
15457 controller.request(nodes[i].id, nodes[i]._level, {
15458 onComplete: function(nodeId, data){
15459 if (data && data.children) {
15460 data.id = nodeId;
15461 viz.op.sum(data, {
15462 type: 'nothing'
15463 });
15464 }
15465 if (++counter == len) {
15466 viz.graph.computeLevels(viz.root, 0);
15467 complete();
15468 }
15469 }
15470 });
15471 }
15472 }
15473});
15474
15475/*
15476 Class: TM.Plot
15477
15478 Custom extension of <Graph.Plot>.
15479
15480 Extends:
15481
15482 All <Graph.Plot> methods
15483
15484 See also:
15485
15486 <Graph.Plot>
15487
15488 */
15489TM.Plot = new Class({
15490
15491 Implements: Graph.Plot,
15492
15493 initialize: function(viz){
15494 this.viz = viz;
15495 this.config = viz.config;
15496 this.node = this.config.Node;
15497 this.edge = this.config.Edge;
15498 this.animation = new Animation;
15499 this.nodeTypes = new TM.Plot.NodeTypes;
15500 this.edgeTypes = new TM.Plot.EdgeTypes;
15501 this.labels = viz.labels;
15502 },
15503
15504 plot: function(opt, animating){
15505 var viz = this.viz,
15506 graph = viz.graph;
15507 viz.canvas.clear();
15508 this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
15509 'withLabels': true,
15510 'hideLabels': false,
15511 'plotSubtree': function(n, ch){
15512 return n.anySubnode("exist");
15513 }
15514 }), animating);
15515 }
15516});
15517
15518/*
15519 Class: TM.Label
15520
15521 Custom extension of <Graph.Label>.
15522 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
15523
15524 Extends:
15525
15526 All <Graph.Label> methods and subclasses.
15527
15528 See also:
15529
15530 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
15531
15532*/
15533TM.Label = {};
15534
15535/*
15536 TM.Label.Native
15537
15538 Custom extension of <Graph.Label.Native>.
15539
15540 Extends:
15541
15542 All <Graph.Label.Native> methods
15543
15544 See also:
15545
15546 <Graph.Label.Native>
15547*/
15548TM.Label.Native = new Class({
15549 Implements: Graph.Label.Native,
15550
15551 initialize: function(viz) {
15552 this.config = viz.config;
15553 this.leaf = viz.leaf;
15554 },
15555
15556 renderLabel: function(canvas, node, controller){
15557 if(!this.leaf(node) && !this.config.titleHeight) return;
15558 var pos = node.pos.getc(true),
15559 ctx = canvas.getCtx(),
15560 width = node.getData('width'),
15561 height = node.getData('height'),
15562 x = pos.x + width/2,
15563 y = pos.y;
15564
15565 ctx.fillText(node.name, x, y, width);
15566 }
15567});
15568
15569/*
15570 TM.Label.SVG
15571
15572 Custom extension of <Graph.Label.SVG>.
15573
15574 Extends:
15575
15576 All <Graph.Label.SVG> methods
15577
15578 See also:
15579
15580 <Graph.Label.SVG>
15581*/
15582TM.Label.SVG = new Class( {
15583 Implements: Graph.Label.SVG,
15584
15585 initialize: function(viz){
15586 this.viz = viz;
15587 this.leaf = viz.leaf;
15588 this.config = viz.config;
15589 },
15590
15591 /*
15592 placeLabel
15593
15594 Overrides abstract method placeLabel in <Graph.Plot>.
15595
15596 Parameters:
15597
15598 tag - A DOM label element.
15599 node - A <Graph.Node>.
15600 controller - A configuration/controller object passed to the visualization.
15601
15602 */
15603 placeLabel: function(tag, node, controller){
15604 var pos = node.pos.getc(true),
15605 canvas = this.viz.canvas,
15606 ox = canvas.translateOffsetX,
15607 oy = canvas.translateOffsetY,
15608 sx = canvas.scaleOffsetX,
15609 sy = canvas.scaleOffsetY,
15610 radius = canvas.getSize();
15611 var labelPos = {
15612 x: Math.round(pos.x * sx + ox + radius.width / 2),
15613 y: Math.round(pos.y * sy + oy + radius.height / 2)
15614 };
15615 tag.setAttribute('x', labelPos.x);
15616 tag.setAttribute('y', labelPos.y);
15617
15618 if(!this.leaf(node) && !this.config.titleHeight) {
15619 tag.style.display = 'none';
15620 }
15621 controller.onPlaceLabel(tag, node);
15622 }
15623});
15624
15625/*
15626 TM.Label.HTML
15627
15628 Custom extension of <Graph.Label.HTML>.
15629
15630 Extends:
15631
15632 All <Graph.Label.HTML> methods.
15633
15634 See also:
15635
15636 <Graph.Label.HTML>
15637
15638*/
15639TM.Label.HTML = new Class( {
15640 Implements: Graph.Label.HTML,
15641
15642 initialize: function(viz){
15643 this.viz = viz;
15644 this.leaf = viz.leaf;
15645 this.config = viz.config;
15646 },
15647
15648 /*
15649 placeLabel
15650
15651 Overrides abstract method placeLabel in <Graph.Plot>.
15652
15653 Parameters:
15654
15655 tag - A DOM label element.
15656 node - A <Graph.Node>.
15657 controller - A configuration/controller object passed to the visualization.
15658
15659 */
15660 placeLabel: function(tag, node, controller){
15661 var pos = node.pos.getc(true),
15662 canvas = this.viz.canvas,
15663 ox = canvas.translateOffsetX,
15664 oy = canvas.translateOffsetY,
15665 sx = canvas.scaleOffsetX,
15666 sy = canvas.scaleOffsetY,
15667 radius = canvas.getSize();
15668 var labelPos = {
15669 x: Math.round(pos.x * sx + ox + radius.width / 2),
15670 y: Math.round(pos.y * sy + oy + radius.height / 2)
15671 };
15672
15673 var style = tag.style;
15674 style.left = labelPos.x + 'px';
15675 style.top = labelPos.y + 'px';
15676 style.width = node.getData('width') * sx + 'px';
15677 style.height = node.getData('height') * sy + 'px';
15678 style.zIndex = node._depth * 100;
15679 style.display = '';
15680
15681 if(!this.leaf(node) && !this.config.titleHeight) {
15682 tag.style.display = 'none';
15683 }
15684 controller.onPlaceLabel(tag, node);
15685 }
15686});
15687
15688/*
15689 Class: TM.Plot.NodeTypes
15690
15691 This class contains a list of <Graph.Node> built-in types.
15692 Node types implemented are 'none', 'rectangle'.
15693
15694 You can add your custom node types, customizing your visualization to the extreme.
15695
15696 Example:
15697
15698 (start code js)
15699 TM.Plot.NodeTypes.implement({
15700 'mySpecialType': {
15701 'render': function(node, canvas) {
15702 //print your custom node to canvas
15703 },
15704 //optional
15705 'contains': function(node, pos) {
15706 //return true if pos is inside the node or false otherwise
15707 }
15708 }
15709 });
15710 (end code)
15711
15712*/
15713TM.Plot.NodeTypes = new Class( {
15714 'none': {
15715 'render': $.empty
15716 },
15717
15718 'rectangle': {
15719 'render': function(node, canvas, animating){
15720 var leaf = this.viz.leaf(node),
15721 config = this.config,
15722 offst = config.offset,
15723 titleHeight = config.titleHeight,
15724 pos = node.pos.getc(true),
15725 width = node.getData('width'),
15726 height = node.getData('height'),
15727 border = node.getData('border'),
15728 ctx = canvas.getCtx(),
15729 posx = pos.x + offst / 2,
15730 posy = pos.y + offst / 2;
15731 if(width <= offst || height <= offst) return;
15732 if (leaf) {
15733 if(config.cushion) {
15734 var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1,
15735 posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
15736 var color = node.getData('color');
15737 var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
15738 function(r) { return r * 0.2 >> 0; }));
15739 lg.addColorStop(0, color);
15740 lg.addColorStop(1, colorGrad);
15741 ctx.fillStyle = lg;
15742 }
15743 ctx.fillRect(posx, posy, width - offst, height - offst);
15744 if(border) {
15745 ctx.save();
15746 ctx.strokeStyle = border;
15747 ctx.strokeRect(posx, posy, width - offst, height - offst);
15748 ctx.restore();
15749 }
15750 } else if(titleHeight > 0){
15751 ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
15752 titleHeight - offst);
15753 if(border) {
15754 ctx.save();
15755 ctx.strokeStyle = border;
15756 ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
15757 height - offst);
15758 ctx.restore();
15759 }
15760 }
15761 },
15762 'contains': function(node, pos) {
15763 if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
15764 var npos = node.pos.getc(true),
15765 width = node.getData('width'),
15766 leaf = this.viz.leaf(node),
15767 height = leaf? node.getData('height') : this.config.titleHeight;
15768 return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
15769 }
15770 }
15771});
15772
15773TM.Plot.EdgeTypes = new Class( {
15774 'none': $.empty
15775});
15776
15777/*
15778 Class: TM.SliceAndDice
15779
15780 A slice and dice TreeMap visualization.
15781
15782 Implements:
15783
15784 All <TM.Base> methods and properties.
15785*/
15786TM.SliceAndDice = new Class( {
15787 Implements: [
15788 Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
15789 ]
15790});
15791
15792/*
15793 Class: TM.Squarified
15794
15795 A squarified TreeMap visualization.
15796
15797 Implements:
15798
15799 All <TM.Base> methods and properties.
15800*/
15801TM.Squarified = new Class( {
15802 Implements: [
15803 Loader, Extras, TM.Base, Layouts.TM.Squarified
15804 ]
15805});
15806
15807/*
15808 Class: TM.Strip
15809
15810 A strip TreeMap visualization.
15811
15812 Implements:
15813
15814 All <TM.Base> methods and properties.
15815*/
15816TM.Strip = new Class( {
15817 Implements: [
15818 Loader, Extras, TM.Base, Layouts.TM.Strip
15819 ]
15820});
15821
15822
15823/*
15824 * File: RGraph.js
15825 *
15826 */
15827
15828/*
15829 Class: RGraph
15830
15831 A radial graph visualization with advanced animations.
15832
15833 Inspired by:
15834
15835 Animated Exploration of Dynamic Graphs with Radial Layout (Ka-Ping Yee, Danyel Fisher, Rachna Dhamija, Marti Hearst) <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
15836
15837 Note:
15838
15839 This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the visualization described in the paper.
15840
15841 Implements:
15842
15843 All <Loader> methods
15844
15845 Constructor Options:
15846
15847 Inherits options from
15848
15849 - <Options.Canvas>
15850 - <Options.Controller>
15851 - <Options.Node>
15852 - <Options.Edge>
15853 - <Options.Label>
15854 - <Options.Events>
15855 - <Options.Tips>
15856 - <Options.NodeStyles>
15857 - <Options.Navigation>
15858
15859 Additionally, there are other parameters and some default values changed
15860
15861 interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
15862 levelDistance - (number) Default's *100*. The distance between levels of the tree.
15863
15864 Instance Properties:
15865
15866 canvas - Access a <Canvas> instance.
15867 graph - Access a <Graph> instance.
15868 op - Access a <RGraph.Op> instance.
15869 fx - Access a <RGraph.Plot> instance.
15870 labels - Access a <RGraph.Label> interface implementation.
15871*/
15872
15873$jit.RGraph = new Class( {
15874
15875 Implements: [
15876 Loader, Extras, Layouts.Radial
15877 ],
15878
15879 initialize: function(controller){
15880 var $RGraph = $jit.RGraph;
15881
15882 var config = {
15883 interpolation: 'linear',
15884 levelDistance: 100
15885 };
15886
15887 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
15888 "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
15889
15890 var canvasConfig = this.config;
15891 if(canvasConfig.useCanvas) {
15892 this.canvas = canvasConfig.useCanvas;
15893 this.config.labelContainer = this.canvas.id + '-label';
15894 } else {
15895 if(canvasConfig.background) {
15896 canvasConfig.background = $.merge({
15897 type: 'Circles'
15898 }, canvasConfig.background);
15899 }
15900 this.canvas = new Canvas(this, canvasConfig);
15901 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
15902 }
15903
15904 this.graphOptions = {
15905 'klass': Polar,
15906 'Node': {
15907 'selected': false,
15908 'exist': true,
15909 'drawn': true
15910 }
15911 };
15912 this.graph = new Graph(this.graphOptions, this.config.Node,
15913 this.config.Edge);
15914 this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
15915 this.fx = new $RGraph.Plot(this, $RGraph);
15916 this.op = new $RGraph.Op(this);
15917 this.json = null;
15918 this.root = null;
15919 this.busy = false;
15920 this.parent = false;
15921 // initialize extras
15922 this.initializeExtras();
15923 },
15924
15925 /*
15926
15927 createLevelDistanceFunc
15928
15929 Returns the levelDistance function used for calculating a node distance
15930 to its origin. This function returns a function that is computed
15931 per level and not per node, such that all nodes with the same depth will have the
15932 same distance to the origin. The resulting function gets the
15933 parent node as parameter and returns a float.
15934
15935 */
15936 createLevelDistanceFunc: function(){
15937 var ld = this.config.levelDistance;
15938 return function(elem){
15939 return (elem._depth + 1) * ld;
15940 };
15941 },
15942
15943 /*
15944 Method: refresh
15945
15946 Computes positions and plots the tree.
15947
15948 */
15949 refresh: function(){
15950 this.compute();
15951 this.plot();
15952 },
15953
15954 reposition: function(){
15955 this.compute('end');
15956 },
15957
15958 /*
15959 Method: plot
15960
15961 Plots the RGraph. This is a shortcut to *fx.plot*.
15962 */
15963 plot: function(){
15964 this.fx.plot();
15965 },
15966 /*
15967 getNodeAndParentAngle
15968
15969 Returns the _parent_ of the given node, also calculating its angle span.
15970 */
15971 getNodeAndParentAngle: function(id){
15972 var theta = false;
15973 var n = this.graph.getNode(id);
15974 var ps = n.getParents();
15975 var p = (ps.length > 0)? ps[0] : false;
15976 if (p) {
15977 var posParent = p.pos.getc(), posChild = n.pos.getc();
15978 var newPos = posParent.add(posChild.scale(-1));
15979 theta = Math.atan2(newPos.y, newPos.x);
15980 if (theta < 0)
15981 theta += 2 * Math.PI;
15982 }
15983 return {
15984 parent: p,
15985 theta: theta
15986 };
15987 },
15988 /*
15989 tagChildren
15990
15991 Enumerates the children in order to maintain child ordering (second constraint of the paper).
15992 */
15993 tagChildren: function(par, id){
15994 if (par.angleSpan) {
15995 var adjs = [];
15996 par.eachAdjacency(function(elem){
15997 adjs.push(elem.nodeTo);
15998 }, "ignore");
15999 var len = adjs.length;
16000 for ( var i = 0; i < len && id != adjs[i].id; i++)
16001 ;
16002 for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
16003 adjs[j].dist = k++;
16004 }
16005 }
16006 },
16007 /*
16008 Method: onClick
16009
16010 Animates the <RGraph> to center the node specified by *id*.
16011
16012 Parameters:
16013
16014 id - A <Graph.Node> id.
16015 opt - (optional|object) An object containing some extra properties described below
16016 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
16017
16018 Example:
16019
16020 (start code js)
16021 rgraph.onClick('someid');
16022 //or also...
16023 rgraph.onClick('someid', {
16024 hideLabels: false
16025 });
16026 (end code)
16027
16028 */
16029 onClick: function(id, opt){
16030 if (this.root != id && !this.busy) {
16031 this.busy = true;
16032 this.root = id;
16033 var that = this;
16034 this.controller.onBeforeCompute(this.graph.getNode(id));
16035 var obj = this.getNodeAndParentAngle(id);
16036
16037 // second constraint
16038 this.tagChildren(obj.parent, id);
16039 this.parent = obj.parent;
16040 this.compute('end');
16041
16042 // first constraint
16043 var thetaDiff = obj.theta - obj.parent.endPos.theta;
16044 this.graph.eachNode(function(elem){
16045 elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
16046 });
16047
16048 var mode = this.config.interpolation;
16049 opt = $.merge( {
16050 onComplete: $.empty
16051 }, opt || {});
16052
16053 this.fx.animate($.merge( {
16054 hideLabels: true,
16055 modes: [
16056 mode
16057 ]
16058 }, opt, {
16059 onComplete: function(){
16060 that.busy = false;
16061 opt.onComplete();
16062 }
16063 }));
16064 }
16065 }
16066});
16067
16068$jit.RGraph.$extend = true;
16069
16070(function(RGraph){
16071
16072 /*
16073 Class: RGraph.Op
16074
16075 Custom extension of <Graph.Op>.
16076
16077 Extends:
16078
16079 All <Graph.Op> methods
16080
16081 See also:
16082
16083 <Graph.Op>
16084
16085 */
16086 RGraph.Op = new Class( {
16087
16088 Implements: Graph.Op
16089
16090 });
16091
16092 /*
16093 Class: RGraph.Plot
16094
16095 Custom extension of <Graph.Plot>.
16096
16097 Extends:
16098
16099 All <Graph.Plot> methods
16100
16101 See also:
16102
16103 <Graph.Plot>
16104
16105 */
16106 RGraph.Plot = new Class( {
16107
16108 Implements: Graph.Plot
16109
16110 });
16111
16112 /*
16113 Object: RGraph.Label
16114
16115 Custom extension of <Graph.Label>.
16116 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
16117
16118 Extends:
16119
16120 All <Graph.Label> methods and subclasses.
16121
16122 See also:
16123
16124 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
16125
16126 */
16127 RGraph.Label = {};
16128
16129 /*
16130 RGraph.Label.Native
16131
16132 Custom extension of <Graph.Label.Native>.
16133
16134 Extends:
16135
16136 All <Graph.Label.Native> methods
16137
16138 See also:
16139
16140 <Graph.Label.Native>
16141
16142 */
16143 RGraph.Label.Native = new Class( {
16144 Implements: Graph.Label.Native
16145 });
16146
16147 /*
16148 RGraph.Label.SVG
16149
16150 Custom extension of <Graph.Label.SVG>.
16151
16152 Extends:
16153
16154 All <Graph.Label.SVG> methods
16155
16156 See also:
16157
16158 <Graph.Label.SVG>
16159
16160 */
16161 RGraph.Label.SVG = new Class( {
16162 Implements: Graph.Label.SVG,
16163
16164 initialize: function(viz){
16165 this.viz = viz;
16166 },
16167
16168 /*
16169 placeLabel
16170
16171 Overrides abstract method placeLabel in <Graph.Plot>.
16172
16173 Parameters:
16174
16175 tag - A DOM label element.
16176 node - A <Graph.Node>.
16177 controller - A configuration/controller object passed to the visualization.
16178
16179 */
16180 placeLabel: function(tag, node, controller){
16181 var pos = node.pos.getc(true),
16182 canvas = this.viz.canvas,
16183 ox = canvas.translateOffsetX,
16184 oy = canvas.translateOffsetY,
16185 sx = canvas.scaleOffsetX,
16186 sy = canvas.scaleOffsetY,
16187 radius = canvas.getSize();
16188 var labelPos = {
16189 x: Math.round(pos.x * sx + ox + radius.width / 2),
16190 y: Math.round(pos.y * sy + oy + radius.height / 2)
16191 };
16192 tag.setAttribute('x', labelPos.x);
16193 tag.setAttribute('y', labelPos.y);
16194
16195 controller.onPlaceLabel(tag, node);
16196 }
16197 });
16198
16199 /*
16200 RGraph.Label.HTML
16201
16202 Custom extension of <Graph.Label.HTML>.
16203
16204 Extends:
16205
16206 All <Graph.Label.HTML> methods.
16207
16208 See also:
16209
16210 <Graph.Label.HTML>
16211
16212 */
16213 RGraph.Label.HTML = new Class( {
16214 Implements: Graph.Label.HTML,
16215
16216 initialize: function(viz){
16217 this.viz = viz;
16218 },
16219 /*
16220 placeLabel
16221
16222 Overrides abstract method placeLabel in <Graph.Plot>.
16223
16224 Parameters:
16225
16226 tag - A DOM label element.
16227 node - A <Graph.Node>.
16228 controller - A configuration/controller object passed to the visualization.
16229
16230 */
16231 placeLabel: function(tag, node, controller){
16232 var pos = node.pos.getc(true),
16233 canvas = this.viz.canvas,
16234 ox = canvas.translateOffsetX,
16235 oy = canvas.translateOffsetY,
16236 sx = canvas.scaleOffsetX,
16237 sy = canvas.scaleOffsetY,
16238 radius = canvas.getSize();
16239 var labelPos = {
16240 x: Math.round(pos.x * sx + ox + radius.width / 2),
16241 y: Math.round(pos.y * sy + oy + radius.height / 2)
16242 };
16243
16244 var style = tag.style;
16245 style.left = labelPos.x + 'px';
16246 style.top = labelPos.y + 'px';
16247 style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
16248
16249 controller.onPlaceLabel(tag, node);
16250 }
16251 });
16252
16253 /*
16254 Class: RGraph.Plot.NodeTypes
16255
16256 This class contains a list of <Graph.Node> built-in types.
16257 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
16258
16259 You can add your custom node types, customizing your visualization to the extreme.
16260
16261 Example:
16262
16263 (start code js)
16264 RGraph.Plot.NodeTypes.implement({
16265 'mySpecialType': {
16266 'render': function(node, canvas) {
16267 //print your custom node to canvas
16268 },
16269 //optional
16270 'contains': function(node, pos) {
16271 //return true if pos is inside the node or false otherwise
16272 }
16273 }
16274 });
16275 (end code)
16276
16277 */
16278 RGraph.Plot.NodeTypes = new Class({
16279 'none': {
16280 'render': $.empty,
16281 'contains': $.lambda(false)
16282 },
16283 'circle': {
16284 'render': function(node, canvas){
16285 var pos = node.pos.getc(true),
16286 dim = node.getData('dim');
16287 this.nodeHelper.circle.render('fill', pos, dim, canvas);
16288 },
16289 'contains': function(node, pos){
16290 var npos = node.pos.getc(true),
16291 dim = node.getData('dim');
16292 return this.nodeHelper.circle.contains(npos, pos, dim);
16293 }
16294 },
16295 'ellipse': {
16296 'render': function(node, canvas){
16297 var pos = node.pos.getc(true),
16298 width = node.getData('width'),
16299 height = node.getData('height');
16300 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
16301 },
16302 'contains': function(node, pos){
16303 var npos = node.pos.getc(true),
16304 width = node.getData('width'),
16305 height = node.getData('height');
16306 return this.nodeHelper.ellipse.contains(npos, pos, width, height);
16307 }
16308 },
16309 'square': {
16310 'render': function(node, canvas){
16311 var pos = node.pos.getc(true),
16312 dim = node.getData('dim');
16313 this.nodeHelper.square.render('fill', pos, dim, canvas);
16314 },
16315 'contains': function(node, pos){
16316 var npos = node.pos.getc(true),
16317 dim = node.getData('dim');
16318 return this.nodeHelper.square.contains(npos, pos, dim);
16319 }
16320 },
16321 'rectangle': {
16322 'render': function(node, canvas){
16323 var pos = node.pos.getc(true),
16324 width = node.getData('width'),
16325 height = node.getData('height');
16326 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
16327 },
16328 'contains': function(node, pos){
16329 var npos = node.pos.getc(true),
16330 width = node.getData('width'),
16331 height = node.getData('height');
16332 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
16333 }
16334 },
16335 'triangle': {
16336 'render': function(node, canvas){
16337 var pos = node.pos.getc(true),
16338 dim = node.getData('dim');
16339 this.nodeHelper.triangle.render('fill', pos, dim, canvas);
16340 },
16341 'contains': function(node, pos) {
16342 var npos = node.pos.getc(true),
16343 dim = node.getData('dim');
16344 return this.nodeHelper.triangle.contains(npos, pos, dim);
16345 }
16346 },
16347 'star': {
16348 'render': function(node, canvas){
16349 var pos = node.pos.getc(true),
16350 dim = node.getData('dim');
16351 this.nodeHelper.star.render('fill', pos, dim, canvas);
16352 },
16353 'contains': function(node, pos) {
16354 var npos = node.pos.getc(true),
16355 dim = node.getData('dim');
16356 return this.nodeHelper.star.contains(npos, pos, dim);
16357 }
16358 }
16359 });
16360
16361 /*
16362 Class: RGraph.Plot.EdgeTypes
16363
16364 This class contains a list of <Graph.Adjacence> built-in types.
16365 Edge types implemented are 'none', 'line' and 'arrow'.
16366
16367 You can add your custom edge types, customizing your visualization to the extreme.
16368
16369 Example:
16370
16371 (start code js)
16372 RGraph.Plot.EdgeTypes.implement({
16373 'mySpecialType': {
16374 'render': function(adj, canvas) {
16375 //print your custom edge to canvas
16376 },
16377 //optional
16378 'contains': function(adj, pos) {
16379 //return true if pos is inside the arc or false otherwise
16380 }
16381 }
16382 });
16383 (end code)
16384
16385 */
16386 RGraph.Plot.EdgeTypes = new Class({
16387 'none': $.empty,
16388 'line': {
16389 'render': function(adj, canvas) {
16390 var from = adj.nodeFrom.pos.getc(true),
16391 to = adj.nodeTo.pos.getc(true);
16392 this.edgeHelper.line.render(from, to, canvas);
16393 },
16394 'contains': function(adj, pos) {
16395 var from = adj.nodeFrom.pos.getc(true),
16396 to = adj.nodeTo.pos.getc(true);
16397 return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
16398 }
16399 },
16400 'arrow': {
16401 'render': function(adj, canvas) {
16402 var from = adj.nodeFrom.pos.getc(true),
16403 to = adj.nodeTo.pos.getc(true),
16404 dim = adj.getData('dim'),
16405 direction = adj.data.$direction,
16406 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
16407 this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
16408 },
16409 'contains': function(adj, pos) {
16410 var from = adj.nodeFrom.pos.getc(true),
16411 to = adj.nodeTo.pos.getc(true);
16412 return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
16413 }
16414 }
16415 });
16416
16417})($jit.RGraph);
16418
16419
16420/*
16421 * File: Hypertree.js
16422 *
16423*/
16424
16425/*
16426 Complex
16427
16428 A multi-purpose Complex Class with common methods. Extended for the Hypertree.
16429
16430*/
16431/*
16432 moebiusTransformation
16433
16434 Calculates a moebius transformation for this point / complex.
16435 For more information go to:
16436 http://en.wikipedia.org/wiki/Moebius_transformation.
16437
16438 Parameters:
16439
16440 c - An initialized Complex instance representing a translation Vector.
16441*/
16442
16443Complex.prototype.moebiusTransformation = function(c) {
16444 var num = this.add(c);
16445 var den = c.$conjugate().$prod(this);
16446 den.x++;
16447 return num.$div(den);
16448};
16449
16450/*
16451 moebiusTransformation
16452
16453 Calculates a moebius transformation for the hyperbolic tree.
16454
16455 <http://en.wikipedia.org/wiki/Moebius_transformation>
16456
16457 Parameters:
16458
16459 graph - A <Graph> instance.
16460 pos - A <Complex>.
16461 prop - A property array.
16462 theta - Rotation angle.
16463 startPos - _optional_ start position.
16464*/
16465Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
16466 this.eachNode(graph, function(elem) {
16467 for ( var i = 0; i < prop.length; i++) {
16468 var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
16469 elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
16470 }
16471 }, flags);
16472};
16473
16474/*
16475 Class: Hypertree
16476
16477 A Hyperbolic Tree/Graph visualization.
16478
16479 Inspired by:
16480
16481 A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli).
16482 <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
16483
16484 Note:
16485
16486 This visualization was built and engineered from scratch, taking only the paper as inspiration, and only shares some features with the Hypertree described in the paper.
16487
16488 Implements:
16489
16490 All <Loader> methods
16491
16492 Constructor Options:
16493
16494 Inherits options from
16495
16496 - <Options.Canvas>
16497 - <Options.Controller>
16498 - <Options.Node>
16499 - <Options.Edge>
16500 - <Options.Label>
16501 - <Options.Events>
16502 - <Options.Tips>
16503 - <Options.NodeStyles>
16504 - <Options.Navigation>
16505
16506 Additionally, there are other parameters and some default values changed
16507
16508 radius - (string|number) Default's *auto*. The radius of the disc to plot the <Hypertree> in. 'auto' will take the smaller value from the width and height canvas dimensions. You can also set this to a custom value, for example *250*.
16509 offset - (number) Default's *0*. A number in the range [0, 1) that will be substracted to each node position to make a more compact <Hypertree>. This will avoid placing nodes too far from each other when a there's a selected node.
16510 fps - Described in <Options.Fx>. It's default value has been changed to *35*.
16511 duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
16512 Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
16513
16514 Instance Properties:
16515
16516 canvas - Access a <Canvas> instance.
16517 graph - Access a <Graph> instance.
16518 op - Access a <Hypertree.Op> instance.
16519 fx - Access a <Hypertree.Plot> instance.
16520 labels - Access a <Hypertree.Label> interface implementation.
16521
16522*/
16523
16524$jit.Hypertree = new Class( {
16525
16526 Implements: [ Loader, Extras, Layouts.Radial ],
16527
16528 initialize: function(controller) {
16529 var $Hypertree = $jit.Hypertree;
16530
16531 var config = {
16532 radius: "auto",
16533 offset: 0,
16534 Edge: {
16535 type: 'hyperline'
16536 },
16537 duration: 1500,
16538 fps: 35
16539 };
16540 this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
16541 "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
16542
16543 var canvasConfig = this.config;
16544 if(canvasConfig.useCanvas) {
16545 this.canvas = canvasConfig.useCanvas;
16546 this.config.labelContainer = this.canvas.id + '-label';
16547 } else {
16548 if(canvasConfig.background) {
16549 canvasConfig.background = $.merge({
16550 type: 'Circles'
16551 }, canvasConfig.background);
16552 }
16553 this.canvas = new Canvas(this, canvasConfig);
16554 this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
16555 }
16556
16557 this.graphOptions = {
16558 'klass': Polar,
16559 'Node': {
16560 'selected': false,
16561 'exist': true,
16562 'drawn': true
16563 }
16564 };
16565 this.graph = new Graph(this.graphOptions, this.config.Node,
16566 this.config.Edge);
16567 this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
16568 this.fx = new $Hypertree.Plot(this, $Hypertree);
16569 this.op = new $Hypertree.Op(this);
16570 this.json = null;
16571 this.root = null;
16572 this.busy = false;
16573 // initialize extras
16574 this.initializeExtras();
16575 },
16576
16577 /*
16578
16579 createLevelDistanceFunc
16580
16581 Returns the levelDistance function used for calculating a node distance
16582 to its origin. This function returns a function that is computed
16583 per level and not per node, such that all nodes with the same depth will have the
16584 same distance to the origin. The resulting function gets the
16585 parent node as parameter and returns a float.
16586
16587 */
16588 createLevelDistanceFunc: function() {
16589 // get max viz. length.
16590 var r = this.getRadius();
16591 // get max depth.
16592 var depth = 0, max = Math.max, config = this.config;
16593 this.graph.eachNode(function(node) {
16594 depth = max(node._depth, depth);
16595 }, "ignore");
16596 depth++;
16597 // node distance generator
16598 var genDistFunc = function(a) {
16599 return function(node) {
16600 node.scale = r;
16601 var d = node._depth + 1;
16602 var acum = 0, pow = Math.pow;
16603 while (d) {
16604 acum += pow(a, d--);
16605 }
16606 return acum - config.offset;
16607 };
16608 };
16609 // estimate better edge length.
16610 for ( var i = 0.51; i <= 1; i += 0.01) {
16611 var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
16612 if (valSeries >= 2) { return genDistFunc(i - 0.01); }
16613 }
16614 return genDistFunc(0.75);
16615 },
16616
16617 /*
16618 Method: getRadius
16619
16620 Returns the current radius of the visualization. If *config.radius* is *auto* then it
16621 calculates the radius by taking the smaller size of the <Canvas> widget.
16622
16623 See also:
16624
16625 <Canvas.getSize>
16626
16627 */
16628 getRadius: function() {
16629 var rad = this.config.radius;
16630 if (rad !== "auto") { return rad; }
16631 var s = this.canvas.getSize();
16632 return Math.min(s.width, s.height) / 2;
16633 },
16634
16635 /*
16636 Method: refresh
16637
16638 Computes positions and plots the tree.
16639
16640 Parameters:
16641
16642 reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
16643
16644 */
16645 refresh: function(reposition) {
16646 if (reposition) {
16647 this.reposition();
16648 this.graph.eachNode(function(node) {
16649 node.startPos.rho = node.pos.rho = node.endPos.rho;
16650 node.startPos.theta = node.pos.theta = node.endPos.theta;
16651 });
16652 } else {
16653 this.compute();
16654 }
16655 this.plot();
16656 },
16657
16658 /*
16659 reposition
16660
16661 Computes nodes' positions and restores the tree to its previous position.
16662
16663 For calculating nodes' positions the root must be placed on its origin. This method does this
16664 and then attemps to restore the hypertree to its previous position.
16665
16666 */
16667 reposition: function() {
16668 this.compute('end');
16669 var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
16670 Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
16671 'end', "ignore");
16672 this.graph.eachNode(function(node) {
16673 if (node.ignore) {
16674 node.endPos.rho = node.pos.rho;
16675 node.endPos.theta = node.pos.theta;
16676 }
16677 });
16678 },
16679
16680 /*
16681 Method: plot
16682
16683 Plots the <Hypertree>. This is a shortcut to *fx.plot*.
16684
16685 */
16686 plot: function() {
16687 this.fx.plot();
16688 },
16689
16690 /*
16691 Method: onClick
16692
16693 Animates the <Hypertree> to center the node specified by *id*.
16694
16695 Parameters:
16696
16697 id - A <Graph.Node> id.
16698 opt - (optional|object) An object containing some extra properties described below
16699 hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
16700
16701 Example:
16702
16703 (start code js)
16704 ht.onClick('someid');
16705 //or also...
16706 ht.onClick('someid', {
16707 hideLabels: false
16708 });
16709 (end code)
16710
16711 */
16712 onClick: function(id, opt) {
16713 var pos = this.graph.getNode(id).pos.getc(true);
16714 this.move(pos, opt);
16715 },
16716
16717 /*
16718 Method: move
16719
16720 Translates the tree to the given position.
16721
16722 Parameters:
16723
16724 pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
16725 opt - This object has been defined in <Hypertree.onClick>
16726
16727 Example:
16728
16729 (start code js)
16730 ht.move({ x: 0, y: 0.7 }, {
16731 hideLabels: false
16732 });
16733 (end code)
16734
16735 */
16736 move: function(pos, opt) {
16737 var versor = $C(pos.x, pos.y);
16738 if (this.busy === false && versor.norm() < 1) {
16739 this.busy = true;
16740 var root = this.graph.getClosestNodeToPos(versor), that = this;
16741 this.graph.computeLevels(root.id, 0);
16742 this.controller.onBeforeCompute(root);
16743 opt = $.merge( {
16744 onComplete: $.empty
16745 }, opt || {});
16746 this.fx.animate($.merge( {
16747 modes: [ 'moebius' ],
16748 hideLabels: true
16749 }, opt, {
16750 onComplete: function() {
16751 that.busy = false;
16752 opt.onComplete();
16753 }
16754 }), versor);
16755 }
16756 }
16757});
16758
16759$jit.Hypertree.$extend = true;
16760
16761(function(Hypertree) {
16762
16763 /*
16764 Class: Hypertree.Op
16765
16766 Custom extension of <Graph.Op>.
16767
16768 Extends:
16769
16770 All <Graph.Op> methods
16771
16772 See also:
16773
16774 <Graph.Op>
16775
16776 */
16777 Hypertree.Op = new Class( {
16778
16779 Implements: Graph.Op
16780
16781 });
16782
16783 /*
16784 Class: Hypertree.Plot
16785
16786 Custom extension of <Graph.Plot>.
16787
16788 Extends:
16789
16790 All <Graph.Plot> methods
16791
16792 See also:
16793
16794 <Graph.Plot>
16795
16796 */
16797 Hypertree.Plot = new Class( {
16798
16799 Implements: Graph.Plot
16800
16801 });
16802
16803 /*
16804 Object: Hypertree.Label
16805
16806 Custom extension of <Graph.Label>.
16807 Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
16808
16809 Extends:
16810
16811 All <Graph.Label> methods and subclasses.
16812
16813 See also:
16814
16815 <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
16816
16817 */
16818 Hypertree.Label = {};
16819
16820 /*
16821 Hypertree.Label.Native
16822
16823 Custom extension of <Graph.Label.Native>.
16824
16825 Extends:
16826
16827 All <Graph.Label.Native> methods
16828
16829 See also:
16830
16831 <Graph.Label.Native>
16832
16833 */
16834 Hypertree.Label.Native = new Class( {
16835 Implements: Graph.Label.Native,
16836
16837 initialize: function(viz) {
16838 this.viz = viz;
16839 },
16840
16841 renderLabel: function(canvas, node, controller) {
16842 var ctx = canvas.getCtx();
16843 var coord = node.pos.getc(true);
16844 var s = this.viz.getRadius();
16845 ctx.fillText(node.name, coord.x * s, coord.y * s);
16846 }
16847 });
16848
16849 /*
16850 Hypertree.Label.SVG
16851
16852 Custom extension of <Graph.Label.SVG>.
16853
16854 Extends:
16855
16856 All <Graph.Label.SVG> methods
16857
16858 See also:
16859
16860 <Graph.Label.SVG>
16861
16862 */
16863 Hypertree.Label.SVG = new Class( {
16864 Implements: Graph.Label.SVG,
16865
16866 initialize: function(viz) {
16867 this.viz = viz;
16868 },
16869
16870 /*
16871 placeLabel
16872
16873 Overrides abstract method placeLabel in <Graph.Plot>.
16874
16875 Parameters:
16876
16877 tag - A DOM label element.
16878 node - A <Graph.Node>.
16879 controller - A configuration/controller object passed to the visualization.
16880
16881 */
16882 placeLabel: function(tag, node, controller) {
16883 var pos = node.pos.getc(true),
16884 canvas = this.viz.canvas,
16885 ox = canvas.translateOffsetX,
16886 oy = canvas.translateOffsetY,
16887 sx = canvas.scaleOffsetX,
16888 sy = canvas.scaleOffsetY,
16889 radius = canvas.getSize(),
16890 r = this.viz.getRadius();
16891 var labelPos = {
16892 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
16893 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
16894 };
16895 tag.setAttribute('x', labelPos.x);
16896 tag.setAttribute('y', labelPos.y);
16897 controller.onPlaceLabel(tag, node);
16898 }
16899 });
16900
16901 /*
16902 Hypertree.Label.HTML
16903
16904 Custom extension of <Graph.Label.HTML>.
16905
16906 Extends:
16907
16908 All <Graph.Label.HTML> methods.
16909
16910 See also:
16911
16912 <Graph.Label.HTML>
16913
16914 */
16915 Hypertree.Label.HTML = new Class( {
16916 Implements: Graph.Label.HTML,
16917
16918 initialize: function(viz) {
16919 this.viz = viz;
16920 },
16921 /*
16922 placeLabel
16923
16924 Overrides abstract method placeLabel in <Graph.Plot>.
16925
16926 Parameters:
16927
16928 tag - A DOM label element.
16929 node - A <Graph.Node>.
16930 controller - A configuration/controller object passed to the visualization.
16931
16932 */
16933 placeLabel: function(tag, node, controller) {
16934 var pos = node.pos.getc(true),
16935 canvas = this.viz.canvas,
16936 ox = canvas.translateOffsetX,
16937 oy = canvas.translateOffsetY,
16938 sx = canvas.scaleOffsetX,
16939 sy = canvas.scaleOffsetY,
16940 radius = canvas.getSize(),
16941 r = this.viz.getRadius();
16942 var labelPos = {
16943 x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
16944 y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
16945 };
16946 var style = tag.style;
16947 style.left = labelPos.x + 'px';
16948 style.top = labelPos.y + 'px';
16949 style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
16950
16951 controller.onPlaceLabel(tag, node);
16952 }
16953 });
16954
16955 /*
16956 Class: Hypertree.Plot.NodeTypes
16957
16958 This class contains a list of <Graph.Node> built-in types.
16959 Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
16960
16961 You can add your custom node types, customizing your visualization to the extreme.
16962
16963 Example:
16964
16965 (start code js)
16966 Hypertree.Plot.NodeTypes.implement({
16967 'mySpecialType': {
16968 'render': function(node, canvas) {
16969 //print your custom node to canvas
16970 },
16971 //optional
16972 'contains': function(node, pos) {
16973 //return true if pos is inside the node or false otherwise
16974 }
16975 }
16976 });
16977 (end code)
16978
16979 */
16980 Hypertree.Plot.NodeTypes = new Class({
16981 'none': {
16982 'render': $.empty,
16983 'contains': $.lambda(false)
16984 },
16985 'circle': {
16986 'render': function(node, canvas) {
16987 var nconfig = this.node,
16988 dim = node.getData('dim'),
16989 p = node.pos.getc();
16990 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
16991 p.$scale(node.scale);
16992 if (dim > 0.2) {
16993 this.nodeHelper.circle.render('fill', p, dim, canvas);
16994 }
16995 },
16996 'contains': function(node, pos) {
16997 var dim = node.getData('dim'),
16998 npos = node.pos.getc().$scale(node.scale);
16999 return this.nodeHelper.circle.contains(npos, pos, dim);
17000 }
17001 },
17002 'ellipse': {
17003 'render': function(node, canvas) {
17004 var pos = node.pos.getc().$scale(node.scale),
17005 width = node.getData('width'),
17006 height = node.getData('height');
17007 this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
17008 },
17009 'contains': function(node, pos) {
17010 var width = node.getData('width'),
17011 height = node.getData('height'),
17012 npos = node.pos.getc().$scale(node.scale);
17013 return this.nodeHelper.circle.contains(npos, pos, width, height);
17014 }
17015 },
17016 'square': {
17017 'render': function(node, canvas) {
17018 var nconfig = this.node,
17019 dim = node.getData('dim'),
17020 p = node.pos.getc();
17021 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17022 p.$scale(node.scale);
17023 if (dim > 0.2) {
17024 this.nodeHelper.square.render('fill', p, dim, canvas);
17025 }
17026 },
17027 'contains': function(node, pos) {
17028 var dim = node.getData('dim'),
17029 npos = node.pos.getc().$scale(node.scale);
17030 return this.nodeHelper.square.contains(npos, pos, dim);
17031 }
17032 },
17033 'rectangle': {
17034 'render': function(node, canvas) {
17035 var nconfig = this.node,
17036 width = node.getData('width'),
17037 height = node.getData('height'),
17038 pos = node.pos.getc();
17039 width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
17040 height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
17041 pos.$scale(node.scale);
17042 if (width > 0.2 && height > 0.2) {
17043 this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
17044 }
17045 },
17046 'contains': function(node, pos) {
17047 var width = node.getData('width'),
17048 height = node.getData('height'),
17049 npos = node.pos.getc().$scale(node.scale);
17050 return this.nodeHelper.rectangle.contains(npos, pos, width, height);
17051 }
17052 },
17053 'triangle': {
17054 'render': function(node, canvas) {
17055 var nconfig = this.node,
17056 dim = node.getData('dim'),
17057 p = node.pos.getc();
17058 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17059 p.$scale(node.scale);
17060 if (dim > 0.2) {
17061 this.nodeHelper.triangle.render('fill', p, dim, canvas);
17062 }
17063 },
17064 'contains': function(node, pos) {
17065 var dim = node.getData('dim'),
17066 npos = node.pos.getc().$scale(node.scale);
17067 return this.nodeHelper.triangle.contains(npos, pos, dim);
17068 }
17069 },
17070 'star': {
17071 'render': function(node, canvas) {
17072 var nconfig = this.node,
17073 dim = node.getData('dim'),
17074 p = node.pos.getc();
17075 dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
17076 p.$scale(node.scale);
17077 if (dim > 0.2) {
17078 this.nodeHelper.star.render('fill', p, dim, canvas);
17079 }
17080 },
17081 'contains': function(node, pos) {
17082 var dim = node.getData('dim'),
17083 npos = node.pos.getc().$scale(node.scale);
17084 return this.nodeHelper.star.contains(npos, pos, dim);
17085 }
17086 }
17087 });
17088
17089 /*
17090 Class: Hypertree.Plot.EdgeTypes
17091
17092 This class contains a list of <Graph.Adjacence> built-in types.
17093 Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
17094
17095 You can add your custom edge types, customizing your visualization to the extreme.
17096
17097 Example:
17098
17099 (start code js)
17100 Hypertree.Plot.EdgeTypes.implement({
17101 'mySpecialType': {
17102 'render': function(adj, canvas) {
17103 //print your custom edge to canvas
17104 },
17105 //optional
17106 'contains': function(adj, pos) {
17107 //return true if pos is inside the arc or false otherwise
17108 }
17109 }
17110 });
17111 (end code)
17112
17113 */
17114 Hypertree.Plot.EdgeTypes = new Class({
17115 'none': $.empty,
17116 'line': {
17117 'render': function(adj, canvas) {
17118 var from = adj.nodeFrom.pos.getc(true),
17119 to = adj.nodeTo.pos.getc(true),
17120 r = adj.nodeFrom.scale;
17121 this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
17122 },
17123 'contains': function(adj, pos) {
17124 var from = adj.nodeFrom.pos.getc(true),
17125 to = adj.nodeTo.pos.getc(true),
17126 r = adj.nodeFrom.scale;
17127 this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
17128 }
17129 },
17130 'arrow': {
17131 'render': function(adj, canvas) {
17132 var from = adj.nodeFrom.pos.getc(true),
17133 to = adj.nodeTo.pos.getc(true),
17134 r = adj.nodeFrom.scale,
17135 dim = adj.getData('dim'),
17136 direction = adj.data.$direction,
17137 inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
17138 this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
17139 },
17140 'contains': function(adj, pos) {
17141 var from = adj.nodeFrom.pos.getc(true),
17142 to = adj.nodeTo.pos.getc(true),
17143 r = adj.nodeFrom.scale;
17144 this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
17145 }
17146 },
17147 'hyperline': {
17148 'render': function(adj, canvas) {
17149 var from = adj.nodeFrom.pos.getc(),
17150 to = adj.nodeTo.pos.getc(),
17151 dim = this.viz.getRadius();
17152 this.edgeHelper.hyperline.render(from, to, dim, canvas);
17153 },
17154 'contains': $.lambda(false)
17155 }
17156 });
17157
17158})($jit.Hypertree);
17159
17160
17161
17162
17163 })();