]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/SugarCharts/Jit/js/Jit/jit.js
Release 6.2.0
[Github/sugarcrm.git] / jssource / src_files / include / SugarCharts / Jit / js / Jit / jit.js
1 /*
2   Copyright (c) 2010, Nicolas Garcia Belmonte
3   All rights reserved
4
5   > Redistribution and use in source and binary forms, with or without
6   > modification, are permitted provided that the following conditions are met:
7   >      * Redistributions of source code must retain the above copyright
8   >        notice, this list of conditions and the following disclaimer.
9   >      * Redistributions in binary form must reproduce the above copyright
10   >        notice, this list of conditions and the following disclaimer in the
11   >        documentation and/or other materials provided with the distribution.
12   >      * Neither the name of the organization nor the
13   >        names of its contributors may be used to endorse or promote products
14   >        derived from this software without specific prior written permission.
15   >
16   >  THIS SOFTWARE IS PROVIDED BY NICOLAS GARCIA BELMONTE ``AS IS'' AND ANY
17   >  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   >  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   >  DISCLAIMED. IN NO EVENT SHALL NICOLAS GARCIA BELMONTE BE LIABLE FOR ANY
20   >  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   >  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   >  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   >  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   >  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   >  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27  
28  /** Lam Huynh on 10/10/2010 added funnel,guage charts **/
29  /** Lam Huynh on 02/27/2011 added image exporting **/
30  /** Lam Huynh on 02/23/2011 added line charts **/
31  
32  (function () { 
33
34 /*
35   File: Core.js
36
37  */
38
39 /*
40  Object: $jit
41  
42  Defines the namespace for all library Classes and Objects. 
43  This variable is the *only* global variable defined in the Toolkit. 
44  There are also other interesting properties attached to this variable described below.
45  */
46 window.$jit = function(w) {
47   w = w || window;
48   for(var k in $jit) {
49     if($jit[k].$extend) {
50       w[k] = $jit[k];
51     }
52   }
53 };
54
55 $jit.version = '2.0.0b';
56 /*
57   Object: $jit.id
58   
59   Works just like *document.getElementById*
60   
61   Example:
62   (start code js)
63   var element = $jit.id('elementId');
64   (end code)
65
66 */
67
68 /*
69  Object: $jit.util
70  
71  Contains utility functions.
72  
73  Some of the utility functions and the Class system were based in the MooTools Framework 
74  <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. 
75  MIT license <http://mootools.net/license.txt>.
76  
77  These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
78  I'd suggest you to use the functions from those libraries instead of using these, since their functions 
79  are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
80  
81  */
82 var $ = function(d) {
83   return document.getElementById(d);
84 };
85
86 $.empty = function() {
87 };
88
89 function pad(number, length) {
90    
91     var str = '' + number;
92     while (str.length < length) {
93         str =  str + '0';
94     }
95    
96     return str;
97
98 };
99
100 var Url = {
101  
102         // public method for url encoding
103         encode : function (string) {
104                 return escape(this._utf8_encode(string));
105         },
106  
107         // public method for url decoding
108         decode : function (string) {
109                 return this._utf8_decode(unescape(string));
110         },
111  
112         // private method for UTF-8 encoding
113         _utf8_encode : function (string) {
114                 string = string.replace(/\r\n/g,"\n");
115                 var utftext = "";
116  
117                 for (var n = 0; n < string.length; n++) {
118  
119                         var c = string.charCodeAt(n);
120  
121                         if (c < 128) {
122                                 utftext += String.fromCharCode(c);
123                         }
124                         else if((c > 127) && (c < 2048)) {
125                                 utftext += String.fromCharCode((c >> 6) | 192);
126                                 utftext += String.fromCharCode((c & 63) | 128);
127                         }
128                         else {
129                                 utftext += String.fromCharCode((c >> 12) | 224);
130                                 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
131                                 utftext += String.fromCharCode((c & 63) | 128);
132                         }
133  
134                 }
135  
136                 return utftext;
137         },
138  
139         // private method for UTF-8 decoding
140         _utf8_decode : function (utftext) {
141                 var string = "";
142                 var i = 0;
143                 var c = c1 = c2 = 0;
144  
145                 while ( i < utftext.length ) {
146  
147                         c = utftext.charCodeAt(i);
148  
149                         if (c < 128) {
150                                 string += String.fromCharCode(c);
151                                 i++;
152                         }
153                         else if((c > 191) && (c < 224)) {
154                                 c2 = utftext.charCodeAt(i+1);
155                                 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
156                                 i += 2;
157                         }
158                         else {
159                                 c2 = utftext.charCodeAt(i+1);
160                                 c3 = utftext.charCodeAt(i+2);
161                                 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
162                                 i += 3;
163                         }
164  
165                 }
166  
167                 return string;
168         }
169  
170 };
171
172 Array.prototype.sum = function() {
173   return (! this.length) ? 0 : this.slice(1).sum() +
174       ((typeof this[0] == 'number') ? this[0] : 0);
175 };
176
177 function array_match(needle, haystack) {
178     var length = haystack.length;
179     var indexValue = new Array();
180     for(var i = 0, count = 0; i < length; i++) {
181         if(haystack[i] == needle) {
182                 indexValue[count] = i;
183                 count++;
184         }
185     }
186     return new Array(count,indexValue);
187 };
188
189 $.roundedRect = function (ctx,x,y,width,height,radius,fillType){
190   ctx.beginPath();
191   ctx.moveTo(x,y+radius);
192   ctx.lineTo(x,y+height-radius);
193   ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
194   ctx.lineTo(x+width-radius,y+height);
195   ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
196   ctx.lineTo(x+width,y+radius);
197   ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
198   ctx.lineTo(x+radius,y);
199   ctx.quadraticCurveTo(x,y,x,y+radius);
200   if(fillType=="fill") {
201         ctx.fill();
202   } else {
203         ctx.stroke();
204         }
205 };
206
207 $.saveImageFile = function (id,jsonfilename,imageExt) {
208         var parts = jsonfilename.split("/");
209         var filename = parts[2].replace(".js","."+imageExt);
210         var oCanvas = document.getElementById(id+"-canvas");
211         
212         if(oCanvas) {
213                 if(imageExt == "jpg") {
214                         var strDataURI = oCanvas.toDataURL("image/jpeg"); 
215                 } else {
216                         var strDataURI = oCanvas.toDataURL("image/png");
217                 }
218                 var handleFailure = function(o){
219                         //alert('failed to write image' + filename);
220                         //remove alert since chrome triggers this function when user navigates away from page before image gets written.
221                 }       
222                 var handleSuccess = function(o){
223                 }                       
224                 var callback =
225                 {
226                   success:handleSuccess,
227                   failure:handleFailure,
228                   argument: { foo:'foo', bar:''}
229                 };
230                 var path = "index.php?action=DynamicAction&DynamicAction=saveImage&module=Charts&to_pdf=1";
231                 var postData = "imageStr=" + strDataURI + "&filename=" + filename;
232                 var request = YAHOO.util.Connect.asyncRequest('POST', path, callback, postData);
233         }
234 };
235
236 $.saveImageTest = function (id,jsonfilename,imageExt) {
237                 if(typeof FlashCanvas != "undefined") {
238                         setTimeout(function(){$.saveImageFile(id,jsonfilename,imageExt)},10000);
239                 } else {
240                         $.saveImageFile(id,jsonfilename,imageExt);
241                 }
242         };
243 /*
244   Method: extend
245   
246   Augment an object by appending another object's properties.
247   
248   Parameters:
249   
250   original - (object) The object to be extended.
251   extended - (object) An object which properties are going to be appended to the original object.
252   
253   Example:
254   (start code js)
255   $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
256   (end code)
257 */
258 $.extend = function(original, extended) {
259   for ( var key in (extended || {}))
260     original[key] = extended[key];
261   return original;
262 };
263
264 $.lambda = function(value) {
265   return (typeof value == 'function') ? value : function() {
266     return value;
267   };
268 };
269
270 $.time = Date.now || function() {
271   return +new Date;
272 };
273
274 /*
275   Method: splat
276   
277   Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
278   
279   Parameters:
280   
281   obj - (mixed) The object to be wrapped in an array.
282   
283   Example:
284   (start code js)
285   $jit.util.splat(3);   //[3]
286   $jit.util.splat([3]); //[3]
287   (end code)
288 */
289 $.splat = function(obj) {
290   var type = $.type(obj);
291   return type ? ((type != 'array') ? [ obj ] : obj) : [];
292 };
293
294 $.type = function(elem) {
295   var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
296   if(type != 'object') return type;
297   if(elem && elem.$$family) return elem.$$family;
298   return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
299 };
300 $.type.s = Object.prototype.toString;
301
302 /*
303   Method: each
304   
305   Iterates through an iterable applying *f*.
306   
307   Parameters:
308   
309   iterable - (array) The original array.
310   fn - (function) The function to apply to the array elements.
311   
312   Example:
313   (start code js)
314   $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
315   (end code)
316 */
317 $.each = function(iterable, fn) {
318   var type = $.type(iterable);
319   if (type == 'object') {
320     for ( var key in iterable)
321       fn(iterable[key], key);
322   } else {
323     for ( var i = 0, l = iterable.length; i < l; i++)
324       fn(iterable[i], i);
325   }
326 };
327
328 $.indexOf = function(array, item) {
329   if(Array.indexOf) return array.indexOf(item);
330   for(var i=0,l=array.length; i<l; i++) {
331     if(array[i] === item) return i;
332   }
333   return -1;
334 };
335
336 /*
337   Method: map
338   
339   Maps or collects an array by applying *f*.
340   
341   Parameters:
342   
343   array - (array) The original array.
344   f - (function) The function to apply to the array elements.
345   
346   Example:
347   (start code js)
348   $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
349   (end code)
350 */
351 $.map = function(array, f) {
352   var ans = [];
353   $.each(array, function(elem, i) {
354     ans.push(f(elem, i));
355   });
356   return ans;
357 };
358
359 /*
360   Method: reduce
361   
362   Iteratively applies the binary function *f* storing the result in an accumulator.
363   
364   Parameters:
365   
366   array - (array) The original array.
367   f - (function) The function to apply to the array elements.
368   opt - (optional|mixed) The starting value for the acumulator.
369   
370   Example:
371   (start code js)
372   $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
373   (end code)
374 */
375 $.reduce = function(array, f, opt) {
376   var l = array.length;
377   if(l==0) return opt;
378   var acum = arguments.length == 3? opt : array[--l];
379   while(l--) {
380     acum = f(acum, array[l]);
381   }
382   return acum;
383 };
384
385 /*
386   Method: merge
387   
388   Merges n-objects and their sub-objects creating a new, fresh object.
389   
390   Parameters:
391   
392   An arbitrary number of objects.
393   
394   Example:
395   (start code js)
396   $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
397   (end code)
398 */
399 $.merge = function() {
400   var mix = {};
401   for ( var i = 0, l = arguments.length; i < l; i++) {
402     var object = arguments[i];
403     if ($.type(object) != 'object')
404       continue;
405     for ( var key in object) {
406       var op = object[key], mp = mix[key];
407       mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
408           .merge(mp, op) : $.unlink(op);
409     }
410   }
411   return mix;
412 };
413
414 $.unlink = function(object) {
415   var unlinked;
416   switch ($.type(object)) {
417   case 'object':
418     unlinked = {};
419     for ( var p in object)
420       unlinked[p] = $.unlink(object[p]);
421     break;
422   case 'array':
423     unlinked = [];
424     for ( var i = 0, l = object.length; i < l; i++)
425       unlinked[i] = $.unlink(object[i]);
426     break;
427   default:
428     return object;
429   }
430   return unlinked;
431 };
432
433 $.zip = function() {
434   if(arguments.length === 0) return [];
435   for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
436     for(var i=0, row=[]; i<l; i++) {
437       row.push(arguments[i][j]);
438     }
439     ans.push(row);
440   }
441   return ans;
442 };
443
444 /*
445   Method: rgbToHex
446   
447   Converts an RGB array into a Hex string.
448   
449   Parameters:
450   
451   srcArray - (array) An array with R, G and B values
452   
453   Example:
454   (start code js)
455   $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
456   (end code)
457 */
458 $.rgbToHex = function(srcArray, array) {
459   if (srcArray.length < 3)
460     return null;
461   if (srcArray.length == 4 && srcArray[3] == 0 && !array)
462     return 'transparent';
463   var hex = [];
464   for ( var i = 0; i < 3; i++) {
465     var bit = (srcArray[i] - 0).toString(16);
466     hex.push(bit.length == 1 ? '0' + bit : bit);
467   }
468   return array ? hex : '#' + hex.join('');
469 };
470
471 /*
472   Method: hexToRgb
473   
474   Converts an Hex color string into an RGB array.
475   
476   Parameters:
477   
478   hex - (string) A color hex string.
479   
480   Example:
481   (start code js)
482   $jit.util.hexToRgb('#fff'); //[255, 255, 255]
483   (end code)
484 */
485 $.hexToRgb = function(hex) {
486   if (hex.length != 7) {
487     hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
488     hex.shift();
489     if (hex.length != 3)
490       return null;
491     var rgb = [];
492     for ( var i = 0; i < 3; i++) {
493       var value = hex[i];
494       if (value.length == 1)
495         value += value;
496       rgb.push(parseInt(value, 16));
497     }
498     return rgb;
499   } else {
500     hex = parseInt(hex.slice(1), 16);
501     return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
502   }
503 };
504
505 $.destroy = function(elem) {
506   $.clean(elem);
507   if (elem.parentNode)
508     elem.parentNode.removeChild(elem);
509   if (elem.clearAttributes)
510     elem.clearAttributes();
511 };
512
513 $.clean = function(elem) {
514   for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
515     $.destroy(ch[i]);
516   }
517 };
518
519 /*
520   Method: addEvent
521   
522   Cross-browser add event listener.
523   
524   Parameters:
525   
526   obj - (obj) The Element to attach the listener to.
527   type - (string) The listener type. For example 'click', or 'mousemove'.
528   fn - (function) The callback function to be used when the event is fired.
529   
530   Example:
531   (start code js)
532   $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
533   (end code)
534 */
535 $.addEvent = function(obj, type, fn) {
536   if (obj.addEventListener)
537     obj.addEventListener(type, fn, false);
538   else
539     obj.attachEvent('on' + type, fn);
540 };
541
542 $.addEvents = function(obj, typeObj) {
543   for(var type in typeObj) {
544     $.addEvent(obj, type, typeObj[type]);
545   }
546 };
547
548 $.hasClass = function(obj, klass) {
549   return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
550 };
551
552 $.addClass = function(obj, klass) {
553   if (!$.hasClass(obj, klass))
554     obj.className = (obj.className + " " + klass);
555 };
556
557 $.removeClass = function(obj, klass) {
558   obj.className = obj.className.replace(new RegExp(
559       '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
560 };
561
562 $.getPos = function(elem) {
563   var offset = getOffsets(elem);
564   var scroll = getScrolls(elem);
565   return {
566     x: offset.x - scroll.x,
567     y: offset.y - scroll.y
568   };
569
570   function getOffsets(elem) {
571     var position = {
572       x: 0,
573       y: 0
574     };
575     while (elem && !isBody(elem)) {
576       position.x += elem.offsetLeft;
577       position.y += elem.offsetTop;
578       elem = elem.offsetParent;
579     }
580     return position;
581   }
582
583   function getScrolls(elem) {
584     var position = {
585       x: 0,
586       y: 0
587     };
588     while (elem && !isBody(elem)) {
589       position.x += elem.scrollLeft;
590       position.y += elem.scrollTop;
591       elem = elem.parentNode;
592     }
593     return position;
594   }
595
596   function isBody(element) {
597     return (/^(?:body|html)$/i).test(element.tagName);
598   }
599 };
600
601 $.event = {
602   get: function(e, win) {
603     win = win || window;
604     return e || win.event;
605   },
606   getWheel: function(e) {
607     return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
608   },
609   isRightClick: function(e) {
610     return (e.which == 3 || e.button == 2);
611   },
612   getPos: function(e, win) {
613     // get mouse position
614     win = win || window;
615     e = e || win.event;
616     var doc = win.document;
617     doc = doc.documentElement || doc.body;
618     //TODO(nico): make touch event handling better
619     if(e.touches && e.touches.length) {
620       e = e.touches[0];
621     }
622     var page = {
623       x: e.pageX || (e.clientX + doc.scrollLeft),
624       y: e.pageY || (e.clientY + doc.scrollTop)
625     };
626     return page;
627   },
628   stop: function(e) {
629     if (e.stopPropagation) e.stopPropagation();
630     e.cancelBubble = true;
631     if (e.preventDefault) e.preventDefault();
632     else e.returnValue = false;
633   }
634 };
635
636 $jit.util = $jit.id = $;
637
638 var Class = function(properties) {
639   properties = properties || {};
640   var klass = function() {
641     for ( var key in this) {
642       if (typeof this[key] != 'function')
643         this[key] = $.unlink(this[key]);
644     }
645     this.constructor = klass;
646     if (Class.prototyping)
647       return this;
648     var instance = this.initialize ? this.initialize.apply(this, arguments)
649         : this;
650     //typize
651     this.$$family = 'class';
652     return instance;
653   };
654
655   for ( var mutator in Class.Mutators) {
656     if (!properties[mutator])
657       continue;
658     properties = Class.Mutators[mutator](properties, properties[mutator]);
659     delete properties[mutator];
660   }
661
662   $.extend(klass, this);
663   klass.constructor = Class;
664   klass.prototype = properties;
665   return klass;
666 };
667
668 Class.Mutators = {
669
670   Implements: function(self, klasses) {
671     $.each($.splat(klasses), function(klass) {
672       Class.prototyping = klass;
673       var instance = (typeof klass == 'function') ? new klass : klass;
674       for ( var prop in instance) {
675         if (!(prop in self)) {
676           self[prop] = instance[prop];
677         }
678       }
679       delete Class.prototyping;
680     });
681     return self;
682   }
683
684 };
685
686 $.extend(Class, {
687
688   inherit: function(object, properties) {
689     for ( var key in properties) {
690       var override = properties[key];
691       var previous = object[key];
692       var type = $.type(override);
693       if (previous && type == 'function') {
694         if (override != previous) {
695           Class.override(object, key, override);
696         }
697       } else if (type == 'object') {
698         object[key] = $.merge(previous, override);
699       } else {
700         object[key] = override;
701       }
702     }
703     return object;
704   },
705
706   override: function(object, name, method) {
707     var parent = Class.prototyping;
708     if (parent && object[name] != parent[name])
709       parent = null;
710     var override = function() {
711       var previous = this.parent;
712       this.parent = parent ? parent[name] : object[name];
713       var value = method.apply(this, arguments);
714       this.parent = previous;
715       return value;
716     };
717     object[name] = override;
718   }
719
720 });
721
722 Class.prototype.implement = function() {
723   var proto = this.prototype;
724   $.each(Array.prototype.slice.call(arguments || []), function(properties) {
725     Class.inherit(proto, properties);
726   });
727   return this;
728 };
729
730 $jit.Class = Class;
731
732 /*
733   Object: $jit.json
734   
735   Provides JSON utility functions.
736   
737   Most of these functions are JSON-tree traversal and manipulation functions.
738 */
739 $jit.json = {
740   /*
741      Method: prune
742   
743      Clears all tree nodes having depth greater than maxLevel.
744   
745      Parameters:
746   
747         tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
748         maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
749
750   */
751   prune: function(tree, maxLevel) {
752     this.each(tree, function(elem, i) {
753       if (i == maxLevel && elem.children) {
754         delete elem.children;
755         elem.children = [];
756       }
757     });
758   },
759   /*
760      Method: getParent
761   
762      Returns the parent node of the node having _id_ as id.
763   
764      Parameters:
765   
766         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
767         id - (string) The _id_ of the child node whose parent will be returned.
768
769     Returns:
770
771         A tree JSON node if any, or false otherwise.
772   
773   */
774   getParent: function(tree, id) {
775     if (tree.id == id)
776       return false;
777     var ch = tree.children;
778     if (ch && ch.length > 0) {
779       for ( var i = 0; i < ch.length; i++) {
780         if (ch[i].id == id)
781           return tree;
782         else {
783           var ans = this.getParent(ch[i], id);
784           if (ans)
785             return ans;
786         }
787       }
788     }
789     return false;
790   },
791   /*
792      Method: getSubtree
793   
794      Returns the subtree that matches the given id.
795   
796      Parameters:
797   
798         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
799         id - (string) A node *unique* identifier.
800   
801      Returns:
802   
803         A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
804
805   */
806   getSubtree: function(tree, id) {
807     if (tree.id == id)
808       return tree;
809     for ( var i = 0, ch = tree.children; i < ch.length; i++) {
810       var t = this.getSubtree(ch[i], id);
811       if (t != null)
812         return t;
813     }
814     return null;
815   },
816   /*
817      Method: eachLevel
818   
819       Iterates on tree nodes with relative depth less or equal than a specified level.
820   
821      Parameters:
822   
823         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
824         initLevel - (number) An integer specifying the initial relative level. Usually zero.
825         toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
826         action - (function) A function that receives a node and an integer specifying the actual level of the node.
827           
828     Example:
829    (start code js)
830      $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
831         alert(node.name + ' ' + depth);
832      });
833    (end code)
834   */
835   eachLevel: function(tree, initLevel, toLevel, action) {
836     if (initLevel <= toLevel) {
837       action(tree, initLevel);
838       if(!tree.children) return;
839       for ( var i = 0, ch = tree.children; i < ch.length; i++) {
840         this.eachLevel(ch[i], initLevel + 1, toLevel, action);
841       }
842     }
843   },
844   /*
845      Method: each
846   
847       A JSON tree iterator.
848   
849      Parameters:
850   
851         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
852         action - (function) A function that receives a node.
853
854     Example:
855     (start code js)
856       $jit.json.each(tree, function(node) {
857         alert(node.name);
858       });
859     (end code)
860           
861   */
862   each: function(tree, action) {
863     this.eachLevel(tree, 0, Number.MAX_VALUE, action);
864   }
865 };
866
867
868 /*
869      An object containing multiple type of transformations. 
870 */
871
872 $jit.Trans = {
873   $extend: true,
874   
875   linear: function(p){
876     return p;
877   }
878 };
879
880 var Trans = $jit.Trans;
881
882 (function(){
883
884   var makeTrans = function(transition, params){
885     params = $.splat(params);
886     return $.extend(transition, {
887       easeIn: function(pos){
888         return transition(pos, params);
889       },
890       easeOut: function(pos){
891         return 1 - transition(1 - pos, params);
892       },
893       easeInOut: function(pos){
894         return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
895             2 * (1 - pos), params)) / 2;
896       }
897     });
898   };
899
900   var transitions = {
901
902     Pow: function(p, x){
903       return Math.pow(p, x[0] || 6);
904     },
905
906     Expo: function(p){
907       return Math.pow(2, 8 * (p - 1));
908     },
909
910     Circ: function(p){
911       return 1 - Math.sin(Math.acos(p));
912     },
913
914     Sine: function(p){
915       return 1 - Math.sin((1 - p) * Math.PI / 2);
916     },
917
918     Back: function(p, x){
919       x = x[0] || 1.618;
920       return Math.pow(p, 2) * ((x + 1) * p - x);
921     },
922
923     Bounce: function(p){
924       var value;
925       for ( var a = 0, b = 1; 1; a += b, b /= 2) {
926         if (p >= (7 - 4 * a) / 11) {
927           value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
928           break;
929         }
930       }
931       return value;
932     },
933
934     Elastic: function(p, x){
935       return Math.pow(2, 10 * --p)
936           * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
937     }
938
939   };
940
941   $.each(transitions, function(val, key){
942     Trans[key] = makeTrans(val);
943   });
944
945   $.each( [
946       'Quad', 'Cubic', 'Quart', 'Quint'
947   ], function(elem, i){
948     Trans[elem] = makeTrans(function(p){
949       return Math.pow(p, [
950         i + 2
951       ]);
952     });
953   });
954
955 })();
956
957 /*
958    A Class that can perform animations for generic objects.
959
960    If you are looking for animation transitions please take a look at the <Trans> object.
961
962    Used by:
963
964    <Graph.Plot>
965    
966    Based on:
967    
968    The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
969
970 */
971
972 var Animation = new Class( {
973
974   initialize: function(options){
975     this.setOptions(options);
976   },
977
978   setOptions: function(options){
979     var opt = {
980       duration: 2500,
981       fps: 40,
982       transition: Trans.Quart.easeInOut,
983       compute: $.empty,
984       complete: $.empty,
985       link: 'ignore'
986     };
987     this.opt = $.merge(opt, options || {});
988     return this;
989   },
990
991   step: function(){
992     var time = $.time(), opt = this.opt;
993     if (time < this.time + opt.duration) {
994       var delta = opt.transition((time - this.time) / opt.duration);
995       opt.compute(delta);
996     } else {
997       this.timer = clearInterval(this.timer);
998       opt.compute(1);
999       opt.complete();
1000     }
1001   },
1002
1003   start: function(){
1004     if (!this.check())
1005       return this;
1006     this.time = 0;
1007     this.startTimer();
1008     return this;
1009   },
1010
1011   startTimer: function(){
1012     var that = this, fps = this.opt.fps;
1013     if (this.timer)
1014       return false;
1015     this.time = $.time() - this.time;
1016     this.timer = setInterval((function(){
1017       that.step();
1018     }), Math.round(1000 / fps));
1019     return true;
1020   },
1021
1022   pause: function(){
1023     this.stopTimer();
1024     return this;
1025   },
1026
1027   resume: function(){
1028     this.startTimer();
1029     return this;
1030   },
1031
1032   stopTimer: function(){
1033     if (!this.timer)
1034       return false;
1035     this.time = $.time() - this.time;
1036     this.timer = clearInterval(this.timer);
1037     return true;
1038   },
1039
1040   check: function(){
1041     if (!this.timer)
1042       return true;
1043     if (this.opt.link == 'cancel') {
1044       this.stopTimer();
1045       return true;
1046     }
1047     return false;
1048   }
1049 });
1050
1051
1052 var Options = function() {
1053   var args = arguments;
1054   for(var i=0, l=args.length, ans={}; i<l; i++) {
1055     var opt = Options[args[i]];
1056     if(opt.$extend) {
1057       $.extend(ans, opt);
1058     } else {
1059       ans[args[i]] = opt;  
1060     }
1061   }
1062   return ans;
1063 };
1064
1065 /*
1066  * File: Options.AreaChart.js
1067  *
1068 */
1069
1070 /*
1071   Object: Options.AreaChart
1072   
1073   <AreaChart> options. 
1074   Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1075   
1076   Syntax:
1077   
1078   (start code js)
1079
1080   Options.AreaChart = {
1081     animate: true,
1082     labelOffset: 3,
1083     type: 'stacked',
1084     selectOnHover: true,
1085     showAggregates: true,
1086     showLabels: true,
1087     filterOnClick: false,
1088     restoreOnRightClick: false
1089   };
1090   
1091   (end code)
1092   
1093   Example:
1094   
1095   (start code js)
1096
1097   var areaChart = new $jit.AreaChart({
1098     animate: true,
1099     type: 'stacked:gradient',
1100     selectOnHover: true,
1101     filterOnClick: true,
1102     restoreOnRightClick: true
1103   });
1104   
1105   (end code)
1106
1107   Parameters:
1108   
1109   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1110   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1111   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1112   selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1113   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1114   showLabels - (boolean) Default's *true*. Display the name of the slots.
1115   filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1116   restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1117   
1118 */
1119   
1120 Options.AreaChart = {
1121   $extend: true,
1122
1123   animate: true,
1124   labelOffset: 3, // label offset
1125   type: 'stacked', // gradient
1126   Tips: {
1127     enable: false,
1128     onShow: $.empty,
1129     onHide: $.empty
1130   },
1131   Events: {
1132     enable: false,
1133     onClick: $.empty
1134   },
1135   selectOnHover: true,
1136   showAggregates: true,
1137   showLabels: true,
1138   filterOnClick: false,
1139   restoreOnRightClick: false
1140 };
1141
1142 /*
1143  * File: Options.Margin.js
1144  *
1145 */
1146
1147 /*
1148   Object: Options.Margin
1149   
1150   Canvas drawing margins. 
1151   
1152   Syntax:
1153   
1154   (start code js)
1155
1156   Options.Margin = {
1157     top: 0,
1158     left: 0,
1159     right: 0,
1160     bottom: 0
1161   };
1162   
1163   (end code)
1164   
1165   Example:
1166   
1167   (start code js)
1168
1169   var viz = new $jit.Viz({
1170     Margin: {
1171       right: 10,
1172       bottom: 20
1173     }
1174   });
1175   
1176   (end code)
1177
1178   Parameters:
1179   
1180   top - (number) Default's *0*. Top margin.
1181   left - (number) Default's *0*. Left margin.
1182   right - (number) Default's *0*. Right margin.
1183   bottom - (number) Default's *0*. Bottom margin.
1184   
1185 */
1186
1187 Options.Margin = {
1188   $extend: false,
1189   
1190   top: 0,
1191   left: 0,
1192   right: 0,
1193   bottom: 0
1194 };
1195
1196 /*
1197  * File: Options.Canvas.js
1198  *
1199 */
1200
1201 /*
1202   Object: Options.Canvas
1203   
1204   These are Canvas general options, like where to append it in the DOM, its dimensions, background, 
1205   and other more advanced options.
1206   
1207   Syntax:
1208   
1209   (start code js)
1210
1211   Options.Canvas = {
1212     injectInto: 'id',
1213     width: false,
1214     height: false,
1215     useCanvas: false,
1216     withLabels: true,
1217     background: false
1218   };  
1219   (end code)
1220   
1221   Example:
1222   
1223   (start code js)
1224   var viz = new $jit.Viz({
1225     injectInto: 'someContainerId',
1226     width: 500,
1227     height: 700
1228   });
1229   (end code)
1230   
1231   Parameters:
1232   
1233   injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
1234   width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1235   height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1236   useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1237   withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1238   background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1239 */
1240
1241 Options.Canvas = {
1242     $extend: true,
1243     
1244     injectInto: 'id',
1245     width: false,
1246     height: false,
1247     useCanvas: false,
1248     withLabels: true,
1249     background: false,
1250     colorStop1: 'rgba(255,255,255,1)',
1251     colorStop2: 'rgba(255,255,255,0)'
1252 };
1253
1254 /*
1255  * File: Options.Tree.js
1256  *
1257 */
1258
1259 /*
1260   Object: Options.Tree
1261   
1262   Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1263   
1264   Syntax:
1265   
1266   (start code js)
1267   Options.Tree = {
1268     orientation: "left",
1269     subtreeOffset: 8,
1270     siblingOffset: 5,
1271     indent:10,
1272     multitree: false,
1273     align:"center"
1274   };
1275   (end code)
1276   
1277   Example:
1278   
1279   (start code js)
1280   var st = new $jit.ST({
1281     orientation: 'left',
1282     subtreeOffset: 1,
1283     siblingOFfset: 5,
1284     multitree: true
1285   });
1286   (end code)
1287
1288   Parameters:
1289     
1290   subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1291   siblingOffset - (number) Default's 5. Separation offset between siblings.
1292   orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1293   align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1294   indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1295   multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1296      
1297 */
1298 Options.Tree = {
1299     $extend: true,
1300     
1301     orientation: "left",
1302     subtreeOffset: 8,
1303     siblingOffset: 5,
1304     indent:10,
1305     multitree: false,
1306     align:"center"
1307 };
1308
1309
1310 /*
1311  * File: Options.Node.js
1312  *
1313 */
1314
1315 /*
1316   Object: Options.Node
1317
1318   Provides Node rendering options for Tree and Graph based visualizations.
1319
1320   Syntax:
1321     
1322   (start code js)
1323   Options.Node = {
1324     overridable: false,
1325     type: 'circle',
1326     color: '#ccb',
1327     alpha: 1,
1328     dim: 3,
1329     height: 20,
1330     width: 90,
1331     autoHeight: false,
1332     autoWidth: false,
1333     lineWidth: 1,
1334     transform: true,
1335     align: "center",
1336     angularWidth:1,
1337     span:1,
1338     CanvasStyles: {}
1339   };
1340   (end code)
1341   
1342   Example:
1343   
1344   (start code js)
1345   var viz = new $jit.Viz({
1346     Node: {
1347       overridable: true,
1348       width: 30,
1349       autoHeight: true,
1350       type: 'rectangle'
1351     }
1352   });
1353   (end code)
1354   
1355   Parameters:
1356
1357   overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1358   type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
1359   color - (string) Default's *#ccb*. Node color.
1360   alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1361   dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1362   height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1363   width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1364   autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1365   autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1366   lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1367   transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1368   align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
1369   angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1370   span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1371   CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
1372
1373 */
1374 Options.Node = {
1375   $extend: false,
1376   
1377   overridable: false,
1378   type: 'circle',
1379   color: '#ccb',
1380   alpha: 1,
1381   dim: 3,
1382   height: 20,
1383   width: 90,
1384   autoHeight: false,
1385   autoWidth: false,
1386   lineWidth: 1,
1387   transform: true,
1388   align: "center",
1389   angularWidth:1,
1390   span:1,
1391   //Raw canvas styles to be
1392   //applied to the context instance
1393   //before plotting a node
1394   CanvasStyles: {}
1395 };
1396
1397
1398 /*
1399  * File: Options.Edge.js
1400  *
1401 */
1402
1403 /*
1404   Object: Options.Edge
1405
1406   Provides Edge rendering options for Tree and Graph based visualizations.
1407
1408   Syntax:
1409     
1410   (start code js)
1411   Options.Edge = {
1412     overridable: false,
1413     type: 'line',
1414     color: '#ccb',
1415     lineWidth: 1,
1416     dim:15,
1417     alpha: 1,
1418     CanvasStyles: {}
1419   };
1420   (end code)
1421   
1422   Example:
1423   
1424   (start code js)
1425   var viz = new $jit.Viz({
1426     Edge: {
1427       overridable: true,
1428       type: 'line',
1429       color: '#fff',
1430       CanvasStyles: {
1431         shadowColor: '#ccc',
1432         shadowBlur: 10
1433       }
1434     }
1435   });
1436   (end code)
1437   
1438   Parameters:
1439     
1440    overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1441    type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
1442    color - (string) Default's '#ccb'. Edge color.
1443    lineWidth - (number) Default's *1*. Line/Edge width.
1444    alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1445    dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
1446    epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
1447    CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
1448
1449   See also:
1450    
1451    If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
1452 */
1453 Options.Edge = {
1454   $extend: false,
1455   
1456   overridable: false,
1457   type: 'line',
1458   color: '#ccb',
1459   lineWidth: 1,
1460   dim:15,
1461   alpha: 1,
1462   epsilon: 7,
1463
1464   //Raw canvas styles to be
1465   //applied to the context instance
1466   //before plotting an edge
1467   CanvasStyles: {}
1468 };
1469
1470
1471 /*
1472  * File: Options.Fx.js
1473  *
1474 */
1475
1476 /*
1477   Object: Options.Fx
1478
1479   Provides animation options like duration of the animations, frames per second and animation transitions.  
1480
1481   Syntax:
1482   
1483   (start code js)
1484     Options.Fx = {
1485       fps:40,
1486       duration: 2500,
1487       transition: $jit.Trans.Quart.easeInOut,
1488       clearCanvas: true
1489     };
1490   (end code)
1491   
1492   Example:
1493   
1494   (start code js)
1495   var viz = new $jit.Viz({
1496     duration: 1000,
1497     fps: 35,
1498     transition: $jit.Trans.linear
1499   });
1500   (end code)
1501   
1502   Parameters:
1503   
1504   clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1505   duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1506   fps - (number) Default's *40*. Frames per second.
1507   transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1508   
1509   Object: $jit.Trans
1510   
1511   This object is used for specifying different animation transitions in all visualizations.
1512
1513   There are many different type of animation transitions.
1514
1515   linear:
1516
1517   Displays a linear transition
1518
1519   >Trans.linear
1520   
1521   (see Linear.png)
1522
1523   Quad:
1524
1525   Displays a Quadratic transition.
1526
1527   >Trans.Quad.easeIn
1528   >Trans.Quad.easeOut
1529   >Trans.Quad.easeInOut
1530   
1531  (see Quad.png)
1532
1533  Cubic:
1534
1535  Displays a Cubic transition.
1536
1537  >Trans.Cubic.easeIn
1538  >Trans.Cubic.easeOut
1539  >Trans.Cubic.easeInOut
1540
1541  (see Cubic.png)
1542
1543  Quart:
1544
1545  Displays a Quartetic transition.
1546
1547  >Trans.Quart.easeIn
1548  >Trans.Quart.easeOut
1549  >Trans.Quart.easeInOut
1550
1551  (see Quart.png)
1552
1553  Quint:
1554
1555  Displays a Quintic transition.
1556
1557  >Trans.Quint.easeIn
1558  >Trans.Quint.easeOut
1559  >Trans.Quint.easeInOut
1560
1561  (see Quint.png)
1562
1563  Expo:
1564
1565  Displays an Exponential transition.
1566
1567  >Trans.Expo.easeIn
1568  >Trans.Expo.easeOut
1569  >Trans.Expo.easeInOut
1570
1571  (see Expo.png)
1572
1573  Circ:
1574
1575  Displays a Circular transition.
1576
1577  >Trans.Circ.easeIn
1578  >Trans.Circ.easeOut
1579  >Trans.Circ.easeInOut
1580
1581  (see Circ.png)
1582
1583  Sine:
1584
1585  Displays a Sineousidal transition.
1586
1587  >Trans.Sine.easeIn
1588  >Trans.Sine.easeOut
1589  >Trans.Sine.easeInOut
1590
1591  (see Sine.png)
1592
1593  Back:
1594
1595  >Trans.Back.easeIn
1596  >Trans.Back.easeOut
1597  >Trans.Back.easeInOut
1598
1599  (see Back.png)
1600
1601  Bounce:
1602
1603  Bouncy transition.
1604
1605  >Trans.Bounce.easeIn
1606  >Trans.Bounce.easeOut
1607  >Trans.Bounce.easeInOut
1608
1609  (see Bounce.png)
1610
1611  Elastic:
1612
1613  Elastic curve.
1614
1615  >Trans.Elastic.easeIn
1616  >Trans.Elastic.easeOut
1617  >Trans.Elastic.easeInOut
1618
1619  (see Elastic.png)
1620  
1621  Based on:
1622      
1623  Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
1624
1625
1626 */
1627 Options.Fx = {
1628   $extend: true,
1629   
1630   fps:40,
1631   duration: 2500,
1632   transition: $jit.Trans.Quart.easeInOut,
1633   clearCanvas: true
1634 };
1635
1636 /*
1637  * File: Options.Label.js
1638  *
1639 */
1640 /*
1641   Object: Options.Label
1642
1643   Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.  
1644
1645   Syntax:
1646   
1647   (start code js)
1648     Options.Label = {
1649       overridable: false,
1650       type: 'HTML', //'SVG', 'Native'
1651       style: ' ',
1652       size: 10,
1653       family: 'sans-serif',
1654       textAlign: 'center',
1655       textBaseline: 'alphabetic',
1656       color: '#fff'
1657     };
1658   (end code)
1659   
1660   Example:
1661   
1662   (start code js)
1663   var viz = new $jit.Viz({
1664     Label: {
1665       type: 'Native',
1666       size: 11,
1667       color: '#ccc'
1668     }
1669   });
1670   (end code)
1671   
1672   Parameters:
1673     
1674   overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1675   type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1676   style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1677   size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1678   family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1679   color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1680 */
1681 Options.Label = {
1682   $extend: false,
1683   
1684   overridable: false,
1685   type: 'HTML', //'SVG', 'Native'
1686   style: ' ',
1687   size: 10,
1688   family: 'sans-serif',
1689   textAlign: 'center',
1690   textBaseline: 'alphabetic',
1691   color: '#fff'
1692 };
1693
1694
1695 /*
1696  * File: Options.Tips.js
1697  *
1698  */
1699
1700 /*
1701   Object: Options.Tips
1702   
1703   Tips options
1704   
1705   Syntax:
1706     
1707   (start code js)
1708   Options.Tips = {
1709     enable: false,
1710     type: 'auto',
1711     offsetX: 20,
1712     offsetY: 20,
1713     onShow: $.empty,
1714     onHide: $.empty
1715   };
1716   (end code)
1717   
1718   Example:
1719   
1720   (start code js)
1721   var viz = new $jit.Viz({
1722     Tips: {
1723       enable: true,
1724       type: 'Native',
1725       offsetX: 10,
1726       offsetY: 10,
1727       onShow: function(tip, node) {
1728         tip.innerHTML = node.name;
1729       }
1730     }
1731   });
1732   (end code)
1733
1734   Parameters:
1735
1736   enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class. 
1737   type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
1738   offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
1739   offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
1740   onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
1741   onHide() - This callack is used when hiding a tooltip.
1742
1743 */
1744 Options.Tips = {
1745   $extend: false,
1746   
1747   enable: false,
1748   type: 'auto',
1749   offsetX: 20,
1750   offsetY: 20,
1751   force: false,
1752   onShow: $.empty,
1753   onHide: $.empty
1754 };
1755
1756
1757 /*
1758  * File: Options.NodeStyles.js
1759  *
1760  */
1761
1762 /*
1763   Object: Options.NodeStyles
1764   
1765   Apply different styles when a node is hovered or selected.
1766   
1767   Syntax:
1768     
1769   (start code js)
1770   Options.NodeStyles = {
1771     enable: false,
1772     type: 'auto',
1773     stylesHover: false,
1774     stylesClick: false
1775   };
1776   (end code)
1777   
1778   Example:
1779   
1780   (start code js)
1781   var viz = new $jit.Viz({
1782     NodeStyles: {
1783       enable: true,
1784       type: 'Native',
1785       stylesHover: {
1786         dim: 30,
1787         color: '#fcc'
1788       },
1789       duration: 600
1790     }
1791   });
1792   (end code)
1793
1794   Parameters:
1795   
1796   enable - (boolean) Default's *false*. Whether to enable this option.
1797   type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
1798   stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799   stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1800 */
1801
1802 Options.NodeStyles = {
1803   $extend: false,
1804   
1805   enable: false,
1806   type: 'auto',
1807   stylesHover: false,
1808   stylesClick: false
1809 };
1810
1811
1812 /*
1813  * File: Options.Events.js
1814  *
1815 */
1816
1817 /*
1818   Object: Options.Events
1819   
1820   Configuration for adding mouse/touch event handlers to Nodes.
1821   
1822   Syntax:
1823   
1824   (start code js)
1825   Options.Events = {
1826     enable: false,
1827     enableForEdges: false,
1828     type: 'auto',
1829     onClick: $.empty,
1830     onRightClick: $.empty,
1831     onMouseMove: $.empty,
1832     onMouseEnter: $.empty,
1833     onMouseLeave: $.empty,
1834     onDragStart: $.empty,
1835     onDragMove: $.empty,
1836     onDragCancel: $.empty,
1837     onDragEnd: $.empty,
1838     onTouchStart: $.empty,
1839     onTouchMove: $.empty,
1840     onTouchEnd: $.empty,
1841     onTouchCancel: $.empty,
1842     onMouseWheel: $.empty
1843   };
1844   (end code)
1845   
1846   Example:
1847   
1848   (start code js)
1849   var viz = new $jit.Viz({
1850     Events: {
1851       enable: true,
1852       onClick: function(node, eventInfo, e) {
1853         viz.doSomething();
1854       },
1855       onMouseEnter: function(node, eventInfo, e) {
1856         viz.canvas.getElement().style.cursor = 'pointer';
1857       },
1858       onMouseLeave: function(node, eventInfo, e) {
1859         viz.canvas.getElement().style.cursor = '';
1860       }
1861     }
1862   });
1863   (end code)
1864   
1865   Parameters:
1866   
1867   enable - (boolean) Default's *false*. Whether to enable the Event system.
1868   enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
1869   type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
1870   onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1871   onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1872   onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner).  *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1873   onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1874   onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1875   onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1876   onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1877   onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1878   onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1879   onTouchStart(node, eventInfo, e) - Behaves just like onDragStart. 
1880   onTouchMove(node, eventInfo, e) - Behaves just like onDragMove. 
1881   onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd. 
1882   onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1883   onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
1884 */
1885
1886 Options.Events = {
1887   $extend: false,
1888   
1889   enable: false,
1890   enableForEdges: false,
1891   type: 'auto',
1892   onClick: $.empty,
1893   onRightClick: $.empty,
1894   onMouseMove: $.empty,
1895   onMouseEnter: $.empty,
1896   onMouseLeave: $.empty,
1897   onDragStart: $.empty,
1898   onDragMove: $.empty,
1899   onDragCancel: $.empty,
1900   onDragEnd: $.empty,
1901   onTouchStart: $.empty,
1902   onTouchMove: $.empty,
1903   onTouchEnd: $.empty,
1904   onMouseWheel: $.empty
1905 };
1906
1907 /*
1908  * File: Options.Navigation.js
1909  *
1910 */
1911
1912 /*
1913   Object: Options.Navigation
1914   
1915   Panning and zooming options for Graph/Tree based visualizations. These options are implemented 
1916   by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1917   
1918   Syntax:
1919   
1920   (start code js)
1921
1922   Options.Navigation = {
1923     enable: false,
1924     type: 'auto',
1925     panning: false, //true, 'avoid nodes'
1926     zooming: false
1927   };
1928   
1929   (end code)
1930   
1931   Example:
1932     
1933   (start code js)
1934   var viz = new $jit.Viz({
1935     Navigation: {
1936       enable: true,
1937       panning: 'avoid nodes',
1938       zooming: 20
1939     }
1940   });
1941   (end code)
1942   
1943   Parameters:
1944   
1945   enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1946   panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
1947   zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
1948   
1949 */
1950
1951 Options.Navigation = {
1952   $extend: false,
1953   
1954   enable: false,
1955   type: 'auto',
1956   panning: false, //true | 'avoid nodes'
1957   zooming: false
1958 };
1959
1960 /*
1961  * File: Options.Controller.js
1962  *
1963 */
1964
1965 /*
1966   Object: Options.Controller
1967   
1968   Provides controller methods. Controller methods are callback functions that get called at different stages 
1969   of the animation, computing or plotting of the visualization.
1970   
1971   Implemented by:
1972     
1973   All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1974   
1975   Syntax:
1976   
1977   (start code js)
1978
1979   Options.Controller = {
1980     onBeforeCompute: $.empty,
1981     onAfterCompute:  $.empty,
1982     onCreateLabel:   $.empty,
1983     onPlaceLabel:    $.empty,
1984     onComplete:      $.empty,
1985     onBeforePlotLine:$.empty,
1986     onAfterPlotLine: $.empty,
1987     onBeforePlotNode:$.empty,
1988     onAfterPlotNode: $.empty,
1989     request:         false
1990   };
1991   
1992   (end code)
1993   
1994   Example:
1995     
1996   (start code js)
1997   var viz = new $jit.Viz({
1998     onBeforePlotNode: function(node) {
1999       if(node.selected) {
2000         node.setData('color', '#ffc');
2001       } else {
2002         node.removeData('color');
2003       }
2004     },
2005     onBeforePlotLine: function(adj) {
2006       if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2007         adj.setData('color', '#ffc');
2008       } else {
2009         adj.removeData('color');
2010       }
2011     },
2012     onAfterCompute: function() {
2013       alert("computed!");
2014     }
2015   });
2016   (end code)
2017   
2018   Parameters:
2019
2020    onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2021    onAfterCompute() - This method is triggered after all animations or computations ended.
2022    onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
2023    onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
2024    onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
2025    onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2026    onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
2027    onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2028
2029     *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2030     
2031     request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.  
2032  
2033  */
2034 Options.Controller = {
2035   $extend: true,
2036   
2037   onBeforeCompute: $.empty,
2038   onAfterCompute:  $.empty,
2039   onCreateLabel:   $.empty,
2040   onPlaceLabel:    $.empty,
2041   onComplete:      $.empty,
2042   onBeforePlotLine:$.empty,
2043   onAfterPlotLine: $.empty,
2044   onBeforePlotNode:$.empty,
2045   onAfterPlotNode: $.empty,
2046   request:         false
2047 };
2048
2049
2050 /*
2051  * File: Extras.js
2052  * 
2053  * Provides Extras such as Tips and Style Effects.
2054  * 
2055  * Description:
2056  * 
2057  * Provides the <Tips> and <NodeStyles> classes and functions.
2058  *
2059  */
2060
2061 /*
2062  * Manager for mouse events (clicking and mouse moving).
2063  * 
2064  * This class is used for registering objects implementing onClick
2065  * and onMousemove methods. These methods are called when clicking or
2066  * moving the mouse around  the Canvas.
2067  * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2068  * 
2069  */
2070 var ExtrasInitializer = {
2071   initialize: function(className, viz) {
2072     this.viz = viz;
2073     this.canvas = viz.canvas;
2074     this.config = viz.config[className];
2075     this.nodeTypes = viz.fx.nodeTypes;
2076     var type = this.config.type;
2077     this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2078     this.labelContainer = this.dom && viz.labels.getLabelContainer();
2079     this.isEnabled() && this.initializePost();
2080   },
2081   initializePost: $.empty,
2082   setAsProperty: $.lambda(false),
2083   isEnabled: function() {
2084     return this.config.enable;
2085   },
2086   isLabel: function(e, win) {
2087     e = $.event.get(e, win);
2088     var labelContainer = this.labelContainer,
2089         target = e.target || e.srcElement;
2090     if(target && target.parentNode == labelContainer)
2091       return target;
2092     return false;
2093   }
2094 };
2095
2096 var EventsInterface = {
2097   onMouseUp: $.empty,
2098   onMouseDown: $.empty,
2099   onMouseMove: $.empty,
2100   onMouseOver: $.empty,
2101   onMouseOut: $.empty,
2102   onMouseWheel: $.empty,
2103   onTouchStart: $.empty,
2104   onTouchMove: $.empty,
2105   onTouchEnd: $.empty,
2106   onTouchCancel: $.empty
2107 };
2108
2109 var MouseEventsManager = new Class({
2110   initialize: function(viz) {
2111     this.viz = viz;
2112     this.canvas = viz.canvas;
2113     this.node = false;
2114     this.edge = false;
2115     this.registeredObjects = [];
2116     this.attachEvents();
2117   },
2118   
2119   attachEvents: function() {
2120     var htmlCanvas = this.canvas.getElement(), 
2121         that = this;
2122     htmlCanvas.oncontextmenu = $.lambda(false);
2123     $.addEvents(htmlCanvas, {
2124       'mouseup': function(e, win) {
2125         var event = $.event.get(e, win);
2126         that.handleEvent('MouseUp', e, win, 
2127             that.makeEventObject(e, win), 
2128             $.event.isRightClick(event));
2129       },
2130       'mousedown': function(e, win) {
2131         var event = $.event.get(e, win);
2132         that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win), 
2133             $.event.isRightClick(event));
2134       },
2135       'mousemove': function(e, win) {
2136         that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2137       },
2138       'mouseover': function(e, win) {
2139         that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2140       },
2141       'mouseout': function(e, win) {
2142         that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2143       },
2144       'touchstart': function(e, win) {
2145         that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2146       },
2147       'touchmove': function(e, win) {
2148         that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2149       },
2150       'touchend': function(e, win) {
2151         that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2152       }
2153     });
2154     //attach mousewheel event
2155     var handleMouseWheel = function(e, win) {
2156       var event = $.event.get(e, win);
2157       var wheel = $.event.getWheel(event);
2158       that.handleEvent('MouseWheel', e, win, wheel);
2159     };
2160     //TODO(nico): this is a horrible check for non-gecko browsers!
2161     if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2162       $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2163     } else {
2164       htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2165     }
2166   },
2167   
2168   register: function(obj) {
2169     this.registeredObjects.push(obj);
2170   },
2171   
2172   handleEvent: function() {
2173     var args = Array.prototype.slice.call(arguments),
2174         type = args.shift();
2175     for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2176       regs[i]['on' + type].apply(regs[i], args);
2177     }
2178   },
2179   
2180   makeEventObject: function(e, win) {
2181     var that = this,
2182         graph = this.viz.graph,
2183         fx = this.viz.fx,
2184         ntypes = fx.nodeTypes,
2185         etypes = fx.edgeTypes;
2186     return {
2187       pos: false,
2188       node: false,
2189       edge: false,
2190       contains: false,
2191       getNodeCalled: false,
2192       getEdgeCalled: false,
2193       getPos: function() {
2194         //TODO(nico): check why this can't be cache anymore when using edge detection
2195         //if(this.pos) return this.pos;
2196         var canvas = that.viz.canvas,
2197             s = canvas.getSize(),
2198             p = canvas.getPos(),
2199             ox = canvas.translateOffsetX,
2200             oy = canvas.translateOffsetY,
2201             sx = canvas.scaleOffsetX,
2202             sy = canvas.scaleOffsetY,
2203             pos = $.event.getPos(e, win);
2204         this.pos = {
2205           x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2206           y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2207         };
2208         return this.pos;
2209       },
2210       getNode: function() {
2211         if(this.getNodeCalled) return this.node;
2212         this.getNodeCalled = true;
2213         for(var id in graph.nodes) {
2214           var n = graph.nodes[id],
2215               geom = n && ntypes[n.getData('type')],
2216               contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2217           if(contains) {
2218             this.contains = contains;
2219             return that.node = this.node = n;
2220           }
2221         }
2222         return that.node = this.node = false;
2223       },
2224       getEdge: function() {
2225         if(this.getEdgeCalled) return this.edge;
2226         this.getEdgeCalled = true;
2227         var hashset = {};
2228         for(var id in graph.edges) {
2229           var edgeFrom = graph.edges[id];
2230           hashset[id] = true;
2231           for(var edgeId in edgeFrom) {
2232             if(edgeId in hashset) continue;
2233             var e = edgeFrom[edgeId],
2234                 geom = e && etypes[e.getData('type')],
2235                 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2236             if(contains) {
2237               this.contains = contains;
2238               return that.edge = this.edge = e;
2239             }
2240           }
2241         }
2242         return that.edge = this.edge = false;
2243       },
2244       getContains: function() {
2245         if(this.getNodeCalled) return this.contains;
2246         this.getNode();
2247         return this.contains;
2248       }
2249     };
2250   }
2251 });
2252
2253 /* 
2254  * Provides the initialization function for <NodeStyles> and <Tips> implemented 
2255  * by all main visualizations.
2256  *
2257  */
2258 var Extras = {
2259   initializeExtras: function() {
2260     var mem = new MouseEventsManager(this), that = this;
2261     $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2262       var obj = new Extras.Classes[k](k, that);
2263       if(obj.isEnabled()) {
2264         mem.register(obj);
2265       }
2266       if(obj.setAsProperty()) {
2267         that[k.toLowerCase()] = obj;
2268       }
2269     });
2270   }   
2271 };
2272
2273 Extras.Classes = {};
2274 /*
2275   Class: Events
2276    
2277   This class defines an Event API to be accessed by the user.
2278   The methods implemented are the ones defined in the <Options.Events> object.
2279 */
2280
2281 Extras.Classes.Events = new Class({
2282   Implements: [ExtrasInitializer, EventsInterface],
2283   
2284   initializePost: function() {
2285     this.fx = this.viz.fx;
2286     this.ntypes = this.viz.fx.nodeTypes;
2287     this.etypes = this.viz.fx.edgeTypes;
2288     
2289     this.hovered = false;
2290     this.pressed = false;
2291     this.touched = false;
2292
2293     this.touchMoved = false;
2294     this.moved = false;
2295     
2296   },
2297   
2298   setAsProperty: $.lambda(true),
2299   
2300   onMouseUp: function(e, win, event, isRightClick) {
2301     var evt = $.event.get(e, win);
2302     if(!this.moved) {
2303       if(isRightClick) {
2304         this.config.onRightClick(this.hovered, event, evt);
2305       } else {
2306         this.config.onClick(this.pressed, event, evt);
2307       }
2308     }
2309     if(this.pressed) {
2310       if(this.moved) {
2311         this.config.onDragEnd(this.pressed, event, evt);
2312       } else {
2313         this.config.onDragCancel(this.pressed, event, evt);
2314       }
2315       this.pressed = this.moved = false;
2316     }
2317   },
2318
2319   onMouseOut: function(e, win, event) {
2320    //mouseout a label
2321    var evt = $.event.get(e, win), label;
2322    if(this.dom && (label = this.isLabel(e, win))) {
2323      this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2324                               event, evt);
2325      this.hovered = false;
2326      return;
2327    }
2328    //mouseout canvas
2329    var rt = evt.relatedTarget,
2330        canvasWidget = this.canvas.getElement();
2331    while(rt && rt.parentNode) {
2332      if(canvasWidget == rt.parentNode) return;
2333      rt = rt.parentNode;
2334    }
2335    if(this.hovered) {
2336      this.config.onMouseLeave(this.hovered,
2337          event, evt);
2338      this.hovered = false;
2339    }
2340   },
2341   
2342   onMouseOver: function(e, win, event) {
2343     //mouseover a label
2344     var evt = $.event.get(e, win), label;
2345     if(this.dom && (label = this.isLabel(e, win))) {
2346       this.hovered = this.viz.graph.getNode(label.id);
2347       this.config.onMouseEnter(this.hovered,
2348                                event, evt);
2349     }
2350   },
2351   
2352   onMouseMove: function(e, win, event) {
2353    var label, evt = $.event.get(e, win);
2354    if(this.pressed) {
2355      this.moved = true;
2356      this.config.onDragMove(this.pressed, event, evt);
2357      return;
2358    }
2359    if(this.dom) {
2360      this.config.onMouseMove(this.hovered,
2361          event, evt);
2362    } else {
2363      if(this.hovered) {
2364        var hn = this.hovered;
2365        var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2366        var contains = geom && geom.contains 
2367          && geom.contains.call(this.fx, hn, event.getPos());
2368        if(contains) {
2369          this.config.onMouseMove(hn, event, evt);
2370          return;
2371        } else {
2372          this.config.onMouseLeave(hn, event, evt);
2373          this.hovered = false;
2374        }
2375      }
2376      if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2377        this.config.onMouseEnter(this.hovered, event, evt);
2378      } else {
2379        this.config.onMouseMove(false, event, evt);
2380      }
2381    }
2382   },
2383   
2384   onMouseWheel: function(e, win, delta) {
2385     this.config.onMouseWheel(delta, $.event.get(e, win));
2386   },
2387   
2388   onMouseDown: function(e, win, event) {
2389     var evt = $.event.get(e, win);
2390     this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2391     this.config.onDragStart(this.pressed, event, evt);
2392   },
2393   
2394   onTouchStart: function(e, win, event) {
2395     var evt = $.event.get(e, win);
2396     this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2397     this.config.onTouchStart(this.touched, event, evt);
2398   },
2399   
2400   onTouchMove: function(e, win, event) {
2401     var evt = $.event.get(e, win);
2402     if(this.touched) {
2403       this.touchMoved = true;
2404       this.config.onTouchMove(this.touched, event, evt);
2405     }
2406   },
2407   
2408   onTouchEnd: function(e, win, event) {
2409     var evt = $.event.get(e, win);
2410     if(this.touched) {
2411       if(this.touchMoved) {
2412         this.config.onTouchEnd(this.touched, event, evt);
2413       } else {
2414         this.config.onTouchCancel(this.touched, event, evt);
2415       }
2416       this.touched = this.touchMoved = false;
2417     }
2418   }
2419 });
2420
2421 /*
2422    Class: Tips
2423     
2424    A class containing tip related functions. This class is used internally.
2425    
2426    Used by:
2427    
2428    <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2429    
2430    See also:
2431    
2432    <Options.Tips>
2433 */
2434
2435 Extras.Classes.Tips = new Class({
2436   Implements: [ExtrasInitializer, EventsInterface],
2437   
2438   initializePost: function() {
2439     //add DOM tooltip
2440     if(document.body) {
2441       var tip = $('_tooltip') || document.createElement('div');
2442       tip.id = '_tooltip';
2443       tip.className = 'tip';
2444       $.extend(tip.style, {
2445         position: 'absolute',
2446         display: 'none',
2447         zIndex: 13000
2448       });
2449       document.body.appendChild(tip);
2450       this.tip = tip;
2451       this.node = false;
2452     }
2453   },
2454   
2455   setAsProperty: $.lambda(true),
2456   
2457   onMouseOut: function(e, win) {
2458     //mouseout a label
2459     if(this.dom && this.isLabel(e, win)) {
2460       this.hide(true);
2461       return;
2462     }
2463     //mouseout canvas
2464     var rt = e.relatedTarget,
2465         canvasWidget = this.canvas.getElement();
2466     while(rt && rt.parentNode) {
2467       if(canvasWidget == rt.parentNode) return;
2468       rt = rt.parentNode;
2469     }
2470     this.hide(false);
2471   },
2472   
2473   onMouseOver: function(e, win) {
2474     //mouseover a label
2475     var label;
2476     if(this.dom && (label = this.isLabel(e, win))) {
2477       this.node = this.viz.graph.getNode(label.id);
2478       this.config.onShow(this.tip, this.node, label);
2479     }
2480   },
2481   
2482   onMouseMove: function(e, win, opt) {
2483     if(this.dom && this.isLabel(e, win)) {
2484       this.setTooltipPosition($.event.getPos(e, win));
2485     }
2486     if(!this.dom) {
2487       var node = opt.getNode();
2488       if(!node) {
2489         this.hide(true);
2490         return;
2491       }
2492       if(this.config.force || !this.node || this.node.id != node.id) {
2493         this.node = node;
2494         this.config.onShow(this.tip, node, opt.getContains());
2495       }
2496       this.setTooltipPosition($.event.getPos(e, win));
2497     }
2498   },
2499   
2500   setTooltipPosition: function(pos) {
2501     var tip = this.tip, 
2502         style = tip.style, 
2503         cont = this.config;
2504     style.display = '';
2505     //get window dimensions
2506     var win = {
2507       'height': document.body.clientHeight,
2508       'width': document.body.clientWidth
2509     };
2510     //get tooltip dimensions
2511     var obj = {
2512       'width': tip.offsetWidth,
2513       'height': tip.offsetHeight  
2514     };
2515     //set tooltip position
2516     var x = cont.offsetX, y = cont.offsetY;
2517     style.top = ((pos.y + y + obj.height > win.height)?  
2518         (pos.y - obj.height - y) : pos.y + y) + 'px';
2519     style.left = ((pos.x + obj.width + x > win.width)? 
2520         (pos.x - obj.width - x) : pos.x + x) + 'px';
2521   },
2522   
2523   hide: function(triggerCallback) {
2524     if(!SUGAR.util.isTouchScreen()) {
2525         this.tip.style.display = 'none';
2526     }
2527     triggerCallback && this.config.onHide();
2528   }
2529 });
2530
2531 /*
2532   Class: NodeStyles
2533    
2534   Change node styles when clicking or hovering a node. This class is used internally.
2535   
2536   Used by:
2537   
2538   <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2539   
2540   See also:
2541   
2542   <Options.NodeStyles>
2543 */
2544 Extras.Classes.NodeStyles = new Class({
2545   Implements: [ExtrasInitializer, EventsInterface],
2546   
2547   initializePost: function() {
2548     this.fx = this.viz.fx;
2549     this.types = this.viz.fx.nodeTypes;
2550     this.nStyles = this.config;
2551     this.nodeStylesOnHover = this.nStyles.stylesHover;
2552     this.nodeStylesOnClick = this.nStyles.stylesClick;
2553     this.hoveredNode = false;
2554     this.fx.nodeFxAnimation = new Animation();
2555     
2556     this.down = false;
2557     this.move = false;
2558   },
2559   
2560   onMouseOut: function(e, win) {
2561     this.down = this.move = false;
2562     if(!this.hoveredNode) return;
2563     //mouseout a label
2564     if(this.dom && this.isLabel(e, win)) {
2565       this.toggleStylesOnHover(this.hoveredNode, false);
2566     }
2567     //mouseout canvas
2568     var rt = e.relatedTarget,
2569         canvasWidget = this.canvas.getElement();
2570     while(rt && rt.parentNode) {
2571       if(canvasWidget == rt.parentNode) return;
2572       rt = rt.parentNode;
2573     }
2574     this.toggleStylesOnHover(this.hoveredNode, false);
2575     this.hoveredNode = false;
2576   },
2577   
2578   onMouseOver: function(e, win) {
2579     //mouseover a label
2580     var label;
2581     if(this.dom && (label = this.isLabel(e, win))) {
2582       var node = this.viz.graph.getNode(label.id);
2583       if(node.selected) return;
2584       this.hoveredNode = node;
2585       this.toggleStylesOnHover(this.hoveredNode, true);
2586     }
2587   },
2588   
2589   onMouseDown: function(e, win, event, isRightClick) {
2590     if(isRightClick) return;
2591     var label;
2592     if(this.dom && (label = this.isLabel(e, win))) {
2593       this.down = this.viz.graph.getNode(label.id);
2594     } else if(!this.dom) {
2595       this.down = event.getNode();
2596     }
2597     this.move = false;
2598   },
2599   
2600   onMouseUp: function(e, win, event, isRightClick) {
2601     if(isRightClick) return;
2602     if(!this.move) {
2603       this.onClick(event.getNode());
2604     }
2605     this.down = this.move = false;
2606   },
2607   
2608   getRestoredStyles: function(node, type) {
2609     var restoredStyles = {}, 
2610         nStyles = this['nodeStylesOn' + type];
2611     for(var prop in nStyles) {
2612       restoredStyles[prop] = node.styles['$' + prop];
2613     }
2614     return restoredStyles;
2615   },
2616   
2617   toggleStylesOnHover: function(node, set) {
2618     if(this.nodeStylesOnHover) {
2619       this.toggleStylesOn('Hover', node, set);
2620     }
2621   },
2622
2623   toggleStylesOnClick: function(node, set) {
2624     if(this.nodeStylesOnClick) {
2625       this.toggleStylesOn('Click', node, set);
2626     }
2627   },
2628   
2629   toggleStylesOn: function(type, node, set) {
2630     var viz = this.viz;
2631     var nStyles = this.nStyles;
2632     if(set) {
2633       var that = this;
2634       if(!node.styles) {
2635         node.styles = $.merge(node.data, {});
2636       }
2637       for(var s in this['nodeStylesOn' + type]) {
2638         var $s = '$' + s;
2639         if(!($s in node.styles)) {
2640             node.styles[$s] = node.getData(s); 
2641         }
2642       }
2643       viz.fx.nodeFx($.extend({
2644         'elements': {
2645           'id': node.id,
2646           'properties': that['nodeStylesOn' + type]
2647          },
2648          transition: Trans.Quart.easeOut,
2649          duration:300,
2650          fps:40
2651       }, this.config));
2652     } else {
2653       var restoredStyles = this.getRestoredStyles(node, type);
2654       viz.fx.nodeFx($.extend({
2655         'elements': {
2656           'id': node.id,
2657           'properties': restoredStyles
2658          },
2659          transition: Trans.Quart.easeOut,
2660          duration:300,
2661          fps:40
2662       }, this.config));
2663     }
2664   },
2665
2666   onClick: function(node) {
2667     if(!node) return;
2668     var nStyles = this.nodeStylesOnClick;
2669     if(!nStyles) return;
2670     //if the node is selected then unselect it
2671     if(node.selected) {
2672       this.toggleStylesOnClick(node, false);
2673       delete node.selected;
2674     } else {
2675       //unselect all selected nodes...
2676       this.viz.graph.eachNode(function(n) {
2677         if(n.selected) {
2678           for(var s in nStyles) {
2679             n.setData(s, n.styles['$' + s], 'end');
2680           }
2681           delete n.selected;
2682         }
2683       });
2684       //select clicked node
2685       this.toggleStylesOnClick(node, true);
2686       node.selected = true;
2687       delete node.hovered;
2688       this.hoveredNode = false;
2689     }
2690   },
2691   
2692   onMouseMove: function(e, win, event) {
2693     //if mouse button is down and moving set move=true
2694     if(this.down) this.move = true;
2695     //already handled by mouseover/out
2696     if(this.dom && this.isLabel(e, win)) return;
2697     var nStyles = this.nodeStylesOnHover;
2698     if(!nStyles) return;
2699     
2700     if(!this.dom) {
2701       if(this.hoveredNode) {
2702         var geom = this.types[this.hoveredNode.getData('type')];
2703         var contains = geom && geom.contains && geom.contains.call(this.fx, 
2704             this.hoveredNode, event.getPos());
2705         if(contains) return;
2706       }
2707       var node = event.getNode();
2708       //if no node is being hovered then just exit
2709       if(!this.hoveredNode && !node) return;
2710       //if the node is hovered then exit
2711       if(node.hovered) return;
2712       //select hovered node
2713       if(node && !node.selected) {
2714         //check if an animation is running and exit it
2715         this.fx.nodeFxAnimation.stopTimer();
2716         //unselect all hovered nodes...
2717         this.viz.graph.eachNode(function(n) {
2718           if(n.hovered && !n.selected) {
2719             for(var s in nStyles) {
2720               n.setData(s, n.styles['$' + s], 'end');
2721             }
2722             delete n.hovered;
2723           }
2724         });
2725         //select hovered node
2726         node.hovered = true;
2727         this.hoveredNode = node;
2728         this.toggleStylesOnHover(node, true);
2729       } else if(this.hoveredNode && !this.hoveredNode.selected) {
2730         //check if an animation is running and exit it
2731         this.fx.nodeFxAnimation.stopTimer();
2732         //unselect hovered node
2733         this.toggleStylesOnHover(this.hoveredNode, false);
2734         delete this.hoveredNode.hovered;
2735         this.hoveredNode = false;
2736       }
2737     }
2738   }
2739 });
2740
2741 Extras.Classes.Navigation = new Class({
2742   Implements: [ExtrasInitializer, EventsInterface],
2743   
2744   initializePost: function() {
2745     this.pos = false;
2746     this.pressed = false;
2747   },
2748   
2749   onMouseWheel: function(e, win, scroll) {
2750     if(!this.config.zooming) return;
2751     $.event.stop($.event.get(e, win));
2752     var val = this.config.zooming / 1000,
2753         ans = 1 + scroll * val;
2754     this.canvas.scale(ans, ans);
2755   },
2756   
2757   onMouseDown: function(e, win, eventInfo) {
2758     if(!this.config.panning) return;
2759     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2760     this.pressed = true;
2761     this.pos = eventInfo.getPos();
2762     var canvas = this.canvas,
2763         ox = canvas.translateOffsetX,
2764         oy = canvas.translateOffsetY,
2765         sx = canvas.scaleOffsetX,
2766         sy = canvas.scaleOffsetY;
2767     this.pos.x *= sx;
2768     this.pos.x += ox;
2769     this.pos.y *= sy;
2770     this.pos.y += oy;
2771   },
2772   
2773   onMouseMove: function(e, win, eventInfo) {
2774     if(!this.config.panning) return;
2775     if(!this.pressed) return;
2776     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2777     var thispos = this.pos, 
2778         currentPos = eventInfo.getPos(),
2779         canvas = this.canvas,
2780         ox = canvas.translateOffsetX,
2781         oy = canvas.translateOffsetY,
2782         sx = canvas.scaleOffsetX,
2783         sy = canvas.scaleOffsetY;
2784     currentPos.x *= sx;
2785     currentPos.y *= sy;
2786     currentPos.x += ox;
2787     currentPos.y += oy;
2788     var x = currentPos.x - thispos.x,
2789         y = currentPos.y - thispos.y;
2790     this.pos = currentPos;
2791     this.canvas.translate(x * 1/sx, y * 1/sy);
2792   },
2793   
2794   onMouseUp: function(e, win, eventInfo, isRightClick) {
2795     if(!this.config.panning) return;
2796     this.pressed = false;
2797   }
2798 });
2799
2800
2801 /*
2802  * File: Canvas.js
2803  *
2804  */
2805
2806 /*
2807  Class: Canvas
2808  
2809         A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to 
2810         know more about <Canvas> options take a look at <Options.Canvas>.
2811  
2812  A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior 
2813  across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2814  
2815  Example:
2816  
2817  Suppose we have this HTML
2818  
2819  (start code xml)
2820         <div id="infovis"></div>
2821  (end code)
2822  
2823  Now we create a new Visualization
2824  
2825  (start code js)
2826         var viz = new $jit.Viz({
2827                 //Where to inject the canvas. Any div container will do.
2828                 'injectInto':'infovis',
2829                  //width and height for canvas. 
2830                  //Default's to the container offsetWidth and Height.
2831                  'width': 900,
2832                  'height':500
2833          });
2834  (end code)
2835
2836  The generated HTML will look like this
2837  
2838  (start code xml)
2839  <div id="infovis">
2840         <div id="infovis-canvaswidget" style="position:relative;">
2841         <canvas id="infovis-canvas" width=900 height=500
2842         style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2843         <div id="infovis-label"
2844         style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2845         </div>
2846         </div>
2847  </div>
2848  (end code)
2849  
2850  As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2851  of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2852  */
2853
2854 var Canvas;
2855 (function() {
2856   //check for native canvas support
2857   var canvasType = typeof HTMLCanvasElement,
2858       supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2859   //create element function
2860   function $E(tag, props) {
2861     var elem = document.createElement(tag);
2862     for(var p in props) {
2863       if(typeof props[p] == "object") {
2864         $.extend(elem[p], props[p]);
2865       } else {
2866         elem[p] = props[p];
2867       }
2868     }
2869     if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2870       elem = G_vmlCanvasManager.initElement(elem);
2871     }
2872     return elem;
2873   }
2874   //canvas widget which we will call just Canvas
2875   $jit.Canvas = Canvas = new Class({
2876     canvases: [],
2877     pos: false,
2878     element: false,
2879     labelContainer: false,
2880     translateOffsetX: 0,
2881     translateOffsetY: 0,
2882     scaleOffsetX: 1,
2883     scaleOffsetY: 1,
2884     
2885     initialize: function(viz, opt) {
2886       this.viz = viz;
2887       this.opt = opt;
2888       var id = $.type(opt.injectInto) == 'string'? 
2889           opt.injectInto:opt.injectInto.id,
2890           idLabel = id + "-label", 
2891           wrapper = $(id),
2892           width = opt.width || wrapper.offsetWidth,
2893           height = opt.height || wrapper.offsetHeight;
2894       this.id = id;
2895       //canvas options
2896       var canvasOptions = {
2897         injectInto: id,
2898         width: width,
2899         height: height
2900       };
2901       //create main wrapper
2902       this.element = $E('div', {
2903         'id': id + '-canvaswidget',
2904         'style': {
2905           'position': 'relative',
2906           'width': width + 'px',
2907           'height': height + 'px'
2908         }
2909       });
2910       //create label container
2911       this.labelContainer = this.createLabelContainer(opt.Label.type, 
2912           idLabel, canvasOptions);
2913       //create primary canvas
2914       this.canvases.push(new Canvas.Base({
2915         config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2916         plot: function(base) {
2917           viz.fx.plot();
2918         },
2919         resize: function() {
2920           viz.refresh();
2921         }
2922       }));
2923       //create secondary canvas
2924       var back = opt.background;
2925       if(back) {
2926         var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2927         this.canvases.push(new Canvas.Base(backCanvas));
2928       }
2929       //insert canvases
2930       var len = this.canvases.length;
2931       while(len--) {
2932         this.element.appendChild(this.canvases[len].canvas);
2933         if(len > 0) {
2934           this.canvases[len].plot();
2935         }
2936       }
2937       this.element.appendChild(this.labelContainer);
2938       wrapper.appendChild(this.element);
2939       //Update canvas position when the page is scrolled.
2940       var timer = null, that = this;
2941       $.addEvent(window, 'scroll', function() {
2942         clearTimeout(timer);
2943         timer = setTimeout(function() {
2944           that.getPos(true); //update canvas position
2945         }, 500);
2946       });
2947       $.addEvent(window, 'click', function() {
2948         clearTimeout(timer);
2949         timer = setTimeout(function() {
2950           that.getPos(true); //update canvas position
2951         }, 500);
2952       });
2953       sb = document.getElementById('sb'+id);
2954       $.addEvent(sb, 'scroll', function() {
2955         clearTimeout(timer);
2956         timer = setTimeout(function() {
2957           that.getPos(true); //update canvas position
2958         }, 500);
2959       });
2960     },
2961     /*
2962       Method: getCtx
2963       
2964       Returns the main canvas context object
2965       
2966       Example:
2967       
2968       (start code js)
2969        var ctx = canvas.getCtx();
2970        //Now I can use the native canvas context
2971        //and for example change some canvas styles
2972        ctx.globalAlpha = 1;
2973       (end code)
2974     */
2975     getCtx: function(i) {
2976       return this.canvases[i || 0].getCtx();
2977     },
2978     /*
2979       Method: getConfig
2980       
2981       Returns the current Configuration for this Canvas Widget.
2982       
2983       Example:
2984       
2985       (start code js)
2986        var config = canvas.getConfig();
2987       (end code)
2988     */
2989     getConfig: function() {
2990       return this.opt;
2991     },
2992     /*
2993       Method: getElement
2994
2995       Returns the main Canvas DOM wrapper
2996       
2997       Example:
2998       
2999       (start code js)
3000        var wrapper = canvas.getElement();
3001        //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3002       (end code)
3003     */
3004     getElement: function() {
3005       return this.element;
3006     },
3007     /*
3008       Method: getSize
3009       
3010       Returns canvas dimensions.
3011       
3012       Returns:
3013       
3014       An object with *width* and *height* properties.
3015       
3016       Example:
3017       (start code js)
3018       canvas.getSize(); //returns { width: 900, height: 500 }
3019       (end code)
3020     */
3021     getSize: function(i) {
3022       return this.canvases[i || 0].getSize();
3023     },
3024     /*
3025       Method: resize
3026       
3027       Resizes the canvas.
3028       
3029       Parameters:
3030       
3031       width - New canvas width.
3032       height - New canvas height.
3033       
3034       Example:
3035       
3036       (start code js)
3037        canvas.resize(width, height);
3038       (end code)
3039     
3040     */
3041     resize: function(width, height) {
3042       this.getPos(true);
3043       this.translateOffsetX = this.translateOffsetY = 0;
3044       this.scaleOffsetX = this.scaleOffsetY = 1;
3045       for(var i=0, l=this.canvases.length; i<l; i++) {
3046         this.canvases[i].resize(width, height);
3047       }
3048       var style = this.element.style;
3049       style.width = width + 'px';
3050       style.height = height + 'px';
3051       if(this.labelContainer)
3052         this.labelContainer.style.width = width + 'px';
3053     },
3054     /*
3055       Method: translate
3056       
3057       Applies a translation to the canvas.
3058       
3059       Parameters:
3060       
3061       x - (number) x offset.
3062       y - (number) y offset.
3063       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3064       
3065       Example:
3066       
3067       (start code js)
3068        canvas.translate(30, 30);
3069       (end code)
3070     
3071     */
3072     translate: function(x, y, disablePlot) {
3073       this.translateOffsetX += x*this.scaleOffsetX;
3074       this.translateOffsetY += y*this.scaleOffsetY;
3075       for(var i=0, l=this.canvases.length; i<l; i++) {
3076         this.canvases[i].translate(x, y, disablePlot);
3077       }
3078     },
3079     /*
3080       Method: scale
3081       
3082       Scales the canvas.
3083       
3084       Parameters:
3085       
3086       x - (number) scale value.
3087       y - (number) scale value.
3088       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3089       
3090       Example:
3091       
3092       (start code js)
3093        canvas.scale(0.5, 0.5);
3094       (end code)
3095     
3096     */
3097     scale: function(x, y, disablePlot) {
3098       var px = this.scaleOffsetX * x,
3099           py = this.scaleOffsetY * y;
3100       var dx = this.translateOffsetX * (x -1) / px,
3101           dy = this.translateOffsetY * (y -1) / py;
3102       this.scaleOffsetX = px;
3103       this.scaleOffsetY = py;
3104       for(var i=0, l=this.canvases.length; i<l; i++) {
3105         this.canvases[i].scale(x, y, true);
3106       }
3107       this.translate(dx, dy, false);
3108     },
3109     /*
3110       Method: getPos
3111       
3112       Returns the canvas position as an *x, y* object.
3113       
3114       Parameters:
3115       
3116       force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3117       
3118       Returns:
3119       
3120       An object with *x* and *y* properties.
3121       
3122       Example:
3123       (start code js)
3124       canvas.getPos(true); //returns { x: 900, y: 500 }
3125       (end code)
3126     */
3127     getPos: function(force){
3128       if(force || !this.pos) {
3129         return this.pos = $.getPos(this.getElement());
3130       }
3131       return this.pos;
3132     },
3133     /*
3134        Method: clear
3135        
3136        Clears the canvas.
3137     */
3138     clear: function(i){
3139       this.canvases[i||0].clear();
3140     },
3141     
3142     path: function(type, action){
3143       var ctx = this.canvases[0].getCtx();
3144       ctx.beginPath();
3145       action(ctx);
3146       ctx[type]();
3147       ctx.closePath();
3148     },
3149     
3150     createLabelContainer: function(type, idLabel, dim) {
3151       var NS = 'http://www.w3.org/2000/svg';
3152       if(type == 'HTML' || type == 'Native') {
3153         return $E('div', {
3154           'id': idLabel,
3155           'style': {
3156             'overflow': 'visible',
3157             'position': 'absolute',
3158             'top': 0,
3159             'left': 0,
3160             'width': dim.width + 'px',
3161             'height': 0
3162           }
3163         });
3164       } else if(type == 'SVG') {
3165         var svgContainer = document.createElementNS(NS, 'svg:svg');
3166         svgContainer.setAttribute("width", dim.width);
3167         svgContainer.setAttribute('height', dim.height);
3168         var style = svgContainer.style;
3169         style.position = 'absolute';
3170         style.left = style.top = '0px';
3171         var labelContainer = document.createElementNS(NS, 'svg:g');
3172         labelContainer.setAttribute('width', dim.width);
3173         labelContainer.setAttribute('height', dim.height);
3174         labelContainer.setAttribute('x', 0);
3175         labelContainer.setAttribute('y', 0);
3176         labelContainer.setAttribute('id', idLabel);
3177         svgContainer.appendChild(labelContainer);
3178         return svgContainer;
3179       }
3180     }
3181   });
3182   //base canvas wrapper
3183   Canvas.Base = new Class({
3184     translateOffsetX: 0,
3185     translateOffsetY: 0,
3186     scaleOffsetX: 1,
3187     scaleOffsetY: 1,
3188
3189     initialize: function(viz) {
3190       this.viz = viz;
3191       this.opt = viz.config;
3192       this.size = false;
3193       this.createCanvas();
3194       this.translateToCenter();
3195     },
3196     createCanvas: function() {
3197       var opt = this.opt,
3198           width = opt.width,
3199           height = opt.height;
3200       this.canvas = $E('canvas', {
3201         'id': opt.injectInto + opt.idSuffix,
3202         'width': width,
3203         'height': height,
3204         'style': {
3205           'position': 'absolute',
3206           'top': 0,
3207           'left': 0,
3208           'width': width + 'px',
3209           'height': height + 'px'
3210         }
3211       });
3212     },
3213     getCtx: function() {
3214       if(!this.ctx) 
3215         return this.ctx = this.canvas.getContext('2d');
3216       return this.ctx;
3217     },
3218     getSize: function() {
3219       if(this.size) return this.size;
3220       var canvas = this.canvas;
3221       return this.size = {
3222         width: canvas.width,
3223         height: canvas.height
3224       };
3225     },
3226     translateToCenter: function(ps) {
3227       var size = this.getSize(),
3228           width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3229           height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3230       var ctx = this.getCtx();
3231       ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3232       ctx.translate(width/2, height/2);
3233     },
3234     resize: function(width, height) {
3235       var size = this.getSize(),
3236           canvas = this.canvas,
3237           styles = canvas.style;
3238       this.size = false;
3239       canvas.width = width;
3240       canvas.height = height;
3241       styles.width = width + "px";
3242       styles.height = height + "px";
3243       //small ExCanvas fix
3244       if(!supportsCanvas) {
3245         this.translateToCenter(size);
3246       } else {
3247         this.translateToCenter();
3248       }
3249       this.translateOffsetX =
3250         this.translateOffsetY = 0;
3251       this.scaleOffsetX = 
3252         this.scaleOffsetY = 1;
3253       this.clear();
3254       this.viz.resize(width, height, this);
3255     },
3256     translate: function(x, y, disablePlot) {
3257       var sx = this.scaleOffsetX,
3258           sy = this.scaleOffsetY;
3259       this.translateOffsetX += x*sx;
3260       this.translateOffsetY += y*sy;
3261       this.getCtx().translate(x, y);
3262       !disablePlot && this.plot();
3263     },
3264     scale: function(x, y, disablePlot) {
3265       this.scaleOffsetX *= x;
3266       this.scaleOffsetY *= y;
3267       this.getCtx().scale(x, y);
3268       !disablePlot && this.plot();
3269     },
3270     clear: function(){
3271       var size = this.getSize(),
3272           ox = this.translateOffsetX,
3273           oy = this.translateOffsetY,
3274           sx = this.scaleOffsetX,
3275           sy = this.scaleOffsetY;
3276       this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx, 
3277                               (-size.height / 2 - oy) * 1/sy, 
3278                               size.width * 1/sx, size.height * 1/sy);
3279     },
3280     plot: function() {
3281       this.clear();
3282       this.viz.plot(this);
3283     }
3284   });
3285   //background canvases
3286   //TODO(nico): document this!
3287   Canvas.Background = {};
3288   Canvas.Background.Circles = new Class({
3289     initialize: function(viz, options) {
3290       this.viz = viz;
3291       this.config = $.merge({
3292         idSuffix: '-bkcanvas',
3293         levelDistance: 100,
3294         numberOfCircles: 6,
3295         CanvasStyles: {},
3296         offset: 0
3297       }, options);
3298     },
3299     resize: function(width, height, base) {
3300       this.plot(base);
3301     },
3302     plot: function(base) {
3303       var canvas = base.canvas,
3304           ctx = base.getCtx(),
3305           conf = this.config,
3306           styles = conf.CanvasStyles;
3307       //set canvas styles
3308       for(var s in styles) ctx[s] = styles[s];
3309       var n = conf.numberOfCircles,
3310           rho = conf.levelDistance;
3311       for(var i=1; i<=n; i++) {
3312         ctx.beginPath();
3313         ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3314         ctx.stroke();
3315         ctx.closePath();
3316       }
3317       //TODO(nico): print labels too!
3318     }
3319   });
3320   Canvas.Background.Fade = new Class({
3321     initialize: function(viz, options) {
3322       this.viz = viz;
3323       this.config = $.merge({
3324         idSuffix: '-bkcanvas',
3325         CanvasStyles: {},
3326         offset: 0
3327       }, options);
3328     },
3329     resize: function(width, height, base) {
3330       this.plot(base);
3331     },
3332     plot: function(base) {
3333       var canvas = base.canvas,
3334           ctx = base.getCtx(),
3335           conf = this.config,
3336           styles = conf.CanvasStyles,
3337           size = base.getSize();
3338                   ctx.fillStyle = 'rgb(255,255,255)';
3339                   ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3340       //TODO(nico): print labels too!
3341     }
3342   });
3343 })();
3344
3345
3346 /*
3347  * File: Polar.js
3348  * 
3349  * Defines the <Polar> class.
3350  *
3351  * Description:
3352  *
3353  * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3354  *
3355  * See also:
3356  *
3357  * <http://en.wikipedia.org/wiki/Polar_coordinates>
3358  *
3359 */
3360
3361 /*
3362    Class: Polar
3363
3364    A multi purpose polar representation.
3365
3366    Description:
3367  
3368    The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3369  
3370    See also:
3371  
3372    <http://en.wikipedia.org/wiki/Polar_coordinates>
3373  
3374    Parameters:
3375
3376       theta - An angle.
3377       rho - The norm.
3378 */
3379
3380 var Polar = function(theta, rho) {
3381   this.theta = theta;
3382   this.rho = rho;
3383 };
3384
3385 $jit.Polar = Polar;
3386
3387 Polar.prototype = {
3388     /*
3389        Method: getc
3390     
3391        Returns a complex number.
3392     
3393        Parameters:
3394
3395        simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3396
3397       Returns:
3398     
3399           A complex number.
3400     */
3401     getc: function(simple) {
3402         return this.toComplex(simple);
3403     },
3404
3405     /*
3406        Method: getp
3407     
3408        Returns a <Polar> representation.
3409     
3410        Returns:
3411     
3412           A variable in polar coordinates.
3413     */
3414     getp: function() {
3415         return this;
3416     },
3417
3418
3419     /*
3420        Method: set
3421     
3422        Sets a number.
3423
3424        Parameters:
3425
3426        v - A <Complex> or <Polar> instance.
3427     
3428     */
3429     set: function(v) {
3430         v = v.getp();
3431         this.theta = v.theta; this.rho = v.rho;
3432     },
3433
3434     /*
3435        Method: setc
3436     
3437        Sets a <Complex> number.
3438
3439        Parameters:
3440
3441        x - A <Complex> number real part.
3442        y - A <Complex> number imaginary part.
3443     
3444     */
3445     setc: function(x, y) {
3446         this.rho = Math.sqrt(x * x + y * y);
3447         this.theta = Math.atan2(y, x);
3448         if(this.theta < 0) this.theta += Math.PI * 2;
3449     },
3450
3451     /*
3452        Method: setp
3453     
3454        Sets a polar number.
3455
3456        Parameters:
3457
3458        theta - A <Polar> number angle property.
3459        rho - A <Polar> number rho property.
3460     
3461     */
3462     setp: function(theta, rho) {
3463         this.theta = theta; 
3464         this.rho = rho;
3465     },
3466
3467     /*
3468        Method: clone
3469     
3470        Returns a copy of the current object.
3471     
3472        Returns:
3473     
3474           A copy of the real object.
3475     */
3476     clone: function() {
3477         return new Polar(this.theta, this.rho);
3478     },
3479
3480     /*
3481        Method: toComplex
3482     
3483         Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3484     
3485         Parameters:
3486
3487         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*.
3488  
3489         Returns:
3490     
3491           A new <Complex> instance.
3492     */
3493     toComplex: function(simple) {
3494         var x = Math.cos(this.theta) * this.rho;
3495         var y = Math.sin(this.theta) * this.rho;
3496         if(simple) return { 'x': x, 'y': y};
3497         return new Complex(x, y);
3498     },
3499
3500     /*
3501        Method: add
3502     
3503         Adds two <Polar> instances.
3504     
3505        Parameters:
3506
3507        polar - A <Polar> number.
3508
3509        Returns:
3510     
3511           A new Polar instance.
3512     */
3513     add: function(polar) {
3514         return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3515     },
3516     
3517     /*
3518        Method: scale
3519     
3520         Scales a polar norm.
3521     
3522         Parameters:
3523
3524         number - A scale factor.
3525         
3526         Returns:
3527     
3528           A new Polar instance.
3529     */
3530     scale: function(number) {
3531         return new Polar(this.theta, this.rho * number);
3532     },
3533     
3534     /*
3535        Method: equals
3536     
3537        Comparison method.
3538
3539        Returns *true* if the theta and rho properties are equal.
3540
3541        Parameters:
3542
3543        c - A <Polar> number.
3544
3545        Returns:
3546
3547        *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3548     */
3549     equals: function(c) {
3550         return this.theta == c.theta && this.rho == c.rho;
3551     },
3552     
3553     /*
3554        Method: $add
3555     
3556         Adds two <Polar> instances affecting the current object.
3557     
3558        Paramters:
3559
3560        polar - A <Polar> instance.
3561
3562        Returns:
3563     
3564           The changed object.
3565     */
3566     $add: function(polar) {
3567         this.theta = this.theta + polar.theta; this.rho += polar.rho;
3568         return this;
3569     },
3570
3571     /*
3572        Method: $madd
3573     
3574         Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3575     
3576        Parameters:
3577
3578        polar - A <Polar> instance.
3579
3580        Returns:
3581     
3582           The changed object.
3583     */
3584     $madd: function(polar) {
3585         this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3586         return this;
3587     },
3588
3589     
3590     /*
3591        Method: $scale
3592     
3593         Scales a polar instance affecting the object.
3594     
3595       Parameters:
3596
3597       number - A scaling factor.
3598
3599       Returns:
3600     
3601           The changed object.
3602     */
3603     $scale: function(number) {
3604         this.rho *= number;
3605         return this;
3606     },
3607     
3608     /*
3609        Method: interpolate
3610     
3611         Calculates a polar interpolation between two points at a given delta moment.
3612
3613         Parameters:
3614       
3615         elem - A <Polar> instance.
3616         delta - A delta factor ranging [0, 1].
3617     
3618        Returns:
3619     
3620           A new <Polar> instance representing an interpolation between _this_ and _elem_
3621     */
3622     interpolate: function(elem, delta) {
3623         var pi = Math.PI, pi2 = pi * 2;
3624         var ch = function(t) {
3625             var a =  (t < 0)? (t % pi2) + pi2 : t % pi2;
3626             return a;
3627         };
3628         var tt = this.theta, et = elem.theta;
3629         var sum, diff = Math.abs(tt - et);
3630         if(diff == pi) {
3631           if(tt > et) {
3632             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3633           } else {
3634             sum = ch((et - pi2 + (tt - (et)) * delta));
3635           }
3636         } else if(diff >= pi) {
3637           if(tt > et) {
3638             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3639           } else {
3640             sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3641           }
3642         } else {  
3643           sum = ch((et + (tt - et) * delta)) ;
3644         }
3645         var r = (this.rho - elem.rho) * delta + elem.rho;
3646         return {
3647           'theta': sum,
3648           'rho': r
3649         };
3650     }
3651 };
3652
3653
3654 var $P = function(a, b) { return new Polar(a, b); };
3655
3656 Polar.KER = $P(0, 0);
3657
3658
3659
3660 /*
3661  * File: Complex.js
3662  * 
3663  * Defines the <Complex> class.
3664  *
3665  * Description:
3666  *
3667  * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3668  *
3669  * See also:
3670  *
3671  * <http://en.wikipedia.org/wiki/Complex_number>
3672  *
3673 */
3674
3675 /*
3676    Class: Complex
3677     
3678    A multi-purpose Complex Class with common methods.
3679  
3680    Description:
3681  
3682    The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3683  
3684    See also:
3685  
3686    <http://en.wikipedia.org/wiki/Complex_number>
3687
3688    Parameters:
3689
3690    x - _optional_ A Complex number real part.
3691    y - _optional_ A Complex number imaginary part.
3692  
3693 */
3694
3695 var Complex = function(x, y) {
3696   this.x = x;
3697   this.y = y;
3698 };
3699
3700 $jit.Complex = Complex;
3701
3702 Complex.prototype = {
3703     /*
3704        Method: getc
3705     
3706        Returns a complex number.
3707     
3708        Returns:
3709     
3710           A complex number.
3711     */
3712     getc: function() {
3713         return this;
3714     },
3715
3716     /*
3717        Method: getp
3718     
3719        Returns a <Polar> representation of this number.
3720     
3721        Parameters:
3722
3723        simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3724
3725        Returns:
3726     
3727           A variable in <Polar> coordinates.
3728     */
3729     getp: function(simple) {
3730         return this.toPolar(simple);
3731     },
3732
3733
3734     /*
3735        Method: set
3736     
3737        Sets a number.
3738
3739        Parameters:
3740
3741        c - A <Complex> or <Polar> instance.
3742     
3743     */
3744     set: function(c) {
3745       c = c.getc(true);
3746       this.x = c.x; 
3747       this.y = c.y;
3748     },
3749
3750     /*
3751        Method: setc
3752     
3753        Sets a complex number.
3754
3755        Parameters:
3756
3757        x - A <Complex> number Real part.
3758        y - A <Complex> number Imaginary part.
3759     
3760     */
3761     setc: function(x, y) {
3762         this.x = x; 
3763         this.y = y;
3764     },
3765
3766     /*
3767        Method: setp
3768     
3769        Sets a polar number.
3770
3771        Parameters:
3772
3773        theta - A <Polar> number theta property.
3774        rho - A <Polar> number rho property.
3775     
3776     */
3777     setp: function(theta, rho) {
3778         this.x = Math.cos(theta) * rho;
3779         this.y = Math.sin(theta) * rho;
3780     },
3781
3782     /*
3783        Method: clone
3784     
3785        Returns a copy of the current object.
3786     
3787        Returns:
3788     
3789           A copy of the real object.
3790     */
3791     clone: function() {
3792         return new Complex(this.x, this.y);
3793     },
3794
3795     /*
3796        Method: toPolar
3797     
3798        Transforms cartesian to polar coordinates.
3799     
3800        Parameters:
3801
3802        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*.
3803        
3804        Returns:
3805     
3806           A new <Polar> instance.
3807     */
3808     
3809     toPolar: function(simple) {
3810         var rho = this.norm();
3811         var atan = Math.atan2(this.y, this.x);
3812         if(atan < 0) atan += Math.PI * 2;
3813         if(simple) return { 'theta': atan, 'rho': rho };
3814         return new Polar(atan, rho);
3815     },
3816     /*
3817        Method: norm
3818     
3819        Calculates a <Complex> number norm.
3820     
3821        Returns:
3822     
3823           A real number representing the complex norm.
3824     */
3825     norm: function () {
3826         return Math.sqrt(this.squaredNorm());
3827     },
3828     
3829     /*
3830        Method: squaredNorm
3831     
3832        Calculates a <Complex> number squared norm.
3833     
3834        Returns:
3835     
3836           A real number representing the complex squared norm.
3837     */
3838     squaredNorm: function () {
3839         return this.x*this.x + this.y*this.y;
3840     },
3841
3842     /*
3843        Method: add
3844     
3845        Returns the result of adding two complex numbers.
3846        
3847        Does not alter the original object.
3848
3849        Parameters:
3850     
3851           pos - A <Complex> instance.
3852     
3853        Returns:
3854     
3855          The result of adding two complex numbers.
3856     */
3857     add: function(pos) {
3858         return new Complex(this.x + pos.x, this.y + pos.y);
3859     },
3860
3861     /*
3862        Method: prod
3863     
3864        Returns the result of multiplying two <Complex> numbers.
3865        
3866        Does not alter the original object.
3867
3868        Parameters:
3869     
3870           pos - A <Complex> instance.
3871     
3872        Returns:
3873     
3874          The result of multiplying two complex numbers.
3875     */
3876     prod: function(pos) {
3877         return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3878     },
3879
3880     /*
3881        Method: conjugate
3882     
3883        Returns the conjugate of this <Complex> number.
3884
3885        Does not alter the original object.
3886
3887        Returns:
3888     
3889          The conjugate of this <Complex> number.
3890     */
3891     conjugate: function() {
3892         return new Complex(this.x, -this.y);
3893     },
3894
3895
3896     /*
3897        Method: scale
3898     
3899        Returns the result of scaling a <Complex> instance.
3900        
3901        Does not alter the original object.
3902
3903        Parameters:
3904     
3905           factor - A scale factor.
3906     
3907        Returns:
3908     
3909          The result of scaling this complex to a factor.
3910     */
3911     scale: function(factor) {
3912         return new Complex(this.x * factor, this.y * factor);
3913     },
3914
3915     /*
3916        Method: equals
3917     
3918        Comparison method.
3919
3920        Returns *true* if both real and imaginary parts are equal.
3921
3922        Parameters:
3923
3924        c - A <Complex> instance.
3925
3926        Returns:
3927
3928        A boolean instance indicating if both <Complex> numbers are equal.
3929     */
3930     equals: function(c) {
3931         return this.x == c.x && this.y == c.y;
3932     },
3933
3934     /*
3935        Method: $add
3936     
3937        Returns the result of adding two <Complex> numbers.
3938        
3939        Alters the original object.
3940
3941        Parameters:
3942     
3943           pos - A <Complex> instance.
3944     
3945        Returns:
3946     
3947          The result of adding two complex numbers.
3948     */
3949     $add: function(pos) {
3950         this.x += pos.x; this.y += pos.y;
3951         return this;    
3952     },
3953     
3954     /*
3955        Method: $prod
3956     
3957        Returns the result of multiplying two <Complex> numbers.
3958        
3959        Alters the original object.
3960
3961        Parameters:
3962     
3963           pos - A <Complex> instance.
3964     
3965        Returns:
3966     
3967          The result of multiplying two complex numbers.
3968     */
3969     $prod:function(pos) {
3970         var x = this.x, y = this.y;
3971         this.x = x*pos.x - y*pos.y;
3972         this.y = y*pos.x + x*pos.y;
3973         return this;
3974     },
3975     
3976     /*
3977        Method: $conjugate
3978     
3979        Returns the conjugate for this <Complex>.
3980        
3981        Alters the original object.
3982
3983        Returns:
3984     
3985          The conjugate for this complex.
3986     */
3987     $conjugate: function() {
3988         this.y = -this.y;
3989         return this;
3990     },
3991     
3992     /*
3993        Method: $scale
3994     
3995        Returns the result of scaling a <Complex> instance.
3996        
3997        Alters the original object.
3998
3999        Parameters:
4000     
4001           factor - A scale factor.
4002     
4003        Returns:
4004     
4005          The result of scaling this complex to a factor.
4006     */
4007     $scale: function(factor) {
4008         this.x *= factor; this.y *= factor;
4009         return this;
4010     },
4011     
4012     /*
4013        Method: $div
4014     
4015        Returns the division of two <Complex> numbers.
4016        
4017        Alters the original object.
4018
4019        Parameters:
4020     
4021           pos - A <Complex> number.
4022     
4023        Returns:
4024     
4025          The result of scaling this complex to a factor.
4026     */
4027     $div: function(pos) {
4028         var x = this.x, y = this.y;
4029         var sq = pos.squaredNorm();
4030         this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4031         return this.$scale(1 / sq);
4032     }
4033 };
4034
4035 var $C = function(a, b) { return new Complex(a, b); };
4036
4037 Complex.KER = $C(0, 0);
4038
4039
4040
4041 /*
4042  * File: Graph.js
4043  *
4044 */
4045
4046 /*
4047  Class: Graph
4048
4049  A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4050
4051  An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4052  
4053  Example:
4054
4055  (start code js)
4056    //create new visualization
4057    var viz = new $jit.Viz(options);
4058    //load JSON data
4059    viz.loadJSON(json);
4060    //access model
4061    viz.graph; //<Graph> instance
4062  (end code)
4063  
4064  Implements:
4065  
4066  The following <Graph.Util> methods are implemented in <Graph>
4067  
4068   - <Graph.Util.getNode>
4069   - <Graph.Util.eachNode>
4070   - <Graph.Util.computeLevels>
4071   - <Graph.Util.eachBFS>
4072   - <Graph.Util.clean>
4073   - <Graph.Util.getClosestNodeToPos>
4074   - <Graph.Util.getClosestNodeToOrigin>
4075  
4076 */  
4077
4078 $jit.Graph = new Class({
4079
4080   initialize: function(opt, Node, Edge, Label) {
4081     var innerOptions = {
4082     'complex': false,
4083     'Node': {}
4084     };
4085     this.Node = Node;
4086     this.Edge = Edge;
4087     this.Label = Label;
4088     this.opt = $.merge(innerOptions, opt || {});
4089     this.nodes = {};
4090     this.edges = {};
4091     
4092     //add nodeList methods
4093     var that = this;
4094     this.nodeList = {};
4095     for(var p in Accessors) {
4096       that.nodeList[p] = (function(p) {
4097         return function() {
4098           var args = Array.prototype.slice.call(arguments);
4099           that.eachNode(function(n) {
4100             n[p].apply(n, args);
4101           });
4102         };
4103       })(p);
4104     }
4105
4106  },
4107
4108 /*
4109      Method: getNode
4110     
4111      Returns a <Graph.Node> by *id*.
4112
4113      Parameters:
4114
4115      id - (string) A <Graph.Node> id.
4116
4117      Example:
4118
4119      (start code js)
4120        var node = graph.getNode('nodeId');
4121      (end code)
4122 */  
4123  getNode: function(id) {
4124     if(this.hasNode(id)) return this.nodes[id];
4125     return false;
4126  },
4127
4128  /*
4129    Method: getByName
4130   
4131    Returns a <Graph.Node> by *name*.
4132   
4133    Parameters:
4134   
4135    name - (string) A <Graph.Node> name.
4136   
4137    Example:
4138   
4139    (start code js)
4140      var node = graph.getByName('someName');
4141    (end code)
4142   */  
4143   getByName: function(name) {
4144     for(var id in this.nodes) {
4145       var n = this.nodes[id];
4146       if(n.name == name) return n;
4147     }
4148     return false;
4149   },
4150
4151 /*
4152    Method: getAdjacence
4153   
4154    Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4155
4156    Parameters:
4157
4158    id - (string) A <Graph.Node> id.
4159    id2 - (string) A <Graph.Node> id.
4160 */  
4161   getAdjacence: function (id, id2) {
4162     if(id in this.edges) {
4163       return this.edges[id][id2];
4164     }
4165     return false;
4166  },
4167
4168     /*
4169      Method: addNode
4170     
4171      Adds a node.
4172      
4173      Parameters:
4174     
4175       obj - An object with the properties described below
4176
4177       id - (string) A node id
4178       name - (string) A node's name
4179       data - (object) A node's data hash
4180
4181     See also:
4182     <Graph.Node>
4183
4184   */  
4185   addNode: function(obj) { 
4186    if(!this.nodes[obj.id]) {  
4187      var edges = this.edges[obj.id] = {};
4188      this.nodes[obj.id] = new Graph.Node($.extend({
4189         'id': obj.id,
4190         'name': obj.name,
4191         'data': $.merge(obj.data || {}, {}),
4192         'adjacencies': edges 
4193       }, this.opt.Node), 
4194       this.opt.complex, 
4195       this.Node, 
4196       this.Edge,
4197       this.Label);
4198     }
4199     return this.nodes[obj.id];
4200   },
4201   
4202     /*
4203      Method: addAdjacence
4204     
4205      Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4206      
4207      Parameters:
4208     
4209       obj - (object) A <Graph.Node> object.
4210       obj2 - (object) Another <Graph.Node> object.
4211       data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4212
4213     See also:
4214
4215     <Graph.Node>, <Graph.Adjacence>
4216     */  
4217   addAdjacence: function (obj, obj2, data) {
4218     if(!this.hasNode(obj.id)) { this.addNode(obj); }
4219     if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4220     obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4221     if(!obj.adjacentTo(obj2)) {
4222       var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4223       var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4224       adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4225       return adjsObj[obj2.id];
4226     }
4227     return this.edges[obj.id][obj2.id];
4228  },
4229
4230     /*
4231      Method: removeNode
4232     
4233      Removes a <Graph.Node> matching the specified *id*.
4234
4235      Parameters:
4236
4237      id - (string) A node's id.
4238
4239     */  
4240   removeNode: function(id) {
4241     if(this.hasNode(id)) {
4242       delete this.nodes[id];
4243       var adjs = this.edges[id];
4244       for(var to in adjs) {
4245         delete this.edges[to][id];
4246       }
4247       delete this.edges[id];
4248     }
4249   },
4250   
4251 /*
4252      Method: removeAdjacence
4253     
4254      Removes a <Graph.Adjacence> matching *id1* and *id2*.
4255
4256      Parameters:
4257
4258      id1 - (string) A <Graph.Node> id.
4259      id2 - (string) A <Graph.Node> id.
4260 */  
4261   removeAdjacence: function(id1, id2) {
4262     delete this.edges[id1][id2];
4263     delete this.edges[id2][id1];
4264   },
4265
4266    /*
4267      Method: hasNode
4268     
4269      Returns a boolean indicating if the node belongs to the <Graph> or not.
4270      
4271      Parameters:
4272     
4273         id - (string) Node id.
4274    */  
4275   hasNode: function(id) {
4276     return id in this.nodes;
4277   },
4278   
4279   /*
4280     Method: empty
4281
4282     Empties the Graph
4283
4284   */
4285   empty: function() { this.nodes = {}; this.edges = {};}
4286
4287 });
4288
4289 var Graph = $jit.Graph;
4290
4291 /*
4292  Object: Accessors
4293  
4294  Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4295  
4296  */
4297 var Accessors;
4298
4299 (function () {
4300   var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4301     var data;
4302     type = type || 'current';
4303     prefix = "$" + (prefix ? prefix + "-" : "");
4304
4305     if(type == 'current') {
4306       data = this.data;
4307     } else if(type == 'start') {
4308       data = this.startData;
4309     } else if(type == 'end') {
4310       data = this.endData;
4311     }
4312
4313     var dollar = prefix + prop;
4314
4315     if(force) {
4316       return data[dollar];
4317     }
4318
4319     if(!this.Config.overridable)
4320       return prefixConfig[prop] || 0;
4321
4322     return (dollar in data) ?
4323       data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4324   }
4325
4326   var setDataInternal = function(prefix, prop, value, type) {
4327     type = type || 'current';
4328     prefix = '$' + (prefix ? prefix + '-' : '');
4329
4330     var data;
4331
4332     if(type == 'current') {
4333       data = this.data;
4334     } else if(type == 'start') {
4335       data = this.startData;
4336     } else if(type == 'end') {
4337       data = this.endData;
4338     }
4339
4340     data[prefix + prop] = value;
4341   }
4342
4343   var removeDataInternal = function(prefix, properties) {
4344     prefix = '$' + (prefix ? prefix + '-' : '');
4345     var that = this;
4346     $.each(properties, function(prop) {
4347       var pref = prefix + prop;
4348       delete that.data[pref];
4349       delete that.endData[pref];
4350       delete that.startData[pref];
4351     });
4352   }
4353
4354   Accessors = {
4355     /*
4356     Method: getData
4357
4358     Returns the specified data value property.
4359     This is useful for querying special/reserved <Graph.Node> data properties
4360     (i.e dollar prefixed properties).
4361
4362     Parameters:
4363
4364       prop  - (string) The name of the property. The dollar sign is not needed. For
4365               example *getData(width)* will return *data.$width*.
4366       type  - (string) The type of the data property queried. Default's "current". You can access *start* and *end* 
4367               data properties also. These properties are used when making animations.
4368       force - (boolean) Whether to obtain the true value of the property (equivalent to
4369               *data.$prop*) or to check for *node.overridable = true* first.
4370
4371     Returns:
4372
4373       The value of the dollar prefixed property or the global Node/Edge property
4374       value if *overridable=false*
4375
4376     Example:
4377     (start code js)
4378      node.getData('width'); //will return node.data.$width if Node.overridable=true;
4379     (end code)
4380     */
4381     getData: function(prop, type, force) {
4382       return getDataInternal.call(this, "", prop, type, force, this.Config);
4383     },
4384
4385
4386     /*
4387     Method: setData
4388
4389     Sets the current data property with some specific value.
4390     This method is only useful for reserved (dollar prefixed) properties.
4391
4392     Parameters:
4393
4394       prop  - (string) The name of the property. The dollar sign is not necessary. For
4395               example *setData(width)* will set *data.$width*.
4396       value - (mixed) The value to store.
4397       type  - (string) The type of the data property to store. Default's "current" but
4398               can also be "start" or "end".
4399
4400     Example:
4401     
4402     (start code js)
4403      node.setData('width', 30);
4404     (end code)
4405     
4406     If we were to make an animation of a node/edge width then we could do
4407     
4408     (start code js)
4409       var node = viz.getNode('nodeId');
4410       //set start and end values
4411       node.setData('width', 10, 'start');
4412       node.setData('width', 30, 'end');
4413       //will animate nodes width property
4414       viz.fx.animate({
4415         modes: ['node-property:width'],
4416         duration: 1000
4417       });
4418     (end code)
4419     */
4420     setData: function(prop, value, type) {
4421       setDataInternal.call(this, "", prop, value, type);
4422     },
4423
4424     /*
4425     Method: setDataset
4426
4427     Convenience method to set multiple data values at once.
4428     
4429     Parameters:
4430     
4431     types - (array|string) A set of 'current', 'end' or 'start' values.
4432     obj - (object) A hash containing the names and values of the properties to be altered.
4433
4434     Example:
4435     (start code js)
4436       node.setDataset(['current', 'end'], {
4437         'width': [100, 5],
4438         'color': ['#fff', '#ccc']
4439       });
4440       //...or also
4441       node.setDataset('end', {
4442         'width': 5,
4443         'color': '#ccc'
4444       });
4445     (end code)
4446     
4447     See also: 
4448     
4449     <Accessors.setData>
4450     
4451     */
4452     setDataset: function(types, obj) {
4453       types = $.splat(types);
4454       for(var attr in obj) {
4455         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4456           this.setData(attr, val[i], types[i]);
4457         }
4458       }
4459     },
4460     
4461     /*
4462     Method: removeData
4463
4464     Remove data properties.
4465
4466     Parameters:
4467
4468     One or more property names as arguments. The dollar sign is not needed.
4469
4470     Example:
4471     (start code js)
4472     node.removeData('width'); //now the default width value is returned
4473     (end code)
4474     */
4475     removeData: function() {
4476       removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4477     },
4478
4479     /*
4480     Method: getCanvasStyle
4481
4482     Returns the specified canvas style data value property. This is useful for
4483     querying special/reserved <Graph.Node> canvas style data properties (i.e.
4484     dollar prefixed properties that match with $canvas-<name of canvas style>).
4485
4486     Parameters:
4487
4488       prop  - (string) The name of the property. The dollar sign is not needed. For
4489               example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4490       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4491               data properties also.
4492               
4493     Example:
4494     (start code js)
4495       node.getCanvasStyle('shadowBlur');
4496     (end code)
4497     
4498     See also:
4499     
4500     <Accessors.getData>
4501     */
4502     getCanvasStyle: function(prop, type, force) {
4503       return getDataInternal.call(
4504           this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4505     },
4506
4507     /*
4508     Method: setCanvasStyle
4509
4510     Sets the canvas style data property with some specific value.
4511     This method is only useful for reserved (dollar prefixed) properties.
4512     
4513     Parameters:
4514     
4515     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4516     value - (mixed) The value to set to the property.
4517     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4518     
4519     Example:
4520     
4521     (start code js)
4522      node.setCanvasStyle('shadowBlur', 30);
4523     (end code)
4524     
4525     If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4526     
4527     (start code js)
4528       var node = viz.getNode('nodeId');
4529       //set start and end values
4530       node.setCanvasStyle('shadowBlur', 10, 'start');
4531       node.setCanvasStyle('shadowBlur', 30, 'end');
4532       //will animate nodes canvas style property for nodes
4533       viz.fx.animate({
4534         modes: ['node-style:shadowBlur'],
4535         duration: 1000
4536       });
4537     (end code)
4538     
4539     See also:
4540     
4541     <Accessors.setData>.
4542     */
4543     setCanvasStyle: function(prop, value, type) {
4544       setDataInternal.call(this, 'canvas', prop, value, type);
4545     },
4546
4547     /*
4548     Method: setCanvasStyles
4549
4550     Convenience method to set multiple styles at once.
4551
4552     Parameters:
4553     
4554     types - (array|string) A set of 'current', 'end' or 'start' values.
4555     obj - (object) A hash containing the names and values of the properties to be altered.
4556
4557     See also:
4558     
4559     <Accessors.setDataset>.
4560     */
4561     setCanvasStyles: function(types, obj) {
4562       types = $.splat(types);
4563       for(var attr in obj) {
4564         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4565           this.setCanvasStyle(attr, val[i], types[i]);
4566         }
4567       }
4568     },
4569
4570     /*
4571     Method: removeCanvasStyle
4572
4573     Remove canvas style properties from data.
4574
4575     Parameters:
4576     
4577     A variable number of canvas style strings.
4578
4579     See also:
4580     
4581     <Accessors.removeData>.
4582     */
4583     removeCanvasStyle: function() {
4584       removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4585     },
4586
4587     /*
4588     Method: getLabelData
4589
4590     Returns the specified label data value property. This is useful for
4591     querying special/reserved <Graph.Node> label options (i.e.
4592     dollar prefixed properties that match with $label-<name of label style>).
4593
4594     Parameters:
4595
4596       prop  - (string) The name of the property. The dollar sign prefix is not needed. For
4597               example *getLabelData(size)* will return *data[$label-size]*.
4598       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4599               data properties also.
4600               
4601     See also:
4602     
4603     <Accessors.getData>.
4604     */
4605     getLabelData: function(prop, type, force) {
4606       return getDataInternal.call(
4607           this, 'label', prop, type, force, this.Label);
4608     },
4609
4610     /*
4611     Method: setLabelData
4612
4613     Sets the current label data with some specific value.
4614     This method is only useful for reserved (dollar prefixed) properties.
4615
4616     Parameters:
4617     
4618     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4619     value - (mixed) The value to set to the property.
4620     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4621     
4622     Example:
4623     
4624     (start code js)
4625      node.setLabelData('size', 30);
4626     (end code)
4627     
4628     If we were to make an animation of a node label size then we could do
4629     
4630     (start code js)
4631       var node = viz.getNode('nodeId');
4632       //set start and end values
4633       node.setLabelData('size', 10, 'start');
4634       node.setLabelData('size', 30, 'end');
4635       //will animate nodes label size
4636       viz.fx.animate({
4637         modes: ['label-property:size'],
4638         duration: 1000
4639       });
4640     (end code)
4641     
4642     See also:
4643     
4644     <Accessors.setData>.
4645     */
4646     setLabelData: function(prop, value, type) {
4647       setDataInternal.call(this, 'label', prop, value, type);
4648     },
4649
4650     /*
4651     Method: setLabelDataset
4652
4653     Convenience function to set multiple label data at once.
4654
4655     Parameters:
4656     
4657     types - (array|string) A set of 'current', 'end' or 'start' values.
4658     obj - (object) A hash containing the names and values of the properties to be altered.
4659
4660     See also:
4661     
4662     <Accessors.setDataset>.
4663     */
4664     setLabelDataset: function(types, obj) {
4665       types = $.splat(types);
4666       for(var attr in obj) {
4667         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4668           this.setLabelData(attr, val[i], types[i]);
4669         }
4670       }
4671     },
4672
4673     /*
4674     Method: removeLabelData
4675
4676     Remove label properties from data.
4677     
4678     Parameters:
4679     
4680     A variable number of label property strings.
4681
4682     See also:
4683     
4684     <Accessors.removeData>.
4685     */
4686     removeLabelData: function() {
4687       removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4688     }
4689   };
4690 })();
4691
4692 /*
4693      Class: Graph.Node
4694
4695      A <Graph> node.
4696      
4697      Implements:
4698      
4699      <Accessors> methods.
4700      
4701      The following <Graph.Util> methods are implemented by <Graph.Node>
4702      
4703     - <Graph.Util.eachAdjacency>
4704     - <Graph.Util.eachLevel>
4705     - <Graph.Util.eachSubgraph>
4706     - <Graph.Util.eachSubnode>
4707     - <Graph.Util.anySubnode>
4708     - <Graph.Util.getSubnodes>
4709     - <Graph.Util.getParents>
4710     - <Graph.Util.isDescendantOf>     
4711 */
4712 Graph.Node = new Class({
4713     
4714   initialize: function(opt, complex, Node, Edge, Label) {
4715     var innerOptions = {
4716       'id': '',
4717       'name': '',
4718       'data': {},
4719       'startData': {},
4720       'endData': {},
4721       'adjacencies': {},
4722
4723       'selected': false,
4724       'drawn': false,
4725       'exist': false,
4726
4727       'angleSpan': {
4728         'begin': 0,
4729         'end' : 0
4730       },
4731
4732       'pos': (complex && $C(0, 0)) || $P(0, 0),
4733       'startPos': (complex && $C(0, 0)) || $P(0, 0),
4734       'endPos': (complex && $C(0, 0)) || $P(0, 0)
4735     };
4736     
4737     $.extend(this, $.extend(innerOptions, opt));
4738     this.Config = this.Node = Node;
4739     this.Edge = Edge;
4740     this.Label = Label;
4741   },
4742
4743     /*
4744        Method: adjacentTo
4745     
4746        Indicates if the node is adjacent to the node specified by id
4747
4748        Parameters:
4749     
4750           id - (string) A node id.
4751     
4752        Example:
4753        (start code js)
4754         node.adjacentTo('nodeId') == true;
4755        (end code)
4756     */
4757     adjacentTo: function(node) {
4758         return node.id in this.adjacencies;
4759     },
4760
4761     /*
4762        Method: getAdjacency
4763     
4764        Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4765
4766        Parameters:
4767     
4768           id - (string) A node id.
4769     */  
4770     getAdjacency: function(id) {
4771         return this.adjacencies[id];
4772     },
4773
4774     /*
4775       Method: getPos
4776    
4777       Returns the position of the node.
4778   
4779       Parameters:
4780    
4781          type - (string) Default's *current*. Possible values are "start", "end" or "current".
4782    
4783       Returns:
4784    
4785         A <Complex> or <Polar> instance.
4786   
4787       Example:
4788       (start code js)
4789        var pos = node.getPos('end');
4790       (end code)
4791    */
4792    getPos: function(type) {
4793        type = type || "current";
4794        if(type == "current") {
4795          return this.pos;
4796        } else if(type == "end") {
4797          return this.endPos;
4798        } else if(type == "start") {
4799          return this.startPos;
4800        }
4801    },
4802    /*
4803      Method: setPos
4804   
4805      Sets the node's position.
4806   
4807      Parameters:
4808   
4809         value - (object) A <Complex> or <Polar> instance.
4810         type - (string) Default's *current*. Possible values are "start", "end" or "current".
4811   
4812      Example:
4813      (start code js)
4814       node.setPos(new $jit.Complex(0, 0), 'end');
4815      (end code)
4816   */
4817   setPos: function(value, type) {
4818       type = type || "current";
4819       var pos;
4820       if(type == "current") {
4821         pos = this.pos;
4822       } else if(type == "end") {
4823         pos = this.endPos;
4824       } else if(type == "start") {
4825         pos = this.startPos;
4826       }
4827       pos.set(value);
4828   }
4829 });
4830
4831 Graph.Node.implement(Accessors);
4832
4833 /*
4834      Class: Graph.Adjacence
4835
4836      A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4837      
4838      Implements:
4839      
4840      <Accessors> methods.
4841
4842      See also:
4843
4844      <Graph>, <Graph.Node>
4845
4846      Properties:
4847      
4848       nodeFrom - A <Graph.Node> connected by this edge.
4849       nodeTo - Another  <Graph.Node> connected by this edge.
4850       data - Node data property containing a hash (i.e {}) with custom options.
4851 */
4852 Graph.Adjacence = new Class({
4853   
4854   initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4855     this.nodeFrom = nodeFrom;
4856     this.nodeTo = nodeTo;
4857     this.data = data || {};
4858     this.startData = {};
4859     this.endData = {};
4860     this.Config = this.Edge = Edge;
4861     this.Label = Label;
4862   }
4863 });
4864
4865 Graph.Adjacence.implement(Accessors);
4866
4867 /*
4868    Object: Graph.Util
4869
4870    <Graph> traversal and processing utility object.
4871    
4872    Note:
4873    
4874    For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4875 */
4876 Graph.Util = {
4877     /*
4878        filter
4879     
4880        For internal use only. Provides a filtering function based on flags.
4881     */
4882     filter: function(param) {
4883         if(!param || !($.type(param) == 'string')) return function() { return true; };
4884         var props = param.split(" ");
4885         return function(elem) {
4886             for(var i=0; i<props.length; i++) { 
4887               if(elem[props[i]]) { 
4888                 return false; 
4889               }
4890             }
4891             return true;
4892         };
4893     },
4894     /*
4895        Method: getNode
4896     
4897        Returns a <Graph.Node> by *id*.
4898        
4899        Also implemented by:
4900        
4901        <Graph>
4902
4903        Parameters:
4904
4905        graph - (object) A <Graph> instance.
4906        id - (string) A <Graph.Node> id.
4907
4908        Example:
4909
4910        (start code js)
4911          $jit.Graph.Util.getNode(graph, 'nodeid');
4912          //or...
4913          graph.getNode('nodeid');
4914        (end code)
4915     */
4916     getNode: function(graph, id) {
4917         return graph.nodes[id];
4918     },
4919     
4920     /*
4921        Method: eachNode
4922     
4923        Iterates over <Graph> nodes performing an *action*.
4924        
4925        Also implemented by:
4926        
4927        <Graph>.
4928
4929        Parameters:
4930
4931        graph - (object) A <Graph> instance.
4932        action - (function) A callback function having a <Graph.Node> as first formal parameter.
4933
4934        Example:
4935        (start code js)
4936          $jit.Graph.Util.eachNode(graph, function(node) {
4937           alert(node.name);
4938          });
4939          //or...
4940          graph.eachNode(function(node) {
4941            alert(node.name);
4942          });
4943        (end code)
4944     */
4945     eachNode: function(graph, action, flags) {
4946         var filter = this.filter(flags);
4947         for(var i in graph.nodes) {
4948           if(filter(graph.nodes[i])) action(graph.nodes[i]);
4949         } 
4950     },
4951     
4952     /*
4953        Method: eachAdjacency
4954     
4955        Iterates over <Graph.Node> adjacencies applying the *action* function.
4956        
4957        Also implemented by:
4958        
4959        <Graph.Node>.
4960
4961        Parameters:
4962
4963        node - (object) A <Graph.Node>.
4964        action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4965
4966        Example:
4967        (start code js)
4968          $jit.Graph.Util.eachAdjacency(node, function(adj) {
4969           alert(adj.nodeTo.name);
4970          });
4971          //or...
4972          node.eachAdjacency(function(adj) {
4973            alert(adj.nodeTo.name);
4974          });
4975        (end code)
4976     */
4977     eachAdjacency: function(node, action, flags) {
4978         var adj = node.adjacencies, filter = this.filter(flags);
4979         for(var id in adj) {
4980           var a = adj[id];
4981           if(filter(a)) {
4982             if(a.nodeFrom != node) {
4983               var tmp = a.nodeFrom;
4984               a.nodeFrom = a.nodeTo;
4985               a.nodeTo = tmp;
4986             }
4987             action(a, id);
4988           }
4989         }
4990     },
4991
4992      /*
4993        Method: computeLevels
4994     
4995        Performs a BFS traversal setting the correct depth for each node.
4996         
4997        Also implemented by:
4998        
4999        <Graph>.
5000        
5001        Note:
5002        
5003        The depth of each node can then be accessed by 
5004        >node._depth
5005
5006        Parameters:
5007
5008        graph - (object) A <Graph>.
5009        id - (string) A starting node id for the BFS traversal.
5010        startDepth - (optional|number) A minimum depth value. Default's 0.
5011
5012     */
5013     computeLevels: function(graph, id, startDepth, flags) {
5014         startDepth = startDepth || 0;
5015         var filter = this.filter(flags);
5016         this.eachNode(graph, function(elem) {
5017             elem._flag = false;
5018             elem._depth = -1;
5019         }, flags);
5020         var root = graph.getNode(id);
5021         root._depth = startDepth;
5022         var queue = [root];
5023         while(queue.length != 0) {
5024             var node = queue.pop();
5025             node._flag = true;
5026             this.eachAdjacency(node, function(adj) {
5027                 var n = adj.nodeTo;
5028                 if(n._flag == false && filter(n)) {
5029                     if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5030                     queue.unshift(n);
5031                 }
5032             }, flags);
5033         }
5034     },
5035
5036     /*
5037        Method: eachBFS
5038     
5039        Performs a BFS traversal applying *action* to each <Graph.Node>.
5040        
5041        Also implemented by:
5042        
5043        <Graph>.
5044
5045        Parameters:
5046
5047        graph - (object) A <Graph>.
5048        id - (string) A starting node id for the BFS traversal.
5049        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5050
5051        Example:
5052        (start code js)
5053          $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5054           alert(node.name);
5055          });
5056          //or...
5057          graph.eachBFS('mynodeid', function(node) {
5058            alert(node.name);
5059          });
5060        (end code)
5061     */
5062     eachBFS: function(graph, id, action, flags) {
5063         var filter = this.filter(flags);
5064         this.clean(graph);
5065         var queue = [graph.getNode(id)];
5066         while(queue.length != 0) {
5067             var node = queue.pop();
5068             node._flag = true;
5069             action(node, node._depth);
5070             this.eachAdjacency(node, function(adj) {
5071                 var n = adj.nodeTo;
5072                 if(n._flag == false && filter(n)) {
5073                     n._flag = true;
5074                     queue.unshift(n);
5075                 }
5076             }, flags);
5077         }
5078     },
5079     
5080     /*
5081        Method: eachLevel
5082     
5083        Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5084        
5085        Also implemented by:
5086        
5087        <Graph.Node>.
5088
5089        Parameters:
5090        
5091        node - (object) A <Graph.Node>.
5092        levelBegin - (number) A relative level value.
5093        levelEnd - (number) A relative level value.
5094        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5095
5096     */
5097     eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5098         var d = node._depth, filter = this.filter(flags), that = this;
5099         levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5100         (function loopLevel(node, levelBegin, levelEnd) {
5101             var d = node._depth;
5102             if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5103             if(d < levelEnd) {
5104                 that.eachAdjacency(node, function(adj) {
5105                     var n = adj.nodeTo;
5106                     if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5107                 });
5108             }
5109         })(node, levelBegin + d, levelEnd + d);      
5110     },
5111
5112     /*
5113        Method: eachSubgraph
5114     
5115        Iterates over a node's children recursively.
5116        
5117        Also implemented by:
5118        
5119        <Graph.Node>.
5120
5121        Parameters:
5122        node - (object) A <Graph.Node>.
5123        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5124
5125        Example:
5126        (start code js)
5127          $jit.Graph.Util.eachSubgraph(node, function(node) {
5128            alert(node.name);
5129          });
5130          //or...
5131          node.eachSubgraph(function(node) {
5132            alert(node.name);
5133          });
5134        (end code)
5135     */
5136     eachSubgraph: function(node, action, flags) {
5137       this.eachLevel(node, 0, false, action, flags);
5138     },
5139
5140     /*
5141        Method: eachSubnode
5142     
5143        Iterates over a node's children (without deeper recursion).
5144        
5145        Also implemented by:
5146        
5147        <Graph.Node>.
5148        
5149        Parameters:
5150        node - (object) A <Graph.Node>.
5151        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5152
5153        Example:
5154        (start code js)
5155          $jit.Graph.Util.eachSubnode(node, function(node) {
5156           alert(node.name);
5157          });
5158          //or...
5159          node.eachSubnode(function(node) {
5160            alert(node.name);
5161          });
5162        (end code)
5163     */
5164     eachSubnode: function(node, action, flags) {
5165         this.eachLevel(node, 1, 1, action, flags);
5166     },
5167
5168     /*
5169        Method: anySubnode
5170     
5171        Returns *true* if any subnode matches the given condition.
5172        
5173        Also implemented by:
5174        
5175        <Graph.Node>.
5176
5177        Parameters:
5178        node - (object) A <Graph.Node>.
5179        cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5180
5181        Example:
5182        (start code js)
5183          $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5184          //or...
5185          node.anySubnode(function(node) { return node.name == 'mynodename'; });
5186        (end code)
5187     */
5188     anySubnode: function(node, cond, flags) {
5189       var flag = false;
5190       cond = cond || $.lambda(true);
5191       var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5192       this.eachSubnode(node, function(elem) {
5193         if(c(elem)) flag = true;
5194       }, flags);
5195       return flag;
5196     },
5197   
5198     /*
5199        Method: getSubnodes
5200     
5201        Collects all subnodes for a specified node. 
5202        The *level* parameter filters nodes having relative depth of *level* from the root node. 
5203        
5204        Also implemented by:
5205        
5206        <Graph.Node>.
5207
5208        Parameters:
5209        node - (object) A <Graph.Node>.
5210        level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5211
5212        Returns:
5213        An array of nodes.
5214
5215     */
5216     getSubnodes: function(node, level, flags) {
5217         var ans = [], that = this;
5218         level = level || 0;
5219         var levelStart, levelEnd;
5220         if($.type(level) == 'array') {
5221             levelStart = level[0];
5222             levelEnd = level[1];
5223         } else {
5224             levelStart = level;
5225             levelEnd = Number.MAX_VALUE - node._depth;
5226         }
5227         this.eachLevel(node, levelStart, levelEnd, function(n) {
5228             ans.push(n);
5229         }, flags);
5230         return ans;
5231     },
5232   
5233   
5234     /*
5235        Method: getParents
5236     
5237        Returns an Array of <Graph.Nodes> which are parents of the given node.
5238        
5239        Also implemented by:
5240        
5241        <Graph.Node>.
5242
5243        Parameters:
5244        node - (object) A <Graph.Node>.
5245
5246        Returns:
5247        An Array of <Graph.Nodes>.
5248
5249        Example:
5250        (start code js)
5251          var pars = $jit.Graph.Util.getParents(node);
5252          //or...
5253          var pars = node.getParents();
5254          
5255          if(pars.length > 0) {
5256            //do stuff with parents
5257          }
5258        (end code)
5259     */
5260     getParents: function(node) {
5261         var ans = [];
5262         this.eachAdjacency(node, function(adj) {
5263             var n = adj.nodeTo;
5264             if(n._depth < node._depth) ans.push(n);
5265         });
5266         return ans;
5267     },
5268     
5269     /*
5270     Method: isDescendantOf
5271  
5272     Returns a boolean indicating if some node is descendant of the node with the given id. 
5273
5274     Also implemented by:
5275     
5276     <Graph.Node>.
5277     
5278     
5279     Parameters:
5280     node - (object) A <Graph.Node>.
5281     id - (string) A <Graph.Node> id.
5282
5283     Example:
5284     (start code js)
5285       $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5286       //or...
5287       node.isDescendantOf('nodeid');//true|false
5288     (end code)
5289  */
5290  isDescendantOf: function(node, id) {
5291     if(node.id == id) return true;
5292     var pars = this.getParents(node), ans = false;
5293     for ( var i = 0; !ans && i < pars.length; i++) {
5294     ans = ans || this.isDescendantOf(pars[i], id);
5295   }
5296     return ans;
5297  },
5298
5299  /*
5300      Method: clean
5301   
5302      Cleans flags from nodes.
5303
5304      Also implemented by:
5305      
5306      <Graph>.
5307      
5308      Parameters:
5309      graph - A <Graph> instance.
5310   */
5311   clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5312   
5313   /* 
5314     Method: getClosestNodeToOrigin 
5315   
5316     Returns the closest node to the center of canvas.
5317   
5318     Also implemented by:
5319     
5320     <Graph>.
5321     
5322     Parameters:
5323    
5324      graph - (object) A <Graph> instance.
5325      prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5326   
5327   */
5328   getClosestNodeToOrigin: function(graph, prop, flags) {
5329    return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5330   },
5331   
5332   /* 
5333     Method: getClosestNodeToPos
5334   
5335     Returns the closest node to the given position.
5336   
5337     Also implemented by:
5338     
5339     <Graph>.
5340     
5341     Parameters:
5342    
5343      graph - (object) A <Graph> instance.
5344      pos - (object) A <Complex> or <Polar> instance.
5345      prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5346   
5347   */
5348   getClosestNodeToPos: function(graph, pos, prop, flags) {
5349    var node = null;
5350    prop = prop || 'current';
5351    pos = pos && pos.getc(true) || Complex.KER;
5352    var distance = function(a, b) {
5353      var d1 = a.x - b.x, d2 = a.y - b.y;
5354      return d1 * d1 + d2 * d2;
5355    };
5356    this.eachNode(graph, function(elem) {
5357      node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5358          node.getPos(prop).getc(true), pos)) ? elem : node;
5359    }, flags);
5360    return node;
5361   } 
5362 };
5363
5364 //Append graph methods to <Graph>
5365 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5366   Graph.prototype[m] = function() {
5367     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5368   };
5369 });
5370
5371 //Append node methods to <Graph.Node>
5372 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5373   Graph.Node.prototype[m] = function() {
5374     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5375   };
5376 });
5377
5378 /*
5379  * File: Graph.Op.js
5380  *
5381 */
5382
5383 /*
5384    Object: Graph.Op
5385
5386    Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>, 
5387    morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5388
5389 */
5390 Graph.Op = {
5391
5392     options: {
5393       type: 'nothing',
5394       duration: 2000,
5395       hideLabels: true,
5396       fps:30
5397     },
5398     
5399     initialize: function(viz) {
5400       this.viz = viz;
5401     },
5402
5403     /*
5404        Method: removeNode
5405     
5406        Removes one or more <Graph.Nodes> from the visualization. 
5407        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5408
5409        Parameters:
5410     
5411         node - (string|array) The node's id. Can also be an array having many ids.
5412         opt - (object) Animation options. It's an object with optional properties described below
5413         type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5414         duration - Described in <Options.Fx>.
5415         fps - Described in <Options.Fx>.
5416         transition - Described in <Options.Fx>.
5417         hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5418    
5419       Example:
5420       (start code js)
5421         var viz = new $jit.Viz(options);
5422         viz.op.removeNode('nodeId', {
5423           type: 'fade:seq',
5424           duration: 1000,
5425           hideLabels: false,
5426           transition: $jit.Trans.Quart.easeOut
5427         });
5428         //or also
5429         viz.op.removeNode(['someId', 'otherId'], {
5430           type: 'fade:con',
5431           duration: 1500
5432         });
5433       (end code)
5434     */
5435   
5436     removeNode: function(node, opt) {
5437         var viz = this.viz;
5438         var options = $.merge(this.options, viz.controller, opt);
5439         var n = $.splat(node);
5440         var i, that, nodeObj;
5441         switch(options.type) {
5442             case 'nothing':
5443                 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5444                 break;
5445             
5446             case 'replot':
5447                 this.removeNode(n, { type: 'nothing' });
5448                 viz.labels.clearLabels();
5449                 viz.refresh(true);
5450                 break;
5451             
5452             case 'fade:seq': case 'fade':
5453                 that = this;
5454                 //set alpha to 0 for nodes to remove.
5455                 for(i=0; i<n.length; i++) {
5456                     nodeObj = viz.graph.getNode(n[i]);
5457                     nodeObj.setData('alpha', 0, 'end');
5458                 }
5459                 viz.fx.animate($.merge(options, {
5460                     modes: ['node-property:alpha'],
5461                     onComplete: function() {
5462                         that.removeNode(n, { type: 'nothing' });
5463                         viz.labels.clearLabels();
5464                         viz.reposition();
5465                         viz.fx.animate($.merge(options, {
5466                             modes: ['linear']
5467                         }));
5468                     }
5469                 }));
5470                 break;
5471             
5472             case 'fade:con':
5473                 that = this;
5474                 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5475                 for(i=0; i<n.length; i++) {
5476                     nodeObj = viz.graph.getNode(n[i]);
5477                     nodeObj.setData('alpha', 0, 'end');
5478                     nodeObj.ignore = true;
5479                 }
5480                 viz.reposition();
5481                 viz.fx.animate($.merge(options, {
5482                     modes: ['node-property:alpha', 'linear'],
5483                     onComplete: function() {
5484                         that.removeNode(n, { type: 'nothing' });
5485                     }
5486                 }));
5487                 break;
5488             
5489             case 'iter':
5490                 that = this;
5491                 viz.fx.sequence({
5492                     condition: function() { return n.length != 0; },
5493                     step: function() { that.removeNode(n.shift(), { type: 'nothing' });  viz.labels.clearLabels(); },
5494                     onComplete: function() { options.onComplete(); },
5495                     duration: Math.ceil(options.duration / n.length)
5496                 });
5497                 break;
5498                 
5499             default: this.doError();
5500         }
5501     },
5502     
5503     /*
5504        Method: removeEdge
5505     
5506        Removes one or more <Graph.Adjacences> from the visualization. 
5507        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5508
5509        Parameters:
5510     
5511        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'], ...]).
5512        opt - (object) Animation options. It's an object with optional properties described below
5513        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5514        duration - Described in <Options.Fx>.
5515        fps - Described in <Options.Fx>.
5516        transition - Described in <Options.Fx>.
5517        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5518    
5519       Example:
5520       (start code js)
5521         var viz = new $jit.Viz(options);
5522         viz.op.removeEdge(['nodeId', 'otherId'], {
5523           type: 'fade:seq',
5524           duration: 1000,
5525           hideLabels: false,
5526           transition: $jit.Trans.Quart.easeOut
5527         });
5528         //or also
5529         viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5530           type: 'fade:con',
5531           duration: 1500
5532         });
5533       (end code)
5534     
5535     */
5536     removeEdge: function(vertex, opt) {
5537         var viz = this.viz;
5538         var options = $.merge(this.options, viz.controller, opt);
5539         var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5540         var i, that, adj;
5541         switch(options.type) {
5542             case 'nothing':
5543                 for(i=0; i<v.length; i++)   viz.graph.removeAdjacence(v[i][0], v[i][1]);
5544                 break;
5545             
5546             case 'replot':
5547                 this.removeEdge(v, { type: 'nothing' });
5548                 viz.refresh(true);
5549                 break;
5550             
5551             case 'fade:seq': case 'fade':
5552                 that = this;
5553                 //set alpha to 0 for edges to remove.
5554                 for(i=0; i<v.length; i++) {
5555                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5556                     if(adj) {
5557                         adj.setData('alpha', 0,'end');
5558                     }
5559                 }
5560                 viz.fx.animate($.merge(options, {
5561                     modes: ['edge-property:alpha'],
5562                     onComplete: function() {
5563                         that.removeEdge(v, { type: 'nothing' });
5564                         viz.reposition();
5565                         viz.fx.animate($.merge(options, {
5566                             modes: ['linear']
5567                         }));
5568                     }
5569                 }));
5570                 break;
5571             
5572             case 'fade:con':
5573                 that = this;
5574                 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5575                 for(i=0; i<v.length; i++) {
5576                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5577                     if(adj) {
5578                         adj.setData('alpha',0 ,'end');
5579                         adj.ignore = true;
5580                     }
5581                 }
5582                 viz.reposition();
5583                 viz.fx.animate($.merge(options, {
5584                     modes: ['edge-property:alpha', 'linear'],
5585                     onComplete: function() {
5586                         that.removeEdge(v, { type: 'nothing' });
5587                     }
5588                 }));
5589                 break;
5590             
5591             case 'iter':
5592                 that = this;
5593                 viz.fx.sequence({
5594                     condition: function() { return v.length != 0; },
5595                     step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5596                     onComplete: function() { options.onComplete(); },
5597                     duration: Math.ceil(options.duration / v.length)
5598                 });
5599                 break;
5600                 
5601             default: this.doError();
5602         }
5603     },
5604     
5605     /*
5606        Method: sum
5607     
5608        Adds a new graph to the visualization. 
5609        The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization. 
5610        The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5611
5612        Parameters:
5613     
5614        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5615        opt - (object) Animation options. It's an object with optional properties described below
5616        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con".
5617        duration - Described in <Options.Fx>.
5618        fps - Described in <Options.Fx>.
5619        transition - Described in <Options.Fx>.
5620        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5621    
5622       Example:
5623       (start code js)
5624         //...json contains a tree or graph structure...
5625
5626         var viz = new $jit.Viz(options);
5627         viz.op.sum(json, {
5628           type: 'fade:seq',
5629           duration: 1000,
5630           hideLabels: false,
5631           transition: $jit.Trans.Quart.easeOut
5632         });
5633         //or also
5634         viz.op.sum(json, {
5635           type: 'fade:con',
5636           duration: 1500
5637         });
5638       (end code)
5639     
5640     */
5641     sum: function(json, opt) {
5642         var viz = this.viz;
5643         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5644         var graph;
5645         viz.root = opt.id || viz.root;
5646         switch(options.type) {
5647             case 'nothing':
5648                 graph = viz.construct(json);
5649                 graph.eachNode(function(elem) {
5650                     elem.eachAdjacency(function(adj) {
5651                         viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5652                     });
5653                 });
5654                 break;
5655             
5656             case 'replot':
5657                 viz.refresh(true);
5658                 this.sum(json, { type: 'nothing' });
5659                 viz.refresh(true);
5660                 break;
5661             
5662             case 'fade:seq': case 'fade': case 'fade:con':
5663                 that = this;
5664                 graph = viz.construct(json);
5665
5666                 //set alpha to 0 for nodes to add.
5667                 var fadeEdges = this.preprocessSum(graph);
5668                 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5669                 viz.reposition();
5670                 if(options.type != 'fade:con') {
5671                     viz.fx.animate($.merge(options, {
5672                         modes: ['linear'],
5673                         onComplete: function() {
5674                             viz.fx.animate($.merge(options, {
5675                                 modes: modes,
5676                                 onComplete: function() {
5677                                     options.onComplete();
5678                                 }
5679                             }));
5680                         }
5681                     }));
5682                 } else {
5683                     viz.graph.eachNode(function(elem) {
5684                         if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5685                           elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5686                         }
5687                     });
5688                     viz.fx.animate($.merge(options, {
5689                         modes: ['linear'].concat(modes)
5690                     }));
5691                 }
5692                 break;
5693
5694             default: this.doError();
5695         }
5696     },
5697     
5698     /*
5699        Method: morph
5700     
5701        This method will transform the current visualized graph into the new JSON representation passed in the method. 
5702        The JSON object must at least have the root node in common with the current visualized graph.
5703
5704        Parameters:
5705     
5706        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5707        opt - (object) Animation options. It's an object with optional properties described below
5708        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5709        duration - Described in <Options.Fx>.
5710        fps - Described in <Options.Fx>.
5711        transition - Described in <Options.Fx>.
5712        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5713        id - (string) The shared <Graph.Node> id between both graphs.
5714        
5715        extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to 
5716                     *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation. 
5717                     For animating these extra-parameters you have to specify an object that has animation groups as keys and animation 
5718                     properties as values, just like specified in <Graph.Plot.animate>.
5719    
5720       Example:
5721       (start code js)
5722         //...json contains a tree or graph structure...
5723
5724         var viz = new $jit.Viz(options);
5725         viz.op.morph(json, {
5726           type: 'fade',
5727           duration: 1000,
5728           hideLabels: false,
5729           transition: $jit.Trans.Quart.easeOut
5730         });
5731         //or also
5732         viz.op.morph(json, {
5733           type: 'fade',
5734           duration: 1500
5735         });
5736         //if the json data contains dollar prefixed params
5737         //like $width or $height these too can be animated
5738         viz.op.morph(json, {
5739           type: 'fade',
5740           duration: 1500
5741         }, {
5742           'node-property': ['width', 'height']
5743         });
5744       (end code)
5745     
5746     */
5747     morph: function(json, opt, extraModes) {
5748         var viz = this.viz;
5749         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5750         var graph;
5751         //TODO(nico) this hack makes morphing work with the Hypertree. 
5752         //Need to check if it has been solved and this can be removed.
5753         viz.root = opt.id || viz.root;
5754         switch(options.type) {
5755             case 'nothing':
5756                 graph = viz.construct(json);
5757                 graph.eachNode(function(elem) {
5758                   var nodeExists = viz.graph.hasNode(elem.id);  
5759                   elem.eachAdjacency(function(adj) {
5760                     var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5761                     viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5762                     //Update data properties if the node existed
5763                     if(adjExists) {
5764                       var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5765                       for(var prop in (adj.data || {})) {
5766                         addedAdj.data[prop] = adj.data[prop];
5767                       }
5768                     }
5769                   });
5770                   //Update data properties if the node existed
5771                   if(nodeExists) {
5772                     var addedNode = viz.graph.getNode(elem.id);
5773                     for(var prop in (elem.data || {})) {
5774                       addedNode.data[prop] = elem.data[prop];
5775                     }
5776                   }
5777                 });
5778                 viz.graph.eachNode(function(elem) {
5779                     elem.eachAdjacency(function(adj) {
5780                         if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5781                             viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5782                         }
5783                     });
5784                     if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5785                 });
5786                 
5787                 break;
5788             
5789             case 'replot':
5790                 viz.labels.clearLabels(true);
5791                 this.morph(json, { type: 'nothing' });
5792                 viz.refresh(true);
5793                 viz.refresh(true);
5794                 break;
5795                 
5796             case 'fade:seq': case 'fade': case 'fade:con':
5797                 that = this;
5798                 graph = viz.construct(json);
5799                 //preprocessing for nodes to delete.
5800                 //get node property modes to interpolate
5801                 var nodeModes = extraModes && ('node-property' in extraModes) 
5802                   && $.map($.splat(extraModes['node-property']), 
5803                       function(n) { return '$' + n; });
5804                 viz.graph.eachNode(function(elem) {
5805                   var graphNode = graph.getNode(elem.id);   
5806                   if(!graphNode) {
5807                       elem.setData('alpha', 1);
5808                       elem.setData('alpha', 1, 'start');
5809                       elem.setData('alpha', 0, 'end');
5810                       elem.ignore = true;
5811                     } else {
5812                       //Update node data information
5813                       var graphNodeData = graphNode.data;
5814                       for(var prop in graphNodeData) {
5815                         if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5816                           elem.endData[prop] = graphNodeData[prop];
5817                         } else {
5818                           elem.data[prop] = graphNodeData[prop];
5819                         }
5820                       }
5821                     }
5822                 }); 
5823                 viz.graph.eachNode(function(elem) {
5824                     if(elem.ignore) return;
5825                     elem.eachAdjacency(function(adj) {
5826                         if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5827                         var nodeFrom = graph.getNode(adj.nodeFrom.id);
5828                         var nodeTo = graph.getNode(adj.nodeTo.id);
5829                         if(!nodeFrom.adjacentTo(nodeTo)) {
5830                             var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5831                             fadeEdges = true;
5832                             adj.setData('alpha', 1);
5833                             adj.setData('alpha', 1, 'start');
5834                             adj.setData('alpha', 0, 'end');
5835                         }
5836                     });
5837                 }); 
5838                 //preprocessing for adding nodes.
5839                 var fadeEdges = this.preprocessSum(graph);
5840
5841                 var modes = !fadeEdges? ['node-property:alpha'] : 
5842                                         ['node-property:alpha', 
5843                                          'edge-property:alpha'];
5844                 //Append extra node-property animations (if any)
5845                 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))? 
5846                     (':' + $.splat(extraModes['node-property']).join(':')) : '');
5847                 //Append extra edge-property animations (if any)
5848                 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))? 
5849                     (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5850                 //Add label-property animations (if any)
5851                 if(extraModes && ('label-property' in extraModes)) {
5852                   modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5853                 }
5854                 viz.reposition();
5855                 viz.graph.eachNode(function(elem) {
5856                     if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5857                       elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5858                     }
5859                 });
5860                 viz.fx.animate($.merge(options, {
5861                     modes: ['polar'].concat(modes),
5862                     onComplete: function() {
5863                         viz.graph.eachNode(function(elem) {
5864                             if(elem.ignore) viz.graph.removeNode(elem.id);
5865                         });
5866                         viz.graph.eachNode(function(elem) {
5867                             elem.eachAdjacency(function(adj) {
5868                                 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5869                             });
5870                         });
5871                         options.onComplete();
5872                     }
5873                 }));
5874                 break;
5875
5876             default:;
5877         }
5878     },
5879
5880     
5881   /*
5882     Method: contract
5883  
5884     Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5885     
5886     Parameters:
5887  
5888     node - (object) A <Graph.Node>.
5889     opt - (object) An object containing options described below
5890     type - (string) Whether to 'replot' or 'animate' the contraction.
5891    
5892     There are also a number of Animation options. For more information see <Options.Fx>.
5893
5894     Example:
5895     (start code js)
5896      var viz = new $jit.Viz(options);
5897      viz.op.contract(node, {
5898        type: 'animate',
5899        duration: 1000,
5900        hideLabels: true,
5901        transition: $jit.Trans.Quart.easeOut
5902      });
5903    (end code)
5904  
5905    */
5906     contract: function(node, opt) {
5907       var viz = this.viz;
5908       if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5909       opt = $.merge(this.options, viz.config, opt || {}, {
5910         'modes': ['node-property:alpha:span', 'linear']
5911       });
5912       node.collapsed = true;
5913       (function subn(n) {
5914         n.eachSubnode(function(ch) {
5915           ch.ignore = true;
5916           ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5917           subn(ch);
5918         });
5919       })(node);
5920       if(opt.type == 'animate') {
5921         viz.compute('end');
5922         if(viz.rotated) {
5923           viz.rotate(viz.rotated, 'none', {
5924             'property':'end'
5925           });
5926         }
5927         (function subn(n) {
5928           n.eachSubnode(function(ch) {
5929             ch.setPos(node.getPos('end'), 'end');
5930             subn(ch);
5931           });
5932         })(node);
5933         viz.fx.animate(opt);
5934       } else if(opt.type == 'replot'){
5935         viz.refresh();
5936       }
5937     },
5938     
5939     /*
5940     Method: expand
5941  
5942     Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5943     
5944     Parameters:
5945  
5946     node - (object) A <Graph.Node>.
5947     opt - (object) An object containing options described below
5948     type - (string) Whether to 'replot' or 'animate'.
5949      
5950     There are also a number of Animation options. For more information see <Options.Fx>.
5951
5952     Example:
5953     (start code js)
5954       var viz = new $jit.Viz(options);
5955       viz.op.expand(node, {
5956         type: 'animate',
5957         duration: 1000,
5958         hideLabels: true,
5959         transition: $jit.Trans.Quart.easeOut
5960       });
5961     (end code)
5962  
5963    */
5964     expand: function(node, opt) {
5965       if(!('collapsed' in node)) return;
5966       var viz = this.viz;
5967       opt = $.merge(this.options, viz.config, opt || {}, {
5968         'modes': ['node-property:alpha:span', 'linear']
5969       });
5970       delete node.collapsed;
5971       (function subn(n) {
5972         n.eachSubnode(function(ch) {
5973           delete ch.ignore;
5974           ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5975           subn(ch);
5976         });
5977       })(node);
5978       if(opt.type == 'animate') {
5979         viz.compute('end');
5980         if(viz.rotated) {
5981           viz.rotate(viz.rotated, 'none', {
5982             'property':'end'
5983           });
5984         }
5985         viz.fx.animate(opt);
5986       } else if(opt.type == 'replot'){
5987         viz.refresh();
5988       }
5989     },
5990
5991     preprocessSum: function(graph) {
5992         var viz = this.viz;
5993         graph.eachNode(function(elem) {
5994             if(!viz.graph.hasNode(elem.id)) {
5995                 viz.graph.addNode(elem);
5996                 var n = viz.graph.getNode(elem.id);
5997                 n.setData('alpha', 0);
5998                 n.setData('alpha', 0, 'start');
5999                 n.setData('alpha', 1, 'end');
6000             }
6001         }); 
6002         var fadeEdges = false;
6003         graph.eachNode(function(elem) {
6004             elem.eachAdjacency(function(adj) {
6005                 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6006                 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6007                 if(!nodeFrom.adjacentTo(nodeTo)) {
6008                     var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6009                     if(nodeFrom.startAlpha == nodeFrom.endAlpha 
6010                     && nodeTo.startAlpha == nodeTo.endAlpha) {
6011                         fadeEdges = true;
6012                         adj.setData('alpha', 0);
6013                         adj.setData('alpha', 0, 'start');
6014                         adj.setData('alpha', 1, 'end');
6015                     } 
6016                 }
6017             });
6018         }); 
6019         return fadeEdges;
6020     }
6021 };
6022
6023
6024
6025 /*
6026    File: Helpers.js
6027  
6028    Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6029    Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6030    position is over the rendered shape.
6031    
6032    Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and 
6033    *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6034    
6035    Example:
6036    (start code js)
6037    //implement a new node type
6038    $jit.Viz.Plot.NodeTypes.implement({
6039      'customNodeType': {
6040        'render': function(node, canvas) {
6041          this.nodeHelper.circle.render ...
6042        },
6043        'contains': function(node, pos) {
6044          this.nodeHelper.circle.contains ...
6045        }
6046      }
6047    });
6048    //implement an edge type
6049    $jit.Viz.Plot.EdgeTypes.implement({
6050      'customNodeType': {
6051        'render': function(node, canvas) {
6052          this.edgeHelper.circle.render ...
6053        },
6054        //optional
6055        'contains': function(node, pos) {
6056          this.edgeHelper.circle.contains ...
6057        }
6058      }
6059    });
6060    (end code)
6061
6062 */
6063
6064 /*
6065    Object: NodeHelper
6066    
6067    Contains rendering and other type of primitives for simple shapes.
6068  */
6069 var NodeHelper = {
6070   'none': {
6071     'render': $.empty,
6072     'contains': $.lambda(false)
6073   },
6074   /*
6075    Object: NodeHelper.circle
6076    */
6077   'circle': {
6078     /*
6079      Method: render
6080      
6081      Renders a circle into the canvas.
6082      
6083      Parameters:
6084      
6085      type - (string) Possible options are 'fill' or 'stroke'.
6086      pos - (object) An *x*, *y* object with the position of the center of the circle.
6087      radius - (number) The radius of the circle to be rendered.
6088      canvas - (object) A <Canvas> instance.
6089      
6090      Example:
6091      (start code js)
6092      NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6093      (end code)
6094      */
6095     'render': function(type, pos, radius, canvas){
6096       var ctx = canvas.getCtx();
6097       ctx.beginPath();
6098       ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6099       ctx.closePath();
6100       ctx[type]();
6101     },
6102     /*
6103     Method: contains
6104     
6105     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6106     
6107     Parameters:
6108     
6109     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6110     pos - (object) An *x*, *y* object with the position to check.
6111     radius - (number) The radius of the rendered circle.
6112     
6113     Example:
6114     (start code js)
6115     NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6116     (end code)
6117     */
6118     'contains': function(npos, pos, radius){
6119       var diffx = npos.x - pos.x, 
6120           diffy = npos.y - pos.y, 
6121           diff = diffx * diffx + diffy * diffy;
6122       return diff <= radius * radius;
6123     }
6124   },
6125   /*
6126   Object: NodeHelper.ellipse
6127   */
6128   'ellipse': {
6129     /*
6130     Method: render
6131     
6132     Renders an ellipse into the canvas.
6133     
6134     Parameters:
6135     
6136     type - (string) Possible options are 'fill' or 'stroke'.
6137     pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6138     width - (number) The width of the ellipse.
6139     height - (number) The height of the ellipse.
6140     canvas - (object) A <Canvas> instance.
6141     
6142     Example:
6143     (start code js)
6144     NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6145     (end code)
6146     */
6147     'render': function(type, pos, width, height, canvas){
6148       var ctx = canvas.getCtx();
6149       height /= 2;
6150       width /= 2;
6151       ctx.save();
6152       ctx.scale(width / height, height / width);
6153       ctx.beginPath();
6154       ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6155           Math.PI * 2, true);
6156       ctx.closePath();
6157       ctx[type]();
6158       ctx.restore();
6159     },
6160     /*
6161     Method: contains
6162     
6163     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6164     
6165     Parameters:
6166     
6167     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6168     pos - (object) An *x*, *y* object with the position to check.
6169     width - (number) The width of the rendered ellipse.
6170     height - (number) The height of the rendered ellipse.
6171     
6172     Example:
6173     (start code js)
6174     NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6175     (end code)
6176     */
6177     'contains': function(npos, pos, width, height){
6178       // TODO(nico): be more precise...
6179       width /= 2; 
6180       height /= 2;
6181       var dist = (width + height) / 2, 
6182           diffx = npos.x - pos.x, 
6183           diffy = npos.y - pos.y, 
6184           diff = diffx * diffx + diffy * diffy;
6185       return diff <= dist * dist;
6186     }
6187   },
6188   /*
6189   Object: NodeHelper.square
6190   */
6191   'square': {
6192     /*
6193     Method: render
6194     
6195     Renders a square into the canvas.
6196     
6197     Parameters:
6198     
6199     type - (string) Possible options are 'fill' or 'stroke'.
6200     pos - (object) An *x*, *y* object with the position of the center of the square.
6201     dim - (number) The radius (or half-diameter) of the square.
6202     canvas - (object) A <Canvas> instance.
6203     
6204     Example:
6205     (start code js)
6206     NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6207     (end code)
6208     */
6209     'render': function(type, pos, dim, canvas){
6210       canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6211     },
6212     /*
6213     Method: contains
6214     
6215     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6216     
6217     Parameters:
6218     
6219     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6220     pos - (object) An *x*, *y* object with the position to check.
6221     dim - (number) The radius (or half-diameter) of the square.
6222     
6223     Example:
6224     (start code js)
6225     NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6226     (end code)
6227     */
6228     'contains': function(npos, pos, dim){
6229       return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6230     }
6231   },
6232   /*
6233   Object: NodeHelper.rectangle
6234   */
6235   'rectangle': {
6236     /*
6237     Method: render
6238     
6239     Renders a rectangle into the canvas.
6240     
6241     Parameters:
6242     
6243     type - (string) Possible options are 'fill' or 'stroke'.
6244     pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6245     width - (number) The width of the rectangle.
6246     height - (number) The height of the rectangle.
6247     canvas - (object) A <Canvas> instance.
6248     
6249     Example:
6250     (start code js)
6251     NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6252     (end code)
6253     */
6254     'render': function(type, pos, width, height, canvas){
6255       canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2, 
6256                                       width, height);
6257     },
6258     /*
6259     Method: contains
6260     
6261     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6262     
6263     Parameters:
6264     
6265     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6266     pos - (object) An *x*, *y* object with the position to check.
6267     width - (number) The width of the rendered rectangle.
6268     height - (number) The height of the rendered rectangle.
6269     
6270     Example:
6271     (start code js)
6272     NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6273     (end code)
6274     */
6275     'contains': function(npos, pos, width, height){
6276       return Math.abs(pos.x - npos.x) <= width / 2
6277           && Math.abs(pos.y - npos.y) <= height / 2;
6278     }
6279   },
6280   /*
6281   Object: NodeHelper.triangle
6282   */
6283   'triangle': {
6284     /*
6285     Method: render
6286     
6287     Renders a triangle into the canvas.
6288     
6289     Parameters:
6290     
6291     type - (string) Possible options are 'fill' or 'stroke'.
6292     pos - (object) An *x*, *y* object with the position of the center of the triangle.
6293     dim - (number) The dimension of the triangle.
6294     canvas - (object) A <Canvas> instance.
6295     
6296     Example:
6297     (start code js)
6298     NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6299     (end code)
6300     */
6301     'render': function(type, pos, dim, canvas){
6302       var ctx = canvas.getCtx(), 
6303           c1x = pos.x, 
6304           c1y = pos.y - dim, 
6305           c2x = c1x - dim, 
6306           c2y = pos.y + dim, 
6307           c3x = c1x + dim, 
6308           c3y = c2y;
6309       ctx.beginPath();
6310       ctx.moveTo(c1x, c1y);
6311       ctx.lineTo(c2x, c2y);
6312       ctx.lineTo(c3x, c3y);
6313       ctx.closePath();
6314       ctx[type]();
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 dimension of the shape.
6326     
6327     Example:
6328     (start code js)
6329     NodeHelper.triangle.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   Object: NodeHelper.star
6338   */
6339   'star': {
6340     /*
6341     Method: render
6342     
6343     Renders a star into the canvas.
6344     
6345     Parameters:
6346     
6347     type - (string) Possible options are 'fill' or 'stroke'.
6348     pos - (object) An *x*, *y* object with the position of the center of the star.
6349     dim - (number) The dimension of the star.
6350     canvas - (object) A <Canvas> instance.
6351     
6352     Example:
6353     (start code js)
6354     NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6355     (end code)
6356     */
6357     'render': function(type, pos, dim, canvas){
6358       var ctx = canvas.getCtx(), 
6359           pi5 = Math.PI / 5;
6360       ctx.save();
6361       ctx.translate(pos.x, pos.y);
6362       ctx.beginPath();
6363       ctx.moveTo(dim, 0);
6364       for (var i = 0; i < 9; i++) {
6365         ctx.rotate(pi5);
6366         if (i % 2 == 0) {
6367           ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6368         } else {
6369           ctx.lineTo(dim, 0);
6370         }
6371       }
6372       ctx.closePath();
6373       ctx[type]();
6374       ctx.restore();
6375     },
6376     /*
6377     Method: contains
6378     
6379     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6380     
6381     Parameters:
6382     
6383     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6384     pos - (object) An *x*, *y* object with the position to check.
6385     dim - (number) The dimension of the shape.
6386     
6387     Example:
6388     (start code js)
6389     NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6390     (end code)
6391     */
6392     'contains': function(npos, pos, dim) {
6393       return NodeHelper.circle.contains(npos, pos, dim);
6394     }
6395   }
6396 };
6397
6398 /*
6399   Object: EdgeHelper
6400   
6401   Contains rendering primitives for simple edge shapes.
6402 */
6403 var EdgeHelper = {
6404   /*
6405     Object: EdgeHelper.line
6406   */
6407   'line': {
6408       /*
6409       Method: render
6410       
6411       Renders a line into the canvas.
6412       
6413       Parameters:
6414       
6415       from - (object) An *x*, *y* object with the starting position of the line.
6416       to - (object) An *x*, *y* object with the ending position of the line.
6417       canvas - (object) A <Canvas> instance.
6418       
6419       Example:
6420       (start code js)
6421       EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6422       (end code)
6423       */
6424       'render': function(from, to, canvas){
6425         var ctx = canvas.getCtx();
6426         ctx.beginPath();
6427         ctx.moveTo(from.x, from.y);
6428         ctx.lineTo(to.x, to.y);
6429         ctx.stroke();
6430       },
6431       /*
6432       Method: contains
6433       
6434       Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6435       
6436       Parameters:
6437       
6438       posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6439       posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6440       pos - (object) An *x*, *y* object with the position to check.
6441       epsilon - (number) The dimension of the shape.
6442       
6443       Example:
6444       (start code js)
6445       EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6446       (end code)
6447       */
6448       'contains': function(posFrom, posTo, pos, epsilon) {
6449         var min = Math.min, 
6450             max = Math.max,
6451             minPosX = min(posFrom.x, posTo.x),
6452             maxPosX = max(posFrom.x, posTo.x),
6453             minPosY = min(posFrom.y, posTo.y),
6454             maxPosY = max(posFrom.y, posTo.y);
6455         
6456         if(pos.x >= minPosX && pos.x <= maxPosX 
6457             && pos.y >= minPosY && pos.y <= maxPosY) {
6458           if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6459             return true;
6460           }
6461           var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6462           return Math.abs(dist - pos.y) <= epsilon;
6463         }
6464         return false;
6465       }
6466     },
6467   /*
6468     Object: EdgeHelper.arrow
6469   */
6470   'arrow': {
6471       /*
6472       Method: render
6473       
6474       Renders an arrow into the canvas.
6475       
6476       Parameters:
6477       
6478       from - (object) An *x*, *y* object with the starting position of the arrow.
6479       to - (object) An *x*, *y* object with the ending position of the arrow.
6480       dim - (number) The dimension of the arrow.
6481       swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6482       canvas - (object) A <Canvas> instance.
6483       
6484       Example:
6485       (start code js)
6486       EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6487       (end code)
6488       */
6489     'render': function(from, to, dim, swap, canvas){
6490         var ctx = canvas.getCtx();
6491         // invert edge direction
6492         if (swap) {
6493           var tmp = from;
6494           from = to;
6495           to = tmp;
6496         }
6497         var vect = new Complex(to.x - from.x, to.y - from.y);
6498         vect.$scale(dim / vect.norm());
6499         var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6500             normal = new Complex(-vect.y / 2, vect.x / 2),
6501             v1 = intermediatePoint.add(normal), 
6502             v2 = intermediatePoint.$add(normal.$scale(-1));
6503         
6504         ctx.beginPath();
6505         ctx.moveTo(from.x, from.y);
6506         ctx.lineTo(to.x, to.y);
6507         ctx.stroke();
6508         ctx.beginPath();
6509         ctx.moveTo(v1.x, v1.y);
6510         ctx.lineTo(v2.x, v2.y);
6511         ctx.lineTo(to.x, to.y);
6512         ctx.closePath();
6513         ctx.fill();
6514     },
6515     /*
6516     Method: contains
6517     
6518     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6519     
6520     Parameters:
6521     
6522     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6523     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6524     pos - (object) An *x*, *y* object with the position to check.
6525     epsilon - (number) The dimension of the shape.
6526     
6527     Example:
6528     (start code js)
6529     EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6530     (end code)
6531     */
6532     'contains': function(posFrom, posTo, pos, epsilon) {
6533       return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6534     }
6535   },
6536   /*
6537     Object: EdgeHelper.hyperline
6538   */
6539   'hyperline': {
6540     /*
6541     Method: render
6542     
6543     Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6544     
6545     Parameters:
6546     
6547     from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6548     to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6549     r - (number) The scaling factor.
6550     canvas - (object) A <Canvas> instance.
6551     
6552     Example:
6553     (start code js)
6554     EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6555     (end code)
6556     */
6557     'render': function(from, to, r, canvas){
6558       var ctx = canvas.getCtx();  
6559       var centerOfCircle = computeArcThroughTwoPoints(from, to);
6560       if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6561           || centerOfCircle.ratio < 0) {
6562         ctx.beginPath();
6563         ctx.moveTo(from.x * r, from.y * r);
6564         ctx.lineTo(to.x * r, to.y * r);
6565         ctx.stroke();
6566       } else {
6567         var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6568             - centerOfCircle.x);
6569         var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6570             - centerOfCircle.x);
6571         var sense = sense(angleBegin, angleEnd);
6572         ctx.beginPath();
6573         ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6574             * r, angleBegin, angleEnd, sense);
6575         ctx.stroke();
6576       }
6577       /*      
6578         Calculates the arc parameters through two points.
6579         
6580         More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane> 
6581       
6582         Parameters:
6583       
6584         p1 - A <Complex> instance.
6585         p2 - A <Complex> instance.
6586         scale - The Disk's diameter.
6587       
6588         Returns:
6589       
6590         An object containing some arc properties.
6591       */
6592       function computeArcThroughTwoPoints(p1, p2){
6593         var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6594         var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6595         // Fall back to a straight line
6596         if (aDen == 0)
6597           return {
6598             x: 0,
6599             y: 0,
6600             ratio: -1
6601           };
6602     
6603         var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6604         var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6605         var x = -a / 2;
6606         var y = -b / 2;
6607         var squaredRatio = (a * a + b * b) / 4 - 1;
6608         // Fall back to a straight line
6609         if (squaredRatio < 0)
6610           return {
6611             x: 0,
6612             y: 0,
6613             ratio: -1
6614           };
6615         var ratio = Math.sqrt(squaredRatio);
6616         var out = {
6617           x: x,
6618           y: y,
6619           ratio: ratio > 1000? -1 : ratio,
6620           a: a,
6621           b: b
6622         };
6623     
6624         return out;
6625       }
6626       /*      
6627         Sets angle direction to clockwise (true) or counterclockwise (false). 
6628          
6629         Parameters: 
6630       
6631            angleBegin - Starting angle for drawing the arc. 
6632            angleEnd - The HyperLine will be drawn from angleBegin to angleEnd. 
6633       
6634         Returns: 
6635       
6636            A Boolean instance describing the sense for drawing the HyperLine. 
6637       */
6638       function sense(angleBegin, angleEnd){
6639         return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6640             : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6641       }
6642     },
6643     /*
6644     Method: contains
6645     
6646     Not Implemented
6647     
6648     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6649     
6650     Parameters:
6651     
6652     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6653     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6654     pos - (object) An *x*, *y* object with the position to check.
6655     epsilon - (number) The dimension of the shape.
6656     
6657     Example:
6658     (start code js)
6659     EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6660     (end code)
6661     */
6662     'contains': $.lambda(false)
6663   }
6664 };
6665
6666
6667 /*
6668  * File: Graph.Plot.js
6669  */
6670
6671 /*
6672    Object: Graph.Plot
6673
6674    <Graph> rendering and animation methods.
6675    
6676    Properties:
6677    
6678    nodeHelper - <NodeHelper> object.
6679    edgeHelper - <EdgeHelper> object.
6680 */
6681 Graph.Plot = {
6682     //Default intializer
6683     initialize: function(viz, klass){
6684       this.viz = viz;
6685       this.config = viz.config;
6686       this.node = viz.config.Node;
6687       this.edge = viz.config.Edge;
6688       this.animation = new Animation;
6689       this.nodeTypes = new klass.Plot.NodeTypes;
6690       this.edgeTypes = new klass.Plot.EdgeTypes;
6691       this.labels = viz.labels;
6692    },
6693
6694     //Add helpers
6695     nodeHelper: NodeHelper,
6696     edgeHelper: EdgeHelper,
6697     
6698     Interpolator: {
6699         //node/edge property parsers
6700         'map': {
6701           'border': 'color',
6702           'color': 'color',
6703           'width': 'number',
6704           'height': 'number',
6705           'dim': 'number',
6706           'alpha': 'number',
6707           'lineWidth': 'number',
6708           'angularWidth':'number',
6709           'span':'number',
6710           'valueArray':'array-number',
6711           'dimArray':'array-number'
6712           //'colorArray':'array-color'
6713         },
6714         
6715         //canvas specific parsers
6716         'canvas': {
6717           'globalAlpha': 'number',
6718           'fillStyle': 'color',
6719           'strokeStyle': 'color',
6720           'lineWidth': 'number',
6721           'shadowBlur': 'number',
6722           'shadowColor': 'color',
6723           'shadowOffsetX': 'number',
6724           'shadowOffsetY': 'number',
6725           'miterLimit': 'number'
6726         },
6727   
6728         //label parsers
6729         'label': {
6730           'size': 'number',
6731           'color': 'color'
6732         },
6733   
6734         //Number interpolator
6735         'compute': function(from, to, delta) {
6736           return from + (to - from) * delta;
6737         },
6738         
6739         //Position interpolators
6740         'moebius': function(elem, props, delta, vector) {
6741           var v = vector.scale(-delta);  
6742           if(v.norm() < 1) {
6743               var x = v.x, y = v.y;
6744               var ans = elem.startPos
6745                 .getc().moebiusTransformation(v);
6746               elem.pos.setc(ans.x, ans.y);
6747               v.x = x; v.y = y;
6748             }           
6749         },
6750
6751         'linear': function(elem, props, delta) {
6752             var from = elem.startPos.getc(true);
6753             var to = elem.endPos.getc(true);
6754             elem.pos.setc(this.compute(from.x, to.x, delta), 
6755                           this.compute(from.y, to.y, delta));
6756         },
6757
6758         'polar': function(elem, props, delta) {
6759           var from = elem.startPos.getp(true);
6760           var to = elem.endPos.getp();
6761           var ans = to.interpolate(from, delta);
6762           elem.pos.setp(ans.theta, ans.rho);
6763         },
6764         
6765         //Graph's Node/Edge interpolators
6766         'number': function(elem, prop, delta, getter, setter) {
6767           var from = elem[getter](prop, 'start');
6768           var to = elem[getter](prop, 'end');
6769           elem[setter](prop, this.compute(from, to, delta));
6770         },
6771
6772         'color': function(elem, prop, delta, getter, setter) {
6773           var from = $.hexToRgb(elem[getter](prop, 'start'));
6774           var to = $.hexToRgb(elem[getter](prop, 'end'));
6775           var comp = this.compute;
6776           var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6777                                 parseInt(comp(from[1], to[1], delta)),
6778                                 parseInt(comp(from[2], to[2], delta))]);
6779           
6780           elem[setter](prop, val);
6781         },
6782         
6783         'array-number': function(elem, prop, delta, getter, setter) {
6784           var from = elem[getter](prop, 'start'),
6785               to = elem[getter](prop, 'end'),
6786               cur = [];
6787           for(var i=0, l=from.length; i<l; i++) {
6788             var fromi = from[i], toi = to[i];
6789             if(fromi.length) {
6790               for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6791                 curi.push(this.compute(fromi[j], toi[j], delta));
6792               }
6793               cur.push(curi);
6794             } else {
6795               cur.push(this.compute(fromi, toi, delta));
6796             }
6797           }
6798           elem[setter](prop, cur);
6799         },
6800         
6801         'node': function(elem, props, delta, map, getter, setter) {
6802           map = this[map];
6803           if(props) {
6804             var len = props.length;
6805             for(var i=0; i<len; i++) {
6806               var pi = props[i];
6807               this[map[pi]](elem, pi, delta, getter, setter);
6808             }
6809           } else {
6810             for(var pi in map) {
6811               this[map[pi]](elem, pi, delta, getter, setter);
6812             }
6813           }
6814         },
6815         
6816         'edge': function(elem, props, delta, mapKey, getter, setter) {
6817             var adjs = elem.adjacencies;
6818             for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6819         },
6820         
6821         'node-property': function(elem, props, delta) {
6822           this['node'](elem, props, delta, 'map', 'getData', 'setData');
6823         },
6824         
6825         'edge-property': function(elem, props, delta) {
6826           this['edge'](elem, props, delta, 'map', 'getData', 'setData');  
6827         },
6828
6829         'label-property': function(elem, props, delta) {
6830           this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6831         },
6832         
6833         'node-style': function(elem, props, delta) {
6834           this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6835         },
6836         
6837         'edge-style': function(elem, props, delta) {
6838           this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');  
6839         }
6840     },
6841     
6842   
6843     /*
6844        sequence
6845     
6846        Iteratively performs an action while refreshing the state of the visualization.
6847
6848        Parameters:
6849
6850        options - (object) An object containing some sequence options described below
6851        condition - (function) A function returning a boolean instance in order to stop iterations.
6852        step - (function) A function to execute on each step of the iteration.
6853        onComplete - (function) A function to execute when the sequence finishes.
6854        duration - (number) Duration (in milliseconds) of each step.
6855
6856       Example:
6857        (start code js)
6858         var rg = new $jit.RGraph(options);
6859         var i = 0;
6860         rg.fx.sequence({
6861           condition: function() {
6862            return i == 10;
6863           },
6864           step: function() {
6865             alert(i++);
6866           },
6867           onComplete: function() {
6868            alert('done!');
6869           }
6870         });
6871        (end code)
6872
6873     */
6874     sequence: function(options) {
6875         var that = this;
6876         options = $.merge({
6877           condition: $.lambda(false),
6878           step: $.empty,
6879           onComplete: $.empty,
6880           duration: 200
6881         }, options || {});
6882
6883         var interval = setInterval(function() {
6884           if(options.condition()) {
6885             options.step();
6886           } else {
6887             clearInterval(interval);
6888             options.onComplete();
6889           }
6890           that.viz.refresh(true);
6891         }, options.duration);
6892     },
6893     
6894     /*
6895       prepare
6896  
6897       Prepare graph position and other attribute values before performing an Animation. 
6898       This method is used internally by the Toolkit.
6899       
6900       See also:
6901        
6902        <Animation>, <Graph.Plot.animate>
6903
6904     */
6905     prepare: function(modes) {
6906       var graph = this.viz.graph,
6907           accessors = {
6908             'node-property': {
6909               'getter': 'getData',
6910               'setter': 'setData'
6911             },
6912             'edge-property': {
6913               'getter': 'getData',
6914               'setter': 'setData'
6915             },
6916             'node-style': {
6917               'getter': 'getCanvasStyle',
6918               'setter': 'setCanvasStyle'
6919             },
6920             'edge-style': {
6921               'getter': 'getCanvasStyle',
6922               'setter': 'setCanvasStyle'
6923             }
6924           };
6925
6926       //parse modes
6927       var m = {};
6928       if($.type(modes) == 'array') {
6929         for(var i=0, len=modes.length; i < len; i++) {
6930           var elems = modes[i].split(':');
6931           m[elems.shift()] = elems;
6932         }
6933       } else {
6934         for(var p in modes) {
6935           if(p == 'position') {
6936             m[modes.position] = [];
6937           } else {
6938             m[p] = $.splat(modes[p]);
6939           }
6940         }
6941       }
6942       
6943       graph.eachNode(function(node) { 
6944         node.startPos.set(node.pos);
6945         $.each(['node-property', 'node-style'], function(p) {
6946           if(p in m) {
6947             var prop = m[p];
6948             for(var i=0, l=prop.length; i < l; i++) {
6949               node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6950             }
6951           }
6952         });
6953         $.each(['edge-property', 'edge-style'], function(p) {
6954           if(p in m) {
6955             var prop = m[p];
6956             node.eachAdjacency(function(adj) {
6957               for(var i=0, l=prop.length; i < l; i++) {
6958                 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6959               }
6960             });
6961           }
6962         });
6963       });
6964       return m;
6965     },
6966     
6967     /*
6968        Method: animate
6969     
6970        Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6971
6972        Parameters:
6973
6974        opt - (object) Animation options. The object properties are described below
6975        duration - (optional) Described in <Options.Fx>.
6976        fps - (optional) Described in <Options.Fx>.
6977        hideLabels - (optional|boolean) Whether to hide labels during the animation.
6978        modes - (required|object) An object with animation modes (described below).
6979
6980        Animation modes:
6981        
6982        Animation modes are strings representing different node/edge and graph properties that you'd like to animate. 
6983        They are represented by an object that has as keys main categories of properties to animate and as values a list 
6984        of these specific properties. The properties are described below
6985        
6986        position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6987        node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6988        edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6989        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.
6990        node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6991        edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6992
6993        Example:
6994        (start code js)
6995        var viz = new $jit.Viz(options);
6996        //...tweak some Data, CanvasStyles or LabelData properties...
6997        viz.fx.animate({
6998          modes: {
6999            'position': 'linear',
7000            'node-property': ['width', 'height'],
7001            'node-style': 'shadowColor',
7002            'label-property': 'size'
7003          },
7004          hideLabels: false
7005        });
7006        //...can also be written like this...
7007        viz.fx.animate({
7008          modes: ['linear',
7009                  'node-property:width:height',
7010                  'node-style:shadowColor',
7011                  'label-property:size'],
7012          hideLabels: false
7013        });
7014        (end code)
7015     */
7016     animate: function(opt, versor) {
7017       opt = $.merge(this.viz.config, opt || {});
7018       var that = this,
7019           viz = this.viz,
7020           graph  = viz.graph,
7021           interp = this.Interpolator,
7022           animation =  opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7023       //prepare graph values
7024       var m = this.prepare(opt.modes);
7025       
7026       //animate
7027       if(opt.hideLabels) this.labels.hideLabels(true);
7028       animation.setOptions($.merge(opt, {
7029         $animating: false,
7030         compute: function(delta) {
7031           graph.eachNode(function(node) { 
7032             for(var p in m) {
7033               interp[p](node, m[p], delta, versor);
7034             }
7035           });
7036           that.plot(opt, this.$animating, delta);
7037           this.$animating = true;
7038         },
7039         complete: function() {
7040           if(opt.hideLabels) that.labels.hideLabels(false);
7041           that.plot(opt);
7042           opt.onComplete();
7043           opt.onAfterCompute();
7044         }       
7045       })).start();
7046     },
7047     
7048     /*
7049       nodeFx
7050    
7051       Apply animation to node properties like color, width, height, dim, etc.
7052   
7053       Parameters:
7054   
7055       options - Animation options. This object properties is described below
7056       elements - The Elements to be transformed. This is an object that has a properties
7057       
7058       (start code js)
7059       'elements': {
7060         //can also be an array of ids
7061         'id': 'id-of-node-to-transform',
7062         //properties to be modified. All properties are optional.
7063         'properties': {
7064           'color': '#ccc', //some color
7065           'width': 10, //some width
7066           'height': 10, //some height
7067           'dim': 20, //some dim
7068           'lineWidth': 10 //some line width
7069         } 
7070       }
7071       (end code)
7072       
7073       - _reposition_ Whether to recalculate positions and add a motion animation. 
7074       This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7075       
7076       - _onComplete_ A method that is called when the animation completes.
7077       
7078       ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7079   
7080       Example:
7081       (start code js)
7082        var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7083        rg.fx.nodeFx({
7084          'elements': {
7085            'id':'mynodeid',
7086            'properties': {
7087              'color':'#ccf'
7088            },
7089            'transition': Trans.Quart.easeOut
7090          }
7091        });
7092       (end code)    
7093    */
7094    nodeFx: function(opt) {
7095      var viz = this.viz,
7096          graph  = viz.graph,
7097          animation = this.nodeFxAnimation,
7098          options = $.merge(this.viz.config, {
7099            'elements': {
7100              'id': false,
7101              'properties': {}
7102            },
7103            'reposition': false
7104          });
7105      opt = $.merge(options, opt || {}, {
7106        onBeforeCompute: $.empty,
7107        onAfterCompute: $.empty
7108      });
7109      //check if an animation is running
7110      animation.stopTimer();
7111      var props = opt.elements.properties;
7112      //set end values for nodes
7113      if(!opt.elements.id) {
7114        graph.eachNode(function(n) {
7115          for(var prop in props) {
7116            n.setData(prop, props[prop], 'end');
7117          }
7118        });
7119      } else {
7120        var ids = $.splat(opt.elements.id);
7121        $.each(ids, function(id) {
7122          var n = graph.getNode(id);
7123          if(n) {
7124            for(var prop in props) {
7125              n.setData(prop, props[prop], 'end');
7126            }
7127          }
7128        });
7129      }
7130      //get keys
7131      var propnames = [];
7132      for(var prop in props) propnames.push(prop);
7133      //add node properties modes
7134      var modes = ['node-property:' + propnames.join(':')];
7135      //set new node positions
7136      if(opt.reposition) {
7137        modes.push('linear');
7138        viz.compute('end');
7139      }
7140      //animate
7141      this.animate($.merge(opt, {
7142        modes: modes,
7143        type: 'nodefx'
7144      }));
7145    },
7146
7147     
7148     /*
7149        Method: plot
7150     
7151        Plots a <Graph>.
7152
7153        Parameters:
7154
7155        opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7156
7157        Example:
7158
7159        (start code js)
7160        var viz = new $jit.Viz(options);
7161        viz.fx.plot(); 
7162        (end code)
7163
7164     */
7165     plot: function(opt, animating) {
7166       var viz = this.viz, 
7167       aGraph = viz.graph, 
7168       canvas = viz.canvas, 
7169       id = viz.root, 
7170       that = this, 
7171       ctx = canvas.getCtx(), 
7172       min = Math.min,
7173       opt = opt || this.viz.controller;
7174       opt.clearCanvas && canvas.clear();
7175         
7176       var root = aGraph.getNode(id);
7177       if(!root) return;
7178       
7179       var T = !!root.visited;
7180       aGraph.eachNode(function(node) {
7181         var nodeAlpha = node.getData('alpha');
7182         node.eachAdjacency(function(adj) {
7183           var nodeTo = adj.nodeTo;
7184           if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7185             !animating && opt.onBeforePlotLine(adj);
7186             ctx.save();
7187             ctx.globalAlpha = min(nodeAlpha, 
7188                 nodeTo.getData('alpha'), 
7189                 adj.getData('alpha'));
7190             that.plotLine(adj, canvas, animating);
7191             ctx.restore();
7192             !animating && opt.onAfterPlotLine(adj);
7193           }
7194         });
7195         ctx.save();
7196         if(node.drawn) {
7197           !animating && opt.onBeforePlotNode(node);
7198           that.plotNode(node, canvas, animating);
7199           !animating && opt.onAfterPlotNode(node);
7200         }
7201         if(!that.labelsHidden && opt.withLabels) {
7202           if(node.drawn && nodeAlpha >= 0.95) {
7203             that.labels.plotLabel(canvas, node, opt);
7204           } else {
7205             that.labels.hideLabel(node, false);
7206           }
7207         }
7208         ctx.restore();
7209         node.visited = !T;
7210       });
7211     },
7212
7213   /*
7214       Plots a Subtree.
7215    */
7216    plotTree: function(node, opt, animating) {
7217        var that = this, 
7218        viz = this.viz, 
7219        canvas = viz.canvas,
7220        config = this.config,
7221        ctx = canvas.getCtx();
7222        var nodeAlpha = node.getData('alpha');
7223        node.eachSubnode(function(elem) {
7224          if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7225              var adj = node.getAdjacency(elem.id);
7226              !animating && opt.onBeforePlotLine(adj);
7227              ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7228              that.plotLine(adj, canvas, animating);
7229              !animating && opt.onAfterPlotLine(adj);
7230              that.plotTree(elem, opt, animating);
7231          }
7232        });
7233        if(node.drawn) {
7234            !animating && opt.onBeforePlotNode(node);
7235            this.plotNode(node, canvas, animating);
7236            !animating && opt.onAfterPlotNode(node);
7237            if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95) 
7238                this.labels.plotLabel(canvas, node, opt);
7239            else 
7240                this.labels.hideLabel(node, false);
7241        } else {
7242            this.labels.hideLabel(node, true);
7243        }
7244    },
7245
7246   /*
7247        Method: plotNode
7248     
7249        Plots a <Graph.Node>.
7250
7251        Parameters:
7252        
7253        node - (object) A <Graph.Node>.
7254        canvas - (object) A <Canvas> element.
7255
7256     */
7257     plotNode: function(node, canvas, animating) {
7258         var f = node.getData('type'), 
7259             ctxObj = this.node.CanvasStyles;
7260         if(f != 'none') {
7261           var width = node.getData('lineWidth'),
7262               color = node.getData('color'),
7263               alpha = node.getData('alpha'),
7264               ctx = canvas.getCtx();
7265           
7266           ctx.lineWidth = width;
7267           ctx.fillStyle = ctx.strokeStyle = color;
7268           ctx.globalAlpha = alpha;
7269           
7270           for(var s in ctxObj) {
7271             ctx[s] = node.getCanvasStyle(s);
7272           }
7273
7274           this.nodeTypes[f].render.call(this, node, canvas, animating);
7275         }
7276     },
7277     
7278     /*
7279        Method: plotLine
7280     
7281        Plots a <Graph.Adjacence>.
7282
7283        Parameters:
7284
7285        adj - (object) A <Graph.Adjacence>.
7286        canvas - (object) A <Canvas> instance.
7287
7288     */
7289     plotLine: function(adj, canvas, animating) {
7290       var f = adj.getData('type'),
7291           ctxObj = this.edge.CanvasStyles;
7292       if(f != 'none') {
7293         var width = adj.getData('lineWidth'),
7294             color = adj.getData('color'),
7295             ctx = canvas.getCtx();
7296         
7297         ctx.lineWidth = width;
7298         ctx.fillStyle = ctx.strokeStyle = color;
7299         
7300         for(var s in ctxObj) {
7301           ctx[s] = adj.getCanvasStyle(s);
7302         }
7303
7304         this.edgeTypes[f].render.call(this, adj, canvas, animating);
7305       }
7306     }    
7307   
7308 };
7309
7310
7311
7312 /*
7313  * File: Graph.Label.js
7314  *
7315 */
7316
7317 /*
7318    Object: Graph.Label
7319
7320    An interface for plotting/hiding/showing labels.
7321
7322    Description:
7323
7324    This is a generic interface for plotting/hiding/showing labels.
7325    The <Graph.Label> interface is implemented in multiple ways to provide
7326    different label types.
7327
7328    For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7329    HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels. 
7330    The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7331    
7332    All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7333 */
7334
7335 Graph.Label = {};
7336
7337 /*
7338    Class: Graph.Label.Native
7339
7340    Implements labels natively, using the Canvas text API.
7341 */
7342 Graph.Label.Native = new Class({
7343     /*
7344        Method: plotLabel
7345
7346        Plots a label for a given node.
7347
7348        Parameters:
7349
7350        canvas - (object) A <Canvas> instance.
7351        node - (object) A <Graph.Node>.
7352        controller - (object) A configuration object.
7353        
7354        Example:
7355        
7356        (start code js)
7357        var viz = new $jit.Viz(options);
7358        var node = viz.graph.getNode('nodeId');
7359        viz.labels.plotLabel(viz.canvas, node, viz.config);
7360        (end code)
7361     */
7362     plotLabel: function(canvas, node, controller) {
7363       var ctx = canvas.getCtx();
7364       var pos = node.pos.getc(true);
7365
7366       ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7367       ctx.textAlign = node.getLabelData('textAlign');
7368       ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7369       ctx.textBaseline = node.getLabelData('textBaseline');
7370
7371       this.renderLabel(canvas, node, controller);
7372     },
7373
7374     /*
7375        renderLabel
7376
7377        Does the actual rendering of the label in the canvas. The default
7378        implementation renders the label close to the position of the node, this
7379        method should be overriden to position the labels differently.
7380
7381        Parameters:
7382
7383        canvas - A <Canvas> instance.
7384        node - A <Graph.Node>.
7385        controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7386     */
7387     renderLabel: function(canvas, node, controller) {
7388       var ctx = canvas.getCtx();
7389       var pos = node.pos.getc(true);
7390       ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7391     },
7392
7393     hideLabel: $.empty,
7394     hideLabels: $.empty
7395 });
7396
7397 /*
7398    Class: Graph.Label.DOM
7399
7400    Abstract Class implementing some DOM label methods.
7401
7402    Implemented by:
7403
7404    <Graph.Label.HTML> and <Graph.Label.SVG>.
7405
7406 */
7407 Graph.Label.DOM = new Class({
7408     //A flag value indicating if node labels are being displayed or not.
7409     labelsHidden: false,
7410     //Label container
7411     labelContainer: false,
7412     //Label elements hash.
7413     labels: {},
7414
7415     /*
7416        Method: getLabelContainer
7417
7418        Lazy fetcher for the label container.
7419
7420        Returns:
7421
7422        The label container DOM element.
7423
7424        Example:
7425
7426       (start code js)
7427         var viz = new $jit.Viz(options);
7428         var labelContainer = viz.labels.getLabelContainer();
7429         alert(labelContainer.innerHTML);
7430       (end code)
7431     */
7432     getLabelContainer: function() {
7433       return this.labelContainer ?
7434         this.labelContainer :
7435         this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7436     },
7437
7438     /*
7439        Method: getLabel
7440
7441        Lazy fetcher for the label element.
7442
7443        Parameters:
7444
7445        id - (string) The label id (which is also a <Graph.Node> id).
7446
7447        Returns:
7448
7449        The label element.
7450
7451        Example:
7452
7453       (start code js)
7454         var viz = new $jit.Viz(options);
7455         var label = viz.labels.getLabel('someid');
7456         alert(label.innerHTML);
7457       (end code)
7458
7459     */
7460     getLabel: function(id) {
7461       return (id in this.labels && this.labels[id] != null) ?
7462         this.labels[id] :
7463         this.labels[id] = document.getElementById(id);
7464     },
7465
7466     /*
7467        Method: hideLabels
7468
7469        Hides all labels (by hiding the label container).
7470
7471        Parameters:
7472
7473        hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7474
7475        Example:
7476        (start code js)
7477         var viz = new $jit.Viz(options);
7478         rg.labels.hideLabels(true);
7479        (end code)
7480
7481     */
7482     hideLabels: function (hide) {
7483       var container = this.getLabelContainer();
7484       if(hide)
7485         container.style.display = 'none';
7486       else
7487         container.style.display = '';
7488       this.labelsHidden = hide;
7489     },
7490
7491     /*
7492        Method: clearLabels
7493
7494        Clears the label container.
7495
7496        Useful when using a new visualization with the same canvas element/widget.
7497
7498        Parameters:
7499
7500        force - (boolean) Forces deletion of all labels.
7501
7502        Example:
7503        (start code js)
7504         var viz = new $jit.Viz(options);
7505         viz.labels.clearLabels();
7506         (end code)
7507     */
7508     clearLabels: function(force) {
7509       for(var id in this.labels) {
7510         if (force || !this.viz.graph.hasNode(id)) {
7511           this.disposeLabel(id);
7512           delete this.labels[id];
7513         }
7514       }
7515     },
7516
7517     /*
7518        Method: disposeLabel
7519
7520        Removes a label.
7521
7522        Parameters:
7523
7524        id - (string) A label id (which generally is also a <Graph.Node> id).
7525
7526        Example:
7527        (start code js)
7528         var viz = new $jit.Viz(options);
7529         viz.labels.disposeLabel('labelid');
7530        (end code)
7531     */
7532     disposeLabel: function(id) {
7533       var elem = this.getLabel(id);
7534       if(elem && elem.parentNode) {
7535         elem.parentNode.removeChild(elem);
7536       }
7537     },
7538
7539     /*
7540        Method: hideLabel
7541
7542        Hides the corresponding <Graph.Node> label.
7543
7544        Parameters:
7545
7546        node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7547        show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7548
7549        Example:
7550        (start code js)
7551         var rg = new $jit.Viz(options);
7552         viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7553        (end code)
7554     */
7555     hideLabel: function(node, show) {
7556       node = $.splat(node);
7557       var st = show ? "" : "none", lab, that = this;
7558       $.each(node, function(n) {
7559         var lab = that.getLabel(n.id);
7560         if (lab) {
7561           lab.style.display = st;
7562         }
7563       });
7564     },
7565
7566     /*
7567        fitsInCanvas
7568
7569        Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7570
7571        Parameters:
7572
7573        pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7574        canvas - A <Canvas> instance.
7575
7576        Returns:
7577
7578        A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7579
7580     */
7581     fitsInCanvas: function(pos, canvas) {
7582       var size = canvas.getSize();
7583       if(pos.x >= size.width || pos.x < 0
7584          || pos.y >= size.height || pos.y < 0) return false;
7585        return true;
7586     }
7587 });
7588
7589 /*
7590    Class: Graph.Label.HTML
7591
7592    Implements HTML labels.
7593
7594    Extends:
7595
7596    All <Graph.Label.DOM> methods.
7597
7598 */
7599 Graph.Label.HTML = new Class({
7600     Implements: Graph.Label.DOM,
7601
7602     /*
7603        Method: plotLabel
7604
7605        Plots a label for a given node.
7606
7607        Parameters:
7608
7609        canvas - (object) A <Canvas> instance.
7610        node - (object) A <Graph.Node>.
7611        controller - (object) A configuration object.
7612        
7613       Example:
7614        
7615        (start code js)
7616        var viz = new $jit.Viz(options);
7617        var node = viz.graph.getNode('nodeId');
7618        viz.labels.plotLabel(viz.canvas, node, viz.config);
7619        (end code)
7620
7621
7622     */
7623     plotLabel: function(canvas, node, controller) {
7624       var id = node.id, tag = this.getLabel(id);
7625
7626       if(!tag && !(tag = document.getElementById(id))) {
7627         tag = document.createElement('div');
7628         var container = this.getLabelContainer();
7629         tag.id = id;
7630         tag.className = 'node';
7631         tag.style.position = 'absolute';
7632         controller.onCreateLabel(tag, node);
7633         container.appendChild(tag);
7634         this.labels[node.id] = tag;
7635       }
7636
7637       this.placeLabel(tag, node, controller);
7638     }
7639 });
7640
7641 /*
7642    Class: Graph.Label.SVG
7643
7644    Implements SVG labels.
7645
7646    Extends:
7647
7648    All <Graph.Label.DOM> methods.
7649 */
7650 Graph.Label.SVG = new Class({
7651     Implements: Graph.Label.DOM,
7652
7653     /*
7654        Method: plotLabel
7655
7656        Plots a label for a given node.
7657
7658        Parameters:
7659
7660        canvas - (object) A <Canvas> instance.
7661        node - (object) A <Graph.Node>.
7662        controller - (object) A configuration object.
7663        
7664        Example:
7665        
7666        (start code js)
7667        var viz = new $jit.Viz(options);
7668        var node = viz.graph.getNode('nodeId');
7669        viz.labels.plotLabel(viz.canvas, node, viz.config);
7670        (end code)
7671
7672
7673     */
7674     plotLabel: function(canvas, node, controller) {
7675       var id = node.id, tag = this.getLabel(id);
7676       if(!tag && !(tag = document.getElementById(id))) {
7677         var ns = 'http://www.w3.org/2000/svg';
7678           tag = document.createElementNS(ns, 'svg:text');
7679         var tspan = document.createElementNS(ns, 'svg:tspan');
7680         tag.appendChild(tspan);
7681         var container = this.getLabelContainer();
7682         tag.setAttribute('id', id);
7683         tag.setAttribute('class', 'node');
7684         container.appendChild(tag);
7685         controller.onCreateLabel(tag, node);
7686         this.labels[node.id] = tag;
7687       }
7688       this.placeLabel(tag, node, controller);
7689     }
7690 });
7691
7692
7693
7694 Graph.Geom = new Class({
7695
7696   initialize: function(viz) {
7697     this.viz = viz;
7698     this.config = viz.config;
7699     this.node = viz.config.Node;
7700     this.edge = viz.config.Edge;
7701   },
7702   /*
7703     Applies a translation to the tree.
7704   
7705     Parameters:
7706   
7707     pos - A <Complex> number specifying translation vector.
7708     prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7709   
7710     Example:
7711   
7712     (start code js)
7713       st.geom.translate(new Complex(300, 100), 'end');
7714     (end code)
7715   */  
7716   translate: function(pos, prop) {
7717      prop = $.splat(prop);
7718      this.viz.graph.eachNode(function(elem) {
7719          $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7720      });
7721   },
7722   /*
7723     Hides levels of the tree until it properly fits in canvas.
7724   */  
7725   setRightLevelToShow: function(node, canvas, callback) {
7726      var level = this.getRightLevelToShow(node, canvas), 
7727          fx = this.viz.labels,
7728          opt = $.merge({
7729            execShow:true,
7730            execHide:true,
7731            onHide: $.empty,
7732            onShow: $.empty
7733          }, callback || {});
7734      node.eachLevel(0, this.config.levelsToShow, function(n) {
7735          var d = n._depth - node._depth;
7736          if(d > level) {
7737              opt.onHide(n);
7738              if(opt.execHide) {
7739                n.drawn = false; 
7740                n.exist = false;
7741                fx.hideLabel(n, false);
7742              }
7743          } else {
7744              opt.onShow(n);
7745              if(opt.execShow) {
7746                n.exist = true;
7747              }
7748          }
7749      });
7750      node.drawn= true;
7751   },
7752   /*
7753     Returns the right level to show for the current tree in order to fit in canvas.
7754   */  
7755   getRightLevelToShow: function(node, canvas) {
7756      var config = this.config;
7757      var level = config.levelsToShow;
7758      var constrained = config.constrained;
7759      if(!constrained) return level;
7760      while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7761      return level;
7762   }
7763 });
7764
7765 /*
7766  * File: Loader.js
7767  * 
7768  */
7769
7770 /*
7771    Object: Loader
7772
7773    Provides methods for loading and serving JSON data.
7774 */
7775 var Loader = {
7776      construct: function(json) {
7777         var isGraph = ($.type(json) == 'array');
7778         var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7779         if(!isGraph) 
7780             //make tree
7781             (function (ans, json) {
7782                 ans.addNode(json);
7783                 if(json.children) {
7784                   for(var i=0, ch = json.children; i<ch.length; i++) {
7785                     ans.addAdjacence(json, ch[i]);
7786                     arguments.callee(ans, ch[i]);
7787                   }
7788                 }
7789             })(ans, json);
7790         else
7791             //make graph
7792             (function (ans, json) {
7793                 var getNode = function(id) {
7794                   for(var i=0, l=json.length; i<l; i++) {
7795                     if(json[i].id == id) {
7796                       return json[i];
7797                     }
7798                   }
7799                   // The node was not defined in the JSON
7800                   // Let's create it
7801                   var newNode = {
7802                                 "id" : id,
7803                                 "name" : id
7804                         };
7805                   return ans.addNode(newNode);
7806                 };
7807
7808                 for(var i=0, l=json.length; i<l; i++) {
7809                   ans.addNode(json[i]);
7810                   var adj = json[i].adjacencies;
7811                   if (adj) {
7812                     for(var j=0, lj=adj.length; j<lj; j++) {
7813                       var node = adj[j], data = {};
7814                       if(typeof adj[j] != 'string') {
7815                         data = $.merge(node.data, {});
7816                         node = node.nodeTo;
7817                       }
7818                       ans.addAdjacence(json[i], getNode(node), data);
7819                     }
7820                   }
7821                 }
7822             })(ans, json);
7823
7824         return ans;
7825     },
7826
7827     /*
7828      Method: loadJSON
7829     
7830      Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7831      
7832       A JSON tree or graph structure consists of nodes, each having as properties
7833        
7834        id - (string) A unique identifier for the node
7835        name - (string) A node's name
7836        data - (object) The data optional property contains a hash (i.e {}) 
7837        where you can store all the information you want about this node.
7838         
7839       For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7840       
7841       Example:
7842
7843       (start code js)
7844         var json = {  
7845           "id": "aUniqueIdentifier",  
7846           "name": "usually a nodes name",  
7847           "data": {
7848             "some key": "some value",
7849             "some other key": "some other value"
7850            },  
7851           "children": [ *other nodes or empty* ]  
7852         };  
7853       (end code)
7854         
7855         JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected. 
7856         For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7857         
7858         There are two types of *Graph* structures, *simple* and *extended* graph structures.
7859         
7860         For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the 
7861         id of the node connected to the main node.
7862         
7863         Example:
7864         
7865         (start code js)
7866         var json = [  
7867           {  
7868             "id": "aUniqueIdentifier",  
7869             "name": "usually a nodes name",  
7870             "data": {
7871               "some key": "some value",
7872               "some other key": "some other value"
7873              },  
7874             "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']  
7875           },
7876
7877           'other nodes go here...' 
7878         ];          
7879         (end code)
7880         
7881         For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7882         
7883         nodeTo - (string) The other node connected by this adjacency.
7884         data - (object) A data property, where we can store custom key/value information.
7885         
7886         Example:
7887         
7888         (start code js)
7889         var json = [  
7890           {  
7891             "id": "aUniqueIdentifier",  
7892             "name": "usually a nodes name",  
7893             "data": {
7894               "some key": "some value",
7895               "some other key": "some other value"
7896              },  
7897             "adjacencies": [  
7898             {  
7899               nodeTo:"aNodeId",  
7900               data: {} //put whatever you want here  
7901             },
7902             'other adjacencies go here...'  
7903           },
7904
7905           'other nodes go here...' 
7906         ];          
7907         (end code)
7908        
7909        About the data property:
7910        
7911        As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*. 
7912        You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and 
7913        have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7914        
7915        For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in 
7916        <Options.Node> will override the general value for that option with that particular value. For this to work 
7917        however, you do have to set *overridable = true* in <Options.Node>.
7918        
7919        The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge> 
7920        if <Options.Edge> has *overridable = true*.
7921        
7922        When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key, 
7923        since this is the value which will be taken into account when creating the layout. 
7924        The same thing goes for the *$color* parameter.
7925        
7926        In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example, 
7927        *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set 
7928        canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer 
7929        to the *shadowBlur* property.
7930        
7931        These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences> 
7932        by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7933        
7934        Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more 
7935        information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7936        
7937        loadJSON Parameters:
7938     
7939         json - A JSON Tree or Graph structure.
7940         i - For Graph structures only. Sets the indexed node as root for the visualization.
7941
7942     */
7943     loadJSON: function(json, i) {
7944       this.json = json;
7945       //if they're canvas labels erase them.
7946       if(this.labels && this.labels.clearLabels) {
7947         this.labels.clearLabels(true);
7948       }
7949       this.graph = this.construct(json);
7950       if($.type(json) != 'array'){
7951         this.root = json.id;
7952       } else {
7953         this.root = json[i? i : 0].id;
7954       }
7955     },
7956     
7957     /*
7958       Method: toJSON
7959    
7960       Returns a JSON tree/graph structure from the visualization's <Graph>. 
7961       See <Loader.loadJSON> for the graph formats available.
7962       
7963       See also:
7964       
7965       <Loader.loadJSON>
7966       
7967       Parameters:
7968       
7969       type - (string) Default's "tree". The type of the JSON structure to be returned. 
7970       Possible options are "tree" or "graph".
7971     */    
7972     toJSON: function(type) {
7973       type = type || "tree";
7974       if(type == 'tree') {
7975         var ans = {};
7976         var rootNode = this.graph.getNode(this.root);
7977         var ans = (function recTree(node) {
7978           var ans = {};
7979           ans.id = node.id;
7980           ans.name = node.name;
7981           ans.data = node.data;
7982           var ch =[];
7983           node.eachSubnode(function(n) {
7984             ch.push(recTree(n));
7985           });
7986           ans.children = ch;
7987           return ans;
7988         })(rootNode);
7989         return ans;
7990       } else {
7991         var ans = [];
7992         var T = !!this.graph.getNode(this.root).visited;
7993         this.graph.eachNode(function(node) {
7994           var ansNode = {};
7995           ansNode.id = node.id;
7996           ansNode.name = node.name;
7997           ansNode.data = node.data;
7998           var adjs = [];
7999           node.eachAdjacency(function(adj) {
8000             var nodeTo = adj.nodeTo;
8001             if(!!nodeTo.visited === T) {
8002               var ansAdj = {};
8003               ansAdj.nodeTo = nodeTo.id;
8004               ansAdj.data = adj.data;
8005               adjs.push(ansAdj);
8006             }
8007           });
8008           ansNode.adjacencies = adjs;
8009           ans.push(ansNode);
8010           node.visited = !T;
8011         });
8012         return ans;
8013       }
8014     }
8015 };
8016
8017
8018
8019 /*
8020  * File: Layouts.js
8021  * 
8022  * Implements base Tree and Graph layouts.
8023  *
8024  * Description:
8025  *
8026  * Implements base Tree and Graph layouts like Radial, Tree, etc.
8027  * 
8028  */
8029
8030 /*
8031  * Object: Layouts
8032  * 
8033  * Parent object for common layouts.
8034  *
8035  */
8036 var Layouts = $jit.Layouts = {};
8037
8038
8039 //Some util shared layout functions are defined here.
8040 var NodeDim = {
8041   label: null,
8042   
8043   compute: function(graph, prop, opt) {
8044     this.initializeLabel(opt);
8045     var label = this.label, style = label.style;
8046     graph.eachNode(function(n) {
8047       var autoWidth  = n.getData('autoWidth'),
8048           autoHeight = n.getData('autoHeight');
8049       if(autoWidth || autoHeight) {
8050         //delete dimensions since these are
8051         //going to be overridden now.
8052         delete n.data.$width;
8053         delete n.data.$height;
8054         delete n.data.$dim;
8055         
8056         var width  = n.getData('width'),
8057             height = n.getData('height');
8058         //reset label dimensions
8059         style.width  = autoWidth? 'auto' : width + 'px';
8060         style.height = autoHeight? 'auto' : height + 'px';
8061         
8062         //TODO(nico) should let the user choose what to insert here.
8063         label.innerHTML = n.name;
8064         
8065         var offsetWidth  = label.offsetWidth,
8066             offsetHeight = label.offsetHeight;
8067         var type = n.getData('type');
8068         if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8069           n.setData('width', offsetWidth);
8070           n.setData('height', offsetHeight);
8071         } else {
8072           var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8073           n.setData('width', dim);
8074           n.setData('height', dim);
8075           n.setData('dim', dim); 
8076         }
8077       }
8078     });
8079   },
8080   
8081   initializeLabel: function(opt) {
8082     if(!this.label) {
8083       this.label = document.createElement('div');
8084       document.body.appendChild(this.label);
8085     }
8086     this.setLabelStyles(opt);
8087   },
8088   
8089   setLabelStyles: function(opt) {
8090     $.extend(this.label.style, {
8091       'visibility': 'hidden',
8092       'position': 'absolute',
8093       'width': 'auto',
8094       'height': 'auto'
8095     });
8096     this.label.className = 'jit-autoadjust-label';
8097   }
8098 };
8099
8100
8101 /*
8102  * Class: Layouts.Tree
8103  * 
8104  * Implements a Tree Layout.
8105  * 
8106  * Implemented By:
8107  * 
8108  * <ST>
8109  * 
8110  * Inspired by:
8111  * 
8112  * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8113  * 
8114  */
8115 Layouts.Tree = (function() {
8116   //Layout functions
8117   var slice = Array.prototype.slice;
8118
8119   /*
8120      Calculates the max width and height nodes for a tree level
8121   */  
8122   function getBoundaries(graph, config, level, orn, prop) {
8123     var dim = config.Node;
8124     var multitree = config.multitree;
8125     if (dim.overridable) {
8126       var w = -1, h = -1;
8127       graph.eachNode(function(n) {
8128         if (n._depth == level
8129             && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8130           var dw = n.getData('width', prop);
8131           var dh = n.getData('height', prop);
8132           w = (w < dw) ? dw : w;
8133           h = (h < dh) ? dh : h;
8134         }
8135       });
8136       return {
8137         'width' : w < 0 ? dim.width : w,
8138         'height' : h < 0 ? dim.height : h
8139       };
8140     } else {
8141       return dim;
8142     }
8143   }
8144
8145
8146   function movetree(node, prop, val, orn) {
8147     var p = (orn == "left" || orn == "right") ? "y" : "x";
8148     node.getPos(prop)[p] += val;
8149   }
8150
8151
8152   function moveextent(extent, val) {
8153     var ans = [];
8154     $.each(extent, function(elem) {
8155       elem = slice.call(elem);
8156       elem[0] += val;
8157       elem[1] += val;
8158       ans.push(elem);
8159     });
8160     return ans;
8161   }
8162
8163
8164   function merge(ps, qs) {
8165     if (ps.length == 0)
8166       return qs;
8167     if (qs.length == 0)
8168       return ps;
8169     var p = ps.shift(), q = qs.shift();
8170     return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8171   }
8172
8173
8174   function mergelist(ls, def) {
8175     def = def || [];
8176     if (ls.length == 0)
8177       return def;
8178     var ps = ls.pop();
8179     return mergelist(ls, merge(ps, def));
8180   }
8181
8182
8183   function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8184     if (ext1.length <= i || ext2.length <= i)
8185       return 0;
8186
8187     var p = ext1[i][1], q = ext2[i][0];
8188     return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8189         + subtreeOffset, p - q + siblingOffset);
8190   }
8191
8192
8193   function fitlistl(es, subtreeOffset, siblingOffset) {
8194     function $fitlistl(acc, es, i) {
8195       if (es.length <= i)
8196         return [];
8197       var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8198       return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8199     }
8200     ;
8201     return $fitlistl( [], es, 0);
8202   }
8203
8204
8205   function fitlistr(es, subtreeOffset, siblingOffset) {
8206     function $fitlistr(acc, es, i) {
8207       if (es.length <= i)
8208         return [];
8209       var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8210       return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8211     }
8212     ;
8213     es = slice.call(es);
8214     var ans = $fitlistr( [], es.reverse(), 0);
8215     return ans.reverse();
8216   }
8217
8218
8219   function fitlist(es, subtreeOffset, siblingOffset, align) {
8220     var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8221         subtreeOffset, siblingOffset);
8222
8223     if (align == "left")
8224       esr = esl;
8225     else if (align == "right")
8226       esl = esr;
8227
8228     for ( var i = 0, ans = []; i < esl.length; i++) {
8229       ans[i] = (esl[i] + esr[i]) / 2;
8230     }
8231     return ans;
8232   }
8233
8234
8235   function design(graph, node, prop, config, orn) {
8236     var multitree = config.multitree;
8237     var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8238     var ind = +(orn == "left" || orn == "right");
8239     var p = auxp[ind], notp = auxp[1 - ind];
8240
8241     var cnode = config.Node;
8242     var s = auxs[ind], nots = auxs[1 - ind];
8243
8244     var siblingOffset = config.siblingOffset;
8245     var subtreeOffset = config.subtreeOffset;
8246     var align = config.align;
8247
8248     function $design(node, maxsize, acum) {
8249       var sval = node.getData(s, prop);
8250       var notsval = maxsize
8251           || (node.getData(nots, prop));
8252
8253       var trees = [], extents = [], chmaxsize = false;
8254       var chacum = notsval + config.levelDistance;
8255       node.eachSubnode(function(n) {
8256             if (n.exist
8257                 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8258
8259               if (!chmaxsize)
8260                 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8261
8262               var s = $design(n, chmaxsize[nots], acum + chacum);
8263               trees.push(s.tree);
8264               extents.push(s.extent);
8265             }
8266           });
8267       var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8268       for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8269         movetree(trees[i], prop, positions[i], orn);
8270         pextents.push(moveextent(extents[i], positions[i]));
8271       }
8272       var resultextent = [ [ -sval / 2, sval / 2 ] ]
8273           .concat(mergelist(pextents));
8274       node.getPos(prop)[p] = 0;
8275
8276       if (orn == "top" || orn == "left") {
8277         node.getPos(prop)[notp] = acum;
8278       } else {
8279         node.getPos(prop)[notp] = -acum;
8280       }
8281
8282       return {
8283         tree : node,
8284         extent : resultextent
8285       };
8286     }
8287
8288     $design(node, false, 0);
8289   }
8290
8291
8292   return new Class({
8293     /*
8294     Method: compute
8295     
8296     Computes nodes' positions.
8297
8298      */
8299     compute : function(property, computeLevels) {
8300       var prop = property || 'start';
8301       var node = this.graph.getNode(this.root);
8302       $.extend(node, {
8303         'drawn' : true,
8304         'exist' : true,
8305         'selected' : true
8306       });
8307       NodeDim.compute(this.graph, prop, this.config);
8308       if (!!computeLevels || !("_depth" in node)) {
8309         this.graph.computeLevels(this.root, 0, "ignore");
8310       }
8311       
8312       this.computePositions(node, prop);
8313     },
8314
8315     computePositions : function(node, prop) {
8316       var config = this.config;
8317       var multitree = config.multitree;
8318       var align = config.align;
8319       var indent = align !== 'center' && config.indent;
8320       var orn = config.orientation;
8321       var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8322       var that = this;
8323       $.each(orns, function(orn) {
8324         //calculate layout
8325           design(that.graph, node, prop, that.config, orn, prop);
8326           var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8327           //absolutize
8328           (function red(node) {
8329             node.eachSubnode(function(n) {
8330               if (n.exist
8331                   && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8332
8333                 n.getPos(prop)[i] += node.getPos(prop)[i];
8334                 if (indent) {
8335                   n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8336                 }
8337                 red(n);
8338               }
8339             });
8340           })(node);
8341         });
8342     }
8343   });
8344   
8345 })();
8346
8347 /*
8348  * File: Spacetree.js
8349  */
8350
8351 /*
8352    Class: ST
8353    
8354   A Tree layout with advanced contraction and expansion animations.
8355      
8356   Inspired by:
8357  
8358   SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson) 
8359   <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8360   
8361   Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8362   
8363   Note:
8364  
8365   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.
8366  
8367   Implements:
8368   
8369   All <Loader> methods
8370   
8371   Constructor Options:
8372   
8373   Inherits options from
8374   
8375   - <Options.Canvas>
8376   - <Options.Controller>
8377   - <Options.Tree>
8378   - <Options.Node>
8379   - <Options.Edge>
8380   - <Options.Label>
8381   - <Options.Events>
8382   - <Options.Tips>
8383   - <Options.NodeStyles>
8384   - <Options.Navigation>
8385   
8386   Additionally, there are other parameters and some default values changed
8387   
8388   constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8389   levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8390   levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8391   Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8392   offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8393   offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8394   duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8395   
8396   Instance Properties:
8397   
8398   canvas - Access a <Canvas> instance.
8399   graph - Access a <Graph> instance.
8400   op - Access a <ST.Op> instance.
8401   fx - Access a <ST.Plot> instance.
8402   labels - Access a <ST.Label> interface implementation.
8403
8404  */
8405
8406 $jit.ST= (function() {
8407     // Define some private methods first...
8408     // Nodes in path
8409     var nodesInPath = [];
8410     // Nodes to contract
8411     function getNodesToHide(node) {
8412       node = node || this.clickedNode;
8413       if(!this.config.constrained) {
8414         return [];
8415       }
8416       var Geom = this.geom;
8417       var graph = this.graph;
8418       var canvas = this.canvas;
8419       var level = node._depth, nodeArray = [];
8420           graph.eachNode(function(n) {
8421           if(n.exist && !n.selected) {
8422               if(n.isDescendantOf(node.id)) {
8423                 if(n._depth <= level) nodeArray.push(n);
8424               } else {
8425                 nodeArray.push(n);
8426               }
8427           }
8428           });
8429           var leafLevel = Geom.getRightLevelToShow(node, canvas);
8430           node.eachLevel(leafLevel, leafLevel, function(n) {
8431           if(n.exist && !n.selected) nodeArray.push(n);
8432           });
8433             
8434           for (var i = 0; i < nodesInPath.length; i++) {
8435             var n = this.graph.getNode(nodesInPath[i]);
8436             if(!n.isDescendantOf(node.id)) {
8437               nodeArray.push(n);
8438             }
8439           } 
8440           return nodeArray;       
8441     };
8442     // Nodes to expand
8443      function getNodesToShow(node) {
8444         var nodeArray = [], config = this.config;
8445         node = node || this.clickedNode;
8446         this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8447             if(config.multitree && !('$orn' in n.data) 
8448                         && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8449                 nodeArray.push(n);
8450             } else if(n.drawn && !n.anySubnode("drawn")) {
8451               nodeArray.push(n);
8452             }
8453         });
8454         return nodeArray;
8455      };
8456     // Now define the actual class.
8457     return new Class({
8458     
8459         Implements: [Loader, Extras, Layouts.Tree],
8460         
8461         initialize: function(controller) {            
8462           var $ST = $jit.ST;
8463           
8464           var config= {
8465                 levelsToShow: 2,
8466                 levelDistance: 30,
8467                 constrained: true,                
8468                 Node: {
8469                   type: 'rectangle'
8470                 },
8471                 duration: 700,
8472                 offsetX: 0,
8473                 offsetY: 0
8474             };
8475             
8476             this.controller = this.config = $.merge(
8477                 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller", 
8478                     "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8479
8480             var canvasConfig = this.config;
8481             if(canvasConfig.useCanvas) {
8482               this.canvas = canvasConfig.useCanvas;
8483               this.config.labelContainer = this.canvas.id + '-label';
8484             } else {
8485               if(canvasConfig.background) {
8486                 canvasConfig.background = $.merge({
8487                   type: 'Fade',
8488                   colorStop1: this.config.colorStop1,
8489                   colorStop2: this.config.colorStop2
8490                 }, canvasConfig.background);
8491               }
8492               this.canvas = new Canvas(this, canvasConfig);
8493               this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8494             }
8495
8496             this.graphOptions = {
8497                 'complex': true
8498             };
8499             this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8500             this.labels = new $ST.Label[canvasConfig.Label.type](this);
8501             this.fx = new $ST.Plot(this, $ST);
8502             this.op = new $ST.Op(this);
8503             this.group = new $ST.Group(this);
8504             this.geom = new $ST.Geom(this);
8505             this.clickedNode=  null;
8506             // initialize extras
8507             this.initializeExtras();
8508         },
8509     
8510         /*
8511          Method: plot
8512         
8513          Plots the <ST>. This is a shortcut to *fx.plot*.
8514
8515         */  
8516         plot: function() { this.fx.plot(this.controller); },
8517     
8518       
8519         /*
8520          Method: switchPosition
8521         
8522          Switches the tree orientation.
8523
8524          Parameters:
8525
8526         pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8527         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.
8528         onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8529
8530          Example:
8531
8532          (start code js)
8533            st.switchPosition("right", "animate", {
8534             onComplete: function() {
8535               alert('completed!');
8536             } 
8537            });
8538          (end code)
8539         */  
8540         switchPosition: function(pos, method, onComplete) {
8541           var Geom = this.geom, Plot = this.fx, that = this;
8542           if(!Plot.busy) {
8543               Plot.busy = true;
8544               this.contract({
8545                   onComplete: function() {
8546                       Geom.switchOrientation(pos);
8547                       that.compute('end', false);
8548                       Plot.busy = false;
8549                       if(method == 'animate') {
8550                           that.onClick(that.clickedNode.id, onComplete);  
8551                       } else if(method == 'replot') {
8552                           that.select(that.clickedNode.id, onComplete);
8553                       }
8554                   }
8555               }, pos);
8556           }
8557         },
8558
8559         /*
8560         Method: switchAlignment
8561        
8562         Switches the tree alignment.
8563
8564         Parameters:
8565
8566        align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8567        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.
8568        onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8569
8570         Example:
8571
8572         (start code js)
8573           st.switchAlignment("right", "animate", {
8574            onComplete: function() {
8575              alert('completed!');
8576            } 
8577           });
8578         (end code)
8579        */  
8580        switchAlignment: function(align, method, onComplete) {
8581         this.config.align = align;
8582         if(method == 'animate') {
8583                 this.select(this.clickedNode.id, onComplete);
8584         } else if(method == 'replot') {
8585                 this.onClick(this.clickedNode.id, onComplete);  
8586         }
8587        },
8588
8589        /*
8590         Method: addNodeInPath
8591        
8592         Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8593         
8594
8595         Parameters:
8596
8597        id - (string) A <Graph.Node> id.
8598
8599         Example:
8600
8601         (start code js)
8602           st.addNodeInPath("nodeId");
8603         (end code)
8604        */  
8605        addNodeInPath: function(id) {
8606            nodesInPath.push(id);
8607            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8608        },       
8609
8610        /*
8611        Method: clearNodesInPath
8612       
8613        Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8614        
8615        See also:
8616        
8617        <ST.addNodeInPath>
8618      
8619        Example:
8620
8621        (start code js)
8622          st.clearNodesInPath();
8623        (end code)
8624       */  
8625        clearNodesInPath: function(id) {
8626            nodesInPath.length = 0;
8627            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8628        },
8629         
8630        /*
8631          Method: refresh
8632         
8633          Computes positions and plots the tree.
8634          
8635        */
8636        refresh: function() {
8637            this.reposition();
8638            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8639        },    
8640
8641        reposition: function() {
8642             this.graph.computeLevels(this.root, 0, "ignore");
8643              this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8644             this.graph.eachNode(function(n) {
8645                 if(n.exist) n.drawn = true;
8646             });
8647             this.compute('end');
8648         },
8649         
8650         requestNodes: function(node, onComplete) {
8651           var handler = $.merge(this.controller, onComplete), 
8652           lev = this.config.levelsToShow;
8653           if(handler.request) {
8654               var leaves = [], d = node._depth;
8655               node.eachLevel(0, lev, function(n) {
8656                   if(n.drawn && 
8657                    !n.anySubnode()) {
8658                    leaves.push(n);
8659                    n._level = lev - (n._depth - d);
8660                   }
8661               });
8662               this.group.requestNodes(leaves, handler);
8663           }
8664             else
8665               handler.onComplete();
8666         },
8667      
8668         contract: function(onComplete, switched) {
8669           var orn  = this.config.orientation;
8670           var Geom = this.geom, Group = this.group;
8671           if(switched) Geom.switchOrientation(switched);
8672           var nodes = getNodesToHide.call(this);
8673           if(switched) Geom.switchOrientation(orn);
8674           Group.contract(nodes, $.merge(this.controller, onComplete));
8675         },
8676       
8677          move: function(node, onComplete) {
8678             this.compute('end', false);
8679             var move = onComplete.Move, offset = {
8680                 'x': move.offsetX,
8681                 'y': move.offsetY 
8682             };
8683             if(move.enable) {
8684                 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8685             }
8686             this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8687          },
8688       
8689         expand: function (node, onComplete) {
8690             var nodeArray = getNodesToShow.call(this, node);
8691             this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8692         },
8693     
8694         selectPath: function(node) {
8695           var that = this;
8696           this.graph.eachNode(function(n) { n.selected = false; }); 
8697           function path(node) {
8698               if(node == null || node.selected) return;
8699               node.selected = true;
8700               $.each(that.group.getSiblings([node])[node.id], 
8701               function(n) { 
8702                    n.exist = true; 
8703                    n.drawn = true; 
8704               });    
8705               var parents = node.getParents();
8706               parents = (parents.length > 0)? parents[0] : null;
8707               path(parents);
8708           };
8709           for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8710               path(this.graph.getNode(ns[i]));
8711           }
8712         },
8713       
8714         /*
8715         Method: setRoot
8716      
8717          Switches the current root node. Changes the topology of the Tree.
8718      
8719         Parameters:
8720            id - (string) The id of the node to be set as root.
8721            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.
8722            onComplete - (optional|object) An action to perform after the animation (if any).
8723  
8724         Example:
8725
8726         (start code js)
8727           st.setRoot('nodeId', 'animate', {
8728              onComplete: function() {
8729                alert('complete!');
8730              }
8731           });
8732         (end code)
8733      */
8734      setRoot: function(id, method, onComplete) {
8735                 if(this.busy) return;
8736                 this.busy = true;
8737           var that = this, canvas = this.canvas;
8738                 var rootNode = this.graph.getNode(this.root);
8739                 var clickedNode = this.graph.getNode(id);
8740                 function $setRoot() {
8741                 if(this.config.multitree && clickedNode.data.$orn) {
8742                         var orn = clickedNode.data.$orn;
8743                         var opp = {
8744                                         'left': 'right',
8745                                         'right': 'left',
8746                                         'top': 'bottom',
8747                                         'bottom': 'top'
8748                         }[orn];
8749                         rootNode.data.$orn = opp;
8750                         (function tag(rootNode) {
8751                                 rootNode.eachSubnode(function(n) {
8752                                         if(n.id != id) {
8753                                                 n.data.$orn = opp;
8754                                                 tag(n);
8755                                         }
8756                                 });
8757                         })(rootNode);
8758                         delete clickedNode.data.$orn;
8759                 }
8760                 this.root = id;
8761                 this.clickedNode = clickedNode;
8762                 this.graph.computeLevels(this.root, 0, "ignore");
8763                 this.geom.setRightLevelToShow(clickedNode, canvas, {
8764                   execHide: false,
8765                   onShow: function(node) {
8766                     if(!node.drawn) {
8767                     node.drawn = true;
8768                     node.setData('alpha', 1, 'end');
8769                     node.setData('alpha', 0);
8770                     node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8771                     }
8772                   }
8773                 });
8774               this.compute('end');
8775               this.busy = true;
8776               this.fx.animate({
8777                 modes: ['linear', 'node-property:alpha'],
8778                 onComplete: function() {
8779                   that.busy = false;
8780                   that.onClick(id, {
8781                     onComplete: function() {
8782                       onComplete && onComplete.onComplete();
8783                     }
8784                   });
8785                 }
8786               });
8787                 }
8788
8789                 // delete previous orientations (if any)
8790                 delete rootNode.data.$orns;
8791
8792                 if(method == 'animate') {
8793                   $setRoot.call(this);
8794                   that.selectPath(clickedNode);
8795                 } else if(method == 'replot') {
8796                         $setRoot.call(this);
8797                         this.select(this.root);
8798                 }
8799      },
8800
8801      /*
8802            Method: addSubtree
8803         
8804             Adds a subtree.
8805         
8806            Parameters:
8807               subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8808               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.
8809               onComplete - (optional|object) An action to perform after the animation (if any).
8810     
8811            Example:
8812
8813            (start code js)
8814              st.addSubtree(json, 'animate', {
8815                 onComplete: function() {
8816                   alert('complete!');
8817                 }
8818              });
8819            (end code)
8820         */
8821         addSubtree: function(subtree, method, onComplete) {
8822             if(method == 'replot') {
8823                 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8824             } else if (method == 'animate') {
8825                 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8826             }
8827         },
8828     
8829         /*
8830            Method: removeSubtree
8831         
8832             Removes a subtree.
8833         
8834            Parameters:
8835               id - (string) The _id_ of the subtree to be removed.
8836               removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8837               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.
8838               onComplete - (optional|object) An action to perform after the animation (if any).
8839
8840           Example:
8841
8842           (start code js)
8843             st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8844               onComplete: function() {
8845                 alert('complete!');
8846               }
8847             });
8848           (end code)
8849     
8850         */
8851         removeSubtree: function(id, removeRoot, method, onComplete) {
8852             var node = this.graph.getNode(id), subids = [];
8853             node.eachLevel(+!removeRoot, false, function(n) {
8854                 subids.push(n.id);
8855             });
8856             if(method == 'replot') {
8857                 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8858             } else if (method == 'animate') {
8859                 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8860             }
8861         },
8862     
8863         /*
8864            Method: select
8865         
8866             Selects a node in the <ST> without performing an animation. Useful when selecting 
8867             nodes which are currently hidden or deep inside the tree.
8868
8869           Parameters:
8870             id - (string) The id of the node to select.
8871             onComplete - (optional|object) an onComplete callback.
8872
8873           Example:
8874           (start code js)
8875             st.select('mynodeid', {
8876               onComplete: function() {
8877                 alert('complete!');
8878               }
8879             });
8880           (end code)
8881         */
8882         select: function(id, onComplete) {
8883             var group = this.group, geom = this.geom;
8884             var node=  this.graph.getNode(id), canvas = this.canvas;
8885             var root  = this.graph.getNode(this.root);
8886             var complete = $.merge(this.controller, onComplete);
8887             var that = this;
8888     
8889             complete.onBeforeCompute(node);
8890             this.selectPath(node);
8891             this.clickedNode= node;
8892             this.requestNodes(node, {
8893                 onComplete: function(){
8894                     group.hide(group.prepare(getNodesToHide.call(that)), complete);
8895                     geom.setRightLevelToShow(node, canvas);
8896                     that.compute("current");
8897                     that.graph.eachNode(function(n) { 
8898                         var pos = n.pos.getc(true);
8899                         n.startPos.setc(pos.x, pos.y);
8900                         n.endPos.setc(pos.x, pos.y);
8901                         n.visited = false; 
8902                     });
8903                     var offset = { x: complete.offsetX, y: complete.offsetY };
8904                     that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8905                     group.show(getNodesToShow.call(that));              
8906                     that.plot();
8907                     complete.onAfterCompute(that.clickedNode);
8908                     complete.onComplete();
8909                 }
8910             });     
8911         },
8912     
8913       /*
8914          Method: onClick
8915     
8916         Animates the <ST> to center the node specified by *id*.
8917             
8918         Parameters:
8919         
8920         id - (string) A node id.
8921         options - (optional|object) A group of options and callbacks described below.
8922         onComplete - (object) An object callback called when the animation finishes.
8923         Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8924
8925         Example:
8926
8927         (start code js)
8928           st.onClick('mynodeid', {
8929                   Move: {
8930                         enable: true,
8931                     offsetX: 30,
8932                     offsetY: 5
8933                   },
8934                   onComplete: function() {
8935                       alert('yay!');
8936                   }
8937           });
8938         (end code)
8939     
8940         */    
8941       onClick: function (id, options) {
8942         var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8943         var innerController = {
8944             Move: {
8945                     enable: true,
8946               offsetX: config.offsetX || 0,
8947               offsetY: config.offsetY || 0  
8948             },
8949             setRightLevelToShowConfig: false,
8950             onBeforeRequest: $.empty,
8951             onBeforeContract: $.empty,
8952             onBeforeMove: $.empty,
8953             onBeforeExpand: $.empty
8954         };
8955         var complete = $.merge(this.controller, innerController, options);
8956         
8957         if(!this.busy) {
8958             this.busy = true;
8959             var node = this.graph.getNode(id);
8960             this.selectPath(node, this.clickedNode);
8961                 this.clickedNode = node;
8962             complete.onBeforeCompute(node);
8963             complete.onBeforeRequest(node);
8964             this.requestNodes(node, {
8965                 onComplete: function() {
8966                     complete.onBeforeContract(node);
8967                     that.contract({
8968                         onComplete: function() {
8969                             Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8970                             complete.onBeforeMove(node);
8971                             that.move(node, {
8972                                 Move: complete.Move,
8973                                 onComplete: function() {
8974                                     complete.onBeforeExpand(node);
8975                                     that.expand(node, {
8976                                         onComplete: function() {
8977                                             that.busy = false;
8978                                             complete.onAfterCompute(id);
8979                                             complete.onComplete();
8980                                         }
8981                                     }); // expand
8982                                 }
8983                             }); // move
8984                         }
8985                     });// contract
8986                 }
8987             });// request
8988         }
8989       }
8990     });
8991
8992 })();
8993
8994 $jit.ST.$extend = true;
8995
8996 /*
8997    Class: ST.Op
8998     
8999    Custom extension of <Graph.Op>.
9000
9001    Extends:
9002
9003    All <Graph.Op> methods
9004    
9005    See also:
9006    
9007    <Graph.Op>
9008
9009 */
9010 $jit.ST.Op = new Class({
9011
9012   Implements: Graph.Op
9013     
9014 });
9015
9016 /*
9017     
9018      Performs operations on group of nodes.
9019
9020 */
9021 $jit.ST.Group = new Class({
9022     
9023     initialize: function(viz) {
9024         this.viz = viz;
9025         this.canvas = viz.canvas;
9026         this.config = viz.config;
9027         this.animation = new Animation;
9028         this.nodes = null;
9029     },
9030     
9031     /*
9032     
9033        Calls the request method on the controller to request a subtree for each node. 
9034     */
9035     requestNodes: function(nodes, controller) {
9036         var counter = 0, len = nodes.length, nodeSelected = {};
9037         var complete = function() { controller.onComplete(); };
9038         var viz = this.viz;
9039         if(len == 0) complete();
9040         for(var i=0; i<len; i++) {
9041             nodeSelected[nodes[i].id] = nodes[i];
9042             controller.request(nodes[i].id, nodes[i]._level, {
9043                 onComplete: function(nodeId, data) {
9044                     if(data && data.children) {
9045                         data.id = nodeId;
9046                         viz.op.sum(data, { type: 'nothing' });
9047                     }
9048                     if(++counter == len) {
9049                         viz.graph.computeLevels(viz.root, 0);
9050                         complete();
9051                     }
9052                 }
9053             });
9054         }
9055     },
9056     
9057     /*
9058     
9059        Collapses group of nodes. 
9060     */
9061     contract: function(nodes, controller) {
9062         var viz = this.viz;
9063         var that = this;
9064
9065         nodes = this.prepare(nodes);
9066         this.animation.setOptions($.merge(controller, {
9067             $animating: false,
9068             compute: function(delta) {
9069               if(delta == 1) delta = 0.99;
9070               that.plotStep(1 - delta, controller, this.$animating);
9071               this.$animating = 'contract';
9072             },
9073             
9074             complete: function() {
9075                 that.hide(nodes, controller);
9076             }       
9077         })).start();
9078     },
9079     
9080     hide: function(nodes, controller) {
9081         var viz = this.viz;
9082         for(var i=0; i<nodes.length; i++) {
9083             // TODO nodes are requested on demand, but not
9084             // deleted when hidden. Would that be a good feature?
9085             // Currently that feature is buggy, so I'll turn it off
9086             // Actually this feature is buggy because trimming should take
9087             // place onAfterCompute and not right after collapsing nodes.
9088             if (true || !controller || !controller.request) {
9089                 nodes[i].eachLevel(1, false, function(elem){
9090                     if (elem.exist) {
9091                         $.extend(elem, {
9092                             'drawn': false,
9093                             'exist': false
9094                         });
9095                     }
9096                 });
9097             } else {
9098                 var ids = [];
9099                 nodes[i].eachLevel(1, false, function(n) {
9100                     ids.push(n.id);
9101                 });
9102                 viz.op.removeNode(ids, { 'type': 'nothing' });
9103                 viz.labels.clearLabels();
9104             }
9105         }
9106         controller.onComplete();
9107     },    
9108     
9109
9110     /*
9111        Expands group of nodes. 
9112     */
9113     expand: function(nodes, controller) {
9114         var that = this;
9115         this.show(nodes);
9116         this.animation.setOptions($.merge(controller, {
9117             $animating: false,
9118             compute: function(delta) {
9119                 that.plotStep(delta, controller, this.$animating);
9120                 this.$animating = 'expand';
9121             },
9122             
9123             complete: function() {
9124                 that.plotStep(undefined, controller, false);
9125                 controller.onComplete();
9126             }       
9127         })).start();
9128         
9129     },
9130     
9131     show: function(nodes) {
9132         var config = this.config;
9133         this.prepare(nodes);
9134         $.each(nodes, function(n) {
9135                 // check for root nodes if multitree
9136                 if(config.multitree && !('$orn' in n.data)) {
9137                         delete n.data.$orns;
9138                         var orns = ' ';
9139                         n.eachSubnode(function(ch) {
9140                                 if(('$orn' in ch.data) 
9141                                                 && orns.indexOf(ch.data.$orn) < 0 
9142                                                 && ch.exist && !ch.drawn) {
9143                                         orns += ch.data.$orn + ' ';
9144                                 }
9145                         });
9146                         n.data.$orns = orns;
9147                 }
9148             n.eachLevel(0, config.levelsToShow, function(n) {
9149                 if(n.exist) n.drawn = true;
9150             });     
9151         });
9152     },
9153     
9154     prepare: function(nodes) {
9155         this.nodes = this.getNodesWithChildren(nodes);
9156         return this.nodes;
9157     },
9158     
9159     /*
9160        Filters an array of nodes leaving only nodes with children.
9161     */
9162     getNodesWithChildren: function(nodes) {
9163         var ans = [], config = this.config, root = this.viz.root;
9164         nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9165         for(var i=0; i<nodes.length; i++) {
9166             if(nodes[i].anySubnode("exist")) {
9167                 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9168                     if(!config.multitree || '$orn' in nodes[j].data) {
9169                                 desc = desc || nodes[i].isDescendantOf(nodes[j].id);                            
9170                     }
9171                 }
9172                 if(!desc) ans.push(nodes[i]);
9173             }
9174         }
9175         return ans;
9176     },
9177     
9178     plotStep: function(delta, controller, animating) {
9179         var viz = this.viz,
9180         config = this.config,
9181         canvas = viz.canvas, 
9182         ctx = canvas.getCtx(),
9183         nodes = this.nodes;
9184         var i, node;
9185         // hide nodes that are meant to be collapsed/expanded
9186         var nds = {};
9187         for(i=0; i<nodes.length; i++) {
9188           node = nodes[i];
9189           nds[node.id] = [];
9190           var root = config.multitree && !('$orn' in node.data);
9191           var orns = root && node.data.$orns;
9192           node.eachSubgraph(function(n) { 
9193             // TODO(nico): Cleanup
9194                   // special check for root node subnodes when
9195                   // multitree is checked.
9196                   if(root && orns && orns.indexOf(n.data.$orn) > 0 
9197                                   && n.drawn) {
9198                           n.drawn = false;
9199                   nds[node.id].push(n);
9200               } else if((!root || !orns) && n.drawn) {
9201                 n.drawn = false;
9202                 nds[node.id].push(n);
9203               }
9204             }); 
9205             node.drawn = true;
9206         }
9207         // plot the whole (non-scaled) tree
9208         if(nodes.length > 0) viz.fx.plot();
9209         // show nodes that were previously hidden
9210         for(i in nds) {
9211           $.each(nds[i], function(n) { n.drawn = true; });
9212         }
9213         // plot each scaled subtree
9214         for(i=0; i<nodes.length; i++) {
9215           node = nodes[i];
9216           ctx.save();
9217           viz.fx.plotSubtree(node, controller, delta, animating);                
9218           ctx.restore();
9219         }
9220       },
9221
9222       getSiblings: function(nodes) {
9223         var siblings = {};
9224         $.each(nodes, function(n) {
9225             var par = n.getParents();
9226             if (par.length == 0) {
9227                 siblings[n.id] = [n];
9228             } else {
9229                 var ans = [];
9230                 par[0].eachSubnode(function(sn) {
9231                     ans.push(sn);
9232                 });
9233                 siblings[n.id] = ans;
9234             }
9235         });
9236         return siblings;
9237     }
9238 });
9239
9240 /*
9241    ST.Geom
9242
9243    Performs low level geometrical computations.
9244
9245    Access:
9246
9247    This instance can be accessed with the _geom_ parameter of the st instance created.
9248
9249    Example:
9250
9251    (start code js)
9252     var st = new ST(canvas, config);
9253     st.geom.translate //or can also call any other <ST.Geom> method
9254    (end code)
9255
9256 */
9257
9258 $jit.ST.Geom = new Class({
9259     Implements: Graph.Geom,
9260     /*
9261        Changes the tree current orientation to the one specified.
9262
9263        You should usually use <ST.switchPosition> instead.
9264     */  
9265     switchOrientation: function(orn) {
9266         this.config.orientation = orn;
9267     },
9268
9269     /*
9270        Makes a value dispatch according to the current layout
9271        Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9272      */
9273     dispatch: function() {
9274           // TODO(nico) should store Array.prototype.slice.call somewhere.
9275         var args = Array.prototype.slice.call(arguments);
9276         var s = args.shift(), len = args.length;
9277         var val = function(a) { return typeof a == 'function'? a() : a; };
9278         if(len == 2) {
9279             return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9280         } else if(len == 4) {
9281             switch(s) {
9282                 case "top": return val(args[0]);
9283                 case "right": return val(args[1]);
9284                 case "bottom": return val(args[2]);
9285                 case "left": return val(args[3]);
9286             }
9287         }
9288         return undefined;
9289     },
9290
9291     /*
9292        Returns label height or with, depending on the tree current orientation.
9293     */  
9294     getSize: function(n, invert) {
9295         var data = n.data, config = this.config;
9296         var siblingOffset = config.siblingOffset;
9297         var s = (config.multitree 
9298                         && ('$orn' in data) 
9299                         && data.$orn) || config.orientation;
9300         var w = n.getData('width') + siblingOffset;
9301         var h = n.getData('height') + siblingOffset;
9302         if(!invert)
9303             return this.dispatch(s, h, w);
9304         else
9305             return this.dispatch(s, w, h);
9306     },
9307     
9308     /*
9309        Calculates a subtree base size. This is an utility function used by _getBaseSize_
9310     */  
9311     getTreeBaseSize: function(node, level, leaf) {
9312         var size = this.getSize(node, true), baseHeight = 0, that = this;
9313         if(leaf(level, node)) return size;
9314         if(level === 0) return 0;
9315         node.eachSubnode(function(elem) {
9316             baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9317         });
9318         return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9319     },
9320
9321
9322     /*
9323        getEdge
9324        
9325        Returns a Complex instance with the begin or end position of the edge to be plotted.
9326
9327        Parameters:
9328
9329        node - A <Graph.Node> that is connected to this edge.
9330        type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9331
9332        Returns:
9333
9334        A <Complex> number specifying the begin or end position.
9335     */  
9336     getEdge: function(node, type, s) {
9337         var $C = function(a, b) { 
9338           return function(){
9339             return node.pos.add(new Complex(a, b));
9340           }; 
9341         };
9342         var dim = this.node;
9343         var w = node.getData('width');
9344         var h = node.getData('height');
9345
9346         if(type == 'begin') {
9347             if(dim.align == "center") {
9348                 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9349                                      $C(0, -h/2),$C(w/2, 0));
9350             } else if(dim.align == "left") {
9351                 return this.dispatch(s, $C(0, h), $C(0, 0),
9352                                      $C(0, 0), $C(w, 0));
9353             } else if(dim.align == "right") {
9354                 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9355                                      $C(0, -h),$C(0, 0));
9356             } else throw "align: not implemented";
9357             
9358             
9359         } else if(type == 'end') {
9360             if(dim.align == "center") {
9361                 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9362                                      $C(0, h/2),  $C(-w/2, 0));
9363             } else if(dim.align == "left") {
9364                 return this.dispatch(s, $C(0, 0), $C(w, 0),
9365                                      $C(0, h), $C(0, 0));
9366             } else if(dim.align == "right") {
9367                 return this.dispatch(s, $C(0, -h),$C(0, 0),
9368                                      $C(0, 0), $C(-w, 0));
9369             } else throw "align: not implemented";
9370         }
9371     },
9372
9373     /*
9374        Adjusts the tree position due to canvas scaling or translation.
9375     */  
9376     getScaledTreePosition: function(node, scale) {
9377         var dim = this.node;
9378         var w = node.getData('width');
9379         var h = node.getData('height');
9380         var s = (this.config.multitree 
9381                         && ('$orn' in node.data) 
9382                         && node.data.$orn) || this.config.orientation;
9383
9384         var $C = function(a, b) { 
9385           return function(){
9386             return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9387           }; 
9388         };
9389         if(dim.align == "left") {
9390             return this.dispatch(s, $C(0, h), $C(0, 0),
9391                                  $C(0, 0), $C(w, 0));
9392         } else if(dim.align == "center") {
9393             return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9394                                  $C(0, -h / 2),$C(w / 2, 0));
9395         } else if(dim.align == "right") {
9396             return this.dispatch(s, $C(0, 0), $C(-w, 0),
9397                                  $C(0, -h),$C(0, 0));
9398         } else throw "align: not implemented";
9399     },
9400
9401     /*
9402        treeFitsInCanvas
9403        
9404        Returns a Boolean if the current subtree fits in canvas.
9405
9406        Parameters:
9407
9408        node - A <Graph.Node> which is the current root of the subtree.
9409        canvas - The <Canvas> object.
9410        level - The depth of the subtree to be considered.
9411     */  
9412     treeFitsInCanvas: function(node, canvas, level) {
9413         var csize = canvas.getSize();
9414         var s = (this.config.multitree 
9415                         && ('$orn' in node.data) 
9416                         && node.data.$orn) || this.config.orientation;
9417
9418         var size = this.dispatch(s, csize.width, csize.height);
9419         var baseSize = this.getTreeBaseSize(node, level, function(level, node) { 
9420           return level === 0 || !node.anySubnode();
9421         });
9422         return (baseSize < size);
9423     }
9424 });
9425
9426 /*
9427   Class: ST.Plot
9428   
9429   Custom extension of <Graph.Plot>.
9430
9431   Extends:
9432
9433   All <Graph.Plot> methods
9434   
9435   See also:
9436   
9437   <Graph.Plot>
9438
9439 */
9440 $jit.ST.Plot = new Class({
9441     
9442     Implements: Graph.Plot,
9443     
9444     /*
9445        Plots a subtree from the spacetree.
9446     */
9447     plotSubtree: function(node, opt, scale, animating) {
9448         var viz = this.viz, canvas = viz.canvas, config = viz.config;
9449         scale = Math.min(Math.max(0.001, scale), 1);
9450         if(scale >= 0) {
9451             node.drawn = false;     
9452             var ctx = canvas.getCtx();
9453             var diff = viz.geom.getScaledTreePosition(node, scale);
9454             ctx.translate(diff.x, diff.y);
9455             ctx.scale(scale, scale);
9456         }
9457         this.plotTree(node, $.merge(opt, {
9458           'withLabels': true,
9459           'hideLabels': !!scale,
9460           'plotSubtree': function(n, ch) {
9461             var root = config.multitree && !('$orn' in node.data);
9462             var orns = root && node.getData('orns');
9463             return !root || orns.indexOf(elem.getData('orn')) > -1;
9464           }
9465         }), animating);
9466         if(scale >= 0) node.drawn = true;
9467     },   
9468    
9469     /*
9470         Method: getAlignedPos
9471         
9472         Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9473         
9474         Parameters:
9475         
9476         pos - (object) A <Graph.Node> position.
9477         width - (number) The width of the node.
9478         height - (number) The height of the node.
9479         
9480      */
9481     getAlignedPos: function(pos, width, height) {
9482         var nconfig = this.node;
9483         var square, orn;
9484         if(nconfig.align == "center") {
9485             square = {
9486                 x: pos.x - width / 2,
9487                 y: pos.y - height / 2
9488             };
9489         } else if (nconfig.align == "left") {
9490             orn = this.config.orientation;
9491             if(orn == "bottom" || orn == "top") {
9492                 square = {
9493                     x: pos.x - width / 2,
9494                     y: pos.y
9495                 };
9496             } else {
9497                 square = {
9498                     x: pos.x,
9499                     y: pos.y - height / 2
9500                 };
9501             }
9502         } else if(nconfig.align == "right") {
9503             orn = this.config.orientation;
9504             if(orn == "bottom" || orn == "top") {
9505                 square = {
9506                     x: pos.x - width / 2,
9507                     y: pos.y - height
9508                 };
9509             } else {
9510                 square = {
9511                     x: pos.x - width,
9512                     y: pos.y - height / 2
9513                 };
9514             }
9515         } else throw "align: not implemented";
9516         
9517         return square;
9518     },
9519     
9520     getOrientation: function(adj) {
9521         var config = this.config;
9522         var orn = config.orientation;
9523
9524         if(config.multitree) {
9525                 var nodeFrom = adj.nodeFrom;
9526                 var nodeTo = adj.nodeTo;
9527                 orn = (('$orn' in nodeFrom.data) 
9528                         && nodeFrom.data.$orn) 
9529                         || (('$orn' in nodeTo.data) 
9530                         && nodeTo.data.$orn);
9531         }
9532
9533         return orn; 
9534     }
9535 });
9536
9537 /*
9538   Class: ST.Label
9539
9540   Custom extension of <Graph.Label>. 
9541   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9542
9543   Extends:
9544
9545   All <Graph.Label> methods and subclasses.
9546
9547   See also:
9548
9549   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9550  */ 
9551 $jit.ST.Label = {};
9552
9553 /*
9554    ST.Label.Native
9555
9556    Custom extension of <Graph.Label.Native>.
9557
9558    Extends:
9559
9560    All <Graph.Label.Native> methods
9561
9562    See also:
9563
9564    <Graph.Label.Native>
9565 */
9566 $jit.ST.Label.Native = new Class({
9567   Implements: Graph.Label.Native,
9568
9569   renderLabel: function(canvas, node, controller) {
9570     var ctx = canvas.getCtx();
9571     var coord = node.pos.getc(true);
9572     ctx.fillText(node.name, coord.x, coord.y);
9573   }
9574 });
9575
9576 $jit.ST.Label.DOM = new Class({
9577   Implements: Graph.Label.DOM,
9578
9579   /* 
9580       placeLabel
9581
9582       Overrides abstract method placeLabel in <Graph.Plot>.
9583
9584       Parameters:
9585
9586       tag - A DOM label element.
9587       node - A <Graph.Node>.
9588       controller - A configuration/controller object passed to the visualization.
9589      
9590     */
9591     placeLabel: function(tag, node, controller) {
9592         var pos = node.pos.getc(true), 
9593             config = this.viz.config, 
9594             dim = config.Node, 
9595             canvas = this.viz.canvas,
9596             w = node.getData('width'),
9597             h = node.getData('height'),
9598             radius = canvas.getSize(),
9599             labelPos, orn;
9600         
9601         var ox = canvas.translateOffsetX,
9602             oy = canvas.translateOffsetY,
9603             sx = canvas.scaleOffsetX,
9604             sy = canvas.scaleOffsetY,
9605             posx = pos.x * sx + ox,
9606             posy = pos.y * sy + oy;
9607
9608         if(dim.align == "center") {
9609             labelPos= {
9610                 x: Math.round(posx - w / 2 + radius.width/2),
9611                 y: Math.round(posy - h / 2 + radius.height/2)
9612             };
9613         } else if (dim.align == "left") {
9614             orn = config.orientation;
9615             if(orn == "bottom" || orn == "top") {
9616                 labelPos= {
9617                     x: Math.round(posx - w / 2 + radius.width/2),
9618                     y: Math.round(posy + radius.height/2)
9619                 };
9620             } else {
9621                 labelPos= {
9622                     x: Math.round(posx + radius.width/2),
9623                     y: Math.round(posy - h / 2 + radius.height/2)
9624                 };
9625             }
9626         } else if(dim.align == "right") {
9627             orn = config.orientation;
9628             if(orn == "bottom" || orn == "top") {
9629                 labelPos= {
9630                     x: Math.round(posx - w / 2 + radius.width/2),
9631                     y: Math.round(posy - h + radius.height/2)
9632                 };
9633             } else {
9634                 labelPos= {
9635                     x: Math.round(posx - w + radius.width/2),
9636                     y: Math.round(posy - h / 2 + radius.height/2)
9637                 };
9638             }
9639         } else throw "align: not implemented";
9640
9641         var style = tag.style;
9642         style.left = labelPos.x + 'px';
9643         style.top  = labelPos.y + 'px';
9644         style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9645         controller.onPlaceLabel(tag, node);
9646     }
9647 });
9648
9649 /*
9650   ST.Label.SVG
9651
9652   Custom extension of <Graph.Label.SVG>.
9653
9654   Extends:
9655
9656   All <Graph.Label.SVG> methods
9657
9658   See also:
9659
9660   <Graph.Label.SVG>
9661 */
9662 $jit.ST.Label.SVG = new Class({
9663   Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9664
9665   initialize: function(viz) {
9666     this.viz = viz;
9667   }
9668 });
9669
9670 /*
9671    ST.Label.HTML
9672
9673    Custom extension of <Graph.Label.HTML>.
9674
9675    Extends:
9676
9677    All <Graph.Label.HTML> methods.
9678
9679    See also:
9680
9681    <Graph.Label.HTML>
9682
9683 */
9684 $jit.ST.Label.HTML = new Class({
9685   Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9686
9687   initialize: function(viz) {
9688     this.viz = viz;
9689   }
9690 });
9691
9692
9693 /*
9694   Class: ST.Plot.NodeTypes
9695
9696   This class contains a list of <Graph.Node> built-in types. 
9697   Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9698
9699   You can add your custom node types, customizing your visualization to the extreme.
9700
9701   Example:
9702
9703   (start code js)
9704     ST.Plot.NodeTypes.implement({
9705       'mySpecialType': {
9706         'render': function(node, canvas) {
9707           //print your custom node to canvas
9708         },
9709         //optional
9710         'contains': function(node, pos) {
9711           //return true if pos is inside the node or false otherwise
9712         }
9713       }
9714     });
9715   (end code)
9716
9717 */
9718 $jit.ST.Plot.NodeTypes = new Class({
9719   'none': {
9720     'render': $.empty,
9721     'contains': $.lambda(false)
9722   },
9723   'circle': {
9724     'render': function(node, canvas) {
9725       var dim  = node.getData('dim'),
9726           pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9727           dim2 = dim/2;
9728       this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9729     },
9730     'contains': function(node, pos) {
9731       var dim  = node.getData('dim'),
9732           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9733           dim2 = dim/2;
9734       this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9735     }
9736   },
9737   'square': {
9738     'render': function(node, canvas) {
9739       var dim  = node.getData('dim'),
9740           dim2 = dim/2,
9741           pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9742       this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9743     },
9744     'contains': function(node, pos) {
9745       var dim  = node.getData('dim'),
9746           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9747           dim2 = dim/2;
9748       this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9749     }
9750   },
9751   'ellipse': {
9752     'render': function(node, canvas) {
9753       var width = node.getData('width'),
9754           height = node.getData('height'),
9755           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9756       this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9757     },
9758     'contains': function(node, pos) {
9759       var width = node.getData('width'),
9760           height = node.getData('height'),
9761           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9762       this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9763     }
9764   },
9765   'rectangle': {
9766     'render': function(node, canvas) {
9767       var width = node.getData('width'),
9768           height = node.getData('height'),
9769           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9770       this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9771     },
9772     'contains': function(node, pos) {
9773       var width = node.getData('width'),
9774           height = node.getData('height'),
9775           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9776       this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9777     }
9778   }
9779 });
9780
9781 /*
9782   Class: ST.Plot.EdgeTypes
9783
9784   This class contains a list of <Graph.Adjacence> built-in types. 
9785   Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9786
9787   You can add your custom edge types, customizing your visualization to the extreme.
9788
9789   Example:
9790
9791   (start code js)
9792     ST.Plot.EdgeTypes.implement({
9793       'mySpecialType': {
9794         'render': function(adj, canvas) {
9795           //print your custom edge to canvas
9796         },
9797         //optional
9798         'contains': function(adj, pos) {
9799           //return true if pos is inside the arc or false otherwise
9800         }
9801       }
9802     });
9803   (end code)
9804
9805 */
9806 $jit.ST.Plot.EdgeTypes = new Class({
9807     'none': $.empty,
9808     'line': {
9809       'render': function(adj, canvas) {
9810         var orn = this.getOrientation(adj),
9811             nodeFrom = adj.nodeFrom, 
9812             nodeTo = adj.nodeTo,
9813             rel = nodeFrom._depth < nodeTo._depth,
9814             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9815             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9816         this.edgeHelper.line.render(from, to, canvas);
9817       },
9818       'contains': function(adj, pos) {
9819         var orn = this.getOrientation(adj),
9820             nodeFrom = adj.nodeFrom, 
9821             nodeTo = adj.nodeTo,
9822             rel = nodeFrom._depth < nodeTo._depth,
9823             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9824             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9825         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9826       }
9827     },
9828      'arrow': {
9829        'render': function(adj, canvas) {
9830          var orn = this.getOrientation(adj),
9831              node = adj.nodeFrom, 
9832              child = adj.nodeTo,
9833              dim = adj.getData('dim'),
9834              from = this.viz.geom.getEdge(node, 'begin', orn),
9835              to = this.viz.geom.getEdge(child, 'end', orn),
9836              direction = adj.data.$direction,
9837              inv = (direction && direction.length>1 && direction[0] != node.id);
9838          this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9839        },
9840        'contains': function(adj, pos) {
9841          var orn = this.getOrientation(adj),
9842              nodeFrom = adj.nodeFrom, 
9843              nodeTo = adj.nodeTo,
9844              rel = nodeFrom._depth < nodeTo._depth,
9845              from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9846              to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9847          return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9848        }
9849      },
9850     'quadratic:begin': {
9851        'render': function(adj, canvas) {
9852           var orn = this.getOrientation(adj);
9853           var nodeFrom = adj.nodeFrom, 
9854               nodeTo = adj.nodeTo,
9855               rel = nodeFrom._depth < nodeTo._depth,
9856               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9857               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9858               dim = adj.getData('dim'),
9859               ctx = canvas.getCtx();
9860           ctx.beginPath();
9861           ctx.moveTo(begin.x, begin.y);
9862           switch(orn) {
9863             case "left":
9864               ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9865               break;
9866             case "right":
9867               ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9868               break;
9869             case "top":
9870               ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9871               break;
9872             case "bottom":
9873               ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9874               break;
9875           }
9876           ctx.stroke();
9877         }
9878      },
9879     'quadratic:end': {
9880        'render': function(adj, canvas) {
9881           var orn = this.getOrientation(adj);
9882           var nodeFrom = adj.nodeFrom, 
9883               nodeTo = adj.nodeTo,
9884               rel = nodeFrom._depth < nodeTo._depth,
9885               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9886               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9887               dim = adj.getData('dim'),
9888               ctx = canvas.getCtx();
9889           ctx.beginPath();
9890           ctx.moveTo(begin.x, begin.y);
9891           switch(orn) {
9892             case "left":
9893               ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9894               break;
9895             case "right":
9896               ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9897               break;
9898             case "top":
9899               ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9900               break;
9901             case "bottom":
9902               ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9903               break;
9904           }
9905           ctx.stroke();
9906        }
9907      },
9908     'bezier': {
9909        'render': function(adj, canvas) {
9910          var orn = this.getOrientation(adj),
9911              nodeFrom = adj.nodeFrom, 
9912              nodeTo = adj.nodeTo,
9913              rel = nodeFrom._depth < nodeTo._depth,
9914              begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9915              end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9916              dim = adj.getData('dim'),
9917              ctx = canvas.getCtx();
9918          ctx.beginPath();
9919          ctx.moveTo(begin.x, begin.y);
9920          switch(orn) {
9921            case "left":
9922              ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9923              break;
9924            case "right":
9925              ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9926              break;
9927            case "top":
9928              ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9929              break;
9930            case "bottom":
9931              ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9932              break;
9933          }
9934          ctx.stroke();
9935        }
9936     }
9937 });
9938
9939
9940 Options.LineChart = {
9941   $extend: true,
9942
9943   animate: false,
9944   labelOffset: 3, // label offset
9945   type: 'basic', // gradient
9946   dataPointSize: 10,
9947   Tips: {
9948     enable: false,
9949     onShow: $.empty,
9950     onHide: $.empty
9951   },
9952   Ticks: {
9953         enable: false,
9954         segments: 4,
9955         color: '#000000'
9956   },
9957   Events: {
9958     enable: false,
9959     onClick: $.empty
9960   },
9961   selectOnHover: true,
9962   showAggregates: true,
9963   showLabels: true,
9964   filterOnClick: false,
9965   restoreOnRightClick: false
9966 };
9967
9968
9969 /*
9970  * File: LineChart.js
9971  *
9972 */
9973
9974 $jit.ST.Plot.NodeTypes.implement({
9975   'linechart-basic' : {
9976     'render' : function(node, canvas) {
9977       var pos = node.pos.getc(true), 
9978           width = node.getData('width'),
9979           height = node.getData('height'),
9980           algnPos = this.getAlignedPos(pos, width, height),
9981           x = algnPos.x + width/2 , y = algnPos.y,
9982           stringArray = node.getData('stringArray'),
9983           lastNode = node.getData('lastNode'),
9984           dimArray = node.getData('dimArray'),
9985           valArray = node.getData('valueArray'),
9986           colorArray = node.getData('colorArray'),
9987           colorLength = colorArray.length,
9988           config = node.getData('config'),
9989           gradient = node.getData('gradient'),
9990           showLabels = config.showLabels,
9991           aggregates = config.showAggregates,
9992           label = config.Label,
9993           prev = node.getData('prev'),
9994           dataPointSize = config.dataPointSize;
9995
9996       var ctx = canvas.getCtx(), border = node.getData('border');
9997       if (colorArray && dimArray && stringArray) {
9998         
9999                for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10000                 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10001                         ctx.lineWidth = 4;
10002                         ctx.lineCap = "round";
10003                   if(!lastNode) {
10004
10005                           ctx.save();
10006                                   //render line segment, dimarray[i][0] is the curent datapoint, dimarrya[i][1] is the next datapoint, we need both in the current iteration to draw the line segment
10007                           ctx.beginPath();
10008                           ctx.moveTo(x, y  - dimArray[i][0]); 
10009                           ctx.lineTo(x + width, y - dimArray[i][1]);
10010                           ctx.stroke();
10011                           ctx.restore();
10012                   }
10013                   //render data point
10014                   ctx.fillRect(x - (dataPointSize/2), y  - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10015                 }
10016         
10017
10018           if(label.type == 'Native' && showLabels) {
10019           //bottom labels
10020           ctx.fillStyle = ctx.strokeStyle = label.color;
10021           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10022           ctx.textAlign = 'center';
10023           ctx.textBaseline = 'middle';
10024           ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10025           }
10026           
10027
10028       }
10029     },
10030     'contains': function(node, mpos) {
10031       var pos = node.pos.getc(true), 
10032           width = node.getData('width'),
10033           height = node.getData('height'),
10034           config = node.getData('config'),
10035           dataPointSize = config.dataPointSize,
10036           dataPointMidPoint = dataPointSize/2,
10037           algnPos = this.getAlignedPos(pos, width, height),
10038           x = algnPos.x + width/2, y = algnPos.y,
10039           dimArray = node.getData('dimArray');
10040       //bounding box check
10041       if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10042         return false;
10043       }
10044       //deep check
10045       for(var i=0, l=dimArray.length; i<l; i++) {
10046         var dimi = dimArray[i];
10047                 var url = Url.decode(node.getData('linkArray')[i]);
10048           if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10049                 var valArrayCur = node.getData('valArrayCur');
10050           var results = array_match(valArrayCur[i],valArrayCur);
10051           var matches = results[0];
10052           var indexValues = results[1];
10053           if(matches > 1) {
10054                         var names = new Array(),
10055                                 values = new Array(),
10056                                 percentages = new Array(),
10057                                 linksArr = new Array();
10058                                 for(var j=0, il=indexValues.length; j<il; j++) {
10059                                         names[j] = node.getData('stringArray')[indexValues[j]];
10060                                         values[j] = valArrayCur[indexValues[j]];
10061                                         percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10062                                         linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10063                                         
10064                                 }       
10065                         return {
10066                             'name': names,
10067                             'color': node.getData('colorArray')[i],
10068                             'value': values,
10069                             'percentage': percentages,
10070                             'link': false,
10071                             'collision': true
10072                         };
10073                 }
10074           else {
10075                   return {
10076                     'name': node.getData('stringArray')[i],
10077                     'color': node.getData('colorArray')[i],
10078                     'value': node.getData('valueArray')[i][0],
10079         //            'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10080                     'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10081                     'link': url,
10082                     'collision': false
10083                   };
10084           }
10085         }
10086       }
10087       return false;
10088     }
10089   }
10090 });
10091
10092 /*
10093   Class: Line
10094   
10095   A visualization that displays line charts.
10096   
10097   Constructor Options:
10098   
10099   See <Options.Line>.
10100
10101 */
10102 $jit.LineChart = new Class({
10103   st: null,
10104   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10105   selected: {},
10106   busy: false,
10107   
10108   initialize: function(opt) {
10109     this.controller = this.config = 
10110       $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10111         Label: { type: 'Native' }
10112       }, opt);
10113     //set functions for showLabels and showAggregates
10114     var showLabels = this.config.showLabels,
10115         typeLabels = $.type(showLabels),
10116         showAggregates = this.config.showAggregates,
10117         typeAggregates = $.type(showAggregates);
10118     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10119     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10120     Options.Fx.clearCanvas = false;
10121     this.initializeViz();
10122   },
10123   
10124   initializeViz: function() {
10125     var config = this.config,
10126         that = this,
10127         nodeType = config.type.split(":")[0],
10128         nodeLabels = {};
10129
10130     var st = new $jit.ST({
10131       injectInto: config.injectInto,
10132       orientation: "bottom",
10133       backgroundColor: config.backgroundColor,
10134       renderBackground: config.renderBackground,
10135       levelDistance: 0,
10136       siblingOffset: 0,
10137       subtreeOffset: 0,
10138       withLabels: config.Label.type != 'Native',
10139       useCanvas: config.useCanvas,
10140       Label: {
10141         type: config.Label.type
10142       },
10143       Node: {
10144         overridable: true,
10145         type: 'linechart-' + nodeType,
10146         align: 'left',
10147         width: 1,
10148         height: 1
10149       },
10150       Edge: {
10151         type: 'none'
10152       },
10153       Tips: {
10154         enable: config.Tips.enable,
10155         type: 'Native',
10156         force: true,
10157         onShow: function(tip, node, contains) {
10158           var elem = contains;
10159           config.Tips.onShow(tip, elem, node);
10160         }
10161       },
10162       Events: {
10163         enable: true,
10164         type: 'Native',
10165         onClick: function(node, eventInfo, evt) {
10166           if(!config.filterOnClick && !config.Events.enable) return;
10167           var elem = eventInfo.getContains();
10168           if(elem) config.filterOnClick && that.filter(elem.name);
10169           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10170         },
10171         onRightClick: function(node, eventInfo, evt) {
10172           if(!config.restoreOnRightClick) return;
10173           that.restore();
10174         },
10175         onMouseMove: function(node, eventInfo, evt) {
10176           if(!config.selectOnHover) return;
10177           if(node) {
10178             var elem = eventInfo.getContains();
10179             that.select(node.id, elem.name, elem.index);
10180           } else {
10181             that.select(false, false, false);
10182           }
10183         }
10184       },
10185       onCreateLabel: function(domElement, node) {
10186         var labelConf = config.Label,
10187             valueArray = node.getData('valueArray'),
10188             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10189             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10190         if(node.getData('prev')) {
10191           var nlbs = {
10192             wrapper: document.createElement('div'),
10193             aggregate: document.createElement('div'),
10194             label: document.createElement('div')
10195           };
10196           var wrapper = nlbs.wrapper,
10197               label = nlbs.label,
10198               aggregate = nlbs.aggregate,
10199               wrapperStyle = wrapper.style,
10200               labelStyle = label.style,
10201               aggregateStyle = aggregate.style;
10202           //store node labels
10203           nodeLabels[node.id] = nlbs;
10204           //append labels
10205           wrapper.appendChild(label);
10206           wrapper.appendChild(aggregate);
10207           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10208             label.style.display = 'none';
10209           }
10210           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10211             aggregate.style.display = 'none';
10212           }
10213           wrapperStyle.position = 'relative';
10214           wrapperStyle.overflow = 'visible';
10215           wrapperStyle.fontSize = labelConf.size + 'px';
10216           wrapperStyle.fontFamily = labelConf.family;
10217           wrapperStyle.color = labelConf.color;
10218           wrapperStyle.textAlign = 'center';
10219           aggregateStyle.position = labelStyle.position = 'absolute';
10220           
10221           domElement.style.width = node.getData('width') + 'px';
10222           domElement.style.height = node.getData('height') + 'px';
10223           label.innerHTML = node.name;
10224           
10225           domElement.appendChild(wrapper);
10226         }
10227       },
10228       onPlaceLabel: function(domElement, node) {
10229         if(!node.getData('prev')) return;
10230         var labels = nodeLabels[node.id],
10231             wrapperStyle = labels.wrapper.style,
10232             labelStyle = labels.label.style,
10233             aggregateStyle = labels.aggregate.style,
10234             width = node.getData('width'),
10235             height = node.getData('height'),
10236             dimArray = node.getData('dimArray'),
10237             valArray = node.getData('valueArray'),
10238             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10239             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10240             font = parseInt(wrapperStyle.fontSize, 10),
10241             domStyle = domElement.style;
10242         
10243         if(dimArray && valArray) {
10244           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10245             labelStyle.display = '';
10246           } else {
10247             labelStyle.display = 'none';
10248           }
10249           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10250             aggregateStyle.display = '';
10251           } else {
10252             aggregateStyle.display = 'none';
10253           }
10254           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10255           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10256           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10257             if(dimArray[i][0] > 0) {
10258               acum+= valArray[i][0];
10259               leftAcum+= dimArray[i][0];
10260             }
10261           }
10262           aggregateStyle.top = (-font - config.labelOffset) + 'px';
10263           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10264           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10265           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10266           labels.aggregate.innerHTML = acum;
10267         }
10268       }
10269     });
10270     
10271     var size = st.canvas.getSize(),
10272         margin = config.Margin;
10273     st.config.offsetY = -size.height/2 + margin.bottom 
10274       + (config.showLabels && (config.labelOffset + config.Label.size));
10275     st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10276     this.st = st;
10277     this.canvas = this.st.canvas;
10278   },
10279   
10280     renderTitle: function() {
10281         var canvas = this.canvas,
10282         size = canvas.getSize(),
10283         config = this.config,
10284         margin = config.Margin,
10285         label = config.Label,
10286         title = config.Title;
10287         ctx = canvas.getCtx();
10288         ctx.fillStyle = title.color;
10289         ctx.textAlign = 'left';
10290         ctx.textBaseline = 'top';
10291         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10292         if(label.type == 'Native') {
10293                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10294         }
10295   },  
10296   
10297     renderTicks: function() {
10298
10299         var canvas = this.canvas,
10300         size = canvas.getSize(),
10301         config = this.config,
10302         margin = config.Margin,
10303         ticks = config.Ticks,
10304         title = config.Title,
10305         subtitle = config.Subtitle,
10306         label = config.Label,
10307         maxValue = this.maxValue,
10308         maxTickValue = Math.ceil(maxValue*.1)*10;
10309         if(maxTickValue == maxValue) {
10310                 var length = maxTickValue.toString().length;
10311                 maxTickValue = maxTickValue + parseInt(pad(1,length));
10312         }
10313
10314
10315         labelValue = 0,
10316         labelIncrement = maxTickValue/ticks.segments,
10317         ctx = canvas.getCtx();
10318         ctx.strokeStyle = ticks.color;
10319     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10320         ctx.textAlign = 'center';
10321         ctx.textBaseline = 'middle';
10322         
10323         idLabel = canvas.id + "-label";
10324         labelDim = 100;
10325         container = document.getElementById(idLabel);
10326                   
10327                   
10328                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10329                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10330                 grid = -size.height+(margin.bottom+config.labelOffset+label.size+margin.top+(title.text? title.size+title.offset:0)+(subtitle.text? subtitle.size+subtitle.offset:0)),
10331                 segmentLength = grid/ticks.segments;
10332                 ctx.fillStyle = ticks.color;
10333                 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size-1, -(size.height/2)+margin.top+(title.text? title.size+title.offset:0),1,size.height-margin.top-margin.bottom-label.size-config.labelOffset-(title.text? title.size+title.offset:0)-(subtitle.text? subtitle.size+subtitle.offset:0));
10334
10335                 while(axis>=grid) {
10336                         ctx.save();
10337                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10338                         ctx.rotate(Math.PI / 2);
10339                         ctx.fillStyle = label.color;
10340                         if(config.showLabels) {
10341                                 if(label.type == 'Native') { 
10342                                         ctx.fillText(labelValue, 0, 0);
10343                                 } else {
10344                                         //html labels on y axis
10345                                         labelDiv = document.createElement('div');
10346                                         labelDiv.innerHTML = labelValue;
10347                                         labelDiv.className = "rotatedLabel";
10348 //                                      labelDiv.class = "rotatedLabel";
10349                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10350                                         labelDiv.style.left = margin.left + "px";
10351                                         labelDiv.style.width = labelDim + "px";
10352                                         labelDiv.style.height = labelDim + "px";
10353                                         labelDiv.style.textAlign = "center";
10354                                         labelDiv.style.verticalAlign = "middle";
10355                                         labelDiv.style.position = "absolute";
10356                                         container.appendChild(labelDiv);
10357                                 }
10358                         }
10359                         ctx.restore();
10360                         ctx.fillStyle = ticks.color;
10361                         ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size, Math.round(axis), size.width-margin.right-margin.left-config.labelOffset-label.size,1 );
10362                         htmlOrigin += segmentLength;
10363                         axis += segmentLength;
10364                         labelValue += labelIncrement;
10365                 }
10366         
10367
10368         
10369         
10370         
10371
10372   },
10373   
10374   renderBackground: function() {
10375                 var canvas = this.canvas,
10376                 config = this.config,
10377                 backgroundColor = config.backgroundColor,
10378                 size = canvas.getSize(),
10379                 ctx = canvas.getCtx();
10380                 //ctx.globalCompositeOperation = "destination-over";
10381             ctx.fillStyle = backgroundColor;
10382             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
10383   },
10384   
10385   
10386  /*
10387   Method: loadJSON
10388  
10389   Loads JSON data into the visualization. 
10390   
10391   Parameters:
10392   
10393   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>.
10394   
10395   Example:
10396   (start code js)
10397   var areaChart = new $jit.AreaChart(options);
10398   areaChart.loadJSON(json);
10399   (end code)
10400  */  
10401   loadJSON: function(json) {
10402     var prefix = $.time(), 
10403         ch = [], 
10404         st = this.st,
10405         name = $.splat(json.label), 
10406         color = $.splat(json.color || this.colors),
10407         config = this.config,
10408         ticks = config.Ticks,
10409         renderBackground = config.renderBackground,
10410         gradient = !!config.type.split(":")[1],
10411         animate = config.animate,
10412         title = config.Title,
10413         groupTotalValue = 0;
10414     
10415     var valArrayAll = new Array();
10416
10417     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10418         var val = values[i];
10419         var valArray = $.splat(val.values);
10420         for (var j=0, len=valArray.length; j<len; j++) {
10421                 valArrayAll.push(parseInt(valArray[j]));
10422         }
10423         groupTotalValue += parseInt(valArray.sum());
10424     }
10425     
10426     this.maxValue =  Math.max.apply(null, valArrayAll);
10427     
10428     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10429       var val = values[i], prev = values[i-1];
10430
10431       var next = (i+1 < l) ? values[i+1] : 0;
10432       var valLeft = $.splat(values[i].values);
10433       var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10434       var valArray = $.zip(valLeft, valRight);
10435       var valArrayCur = $.splat(values[i].values);
10436       var linkArray = $.splat(values[i].links);
10437       var acumLeft = 0, acumRight = 0;
10438       var lastNode = (l-1 == i) ? true : false; 
10439       ch.push({
10440         'id': prefix + val.label,
10441         'name': val.label,
10442         'data': {
10443           'value': valArray,
10444           '$valueArray': valArray,
10445           '$valArrayCur': valArrayCur,
10446           '$colorArray': color,
10447           '$linkArray': linkArray,
10448           '$stringArray': name,
10449           '$next': next? next.label:false,
10450           '$prev': prev? prev.label:false,
10451           '$config': config,
10452           '$lastNode': lastNode,
10453           '$groupTotalValue': groupTotalValue,
10454           '$gradient': gradient
10455         },
10456         'children': []
10457       });
10458     }
10459     var root = {
10460       'id': prefix + '$root',
10461       'name': '',
10462       'data': {
10463         '$type': 'none',
10464         '$width': 1,
10465         '$height': 1
10466       },
10467       'children': ch
10468     };
10469     st.loadJSON(root);
10470     
10471     this.normalizeDims();
10472     
10473     if(renderBackground) {
10474         this.renderBackground();        
10475     }
10476     
10477     if(!animate && ticks.enable) {
10478                 this.renderTicks();
10479         }
10480         
10481         
10482         if(title.text) {
10483                 this.renderTitle();     
10484         }
10485         
10486     st.compute();
10487     st.select(st.root);
10488     if(animate) {
10489       st.fx.animate({
10490         modes: ['node-property:height:dimArray'],
10491         duration:1500
10492       });
10493     }
10494   },
10495   
10496  /*
10497   Method: updateJSON
10498  
10499   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.
10500   
10501   Parameters:
10502   
10503   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10504   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10505   
10506   Example:
10507   
10508   (start code js)
10509   areaChart.updateJSON(json, {
10510     onComplete: function() {
10511       alert('update complete!');
10512     }
10513   });
10514   (end code)
10515  */  
10516   updateJSON: function(json, onComplete) {
10517     if(this.busy) return;
10518     this.busy = true;
10519     
10520     var st = this.st,
10521         graph = st.graph,
10522         labels = json.label && $.splat(json.label),
10523         values = json.values,
10524         animate = this.config.animate,
10525         that = this;
10526     $.each(values, function(v) {
10527       var n = graph.getByName(v.label);
10528       if(n) {
10529         v.values = $.splat(v.values);
10530         var stringArray = n.getData('stringArray'),
10531             valArray = n.getData('valueArray');
10532         $.each(valArray, function(a, i) {
10533           a[0] = v.values[i];
10534           if(labels) stringArray[i] = labels[i];
10535         });
10536         n.setData('valueArray', valArray);
10537         var prev = n.getData('prev'),
10538             next = n.getData('next'),
10539             nextNode = graph.getByName(next);
10540         if(prev) {
10541           var p = graph.getByName(prev);
10542           if(p) {
10543             var valArray = p.getData('valueArray');
10544             $.each(valArray, function(a, i) {
10545               a[1] = v.values[i];
10546             });
10547           }
10548         }
10549         if(!nextNode) {
10550           var valArray = n.getData('valueArray');
10551           $.each(valArray, function(a, i) {
10552             a[1] = v.values[i];
10553           });
10554         }
10555       }
10556     });
10557     this.normalizeDims();
10558     st.compute();
10559     
10560     st.select(st.root);
10561     if(animate) {
10562       st.fx.animate({
10563         modes: ['node-property:height:dimArray'],
10564         duration:1500,
10565         onComplete: function() {
10566           that.busy = false;
10567           onComplete && onComplete.onComplete();
10568         }
10569       });
10570     }
10571   },
10572   
10573 /*
10574   Method: filter
10575  
10576   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10577   
10578   Parameters:
10579   
10580   Variable strings arguments with the name of the stacks.
10581   
10582   Example:
10583   
10584   (start code js)
10585   areaChart.filter('label A', 'label C');
10586   (end code)
10587   
10588   See also:
10589   
10590   <AreaChart.restore>.
10591  */  
10592   filter: function() {
10593     if(this.busy) return;
10594     this.busy = true;
10595     if(this.config.Tips.enable) this.st.tips.hide();
10596     this.select(false, false, false);
10597     var args = Array.prototype.slice.call(arguments);
10598     var rt = this.st.graph.getNode(this.st.root);
10599     var that = this;
10600     rt.eachAdjacency(function(adj) {
10601       var n = adj.nodeTo, 
10602           dimArray = n.getData('dimArray'),
10603           stringArray = n.getData('stringArray');
10604       n.setData('dimArray', $.map(dimArray, function(d, i) {
10605         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10606       }), 'end');
10607     });
10608     this.st.fx.animate({
10609       modes: ['node-property:dimArray'],
10610       duration:1500,
10611       onComplete: function() {
10612         that.busy = false;
10613       }
10614     });
10615   },
10616   
10617   /*
10618   Method: restore
10619  
10620   Sets all stacks that could have been filtered visible.
10621   
10622   Example:
10623   
10624   (start code js)
10625   areaChart.restore();
10626   (end code)
10627   
10628   See also:
10629   
10630   <AreaChart.filter>.
10631  */  
10632   restore: function() {
10633     if(this.busy) return;
10634     this.busy = true;
10635     if(this.config.Tips.enable) this.st.tips.hide();
10636     this.select(false, false, false);
10637     this.normalizeDims();
10638     var that = this;
10639     this.st.fx.animate({
10640       modes: ['node-property:height:dimArray'],
10641       duration:1500,
10642       onComplete: function() {
10643         that.busy = false;
10644       }
10645     });
10646   },
10647   //adds the little brown bar when hovering the node
10648   select: function(id, name, index) {
10649     if(!this.config.selectOnHover) return;
10650     var s = this.selected;
10651     if(s.id != id || s.name != name 
10652         || s.index != index) {
10653       s.id = id;
10654       s.name = name;
10655       s.index = index;
10656       this.st.graph.eachNode(function(n) {
10657         n.setData('border', false);
10658       });
10659       if(id) {
10660         var n = this.st.graph.getNode(id);
10661         n.setData('border', s);
10662         var link = index === 0? 'prev':'next';
10663         link = n.getData(link);
10664         if(link) {
10665           n = this.st.graph.getByName(link);
10666           if(n) {
10667             n.setData('border', {
10668               name: name,
10669               index: 1-index
10670             });
10671           }
10672         }
10673       }
10674       this.st.plot();
10675     }
10676   },
10677   
10678   /*
10679     Method: getLegend
10680    
10681     Returns an object containing as keys the legend names and as values hex strings with color values.
10682     
10683     Example:
10684     
10685     (start code js)
10686     var legend = areaChart.getLegend();
10687     (end code)
10688  */  
10689   getLegend: function() {
10690     var legend = new Array();
10691     var name = new Array();
10692     var color = new Array();
10693     var n;
10694     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10695       n = adj.nodeTo;
10696     });
10697     var colors = n.getData('colorArray'),
10698         len = colors.length;
10699     $.each(n.getData('stringArray'), function(s, i) {
10700       color[i] = colors[i % len];
10701       name[i] = s;
10702     });
10703         legend['name'] = name;
10704         legend['color'] = color;
10705     return legend;
10706   },
10707   
10708   /*
10709     Method: getMaxValue
10710    
10711     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10712     
10713     Example:
10714     
10715     (start code js)
10716     var ans = areaChart.getMaxValue();
10717     (end code)
10718     
10719     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10720     
10721     Example:
10722     
10723     (start code js)
10724     //will return 100 for all AreaChart instances,
10725     //displaying all of them with the same scale
10726     $jit.AreaChart.implement({
10727       'getMaxValue': function() {
10728         return 100;
10729       }
10730     });
10731     (end code)
10732     
10733 */  
10734
10735   normalizeDims: function() {
10736     //number of elements
10737     var root = this.st.graph.getNode(this.st.root), l=0;
10738     root.eachAdjacency(function() {
10739       l++;
10740     });
10741     
10742
10743     var maxValue = this.maxValue || 1,
10744         size = this.st.canvas.getSize(),
10745         config = this.config,
10746         margin = config.Margin,
10747         labelOffset = config.labelOffset + config.Label.size,
10748         fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10749         animate = config.animate,
10750         ticks = config.Ticks,
10751         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
10752           - (config.showLabels && labelOffset);
10753           
10754           
10755         var maxTickValue = Math.ceil(maxValue*.1)*10;
10756                 if(maxTickValue == maxValue) {
10757                         var length = maxTickValue.toString().length;
10758                         maxTickValue = maxTickValue + parseInt(pad(1,length));
10759                 }
10760                 
10761                 
10762                 
10763     this.st.graph.eachNode(function(n) {
10764       var acumLeft = 0, acumRight = 0, animateValue = [];
10765       $.each(n.getData('valueArray'), function(v) {
10766         acumLeft += +v[0];
10767         acumRight += +v[1];
10768         animateValue.push([0, 0]);
10769       });
10770       var acum = acumRight>acumLeft? acumRight:acumLeft;
10771       
10772       n.setData('width', fixedDim);
10773       if(animate) {
10774         n.setData('height', acum * height / maxValue, 'end');
10775         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10776           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10777         }), 'end');
10778         var dimArray = n.getData('dimArray');
10779         if(!dimArray) {
10780           n.setData('dimArray', animateValue);
10781         }
10782       } else {
10783         
10784         if(ticks.enable) {
10785                 n.setData('height', acum * height / maxValue);
10786                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10787                   return [n[0] * height / maxTickValue, n[1] * height / maxTickValue]; 
10788                 }));
10789         } else {
10790                 n.setData('height', acum * height / maxValue);
10791                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10792                   return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10793                 }));
10794         }
10795         
10796         
10797       }
10798     });
10799   }
10800 });
10801
10802
10803
10804
10805
10806 /*
10807  * File: AreaChart.js
10808  *
10809 */
10810
10811 $jit.ST.Plot.NodeTypes.implement({
10812   'areachart-stacked' : {
10813     'render' : function(node, canvas) {
10814       var pos = node.pos.getc(true), 
10815           width = node.getData('width'),
10816           height = node.getData('height'),
10817           algnPos = this.getAlignedPos(pos, width, height),
10818           x = algnPos.x, y = algnPos.y,
10819           stringArray = node.getData('stringArray'),
10820           dimArray = node.getData('dimArray'),
10821           valArray = node.getData('valueArray'),
10822           valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10823           valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10824           colorArray = node.getData('colorArray'),
10825           colorLength = colorArray.length,
10826           config = node.getData('config'),
10827           gradient = node.getData('gradient'),
10828           showLabels = config.showLabels,
10829           aggregates = config.showAggregates,
10830           label = config.Label,
10831           prev = node.getData('prev');
10832
10833       var ctx = canvas.getCtx(), border = node.getData('border');
10834       if (colorArray && dimArray && stringArray) {
10835         for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10836           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10837           ctx.save();
10838           if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10839             var h1 = acumLeft + dimArray[i][0],
10840                 h2 = acumRight + dimArray[i][1],
10841                 alpha = Math.atan((h2 - h1) / width),
10842                 delta = 55;
10843             var linear = ctx.createLinearGradient(x + width/2, 
10844                 y - (h1 + h2)/2,
10845                 x + width/2 + delta * Math.sin(alpha),
10846                 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10847             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10848                 function(v) { return (v * 0.85) >> 0; }));
10849             linear.addColorStop(0, colorArray[i % colorLength]);
10850             linear.addColorStop(1, color);
10851             ctx.fillStyle = linear;
10852           }
10853           ctx.beginPath();
10854           ctx.moveTo(x, y - acumLeft);
10855           ctx.lineTo(x + width, y - acumRight);
10856           ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10857           ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10858           ctx.lineTo(x, y - acumLeft);
10859           ctx.fill();
10860           ctx.restore();
10861           if(border) {
10862             var strong = border.name == stringArray[i];
10863             var perc = strong? 0.7 : 0.8;
10864             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10865                 function(v) { return (v * perc) >> 0; }));
10866             ctx.strokeStyle = color;
10867             ctx.lineWidth = strong? 4 : 1;
10868             ctx.save();
10869             ctx.beginPath();
10870             if(border.index === 0) {
10871               ctx.moveTo(x, y - acumLeft);
10872               ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10873             } else {
10874               ctx.moveTo(x + width, y - acumRight);
10875               ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10876             }
10877             ctx.stroke();
10878             ctx.restore();
10879           }
10880           acumLeft += (dimArray[i][0] || 0);
10881           acumRight += (dimArray[i][1] || 0);
10882           
10883           if(dimArray[i][0] > 0)
10884             valAcum += (valArray[i][0] || 0);
10885         }
10886         if(prev && label.type == 'Native') {
10887           ctx.save();
10888           ctx.beginPath();
10889           ctx.fillStyle = ctx.strokeStyle = label.color;
10890           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10891           ctx.textAlign = 'center';
10892           ctx.textBaseline = 'middle';
10893           if(aggregates(node.name, valLeft, valRight, node)) {
10894             ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10895           }
10896           if(showLabels(node.name, valLeft, valRight, node)) {
10897             ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10898           }
10899           ctx.restore();
10900         }
10901       }
10902     },
10903     'contains': function(node, mpos) {
10904       var pos = node.pos.getc(true), 
10905           width = node.getData('width'),
10906           height = node.getData('height'),
10907           algnPos = this.getAlignedPos(pos, width, height),
10908           x = algnPos.x, y = algnPos.y,
10909           dimArray = node.getData('dimArray'),
10910           rx = mpos.x - x;
10911       //bounding box check
10912       if(mpos.x < x || mpos.x > x + width
10913         || mpos.y > y || mpos.y < y - height) {
10914         return false;
10915       }
10916       //deep check
10917       for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10918         var dimi = dimArray[i];
10919         lAcum -= dimi[0];
10920         rAcum -= dimi[1];
10921         var intersec = lAcum + (rAcum - lAcum) * rx / width;
10922         if(mpos.y >= intersec) {
10923           var index = +(rx > width/2);
10924           return {
10925             'name': node.getData('stringArray')[i],
10926             'color': node.getData('colorArray')[i],
10927             'value': node.getData('valueArray')[i][index],
10928             'index': index
10929           };
10930         }
10931       }
10932       return false;
10933     }
10934   }
10935 });
10936
10937 /*
10938   Class: AreaChart
10939   
10940   A visualization that displays stacked area charts.
10941   
10942   Constructor Options:
10943   
10944   See <Options.AreaChart>.
10945
10946 */
10947 $jit.AreaChart = new Class({
10948   st: null,
10949   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10950   selected: {},
10951   busy: false,
10952   
10953   initialize: function(opt) {
10954     this.controller = this.config = 
10955       $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10956         Label: { type: 'Native' }
10957       }, opt);
10958     //set functions for showLabels and showAggregates
10959     var showLabels = this.config.showLabels,
10960         typeLabels = $.type(showLabels),
10961         showAggregates = this.config.showAggregates,
10962         typeAggregates = $.type(showAggregates);
10963     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10964     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10965     
10966     this.initializeViz();
10967   },
10968   
10969   initializeViz: function() {
10970     var config = this.config,
10971         that = this,
10972         nodeType = config.type.split(":")[0],
10973         nodeLabels = {};
10974
10975     var st = new $jit.ST({
10976       injectInto: config.injectInto,
10977       orientation: "bottom",
10978       levelDistance: 0,
10979       siblingOffset: 0,
10980       subtreeOffset: 0,
10981       withLabels: config.Label.type != 'Native',
10982       useCanvas: config.useCanvas,
10983       Label: {
10984         type: config.Label.type
10985       },
10986       Node: {
10987         overridable: true,
10988         type: 'areachart-' + nodeType,
10989         align: 'left',
10990         width: 1,
10991         height: 1
10992       },
10993       Edge: {
10994         type: 'none'
10995       },
10996       Tips: {
10997         enable: config.Tips.enable,
10998         type: 'Native',
10999         force: true,
11000         onShow: function(tip, node, contains) {
11001           var elem = contains;
11002           config.Tips.onShow(tip, elem, node);
11003         }
11004       },
11005       Events: {
11006         enable: true,
11007         type: 'Native',
11008         onClick: function(node, eventInfo, evt) {
11009           if(!config.filterOnClick && !config.Events.enable) return;
11010           var elem = eventInfo.getContains();
11011           if(elem) config.filterOnClick && that.filter(elem.name);
11012           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11013         },
11014         onRightClick: function(node, eventInfo, evt) {
11015           if(!config.restoreOnRightClick) return;
11016           that.restore();
11017         },
11018         onMouseMove: function(node, eventInfo, evt) {
11019           if(!config.selectOnHover) return;
11020           if(node) {
11021             var elem = eventInfo.getContains();
11022             that.select(node.id, elem.name, elem.index);
11023           } else {
11024             that.select(false, false, false);
11025           }
11026         }
11027       },
11028       onCreateLabel: function(domElement, node) {
11029         var labelConf = config.Label,
11030             valueArray = node.getData('valueArray'),
11031             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11032             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11033         if(node.getData('prev')) {
11034           var nlbs = {
11035             wrapper: document.createElement('div'),
11036             aggregate: document.createElement('div'),
11037             label: document.createElement('div')
11038           };
11039           var wrapper = nlbs.wrapper,
11040               label = nlbs.label,
11041               aggregate = nlbs.aggregate,
11042               wrapperStyle = wrapper.style,
11043               labelStyle = label.style,
11044               aggregateStyle = aggregate.style;
11045           //store node labels
11046           nodeLabels[node.id] = nlbs;
11047           //append labels
11048           wrapper.appendChild(label);
11049           wrapper.appendChild(aggregate);
11050           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11051             label.style.display = 'none';
11052           }
11053           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11054             aggregate.style.display = 'none';
11055           }
11056           wrapperStyle.position = 'relative';
11057           wrapperStyle.overflow = 'visible';
11058           wrapperStyle.fontSize = labelConf.size + 'px';
11059           wrapperStyle.fontFamily = labelConf.family;
11060           wrapperStyle.color = labelConf.color;
11061           wrapperStyle.textAlign = 'center';
11062           aggregateStyle.position = labelStyle.position = 'absolute';
11063           
11064           domElement.style.width = node.getData('width') + 'px';
11065           domElement.style.height = node.getData('height') + 'px';
11066           label.innerHTML = node.name;
11067           
11068           domElement.appendChild(wrapper);
11069         }
11070       },
11071       onPlaceLabel: function(domElement, node) {
11072         if(!node.getData('prev')) return;
11073         var labels = nodeLabels[node.id],
11074             wrapperStyle = labels.wrapper.style,
11075             labelStyle = labels.label.style,
11076             aggregateStyle = labels.aggregate.style,
11077             width = node.getData('width'),
11078             height = node.getData('height'),
11079             dimArray = node.getData('dimArray'),
11080             valArray = node.getData('valueArray'),
11081             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11082             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11083             font = parseInt(wrapperStyle.fontSize, 10),
11084             domStyle = domElement.style;
11085         
11086         if(dimArray && valArray) {
11087           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11088             labelStyle.display = '';
11089           } else {
11090             labelStyle.display = 'none';
11091           }
11092           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11093             aggregateStyle.display = '';
11094           } else {
11095             aggregateStyle.display = 'none';
11096           }
11097           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11098           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11099           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11100             if(dimArray[i][0] > 0) {
11101               acum+= valArray[i][0];
11102               leftAcum+= dimArray[i][0];
11103             }
11104           }
11105           aggregateStyle.top = (-font - config.labelOffset) + 'px';
11106           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11107           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11108           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11109           labels.aggregate.innerHTML = acum;
11110         }
11111       }
11112     });
11113     
11114     var size = st.canvas.getSize(),
11115         margin = config.Margin;
11116     st.config.offsetY = -size.height/2 + margin.bottom 
11117       + (config.showLabels && (config.labelOffset + config.Label.size));
11118     st.config.offsetX = (margin.right - margin.left)/2;
11119     this.st = st;
11120     this.canvas = this.st.canvas;
11121   },
11122   
11123  /*
11124   Method: loadJSON
11125  
11126   Loads JSON data into the visualization. 
11127   
11128   Parameters:
11129   
11130   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>.
11131   
11132   Example:
11133   (start code js)
11134   var areaChart = new $jit.AreaChart(options);
11135   areaChart.loadJSON(json);
11136   (end code)
11137  */  
11138   loadJSON: function(json) {
11139     var prefix = $.time(), 
11140         ch = [], 
11141         st = this.st,
11142         name = $.splat(json.label), 
11143         color = $.splat(json.color || this.colors),
11144         config = this.config,
11145         gradient = !!config.type.split(":")[1],
11146         animate = config.animate;
11147     
11148     for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11149       var val = values[i], prev = values[i-1], next = values[i+1];
11150       var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11151       var valArray = $.zip(valLeft, valRight);
11152       var acumLeft = 0, acumRight = 0;
11153       ch.push({
11154         'id': prefix + val.label,
11155         'name': val.label,
11156         'data': {
11157           'value': valArray,
11158           '$valueArray': valArray,
11159           '$colorArray': color,
11160           '$stringArray': name,
11161           '$next': next.label,
11162           '$prev': prev? prev.label:false,
11163           '$config': config,
11164           '$gradient': gradient
11165         },
11166         'children': []
11167       });
11168     }
11169     var root = {
11170       'id': prefix + '$root',
11171       'name': '',
11172       'data': {
11173         '$type': 'none',
11174         '$width': 1,
11175         '$height': 1
11176       },
11177       'children': ch
11178     };
11179     st.loadJSON(root);
11180     
11181     this.normalizeDims();
11182     st.compute();
11183     st.select(st.root);
11184     if(animate) {
11185       st.fx.animate({
11186         modes: ['node-property:height:dimArray'],
11187         duration:1500
11188       });
11189     }
11190   },
11191   
11192  /*
11193   Method: updateJSON
11194  
11195   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.
11196   
11197   Parameters:
11198   
11199   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11200   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11201   
11202   Example:
11203   
11204   (start code js)
11205   areaChart.updateJSON(json, {
11206     onComplete: function() {
11207       alert('update complete!');
11208     }
11209   });
11210   (end code)
11211  */  
11212   updateJSON: function(json, onComplete) {
11213     if(this.busy) return;
11214     this.busy = true;
11215     
11216     var st = this.st,
11217         graph = st.graph,
11218         labels = json.label && $.splat(json.label),
11219         values = json.values,
11220         animate = this.config.animate,
11221         that = this;
11222     $.each(values, function(v) {
11223       var n = graph.getByName(v.label);
11224       if(n) {
11225         v.values = $.splat(v.values);
11226         var stringArray = n.getData('stringArray'),
11227             valArray = n.getData('valueArray');
11228         $.each(valArray, function(a, i) {
11229           a[0] = v.values[i];
11230           if(labels) stringArray[i] = labels[i];
11231         });
11232         n.setData('valueArray', valArray);
11233         var prev = n.getData('prev'),
11234             next = n.getData('next'),
11235             nextNode = graph.getByName(next);
11236         if(prev) {
11237           var p = graph.getByName(prev);
11238           if(p) {
11239             var valArray = p.getData('valueArray');
11240             $.each(valArray, function(a, i) {
11241               a[1] = v.values[i];
11242             });
11243           }
11244         }
11245         if(!nextNode) {
11246           var valArray = n.getData('valueArray');
11247           $.each(valArray, function(a, i) {
11248             a[1] = v.values[i];
11249           });
11250         }
11251       }
11252     });
11253     this.normalizeDims();
11254     st.compute();
11255     st.select(st.root);
11256     if(animate) {
11257       st.fx.animate({
11258         modes: ['node-property:height:dimArray'],
11259         duration:1500,
11260         onComplete: function() {
11261           that.busy = false;
11262           onComplete && onComplete.onComplete();
11263         }
11264       });
11265     }
11266   },
11267   
11268 /*
11269   Method: filter
11270  
11271   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11272   
11273   Parameters:
11274   
11275   Variable strings arguments with the name of the stacks.
11276   
11277   Example:
11278   
11279   (start code js)
11280   areaChart.filter('label A', 'label C');
11281   (end code)
11282   
11283   See also:
11284   
11285   <AreaChart.restore>.
11286  */  
11287   filter: function() {
11288     if(this.busy) return;
11289     this.busy = true;
11290     if(this.config.Tips.enable) this.st.tips.hide();
11291     this.select(false, false, false);
11292     var args = Array.prototype.slice.call(arguments);
11293     var rt = this.st.graph.getNode(this.st.root);
11294     var that = this;
11295     rt.eachAdjacency(function(adj) {
11296       var n = adj.nodeTo, 
11297           dimArray = n.getData('dimArray'),
11298           stringArray = n.getData('stringArray');
11299       n.setData('dimArray', $.map(dimArray, function(d, i) {
11300         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11301       }), 'end');
11302     });
11303     this.st.fx.animate({
11304       modes: ['node-property:dimArray'],
11305       duration:1500,
11306       onComplete: function() {
11307         that.busy = false;
11308       }
11309     });
11310   },
11311   
11312   /*
11313   Method: restore
11314  
11315   Sets all stacks that could have been filtered visible.
11316   
11317   Example:
11318   
11319   (start code js)
11320   areaChart.restore();
11321   (end code)
11322   
11323   See also:
11324   
11325   <AreaChart.filter>.
11326  */  
11327   restore: function() {
11328     if(this.busy) return;
11329     this.busy = true;
11330     if(this.config.Tips.enable) this.st.tips.hide();
11331     this.select(false, false, false);
11332     this.normalizeDims();
11333     var that = this;
11334     this.st.fx.animate({
11335       modes: ['node-property:height:dimArray'],
11336       duration:1500,
11337       onComplete: function() {
11338         that.busy = false;
11339       }
11340     });
11341   },
11342   //adds the little brown bar when hovering the node
11343   select: function(id, name, index) {
11344     if(!this.config.selectOnHover) return;
11345     var s = this.selected;
11346     if(s.id != id || s.name != name 
11347         || s.index != index) {
11348       s.id = id;
11349       s.name = name;
11350       s.index = index;
11351       this.st.graph.eachNode(function(n) {
11352         n.setData('border', false);
11353       });
11354       if(id) {
11355         var n = this.st.graph.getNode(id);
11356         n.setData('border', s);
11357         var link = index === 0? 'prev':'next';
11358         link = n.getData(link);
11359         if(link) {
11360           n = this.st.graph.getByName(link);
11361           if(n) {
11362             n.setData('border', {
11363               name: name,
11364               index: 1-index
11365             });
11366           }
11367         }
11368       }
11369       this.st.plot();
11370     }
11371   },
11372   
11373   /*
11374     Method: getLegend
11375    
11376     Returns an object containing as keys the legend names and as values hex strings with color values.
11377     
11378     Example:
11379     
11380     (start code js)
11381     var legend = areaChart.getLegend();
11382     (end code)
11383  */  
11384   getLegend: function() {
11385     var legend = {};
11386     var n;
11387     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11388       n = adj.nodeTo;
11389     });
11390     var colors = n.getData('colorArray'),
11391         len = colors.length;
11392     $.each(n.getData('stringArray'), function(s, i) {
11393       legend[s] = colors[i % len];
11394     });
11395     return legend;
11396   },
11397   
11398   /*
11399     Method: getMaxValue
11400    
11401     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11402     
11403     Example:
11404     
11405     (start code js)
11406     var ans = areaChart.getMaxValue();
11407     (end code)
11408     
11409     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11410     
11411     Example:
11412     
11413     (start code js)
11414     //will return 100 for all AreaChart instances,
11415     //displaying all of them with the same scale
11416     $jit.AreaChart.implement({
11417       'getMaxValue': function() {
11418         return 100;
11419       }
11420     });
11421     (end code)
11422     
11423 */  
11424   getMaxValue: function() {
11425     var maxValue = 0;
11426     this.st.graph.eachNode(function(n) {
11427       var valArray = n.getData('valueArray'),
11428           acumLeft = 0, acumRight = 0;
11429       $.each(valArray, function(v) { 
11430         acumLeft += +v[0];
11431         acumRight += +v[1];
11432       });
11433       var acum = acumRight>acumLeft? acumRight:acumLeft;
11434       maxValue = maxValue>acum? maxValue:acum;
11435     });
11436     return maxValue;
11437   },
11438   
11439   normalizeDims: function() {
11440     //number of elements
11441     var root = this.st.graph.getNode(this.st.root), l=0;
11442     root.eachAdjacency(function() {
11443       l++;
11444     });
11445     var maxValue = this.getMaxValue() || 1,
11446         size = this.st.canvas.getSize(),
11447         config = this.config,
11448         margin = config.Margin,
11449         labelOffset = config.labelOffset + config.Label.size,
11450         fixedDim = (size.width - (margin.left + margin.right)) / l,
11451         animate = config.animate,
11452         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
11453           - (config.showLabels && labelOffset);
11454     this.st.graph.eachNode(function(n) {
11455       var acumLeft = 0, acumRight = 0, animateValue = [];
11456       $.each(n.getData('valueArray'), function(v) {
11457         acumLeft += +v[0];
11458         acumRight += +v[1];
11459         animateValue.push([0, 0]);
11460       });
11461       var acum = acumRight>acumLeft? acumRight:acumLeft;
11462       n.setData('width', fixedDim);
11463       if(animate) {
11464         n.setData('height', acum * height / maxValue, 'end');
11465         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11466           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11467         }), 'end');
11468         var dimArray = n.getData('dimArray');
11469         if(!dimArray) {
11470           n.setData('dimArray', animateValue);
11471         }
11472       } else {
11473         n.setData('height', acum * height / maxValue);
11474         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11475           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11476         }));
11477       }
11478     });
11479   }
11480 });
11481
11482 /*
11483  * File: Options.BarChart.js
11484  *
11485 */
11486
11487 /*
11488   Object: Options.BarChart
11489   
11490   <BarChart> options. 
11491   Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11492   
11493   Syntax:
11494   
11495   (start code js)
11496
11497   Options.BarChart = {
11498     animate: true,
11499     labelOffset: 3,
11500     barsOffset: 0,
11501     type: 'stacked',
11502     hoveredColor: '#9fd4ff',
11503     orientation: 'horizontal',
11504     showAggregates: true,
11505     showLabels: true
11506   };
11507   
11508   (end code)
11509   
11510   Example:
11511   
11512   (start code js)
11513
11514   var barChart = new $jit.BarChart({
11515     animate: true,
11516     barsOffset: 10,
11517     type: 'stacked:gradient'
11518   });
11519   
11520   (end code)
11521
11522   Parameters:
11523   
11524   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11525   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11526   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11527   barsOffset - (number) Default's *0*. Separation between bars.
11528   type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11529   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11530   orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11531   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11532   showLabels - (boolean) Default's *true*. Display the name of the slots.
11533   
11534 */
11535
11536 Options.BarChart = {
11537   $extend: true,
11538   
11539   animate: true,
11540   type: 'stacked', //stacked, grouped, : gradient
11541   labelOffset: 3, //label offset
11542   barsOffset: 0, //distance between bars
11543   nodeCount: 0, //number of bars
11544   hoveredColor: '#9fd4ff',
11545   background: false,
11546   renderBackground: false,
11547   orientation: 'horizontal',
11548   showAggregates: true,
11549   showLabels: true,
11550   Ticks: {
11551         enable: false,
11552         segments: 4,
11553         color: '#000000'
11554   },
11555   Tips: {
11556     enable: false,
11557     onShow: $.empty,
11558     onHide: $.empty
11559   },
11560   Events: {
11561     enable: false,
11562     onClick: $.empty
11563   }
11564 };
11565
11566 /*
11567  * File: BarChart.js
11568  *
11569 */
11570
11571 $jit.ST.Plot.NodeTypes.implement({
11572   'barchart-stacked' : {
11573     'render' : function(node, canvas) {
11574       var pos = node.pos.getc(true), 
11575           width = node.getData('width'),
11576           height = node.getData('height'),
11577           algnPos = this.getAlignedPos(pos, width, height),
11578           x = algnPos.x, y = algnPos.y,
11579           dimArray = node.getData('dimArray'),
11580           valueArray = node.getData('valueArray'),
11581           stringArray = node.getData('stringArray'),
11582           linkArray = node.getData('linkArray'),
11583           gvl = node.getData('gvl'),
11584           colorArray = node.getData('colorArray'),
11585           colorLength = colorArray.length,
11586           nodeCount = node.getData('nodeCount');
11587       var ctx = canvas.getCtx(),
11588           canvasSize = canvas.getSize(),
11589           opt = {},
11590           border = node.getData('border'),
11591           gradient = node.getData('gradient'),
11592           config = node.getData('config'),
11593           horz = config.orientation == 'horizontal',
11594           aggregates = config.showAggregates,
11595           showLabels = config.showLabels,
11596           label = config.Label,
11597           margin = config.Margin;
11598           
11599           
11600       if (colorArray && dimArray && stringArray) {
11601         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11602                 acum += (dimArray[i] || 0);
11603         }
11604       }
11605       
11606        //drop shadow 
11607        if(config.shadow.enable) {
11608        shadowThickness = config.shadow.size;
11609        ctx.fillStyle = "rgba(0,0,0,.2)";
11610           if(horz) {
11611             ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11612           } else {
11613             ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11614           }
11615        }
11616        
11617       if (colorArray && dimArray && stringArray) {
11618         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11619           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11620           if(gradient) {
11621             var linear;
11622             
11623
11624           
11625             if(horz) {
11626               linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y, 
11627                   x + acum + dimArray[i]/2, y + height);
11628             } else {
11629               linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2, 
11630                   x + width, y - acum- dimArray[i]/2);
11631             }
11632             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11633                 function(v) { return (v * 0.8) >> 0; }));
11634             linear.addColorStop(0, color);
11635             linear.addColorStop(0.3, colorArray[i % colorLength]);
11636             linear.addColorStop(0.7, colorArray[i % colorLength]);
11637             linear.addColorStop(1, color);
11638             ctx.fillStyle = linear;
11639           }
11640           if(horz) {
11641             ctx.fillRect(x + acum, y, dimArray[i], height);
11642           } else {
11643             ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
11644           }
11645           if(border && border.name == stringArray[i]) {
11646             opt.acum = acum;
11647             opt.dimValue = dimArray[i];
11648           }
11649           acum += (dimArray[i] || 0);
11650           valAcum += (valueArray[i] || 0);
11651         }
11652         if(border) {
11653           ctx.save();
11654           ctx.lineWidth = 2;
11655           ctx.strokeStyle = border.color;
11656           if(horz) {
11657             ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11658           } else {
11659             ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11660           }
11661           ctx.restore();
11662         }
11663         if(label.type == 'Native') {
11664           ctx.save();
11665           ctx.fillStyle = ctx.strokeStyle = label.color;
11666           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11667           ctx.textBaseline = 'middle';
11668                         if(gvl) {
11669                                 acumValueLabel = gvl;
11670                         } else {
11671                                 acumValueLabel = valAcum;
11672                         }
11673           if(aggregates(node.name, valAcum)) {
11674             if(!horz) {
11675                           ctx.textAlign = 'center';
11676                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11677                           //background box
11678                           ctx.save();
11679                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11680                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11681                                  (label ? label.size + config.labelOffset : 0));
11682                           mtxt = ctx.measureText(acumValueLabel);
11683                           boxWidth = mtxt.width+10;
11684                           inset = 10;
11685                           boxHeight = label.size+6;
11686                           
11687                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11688                                 bottomPadding = acum - config.labelOffset - boxHeight;
11689                           } else {
11690                                 bottomPadding = acum + config.labelOffset + inset;
11691                           }
11692                         
11693                         
11694                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11695                           cornerRadius = 4;     
11696                           boxX = -inset/2;
11697                           boxY = -boxHeight/2;
11698                           
11699                           ctx.rotate(0 * Math.PI / 180);
11700                           ctx.fillStyle = "rgba(255,255,255,.8)";
11701                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11702                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11703                           }
11704                           //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11705                           ctx.fillStyle = ctx.strokeStyle = label.color;
11706                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11707                           ctx.restore();
11708
11709             }
11710           }
11711           if(showLabels(node.name, valAcum, node)) {
11712             if(horz) {
11713
11714
11715                 //background box
11716                 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11717                                 inset = 10;
11718                                 
11719                                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11720                 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11721                 boxWidth = mtxt.width+10;
11722                 inset = 10;
11723                 
11724                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11725                         leftPadding = acum - config.labelOffset - boxWidth - inset;
11726                 } else {
11727                         leftPadding = acum + config.labelOffset;
11728                 }
11729                 
11730                 
11731                                 ctx.textAlign = 'left';
11732                                 ctx.translate(x + inset + leftPadding, y + height/2);
11733                                 boxHeight = label.size+6;
11734                                 boxX = -inset/2;
11735                                 boxY = -boxHeight/2;
11736                                 ctx.fillStyle = "rgba(255,255,255,.8)";
11737                                 cornerRadius = 4;
11738                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {  
11739                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11740                                 }
11741                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11742                                 
11743                           ctx.fillStyle = label.color;
11744               ctx.rotate(0 * Math.PI / 180);
11745               ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11746
11747
11748             } else {
11749               //if the number of nodes greater than 8 rotate labels 45 degrees
11750               if(nodeCount > 8) {
11751                                 ctx.textAlign = 'left';
11752                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11753                                 ctx.rotate(45* Math.PI / 180);
11754                                 ctx.fillText(node.name, 0, 0);
11755                           } else {
11756                                 ctx.textAlign = 'center';
11757                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11758                           }
11759             }
11760           }
11761           ctx.restore();
11762         }
11763       }
11764     },
11765     'contains': function(node, mpos) {
11766       var pos = node.pos.getc(true), 
11767           width = node.getData('width'),
11768           height = node.getData('height'),
11769           algnPos = this.getAlignedPos(pos, width, height),
11770           x = algnPos.x, y = algnPos.y,
11771           dimArray = node.getData('dimArray'),
11772           config = node.getData('config'),
11773           rx = mpos.x - x,
11774           horz = config.orientation == 'horizontal';
11775       //bounding box check
11776       if(horz) {
11777         if(mpos.x < x || mpos.x > x + width
11778             || mpos.y > y + height || mpos.y < y) {
11779             return false;
11780           }
11781       } else {
11782         if(mpos.x < x || mpos.x > x + width
11783             || mpos.y > y || mpos.y < y - height) {
11784             return false;
11785           }
11786       }
11787       //deep check
11788       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11789         var dimi = dimArray[i];
11790                 var url = Url.decode(node.getData('linkArray')[i]);
11791         if(horz) {
11792           acum += dimi;
11793           var intersec = acum;
11794           if(mpos.x <= intersec) {
11795             return {
11796               'name': node.getData('stringArray')[i],
11797               'color': node.getData('colorArray')[i],
11798               'value': node.getData('valueArray')[i],
11799               'valuelabel': node.getData('valuelabelArray')[i],
11800                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11801                           'link': url,
11802               'label': node.name
11803             };
11804           }
11805         } else {
11806           acum -= dimi;
11807           var intersec = acum;
11808           if(mpos.y >= intersec) {
11809             return {
11810               'name': node.getData('stringArray')[i],
11811               'color': node.getData('colorArray')[i],
11812               'value': node.getData('valueArray')[i],
11813                           'valuelabel': node.getData('valuelabelArray')[i],
11814                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11815               'link': url,
11816               'label': node.name
11817             };
11818           }
11819         }
11820       }
11821       return false;
11822     }
11823   },
11824   'barchart-grouped' : {
11825     'render' : function(node, canvas) {
11826       var pos = node.pos.getc(true), 
11827           width = node.getData('width'),
11828           height = node.getData('height'),
11829           algnPos = this.getAlignedPos(pos, width, height),
11830           x = algnPos.x, y = algnPos.y,
11831           dimArray = node.getData('dimArray'),
11832           valueArray = node.getData('valueArray'),
11833           valuelabelArray = node.getData('valuelabelArray'),
11834           linkArray = node.getData('linkArray'),
11835           valueLength = valueArray.length,
11836           colorArray = node.getData('colorArray'),
11837           colorLength = colorArray.length,
11838           stringArray = node.getData('stringArray'); 
11839
11840       var ctx = canvas.getCtx(),
11841           canvasSize = canvas.getSize(),
11842           opt = {},
11843           border = node.getData('border'),
11844           gradient = node.getData('gradient'),
11845           config = node.getData('config'),
11846           horz = config.orientation == 'horizontal',
11847           aggregates = config.showAggregates,
11848           showLabels = config.showLabels,
11849           label = config.Label,
11850           shadow = config.shadow,
11851           margin = config.Margin,
11852           fixedDim = (horz? height : width) / valueLength;
11853       
11854       //drop shadow
11855       
11856        maxValue = Math.max.apply(null, dimArray);
11857        
11858        
11859           
11860            ctx.fillStyle = "rgba(0,0,0,.2)";
11861       if (colorArray && dimArray && stringArray && shadow.enable) {
11862                  shadowThickness = shadow.size;
11863
11864         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11865                 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11866                 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11867                 if(horz) {
11868                                     
11869                         ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11870                                         
11871                 } else {
11872                         
11873                         if(i == 0) {
11874                                 if(nextBar && nextBar > dimArray[i]) {
11875                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11876                                 } else if (nextBar && nextBar < dimArray[i]){
11877                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11878                                 } else {
11879                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11880                                 }
11881                         } else if (i> 0 && i<l-1) {
11882                                 if(nextBar && nextBar > dimArray[i]) {
11883                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11884                                 } else if (nextBar && nextBar < dimArray[i]){
11885                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11886                                 } else {
11887                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11888                                 }
11889                         } else if (i == l-1) {
11890                                 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11891                         }
11892                         
11893                         
11894                 }
11895         }
11896
11897       } 
11898                         
11899       
11900       if (colorArray && dimArray && stringArray) {
11901         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11902           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11903           if(gradient) {
11904             var linear;
11905             if(horz) {
11906               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
11907                   x + dimArray[i]/2, y + fixedDim * (i + 1));
11908             } else {
11909               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
11910                   x + fixedDim * (i + 1), y - dimArray[i]/2);
11911             }
11912             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11913                 function(v) { return (v * 0.8) >> 0; }));
11914             linear.addColorStop(0, color);
11915             linear.addColorStop(0.3, colorArray[i % colorLength]);
11916             linear.addColorStop(0.7, colorArray[i % colorLength]);
11917             linear.addColorStop(1, color);
11918             ctx.fillStyle = linear;
11919           }
11920           if(horz) {
11921             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11922           } else {
11923             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11924           }
11925           if(border && border.name == stringArray[i]) {
11926             opt.acum = fixedDim * i;
11927             opt.dimValue = dimArray[i];
11928           }
11929           acum += (dimArray[i] || 0);
11930           valAcum += (valueArray[i] || 0);
11931                   ctx.fillStyle = ctx.strokeStyle = label.color;
11932           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11933           
11934           inset = 10;
11935                   if(aggregates(node.name, valAcum) && label.type == 'Native') {
11936                                 if(valuelabelArray[i]) {
11937                                         acumValueLabel = valuelabelArray[i];
11938                                 } else {
11939                                         acumValueLabel = valueArray[i];
11940                                 }
11941                            if(horz) {
11942                                   ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11943                                   ctx.textAlign = 'left';
11944                                   ctx.textBaseline = 'top';
11945                                   ctx.fillStyle = "rgba(255,255,255,.8)";
11946                                   //background box
11947                                   gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
11948                                   mtxt = ctx.measureText(acumValueLabel);
11949                                   boxWidth = mtxt.width+10;
11950                                   
11951                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11952                                         leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
11953                                   } else {
11954                                         leftPadding = dimArray[i] + config.labelOffset + inset;
11955                                   }
11956                               boxHeight = label.size+6;
11957                                   boxX = x + leftPadding;
11958                                   boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
11959                                   cornerRadius = 4;     
11960                                   
11961                                   
11962                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11963                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11964                                   }
11965                                 //  $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11966                                   
11967                                   ctx.fillStyle = ctx.strokeStyle = label.color;
11968                                   ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
11969                                   
11970
11971                                         
11972                                         
11973                                 } else {
11974                                   
11975                                         ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11976                                         ctx.save();
11977                                         ctx.textAlign = 'center';
11978                                         
11979                                         //background box
11980                                         gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11981                                          (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11982                                          (label ? label.size + config.labelOffset : 0));
11983                                         
11984                                         mtxt = ctx.measureText(acumValueLabel);
11985                                         boxWidth = mtxt.width+10;
11986                                         boxHeight = label.size+6;
11987                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
11988                                                 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
11989                                         } else {
11990                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
11991                                         }
11992                                                                                                 
11993                                         
11994                                         ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
11995                                         
11996                                         boxX = -boxWidth/2;
11997                                         boxY = -boxHeight/2;
11998                                         ctx.fillStyle = "rgba(255,255,255,.8)";
11999                                         
12000                                         cornerRadius = 4;       
12001
12002                                         //ctx.rotate(270* Math.PI / 180);
12003                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12004                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12005                                         }
12006                                         //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12007                                         
12008                                         ctx.fillStyle = ctx.strokeStyle = label.color;
12009                                         ctx.fillText(acumValueLabel, 0,0);
12010                                         ctx.restore();
12011
12012                                 }
12013                         }
12014         }
12015         if(border) {
12016           ctx.save();
12017           ctx.lineWidth = 2;
12018           ctx.strokeStyle = border.color;
12019           if(horz) {
12020             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12021           } else {
12022             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12023           }
12024           ctx.restore();
12025         }
12026         if(label.type == 'Native') {
12027           ctx.save();
12028           ctx.fillStyle = ctx.strokeStyle = label.color;
12029           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12030           ctx.textBaseline = 'middle';
12031
12032           if(showLabels(node.name, valAcum, node)) {
12033             if(horz) {
12034               ctx.textAlign = 'center';
12035               ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12036               ctx.rotate(Math.PI / 2);
12037               ctx.fillText(node.name, 0, 0);
12038             } else {
12039               ctx.textAlign = 'center';
12040               ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12041             }
12042           }
12043           ctx.restore();
12044         }
12045       }
12046     },
12047     'contains': function(node, mpos) {
12048       var pos = node.pos.getc(true), 
12049           width = node.getData('width'),
12050           height = node.getData('height'),
12051           algnPos = this.getAlignedPos(pos, width, height),
12052           x = algnPos.x, y = algnPos.y,
12053           dimArray = node.getData('dimArray'),
12054           len = dimArray.length,
12055           config = node.getData('config'),
12056           rx = mpos.x - x,
12057           horz = config.orientation == 'horizontal',
12058           fixedDim = (horz? height : width) / len;
12059       //bounding box check
12060       if(horz) {
12061         if(mpos.x < x || mpos.x > x + width
12062             || mpos.y > y + height || mpos.y < y) {
12063             return false;
12064           }
12065       } else {
12066         if(mpos.x < x || mpos.x > x + width
12067             || mpos.y > y || mpos.y < y - height) {
12068             return false;
12069           }
12070       }
12071       //deep check
12072       for(var i=0, l=dimArray.length; i<l; i++) {
12073         var dimi = dimArray[i];
12074                 var url = Url.decode(node.getData('linkArray')[i]);
12075         if(horz) {
12076           var limit = y + fixedDim * i;
12077           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12078             return {
12079               'name': node.getData('stringArray')[i],
12080               'color': node.getData('colorArray')[i],
12081               'value': node.getData('valueArray')[i],
12082                           'valuelabel': node.getData('valuelabelArray')[i],
12083               'title': node.getData('titleArray')[i],
12084                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12085               'link': url,
12086               'label': node.name
12087             };
12088           }
12089         } else {
12090           var limit = x + fixedDim * i;
12091           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12092             return {
12093               'name': node.getData('stringArray')[i],
12094               'color': node.getData('colorArray')[i],
12095               'value': node.getData('valueArray')[i],
12096                           'valuelabel': node.getData('valuelabelArray')[i],
12097               'title': node.getData('titleArray')[i],
12098                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12099               'link': url,
12100               'label': node.name
12101             };
12102           }
12103         }
12104       }
12105       return false;
12106     }
12107   },
12108   'barchart-basic' : {
12109     'render' : function(node, canvas) {
12110       var pos = node.pos.getc(true), 
12111           width = node.getData('width'),
12112           height = node.getData('height'),
12113           algnPos = this.getAlignedPos(pos, width, height),
12114           x = algnPos.x, y = algnPos.y,
12115           dimArray = node.getData('dimArray'),
12116           valueArray = node.getData('valueArray'),
12117                   valuelabelArray = node.getData('valuelabelArray'),
12118           linkArray = node.getData('linkArray'),
12119           valueLength = valueArray.length,
12120           colorArray = node.getData('colorMono'),
12121           colorLength = colorArray.length,
12122           stringArray = node.getData('stringArray'); 
12123
12124       var ctx = canvas.getCtx(),
12125           canvasSize = canvas.getSize(),
12126           opt = {},
12127           border = node.getData('border'),
12128           gradient = node.getData('gradient'),
12129           config = node.getData('config'),
12130           horz = config.orientation == 'horizontal',
12131           aggregates = config.showAggregates,
12132           showLabels = config.showLabels,
12133           label = config.Label,
12134           fixedDim = (horz? height : width) / valueLength,
12135           margin = config.Margin;
12136       
12137       if (colorArray && dimArray && stringArray) {
12138         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12139           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12140
12141           if(gradient) {
12142             var linear;
12143             if(horz) {
12144               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
12145                   x + dimArray[i]/2, y + fixedDim * (i + 1));
12146             } else {
12147               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
12148                   x + fixedDim * (i + 1), y - dimArray[i]/2);
12149             }
12150             //drop shadow 
12151            if(config.shadow.size) {
12152                   shadowThickness = config.shadow.size;
12153                   ctx.fillStyle = "rgba(0,0,0,.2)";
12154                   if(horz) {
12155                     ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12156                   } else {
12157                     ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12158                   }
12159           }
12160           
12161             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
12162                 function(v) { return (v * 0.8) >> 0; }));
12163             linear.addColorStop(0, color);
12164             linear.addColorStop(0.3, colorArray[i % colorLength]);
12165             linear.addColorStop(0.7, colorArray[i % colorLength]);
12166             linear.addColorStop(1, color);
12167             ctx.fillStyle = linear;
12168           }
12169           if(horz) {
12170             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12171           } else {
12172             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12173           }
12174           if(border && border.name == stringArray[i]) {
12175             opt.acum = fixedDim * i;
12176             opt.dimValue = dimArray[i];
12177           }
12178           acum += (dimArray[i] || 0);
12179           valAcum += (valueArray[i] || 0);
12180                   
12181               if(label.type == 'Native') {
12182                                  ctx.fillStyle = ctx.strokeStyle = label.color;
12183                                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12184                                  if(aggregates(node.name, valAcum)) {
12185                                         if(valuelabelArray[i]) {
12186                                                 acumValueLabel = valuelabelArray[i];
12187                                           } else {
12188                                                 acumValueLabel = valueArray[i];
12189                                           }
12190                                          if(!horz) {
12191                                           ctx.textAlign = 'center';
12192                                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12193                                           //background box
12194                                           ctx.save();
12195                                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12196                                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12197                                                  (label ? label.size + config.labelOffset : 0));
12198                           mtxt = ctx.measureText(acumValueLabel);
12199                                           boxWidth = mtxt.width+10;
12200                                           inset = 10;
12201                                           boxHeight = label.size+6;
12202                                           
12203                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12204                                                 bottomPadding = dimArray[i] - config.labelOffset  - inset;
12205                                           } else {
12206                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12207                                           }
12208                                         
12209                                         
12210                                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12211                                           cornerRadius = 4;     
12212                                           boxX = -inset/2;
12213                                           boxY = -boxHeight/2;
12214                                           
12215                                           //ctx.rotate(270* Math.PI / 180);
12216                                           ctx.fillStyle = "rgba(255,255,255,.6)";
12217                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12218                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12219                                           }
12220                                          // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12221                                           ctx.fillStyle = ctx.strokeStyle = label.color;
12222                                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12223                                           ctx.restore();
12224                                         }
12225                                 }
12226                 }
12227         }
12228         if(border) {
12229           ctx.save();
12230           ctx.lineWidth = 2;
12231           ctx.strokeStyle = border.color;
12232           if(horz) {
12233             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12234           } else {
12235             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12236           }
12237           ctx.restore();
12238         }
12239         if(label.type == 'Native') {
12240           ctx.save();
12241           ctx.fillStyle = ctx.strokeStyle = label.color;
12242           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12243           ctx.textBaseline = 'middle';
12244           if(showLabels(node.name, valAcum, node)) {
12245             if(horz) {
12246                 
12247                 //background box
12248                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12249                 mtxt = ctx.measureText(node.name + ": " + valAcum);
12250                 boxWidth = mtxt.width+10;
12251                 inset = 10;
12252                 
12253                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12254                         leftPadding = acum - config.labelOffset - boxWidth - inset;
12255                 } else {
12256                         leftPadding = acum + config.labelOffset;
12257                 }
12258                 
12259                                 
12260                                 ctx.textAlign = 'left';
12261                                 ctx.translate(x + inset + leftPadding, y + height/2);
12262                                 boxHeight = label.size+6;
12263                                 boxX = -inset/2;
12264                                 boxY = -boxHeight/2;
12265                                 ctx.fillStyle = "rgba(255,255,255,.8)";
12266                                 
12267                                 cornerRadius = 4;       
12268                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12269                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12270                                 }
12271                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12272                 
12273                                 
12274                                 ctx.fillStyle = label.color;
12275                                 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12276
12277             } else {
12278               
12279                           if(stringArray.length > 8) {
12280                                 ctx.textAlign = 'left';
12281                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12282                                 ctx.rotate(45* Math.PI / 180);
12283                                 ctx.fillText(node.name, 0, 0);
12284                           } else {
12285                                 ctx.textAlign = 'center';
12286                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12287                           }
12288               
12289             }
12290           }
12291           ctx.restore();
12292         }
12293       }
12294     },
12295     'contains': function(node, mpos) {
12296       var pos = node.pos.getc(true), 
12297           width = node.getData('width'),
12298           height = node.getData('height'),
12299           config = node.getData('config'),
12300           algnPos = this.getAlignedPos(pos, width, height),
12301           x = algnPos.x, y = algnPos.y ,
12302           dimArray = node.getData('dimArray'),
12303           len = dimArray.length,
12304           rx = mpos.x - x,
12305           horz = config.orientation == 'horizontal',
12306           fixedDim = (horz? height : width) / len;
12307
12308       //bounding box check
12309       if(horz) {
12310         if(mpos.x < x || mpos.x > x + width
12311             || mpos.y > y + height || mpos.y < y) {
12312             return false;
12313           }
12314       } else {
12315         if(mpos.x < x || mpos.x > x + width
12316             || mpos.y > y || mpos.y < y - height) {
12317             return false;
12318           }
12319       }
12320       //deep check
12321       for(var i=0, l=dimArray.length; i<l; i++) {
12322         var dimi = dimArray[i];
12323                 var url = Url.decode(node.getData('linkArray')[i]);
12324         if(horz) {
12325           var limit = y + fixedDim * i;
12326           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12327             return {
12328               'name': node.getData('stringArray')[i],
12329               'color': node.getData('colorArray')[i],
12330               'value': node.getData('valueArray')[i],
12331                           'valuelabel': node.getData('valuelabelArray')[i],
12332                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12333               'link': url,
12334               'label': node.name
12335             };
12336           }
12337         } else {
12338           var limit = x + fixedDim * i;
12339           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12340             return {
12341               'name': node.getData('stringArray')[i],
12342               'color': node.getData('colorArray')[i],
12343               'value': node.getData('valueArray')[i],
12344                           'valuelabel': node.getData('valuelabelArray')[i],
12345                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12346               'link': url,
12347               'label': node.name
12348             };
12349           }
12350         }
12351       }
12352       return false;
12353     }
12354   }
12355 });
12356
12357 /*
12358   Class: BarChart
12359   
12360   A visualization that displays stacked bar charts.
12361   
12362   Constructor Options:
12363   
12364   See <Options.BarChart>.
12365
12366 */
12367 $jit.BarChart = new Class({
12368   st: null,
12369   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12370   selected: {},
12371   busy: false,
12372   
12373   initialize: function(opt) {
12374     this.controller = this.config = 
12375       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12376         Label: { type: 'Native' }
12377       }, opt);
12378     //set functions for showLabels and showAggregates
12379     var showLabels = this.config.showLabels,
12380         typeLabels = $.type(showLabels),
12381         showAggregates = this.config.showAggregates,
12382         typeAggregates = $.type(showAggregates);
12383     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12384     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12385     Options.Fx.clearCanvas = false;
12386     this.initializeViz();
12387   },
12388   
12389   initializeViz: function() {
12390     var config = this.config, that = this;
12391     var nodeType = config.type.split(":")[0],
12392         horz = config.orientation == 'horizontal',
12393         nodeLabels = {};
12394     var st = new $jit.ST({
12395       injectInto: config.injectInto,
12396       orientation: horz? 'left' : 'bottom',
12397       background: config.background,
12398       renderBackground: config.renderBackground,
12399       backgroundColor: config.backgroundColor,
12400       colorStop1: config.colorStop1,
12401       colorStop2: config.colorStop2,
12402       levelDistance: 0,
12403       nodeCount: config.nodeCount,
12404       siblingOffset: config.barsOffset,
12405       subtreeOffset: 0,
12406       withLabels: config.Label.type != 'Native',      
12407       useCanvas: config.useCanvas,
12408       Label: {
12409         type: config.Label.type
12410       },
12411       Node: {
12412         overridable: true,
12413         type: 'barchart-' + nodeType,
12414         align: 'left',
12415         width: 1,
12416         height: 1
12417       },
12418       Edge: {
12419         type: 'none'
12420       },
12421       Tips: {
12422         enable: config.Tips.enable,
12423         type: 'Native',
12424         force: true,
12425         onShow: function(tip, node, contains) {
12426           var elem = contains;
12427           config.Tips.onShow(tip, elem, node);
12428                           if(elem.link != 'undefined' && elem.link != '') {
12429                                 document.body.style.cursor = 'pointer';
12430                           }
12431         },
12432                 onHide: function(call) {
12433                         document.body.style.cursor = 'default';
12434
12435         }
12436       },
12437       Events: {
12438         enable: true,
12439         type: 'Native',
12440         onClick: function(node, eventInfo, evt) {
12441           if(!config.Events.enable) return;
12442           var elem = eventInfo.getContains();
12443           config.Events.onClick(elem, eventInfo, evt);
12444         },
12445         onMouseMove: function(node, eventInfo, evt) {
12446           if(!config.hoveredColor) return;
12447           if(node) {
12448             var elem = eventInfo.getContains();
12449             that.select(node.id, elem.name, elem.index);
12450           } else {
12451             that.select(false, false, false);
12452           }
12453         }
12454       },
12455       onCreateLabel: function(domElement, node) {
12456         var labelConf = config.Label,
12457             valueArray = node.getData('valueArray'),
12458             acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12459             grouped = config.type.split(':')[0] == 'grouped',
12460             horz = config.orientation == 'horizontal';
12461         var nlbs = {
12462           wrapper: document.createElement('div'),
12463           aggregate: document.createElement('div'),
12464           label: document.createElement('div')
12465         };
12466         
12467         var wrapper = nlbs.wrapper,
12468             label = nlbs.label,
12469             aggregate = nlbs.aggregate,
12470             wrapperStyle = wrapper.style,
12471             labelStyle = label.style,
12472             aggregateStyle = aggregate.style;
12473         //store node labels
12474         nodeLabels[node.id] = nlbs;
12475         //append labels
12476         wrapper.appendChild(label);
12477         wrapper.appendChild(aggregate);
12478         if(!config.showLabels(node.name, acum, node)) {
12479           labelStyle.display = 'none';
12480         }
12481         if(!config.showAggregates(node.name, acum, node)) {
12482           aggregateStyle.display = 'none';
12483         }
12484         wrapperStyle.position = 'relative';
12485         wrapperStyle.overflow = 'visible';
12486         wrapperStyle.fontSize = labelConf.size + 'px';
12487         wrapperStyle.fontFamily = labelConf.family;
12488         wrapperStyle.color = labelConf.color;
12489         wrapperStyle.textAlign = 'center';
12490         aggregateStyle.position = labelStyle.position = 'absolute';
12491         
12492         domElement.style.width = node.getData('width') + 'px';
12493         domElement.style.height = node.getData('height') + 'px';
12494         aggregateStyle.left = "0px";
12495         labelStyle.left =  config.labelOffset + 'px';
12496         labelStyle.whiteSpace =  "nowrap";
12497                 label.innerHTML = node.name;       
12498         
12499         domElement.appendChild(wrapper);
12500       },
12501       onPlaceLabel: function(domElement, node) {
12502         if(!nodeLabels[node.id]) return;
12503         var labels = nodeLabels[node.id],
12504             wrapperStyle = labels.wrapper.style,
12505             labelStyle = labels.label.style,
12506             aggregateStyle = labels.aggregate.style,
12507             grouped = config.type.split(':')[0] == 'grouped',
12508             horz = config.orientation == 'horizontal',
12509             dimArray = node.getData('dimArray'),
12510             valArray = node.getData('valueArray'),
12511             nodeCount = node.getData('nodeCount'),
12512             valueLength = valArray.length;
12513             valuelabelArray = node.getData('valuelabelArray'),
12514             stringArray = node.getData('stringArray'),
12515             width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12516             height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12517             font = parseInt(wrapperStyle.fontSize, 10),
12518             domStyle = domElement.style,
12519             fixedDim = (horz? height : width) / valueLength;
12520             
12521         
12522         if(dimArray && valArray) {
12523           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12524           
12525           aggregateStyle.width = width  - config.labelOffset + "px";
12526           for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12527             if(dimArray[i] > 0) {
12528               acum+= valArray[i];
12529             }
12530           }
12531           if(config.showLabels(node.name, acum, node)) {
12532             labelStyle.display = '';
12533           } else {
12534             labelStyle.display = 'none';
12535           }
12536           if(config.showAggregates(node.name, acum, node)) {
12537             aggregateStyle.display = '';
12538           } else {
12539             aggregateStyle.display = 'none';
12540           }
12541           if(config.orientation == 'horizontal') {
12542             aggregateStyle.textAlign = 'right';
12543             labelStyle.textAlign = 'left';
12544             labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12545             aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12546             domElement.style.height = wrapperStyle.height = height + 'px';
12547           } else {
12548             aggregateStyle.top = (-font - config.labelOffset) + 'px';
12549             labelStyle.top = (config.labelOffset + height) + 'px';
12550             domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12551             domElement.style.height = wrapperStyle.height = height + 'px';
12552             if(stringArray.length > 8) {
12553                 labels.label.className = "rotatedLabelReverse";
12554                 labelStyle.textAlign = "left";
12555                 labelStyle.top = config.labelOffset + height + width/2 + "px";
12556             }
12557           }
12558           
12559           if(horz) {
12560
12561                         labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12562                         labels.aggregate.innerHTML = "";
12563
12564           } else {
12565                 
12566                         if(grouped) {
12567                                 maxValue = Math.max.apply(null,dimArray);
12568                                 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12569                                         valueLabelDim = 50;
12570                                         valueLabel = document.createElement('div');
12571                                         valueLabel.innerHTML =  valuelabelArray[i];
12572 //                                      valueLabel.class = "rotatedLabel";
12573                                         valueLabel.className = "rotatedLabel";
12574                                         valueLabel.style.position = "absolute";
12575                                                 valueLabel.style.textAlign = "left";
12576                                                 valueLabel.style.verticalAlign = "middle";
12577                                         valueLabel.style.height = valueLabelDim + "px";
12578                                         valueLabel.style.width = valueLabelDim + "px";
12579                                         valueLabel.style.top =  (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12580                                         valueLabel.style.left = (fixedDim * i) + "px";
12581                                         labels.wrapper.appendChild(valueLabel);
12582                                 }
12583                         } else {
12584                                 labels.aggregate.innerHTML = acum;
12585                         }
12586           }
12587         }
12588       }
12589     });
12590
12591     var size = st.canvas.getSize(),
12592         l = config.nodeCount,
12593         margin = config.Margin;
12594         title = config.Title;
12595         subtitle = config.Subtitle,
12596         grouped = config.type.split(':')[0] == 'grouped',
12597         margin = config.Margin,
12598         ticks = config.Ticks,
12599         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12600         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12601         horz = config.orientation == 'horizontal',
12602         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12603         fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12604         whiteSpace = size.width - (marginWidth + (fixedDim * l));
12605         //bug in IE7 when vertical bar charts load in dashlets where number of bars exceed a certain width, canvas renders with an incorrect width, a hard refresh fixes the problem
12606         if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12607         location.reload();
12608         //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12609         if(!grouped && !horz) {
12610                 st.config.siblingOffset = whiteSpace/(l+1);
12611         }
12612         
12613         
12614         
12615         //Bars offset
12616     if(horz) {
12617       st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);    
12618           if(config.Ticks.enable)       {
12619                 st.config.offsetY = ((margin.bottom+config.Label.size+config.labelOffset+(subtitle.text? subtitle.size+subtitle.offset:0)) - (margin.top + (title.text? title.size+title.offset:0))) /2;
12620           } else {
12621                 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12622           }
12623     } else {
12624       st.config.offsetY = -size.height/2 + margin.bottom 
12625         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12626           if(config.Ticks.enable)       {
12627                 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12628           } else {
12629                 st.config.offsetX = (margin.right - margin.left)/2;
12630           }
12631     }
12632     this.st = st;
12633     this.canvas = this.st.canvas;
12634   },
12635   
12636   renderTitle: function() {
12637         var canvas = this.canvas,
12638         size = canvas.getSize(),
12639         config = this.config,
12640         margin = config.Margin,
12641         label = config.Label,
12642         title = config.Title;
12643         ctx = canvas.getCtx();
12644         ctx.fillStyle = title.color;
12645         ctx.textAlign = 'left';
12646         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12647         if(label.type == 'Native') {
12648                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12649         }
12650   },  
12651   
12652   renderSubtitle: function() {
12653         var canvas = this.canvas,
12654         size = canvas.getSize(),
12655         config = this.config,
12656         margin = config.Margin,
12657         label = config.Label,
12658         subtitle = config.Subtitle,
12659         nodeCount = config.nodeCount,
12660         horz = config.orientation == 'horizontal' ? true : false,
12661         ctx = canvas.getCtx();
12662         ctx.fillStyle = title.color;
12663         ctx.textAlign = 'left';
12664         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12665         if(label.type == 'Native') {
12666                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12667         }
12668   },
12669   
12670   renderScrollNote: function() {
12671         var canvas = this.canvas,
12672         size = canvas.getSize(),
12673         config = this.config,
12674         margin = config.Margin,
12675         label = config.Label,
12676         note = config.ScrollNote;
12677         ctx = canvas.getCtx();
12678         ctx.fillStyle = title.color;
12679         title = config.Title;
12680         ctx.textAlign = 'center';
12681         ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12682         if(label.type == 'Native') {
12683                 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12684         }
12685   },  
12686   
12687   renderTicks: function() {
12688
12689         var canvas = this.canvas,
12690         size = canvas.getSize(),
12691         config = this.config,
12692         margin = config.Margin,
12693         ticks = config.Ticks,
12694         title = config.Title,
12695         subtitle = config.Subtitle,
12696         label = config.Label,
12697         shadow = config.shadow;
12698         horz = config.orientation == 'horizontal',
12699         maxValue = this.getMaxValue(),
12700         maxTickValue = Math.ceil(maxValue*.1)*10;
12701         if(maxTickValue == maxValue) {
12702                 var length = maxTickValue.toString().length;
12703                 maxTickValue = maxTickValue + parseInt(pad(1,length));
12704         }
12705         grouped = config.type.split(':')[0] == 'grouped',
12706         labelValue = 0,
12707         labelIncrement = maxTickValue/ticks.segments,
12708         ctx = canvas.getCtx();
12709         ctx.strokeStyle = ticks.color;
12710     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12711
12712         ctx.textAlign = 'center';
12713         ctx.textBaseline = 'middle';
12714         
12715         idLabel = canvas.id + "-label";
12716         labelDim = 100;
12717         container = document.getElementById(idLabel);
12718                   
12719                   
12720         if(horz) {
12721                 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12722                 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12723                 segmentLength = grid/ticks.segments;
12724                 ctx.fillStyle = ticks.color;
12725                 ctx.fillRect(axis,
12726                  (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12727                  size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12728                  1);
12729                 while(axis<=grid) {
12730                         ctx.fillStyle = ticks.color;
12731                         lineHeight = size.height-margin.bottom-margin.top-config.labelOffset-label.size-(title.text? title.size+title.offset:0)-(subtitle.text? subtitle.size+subtitle.offset:0);
12732                         ctx.fillRect(Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0) - (shadow.enable ? shadow.size : 0), 1, lineHeight + (shadow.enable ? shadow.size * 2: 0));
12733                         ctx.fillStyle = label.color;
12734                         
12735                         if(label.type == 'Native' && config.showLabels) {            
12736                  ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12737                         }
12738                         axis += segmentLength;
12739                         labelValue += labelIncrement;
12740                 }
12741         
12742         } else {
12743         
12744                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12745                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12746                 grid = -size.height+(margin.bottom+config.labelOffset+label.size+margin.top+(title.text? title.size+title.offset:0)+(subtitle.text? subtitle.size+subtitle.offset:0)),
12747                 segmentLength = grid/ticks.segments;
12748                 ctx.fillStyle = ticks.color;
12749                 ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size-1, -(size.height/2)+margin.top+(title.text? title.size+title.offset:0),1,size.height-margin.top-margin.bottom-label.size-config.labelOffset-(title.text? title.size+title.offset:0)-(subtitle.text? subtitle.size+subtitle.offset:0));
12750
12751                 while(axis>=grid) {
12752                         ctx.save();
12753                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12754                         ctx.rotate(0 * Math.PI / 180 );
12755                         ctx.fillStyle = label.color;
12756                         if(config.showLabels) {
12757                                 if(label.type == 'Native') { 
12758                                         ctx.fillText(labelValue, 0, 0);
12759                                 } else {
12760                                         //html labels on y axis
12761                                         labelDiv = document.createElement('div');
12762                                         labelDiv.innerHTML = labelValue;
12763                                         labelDiv.className = "rotatedLabel";
12764 //                                      labelDiv.class = "rotatedLabel";
12765                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12766                                         labelDiv.style.left = margin.left + "px";
12767                                         labelDiv.style.width = labelDim + "px";
12768                                         labelDiv.style.height = labelDim + "px";
12769                                         labelDiv.style.textAlign = "center";
12770                                         labelDiv.style.verticalAlign = "middle";
12771                                         labelDiv.style.position = "absolute";
12772                                         container.appendChild(labelDiv);
12773                                 }
12774                         }
12775                         ctx.restore();
12776                         ctx.fillStyle = ticks.color;
12777                         ctx.fillRect(-(size.width/2)+margin.left+config.labelOffset+label.size, Math.round(axis), size.width-margin.right-margin.left-config.labelOffset-label.size,1 );
12778                         htmlOrigin += segmentLength;
12779                         axis += segmentLength;
12780                         labelValue += labelIncrement;
12781                 }
12782         }
12783         
12784         
12785         
12786
12787   },
12788   
12789   renderBackground: function() {
12790                 var canvas = this.canvas,
12791                 config = this.config,
12792                 backgroundColor = config.backgroundColor,
12793                 size = canvas.getSize(),
12794                 ctx = canvas.getCtx();
12795             ctx.fillStyle = backgroundColor;
12796             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
12797   },
12798   /*
12799     Method: loadJSON
12800    
12801     Loads JSON data into the visualization. 
12802     
12803     Parameters:
12804     
12805     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>.
12806     
12807     Example:
12808     (start code js)
12809     var barChart = new $jit.BarChart(options);
12810     barChart.loadJSON(json);
12811     (end code)
12812  */  
12813   loadJSON: function(json) {
12814     if(this.busy) return;
12815     this.busy = true;
12816     
12817     var prefix = $.time(), 
12818         ch = [], 
12819         st = this.st,
12820         name = $.splat(json.label), 
12821         color = $.splat(json.color || this.colors),
12822         config = this.config,
12823         gradient = !!config.type.split(":")[1],
12824         renderBackground = config.renderBackground,
12825         animate = config.animate,
12826         ticks = config.Ticks,
12827         title = config.Title,
12828         note = config.ScrollNote,
12829         subtitle = config.Subtitle,
12830         horz = config.orientation == 'horizontal',
12831         that = this,
12832                 colorLength = color.length,
12833                 nameLength = name.length;
12834         groupTotalValue = 0;
12835     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12836         var val = values[i];
12837         var valArray = $.splat(val.values);
12838         groupTotalValue += parseInt(valArray.sum());
12839     }
12840
12841     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12842       var val = values[i];
12843       var valArray = $.splat(values[i].values);
12844       var valuelabelArray = $.splat(values[i].valuelabels);
12845       var linkArray = $.splat(values[i].links);
12846       var titleArray = $.splat(values[i].titles);
12847       var barTotalValue = valArray.sum();
12848       var acum = 0;
12849       ch.push({
12850         'id': prefix + val.label,
12851         'name': val.label,
12852         
12853         'data': {
12854           'value': valArray,
12855           '$linkArray': linkArray,
12856                   '$gvl': val.gvaluelabel,
12857           '$titleArray': titleArray,
12858           '$valueArray': valArray,
12859           '$valuelabelArray': valuelabelArray,
12860           '$colorArray': color,
12861           '$colorMono': $.splat(color[i % colorLength]),
12862           '$stringArray': name,
12863           '$barTotalValue': barTotalValue,
12864           '$groupTotalValue': groupTotalValue,
12865           '$nodeCount': values.length,
12866           '$gradient': gradient,
12867           '$config': config
12868         },
12869         'children': []
12870       });
12871     }
12872     var root = {
12873       'id': prefix + '$root',
12874       'name': '',
12875       'data': {
12876         '$type': 'none',
12877         '$width': 1,
12878         '$height': 1
12879       },
12880       'children': ch
12881     };
12882     st.loadJSON(root);
12883     
12884     this.normalizeDims();
12885     
12886     if(renderBackground) {
12887                 this.renderBackground();
12888     }
12889         
12890         if(!animate && ticks.enable) {
12891                 this.renderTicks();
12892         }
12893         if(!animate && note.text) {
12894                 this.renderScrollNote();
12895         }
12896         if(!animate && title.text) {
12897                 this.renderTitle();
12898         }
12899         if(!animate && subtitle.text) {
12900                 this.renderSubtitle();
12901         }
12902     st.compute();
12903     st.select(st.root);
12904     if(animate) {
12905       if(horz) {
12906         st.fx.animate({
12907           modes: ['node-property:width:dimArray'],
12908           duration:1500,
12909           onComplete: function() {
12910             that.busy = false;
12911           }
12912         });
12913       } else {
12914         st.fx.animate({
12915           modes: ['node-property:height:dimArray'],
12916           duration:1500,
12917           onComplete: function() {
12918             that.busy = false;
12919           }
12920         });
12921       }
12922     } else {
12923       this.busy = false;
12924     }
12925   },
12926   
12927   /*
12928     Method: updateJSON
12929    
12930     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.
12931     
12932     Parameters:
12933     
12934     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
12935     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
12936     
12937     Example:
12938     
12939     (start code js)
12940     barChart.updateJSON(json, {
12941       onComplete: function() {
12942         alert('update complete!');
12943       }
12944     });
12945     (end code)
12946  */  
12947   updateJSON: function(json, onComplete) {
12948     if(this.busy) return;
12949     this.busy = true;
12950     
12951     var st = this.st;
12952     var graph = st.graph;
12953     var values = json.values;
12954     var animate = this.config.animate;
12955     var that = this;
12956     var horz = this.config.orientation == 'horizontal';
12957     $.each(values, function(v) {
12958       var n = graph.getByName(v.label);
12959       if(n) {
12960         n.setData('valueArray', $.splat(v.values));
12961         if(json.label) {
12962           n.setData('stringArray', $.splat(json.label));
12963         }
12964       }
12965     });
12966     this.normalizeDims();
12967     st.compute();
12968     st.select(st.root);
12969     if(animate) {
12970       if(horz) {
12971         st.fx.animate({
12972           modes: ['node-property:width:dimArray'],
12973           duration:1500,
12974           onComplete: function() {
12975             that.busy = false;
12976             onComplete && onComplete.onComplete();
12977           }
12978         });
12979       } else {
12980         st.fx.animate({
12981           modes: ['node-property:height:dimArray'],
12982           duration:1500,
12983           onComplete: function() {
12984             that.busy = false;
12985             onComplete && onComplete.onComplete();
12986           }
12987         });
12988       }
12989     }
12990   },
12991   
12992   //adds the little brown bar when hovering the node
12993   select: function(id, name) {
12994
12995     if(!this.config.hoveredColor) return;
12996     var s = this.selected;
12997     if(s.id != id || s.name != name) {
12998       s.id = id;
12999       s.name = name;
13000       s.color = this.config.hoveredColor;
13001       this.st.graph.eachNode(function(n) {
13002         if(id == n.id) {
13003           n.setData('border', s);
13004         } else {
13005           n.setData('border', false);
13006         }
13007       });
13008       this.st.plot();
13009     }
13010   },
13011   
13012   /*
13013     Method: getLegend
13014    
13015     Returns an object containing as keys the legend names and as values hex strings with color values.
13016     
13017     Example:
13018     
13019     (start code js)
13020     var legend = barChart.getLegend();
13021     (end code)
13022   */  
13023   getLegend: function() {
13024     var legend = new Array();
13025     var name = new Array();
13026     var color = new Array();
13027     var n;
13028     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13029       n = adj.nodeTo;
13030     });
13031     var colors = n.getData('colorArray'),
13032         len = colors.length;
13033     $.each(n.getData('stringArray'), function(s, i) {
13034       color[i] = colors[i % len];
13035       name[i] = s;
13036     });
13037         legend['name'] = name;
13038         legend['color'] = color;
13039     return legend;
13040   },
13041   
13042   /*
13043     Method: getMaxValue
13044    
13045     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13046     
13047     Example:
13048     
13049     (start code js)
13050     var ans = barChart.getMaxValue();
13051     (end code)
13052     
13053     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13054     
13055     Example:
13056     
13057     (start code js)
13058     //will return 100 for all BarChart instances,
13059     //displaying all of them with the same scale
13060     $jit.BarChart.implement({
13061       'getMaxValue': function() {
13062         return 100;
13063       }
13064     });
13065     (end code)
13066     
13067   */  
13068   getMaxValue: function() {
13069     var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13070     this.st.graph.eachNode(function(n) {
13071       var valArray = n.getData('valueArray'),
13072           acum = 0;
13073       if(!valArray) return;
13074       if(stacked) {
13075         $.each(valArray, function(v) { 
13076           acum += +v;
13077         });
13078       } else {
13079         acum = Math.max.apply(null, valArray);
13080       }
13081       maxValue = maxValue>acum? maxValue:acum;
13082     });
13083     return maxValue;
13084   },
13085   
13086   setBarType: function(type) {
13087     this.config.type = type;
13088     this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13089   },
13090   
13091   normalizeDims: function() {
13092     //number of elements
13093     var root = this.st.graph.getNode(this.st.root), l=0;
13094     root.eachAdjacency(function() {
13095       l++;
13096     });
13097     var maxValue = this.getMaxValue() || 1,
13098         size = this.st.canvas.getSize(),
13099         config = this.config,
13100         margin = config.Margin,
13101         ticks = config.Ticks,
13102         title = config.Title,
13103         subtitle = config.Subtitle,
13104         grouped = config.type.split(':')[0] == 'grouped',
13105         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13106         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13107         horz = config.orientation == 'horizontal',
13108         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13109         animate = config.animate,
13110         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13111
13112           - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13113         dim1 = horz? 'height':'width',
13114         dim2 = horz? 'width':'height',
13115         basic = config.type.split(':')[0] == 'basic';
13116         
13117         
13118                 var maxTickValue = Math.ceil(maxValue*.1)*10;
13119                 if(maxTickValue == maxValue) {
13120                         var length = maxTickValue.toString().length;
13121                         maxTickValue = maxTickValue + parseInt(pad(1,length));
13122                 }
13123
13124                 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13125
13126                 
13127     this.st.graph.eachNode(function(n) {
13128       var acum = 0, animateValue = [];
13129       $.each(n.getData('valueArray'), function(v) {
13130         acum += +v;
13131         animateValue.push(0);
13132       });
13133       
13134       if(grouped) {
13135         fixedDim = animateValue.length * 40;
13136       }
13137       n.setData(dim1, fixedDim);
13138       
13139       
13140       if(animate) {
13141         n.setData(dim2, acum * height / maxValue, 'end');
13142         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13143           return n * height / maxValue; 
13144         }), 'end');
13145         var dimArray = n.getData('dimArray');
13146         if(!dimArray) {
13147           n.setData('dimArray', animateValue);
13148         }
13149       } else {
13150         
13151
13152                 if(ticks.enable) {
13153                         n.setData(dim2, acum * height / maxTickValue);
13154                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13155                           return n * height / maxTickValue; 
13156                         }));
13157                 } else {
13158                         n.setData(dim2, acum * height / maxValue);
13159                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13160                           return n * height / maxValue; 
13161                         }));
13162                 }
13163       }
13164     });
13165   }
13166 });
13167
13168 //funnel chart options
13169
13170
13171 Options.FunnelChart = {
13172   $extend: true,
13173   
13174   animate: true,
13175   type: 'stacked', //stacked, grouped, : gradient
13176   labelOffset: 3, //label offset
13177   barsOffset: 0, //distance between bars
13178   hoveredColor: '#9fd4ff',
13179   orientation: 'vertical',
13180   showAggregates: true,
13181   showLabels: true,
13182   Tips: {
13183     enable: false,
13184     onShow: $.empty,
13185     onHide: $.empty
13186   },
13187   Events: {
13188     enable: false,
13189     onClick: $.empty
13190   }
13191 };
13192
13193 $jit.ST.Plot.NodeTypes.implement({
13194   'funnelchart-basic' : {
13195     'render' : function(node, canvas) {
13196       var pos = node.pos.getc(true), 
13197           width  = node.getData('width'),
13198           height = node.getData('height'),
13199           algnPos = this.getAlignedPos(pos, width, height),
13200           x = algnPos.x, y = algnPos.y,
13201           dimArray = node.getData('dimArray'),
13202           valueArray = node.getData('valueArray'),
13203           valuelabelArray = node.getData('valuelabelArray'),
13204           linkArray = node.getData('linkArray'),
13205           colorArray = node.getData('colorArray'),
13206           colorLength = colorArray.length,
13207           stringArray = node.getData('stringArray');
13208       var ctx = canvas.getCtx(),
13209           opt = {},
13210           border = node.getData('border'),
13211           gradient = node.getData('gradient'),
13212           config = node.getData('config'),
13213           horz = config.orientation == 'horizontal',
13214           aggregates = config.showAggregates,
13215           showLabels = config.showLabels,
13216           label = config.Label,
13217           size = canvas.getSize(),
13218           labelOffset = config.labelOffset + 10;
13219           minWidth =  width * .25;
13220           ratio = .65;
13221
13222       if (colorArray && dimArray && stringArray) {
13223         
13224         
13225         // horizontal lines
13226         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13227         ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13228
13229         if(label.type == 'Native') {      
13230        if(showLabels(node.name, valAcum, node)) {
13231                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13232                  var stringValue = stringArray[i];
13233                  var valueLabel = String(valuelabelArray[i]);
13234              var mV = ctx.measureText(stringValue);
13235              var mVL = ctx.measureText(valueLabel);
13236                  var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13237                          var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13238                          var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13239                  var bottomWidth = minWidth + ((acum) * ratio);  
13240                  var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);  
13241                          var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13242                          var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13243 //             ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13244
13245                         //right lines
13246                         ctx.beginPath();
13247                         ctx.moveTo(bottomWidth/2,y - acum); //
13248                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13249                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight);  // bottom right
13250                         ctx.stroke();
13251                         //left lines
13252                         ctx.beginPath();
13253                         ctx.moveTo(-bottomWidth/2,y - acum); //
13254                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13255                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight);  // bottom right
13256                         ctx.stroke();
13257        }
13258         }
13259
13260                 acum += (dimArray[i] || 0);
13261           valAcum += (valueArray[i] || 0);
13262           
13263           
13264                 }
13265                 
13266  
13267   
13268         //funnel segments and labels
13269         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13270           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13271                           var colori = colorArray[i % colorLength];
13272                           if(label.type == 'Native') { 
13273                                   var stringValue = stringArray[i];
13274                           var valueLabel = String(valuelabelArray[i]);
13275                               var mV = ctx.measureText(stringValue);
13276                       var mVL = ctx.measureText(valueLabel);
13277                           } else {
13278                                   var mV = 10;
13279                       var mVL = 10;     
13280                           }
13281                       var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13282                       var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13283                       var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13284                       var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13285                       
13286           var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13287           var bottomWidth = minWidth + ((acum) * ratio);
13288           var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13289           
13290
13291           if(gradient) {
13292             var linear;
13293               linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13294                         var colorRgb = $.hexToRgb(colori);
13295             var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
13296                 function(v) { return (v * .5) >> 0; });
13297             linear.addColorStop(0, 'rgba('+color+',1)');
13298             linear.addColorStop(0.5,  'rgba('+colorRgb+',1)');
13299             linear.addColorStop(1, 'rgba('+color+',1)');
13300             ctx.fillStyle = linear;
13301           }
13302           
13303                         ctx.beginPath();
13304                         ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13305                         ctx.lineTo(topWidth/2,y - acum - dimArray[i]);  // top right
13306                         ctx.lineTo(bottomWidth/2,y - acum);  // bottom right
13307                         ctx.lineTo(-bottomWidth/2,y - acum);  // bottom left
13308                         ctx.closePath(); 
13309                         ctx.fill();
13310                 
13311           
13312           if(border && border.name == stringArray[i]) {
13313             opt.acum = acum;
13314             opt.dimValue = dimArray[i];
13315           }
13316           
13317           
13318         if(border) {
13319           ctx.save();
13320           ctx.lineWidth = 2;
13321           ctx.strokeStyle = border.color;
13322
13323             //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13324          
13325           ctx.restore();
13326         }
13327         if(label.type == 'Native') {
13328           ctx.save();
13329           ctx.fillStyle = ctx.strokeStyle = label.color;
13330           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13331           ctx.textBaseline = 'middle';
13332
13333                                 acumValueLabel = valAcum;
13334
13335           if(showLabels(node.name, valAcum, node)) {
13336
13337                       
13338               ctx.textAlign = 'left';
13339               ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13340               ctx.textAlign = 'right';
13341               ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13342               }
13343           ctx.restore();
13344         }
13345
13346           acum += (dimArray[i] || 0);
13347           valAcum += (valueArray[i] || 0);
13348           
13349         }
13350
13351       }
13352     },
13353     'contains': function(node, mpos) {
13354       var pos = node.pos.getc(true), 
13355           width = node.getData('width'),
13356           height = node.getData('height'),
13357           algnPos = this.getAlignedPos(pos, width, height),
13358           x = algnPos.x, y = algnPos.y,
13359           dimArray = node.getData('dimArray'),
13360           config = node.getData('config'),
13361           st = node.getData('st'),
13362           rx = mpos.x - x,
13363           horz = config.orientation == 'horizontal',
13364            minWidth =  width * .25;
13365           ratio = .65,
13366           canvas = node.getData('canvas'),
13367           size = canvas.getSize(),
13368           offsetY = st.config.offsetY;
13369       //bounding box check
13370
13371         if(mpos.y > y || mpos.y < y - height) {
13372             return false;
13373           }
13374           
13375          var newY = Math.abs(mpos.y + offsetY);
13376         var bound = minWidth + (newY * ratio);
13377         var boundLeft = -bound/2;
13378         var boundRight = bound/2;
13379          if(mpos.x < boundLeft || mpos.x > boundRight ) {
13380             return false;
13381           }
13382
13383       
13384       //deep check
13385       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13386         var dimi = dimArray[i];
13387
13388           
13389           
13390                 var url = Url.decode(node.getData('linkArray')[i]);
13391           acum -= dimi;  
13392           var intersec = acum;
13393           if(mpos.y >= intersec) {
13394             return {
13395               'name': node.getData('stringArray')[i],
13396               'color': node.getData('colorArray')[i],
13397               'value': node.getData('valueArray')[i],
13398               'percentage': node.getData('percentageArray')[i],
13399                           'valuelabel': node.getData('valuelabelArray')[i],
13400               'link': url,
13401               'label': node.name
13402             };
13403           }
13404         
13405       }
13406       return false;
13407     }
13408   }
13409 });
13410
13411 /*
13412   Class: FunnelChart
13413   
13414   A visualization that displays funnel charts.
13415   
13416   Constructor Options:
13417   
13418   See <Options.FunnelChart>.
13419
13420 */
13421 $jit.FunnelChart = new Class({
13422   st: null,
13423   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13424   selected: {},
13425   busy: false,
13426   
13427   initialize: function(opt) {
13428     this.controller = this.config = 
13429       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13430         Label: { type: 'Native' }
13431       }, opt);
13432     //set functions for showLabels and showAggregates
13433     var showLabels = this.config.showLabels,
13434         typeLabels = $.type(showLabels),
13435         showAggregates = this.config.showAggregates,
13436         typeAggregates = $.type(showAggregates);
13437     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13438     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13439     Options.Fx.clearCanvas = false;
13440     this.initializeViz();
13441   },
13442   
13443   initializeViz: function() {
13444     var config = this.config, that = this;
13445     var nodeType = config.type.split(":")[0],
13446         horz = config.orientation == 'horizontal',
13447         nodeLabels = {};
13448     var st = new $jit.ST({
13449       injectInto: config.injectInto,
13450       orientation: horz? 'left' : 'bottom',
13451       levelDistance: 0,
13452       background: config.background,
13453       renderBackground: config.renderBackground,
13454       backgroundColor: config.backgroundColor,
13455       colorStop1: config.colorStop1,
13456       colorStop2: config.colorStop2,
13457       siblingOffset: config.segmentOffset,
13458       subtreeOffset: 0,
13459       withLabels: config.Label.type != 'Native',      
13460       useCanvas: config.useCanvas,
13461       Label: {
13462         type: config.Label.type
13463       },
13464       Node: {
13465         overridable: true,
13466         type: 'funnelchart-' + nodeType,
13467         align: 'left',
13468         width: 1,
13469         height: 1
13470       },
13471       Edge: {
13472         type: 'none'
13473       },
13474       Tips: {
13475         enable: config.Tips.enable,
13476         type: 'Native',
13477         force: true,
13478         onShow: function(tip, node, contains) {
13479           var elem = contains;
13480           config.Tips.onShow(tip, elem, node);
13481                           if(elem.link != 'undefined' && elem.link != '') {
13482                                 document.body.style.cursor = 'pointer';
13483                           }
13484         },
13485                 onHide: function(call) {
13486                         document.body.style.cursor = 'default';
13487
13488         }
13489       },
13490       Events: {
13491         enable: true,
13492         type: 'Native',
13493         onClick: function(node, eventInfo, evt) {
13494           if(!config.Events.enable) return;
13495           var elem = eventInfo.getContains();
13496           config.Events.onClick(elem, eventInfo, evt);
13497         },
13498         onMouseMove: function(node, eventInfo, evt) {
13499           if(!config.hoveredColor) return;
13500           if(node) {
13501             var elem = eventInfo.getContains();
13502             that.select(node.id, elem.name, elem.index);
13503           } else {
13504             that.select(false, false, false);
13505           }
13506         }
13507       },
13508       onCreateLabel: function(domElement, node) {
13509         var labelConf = config.Label,
13510             valueArray = node.getData('valueArray'),
13511             idArray = node.getData('idArray'),
13512             valuelabelArray = node.getData('valuelabelArray'),
13513             stringArray = node.getData('stringArray');
13514             size = st.canvas.getSize()
13515             prefix = $.time();
13516                 
13517                 for(var i=0, l=valueArray.length; i<l; i++) {
13518         var nlbs = {
13519           wrapper: document.createElement('div'),
13520           valueLabel: document.createElement('div'),
13521           label: document.createElement('div')
13522         };
13523         var wrapper = nlbs.wrapper,
13524             label = nlbs.label,
13525             valueLabel = nlbs.valueLabel,
13526             wrapperStyle = wrapper.style,
13527             labelStyle = label.style,
13528             valueLabelStyle = valueLabel.style;
13529         //store node labels
13530         nodeLabels[idArray[i]] = nlbs;
13531         //append labels
13532         wrapper.appendChild(label);
13533         wrapper.appendChild(valueLabel);
13534
13535         wrapperStyle.position = 'relative';
13536         wrapperStyle.overflow = 'visible';
13537         wrapperStyle.fontSize = labelConf.size + 'px';
13538         wrapperStyle.fontFamily = labelConf.family;
13539         wrapperStyle.color = labelConf.color;
13540         wrapperStyle.textAlign = 'center';
13541         wrapperStyle.width = size.width + 'px';
13542         valueLabelStyle.position = labelStyle.position = 'absolute';
13543         valueLabelStyle.left = labelStyle.left =  '0px';
13544                 valueLabelStyle.width = (size.width/3) + 'px';
13545                 valueLabelStyle.textAlign = 'right';
13546         label.innerHTML = stringArray[i];
13547         valueLabel.innerHTML = valuelabelArray[i];
13548         domElement.id = prefix+'funnel';
13549         domElement.style.width = size.width + 'px';
13550         
13551                 domElement.appendChild(wrapper);
13552                 }
13553
13554       },
13555       onPlaceLabel: function(domElement, node) {
13556
13557             var dimArray = node.getData('dimArray'),
13558             idArray = node.getData('idArray'),
13559             valueArray = node.getData('valueArray'),
13560             valuelabelArray = node.getData('valuelabelArray'),
13561             stringArray = node.getData('stringArray');
13562             size = st.canvas.getSize(),
13563             pos = node.pos.getc(true),
13564              domElement.style.left = "0px",
13565              domElement.style.top = "0px",
13566              minWidth = node.getData('width') * .25,
13567              ratio = .65,
13568              pos = node.pos.getc(true),
13569              labelConf = config.Label;
13570              
13571              
13572                 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13573
13574         var labels = nodeLabels[idArray[i]],
13575             wrapperStyle = labels.wrapper.style,
13576             labelStyle = labels.label.style,
13577             valueLabelStyle = labels.valueLabel.style;
13578                 var bottomWidth = minWidth + (acum * ratio); 
13579                 
13580             font = parseInt(wrapperStyle.fontSize, 10),
13581             domStyle = domElement.style;
13582            
13583                 
13584        
13585                         wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13586             valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13587             labelStyle.left =  (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13588
13589                         acum += (dimArray[i] || 0);
13590
13591                 }
13592
13593       }
13594
13595     });
13596
13597     var size = st.canvas.getSize(),
13598         margin = config.Margin;
13599         title = config.Title;
13600         subtitle = config.Subtitle;
13601         //y offset
13602
13603       st.config.offsetY = -size.height/2 + margin.bottom 
13604         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13605
13606                 st.config.offsetX = (margin.right - margin.left)/2;
13607           
13608     
13609     this.st = st;
13610     this.canvas = this.st.canvas;
13611   },
13612   
13613   renderTitle: function() {
13614         var canvas = this.canvas,
13615         size = canvas.getSize(),
13616         config = this.config,
13617         margin = config.Margin,
13618         label = config.Label,
13619         title = config.Title;
13620         ctx = canvas.getCtx();
13621         ctx.fillStyle = title.color;
13622         ctx.textAlign = 'left';
13623         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13624         if(label.type == 'Native') {
13625                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13626         }
13627   },  
13628   
13629   renderSubtitle: function() {
13630         var canvas = this.canvas,
13631         size = canvas.getSize(),
13632         config = this.config,
13633         margin = config.Margin,
13634         label = config.Label,
13635         subtitle = config.Subtitle;
13636         ctx = canvas.getCtx();
13637         ctx.fillStyle = title.color;
13638         ctx.textAlign = 'left';
13639         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13640         if(label.type == 'Native') {
13641                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13642         }
13643   },
13644   
13645   
13646   renderDropShadow: function() {
13647         var canvas = this.canvas,
13648         size = canvas.getSize(),
13649         config = this.config,
13650         margin = config.Margin,
13651         horz = config.orientation == 'horizontal',
13652         label = config.Label,
13653         title = config.Title,
13654         shadowThickness = 4,
13655         subtitle = config.Subtitle,
13656         ctx = canvas.getCtx(),
13657         minwidth = (size.width/8) * .25,
13658         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13659         topMargin = (title.text? title.size + title.offset : 0)  + margin.top,
13660     height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13661           - (config.showLabels && (config.Label.size + config.labelOffset)),
13662     ratio = .65,
13663         topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13664         topY = (-size.height/2) + topMargin - shadowThickness;
13665         bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13666         bottomWidth = minwidth + shadowThickness;
13667         ctx.beginPath();
13668         ctx.fillStyle = "rgba(0,0,0,.2)";
13669         ctx.moveTo(0,topY);
13670         ctx.lineTo(-topWidth/2,topY); //top left
13671         ctx.lineTo(-bottomWidth/2,bottomY);  // bottom left
13672         ctx.lineTo(bottomWidth/2,bottomY);  // bottom right
13673         ctx.lineTo(topWidth/2,topY);  // top right
13674         ctx.closePath(); 
13675         ctx.fill();
13676                         
13677                         
13678   },
13679
13680    renderBackground: function() {
13681                 var canvas = this.canvas,
13682                 config = this.config,
13683                 backgroundColor = config.backgroundColor,
13684                 size = canvas.getSize(),
13685                 ctx = canvas.getCtx();
13686                 //ctx.globalCompositeOperation = "destination-over";
13687             ctx.fillStyle = backgroundColor;
13688             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
13689   },
13690   
13691   
13692   loadJSON: function(json) {
13693     if(this.busy) return;
13694     this.busy = true;
13695     var prefix = $.time(), 
13696         ch = [], 
13697         st = this.st,
13698         name = $.splat(json.label), 
13699         color = $.splat(json.color || this.colors),
13700         config = this.config,
13701         canvas = this.canvas,
13702         gradient = !!config.type.split(":")[1],
13703         animate = config.animate,
13704         title = config.Title,
13705         subtitle = config.Subtitle,
13706         renderBackground = config.renderBackground,
13707         horz = config.orientation == 'horizontal',
13708         that = this,
13709                 colorLength = color.length,
13710                 nameLength = name.length,
13711                 totalValue = 0;
13712
13713     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13714         var val = values[i];
13715         var valArray = $.splat(val.values);
13716         totalValue += parseInt(valArray.sum());
13717     }
13718     
13719     
13720     var idArray = new Array();
13721     var valArray = new Array();
13722     var valuelabelArray = new Array();
13723     var linkArray = new Array();
13724     var titleArray = new Array();
13725     var percentageArray = new Array();
13726     
13727     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13728       var val = values[i];
13729       idArray[i] = $.splat(prefix + val.label);
13730       valArray[i] = $.splat(val.values);
13731       valuelabelArray[i] = $.splat(val.valuelabels);
13732       linkArray[i] = $.splat(val.links);
13733       titleArray[i] = $.splat(val.titles);
13734       percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13735       var acum = 0;
13736     }
13737     
13738     
13739
13740     valArray.reverse();
13741     valuelabelArray.reverse();
13742     linkArray.reverse();
13743     titleArray.reverse();
13744     percentageArray.reverse();
13745     
13746      
13747      
13748       ch.push({
13749         'id': prefix + val.label,
13750         'name': val.label,
13751         
13752         'data': {
13753           'value': valArray,
13754           '$idArray': idArray,
13755           '$linkArray': linkArray,
13756           '$titleArray': titleArray,
13757           '$valueArray': valArray,
13758           '$valuelabelArray': valuelabelArray,
13759           '$colorArray': color,
13760           '$colorMono': $.splat(color[i % colorLength]),
13761           '$stringArray': name.reverse(),
13762           '$gradient': gradient,
13763           '$config': config,
13764           '$percentageArray' : percentageArray,
13765           '$canvas': canvas,
13766           '$st': st
13767         },
13768         'children': []
13769       });
13770     
13771     var root = {
13772       'id': prefix + '$root',
13773       'name': '',
13774       'data': {
13775         '$type': 'none',
13776         '$width': 1,
13777         '$height': 1
13778       },
13779       'children': ch
13780     };
13781     st.loadJSON(root);
13782     
13783     this.normalizeDims();
13784         
13785         if(renderBackground) {
13786                 this.renderBackground();        
13787         }
13788         if(!animate && title.text) {
13789                 this.renderTitle();
13790         }
13791         if(!animate && subtitle.text) {
13792                 this.renderSubtitle();
13793         }
13794         if(typeof FlashCanvas == "undefined") {
13795                 this.renderDropShadow();
13796         }
13797     st.compute();
13798     st.select(st.root);
13799     if(animate) {
13800       if(horz) {
13801         st.fx.animate({
13802           modes: ['node-property:width:dimArray'],
13803           duration:1500,
13804           onComplete: function() {
13805             that.busy = false;
13806           }
13807         });
13808       } else {
13809         st.fx.animate({
13810           modes: ['node-property:height:dimArray'],
13811           duration:1500,
13812           onComplete: function() {
13813             that.busy = false;
13814           }
13815         });
13816       }
13817     } else {
13818       this.busy = false;
13819     }
13820   },
13821   
13822   /*
13823     Method: updateJSON
13824    
13825     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.
13826     
13827     Parameters:
13828     
13829     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13830     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13831     
13832     Example:
13833     
13834     (start code js)
13835     barChart.updateJSON(json, {
13836       onComplete: function() {
13837         alert('update complete!');
13838       }
13839     });
13840     (end code)
13841  */  
13842   updateJSON: function(json, onComplete) {
13843     if(this.busy) return;
13844     this.busy = true;
13845     
13846     var st = this.st;
13847     var graph = st.graph;
13848     var values = json.values;
13849     var animate = this.config.animate;
13850     var that = this;
13851     var horz = this.config.orientation == 'horizontal';
13852     $.each(values, function(v) {
13853       var n = graph.getByName(v.label);
13854       if(n) {
13855         n.setData('valueArray', $.splat(v.values));
13856         if(json.label) {
13857           n.setData('stringArray', $.splat(json.label));
13858         }
13859       }
13860     });
13861     this.normalizeDims();
13862     st.compute();
13863     st.select(st.root);
13864     if(animate) {
13865       if(horz) {
13866         st.fx.animate({
13867           modes: ['node-property:width:dimArray'],
13868           duration:1500,
13869           onComplete: function() {
13870             that.busy = false;
13871             onComplete && onComplete.onComplete();
13872           }
13873         });
13874       } else {
13875         st.fx.animate({
13876           modes: ['node-property:height:dimArray'],
13877           duration:1500,
13878           onComplete: function() {
13879             that.busy = false;
13880             onComplete && onComplete.onComplete();
13881           }
13882         });
13883       }
13884     }
13885   },
13886   
13887   //adds the little brown bar when hovering the node
13888   select: function(id, name) {
13889
13890     if(!this.config.hoveredColor) return;
13891     var s = this.selected;
13892     if(s.id != id || s.name != name) {
13893       s.id = id;
13894       s.name = name;
13895       s.color = this.config.hoveredColor;
13896       this.st.graph.eachNode(function(n) {
13897         if(id == n.id) {
13898           n.setData('border', s);
13899         } else {
13900           n.setData('border', false);
13901         }
13902       });
13903       this.st.plot();
13904     }
13905   },
13906   
13907   /*
13908     Method: getLegend
13909    
13910     Returns an object containing as keys the legend names and as values hex strings with color values.
13911     
13912     Example:
13913     
13914     (start code js)
13915     var legend = barChart.getLegend();
13916     (end code)
13917   */  
13918   getLegend: function() {
13919     var legend = new Array();
13920     var name = new Array();
13921     var color = new Array();
13922     var n;
13923     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13924       n = adj.nodeTo;
13925     });
13926     var colors = n.getData('colorArray'),
13927         len = colors.length;
13928     $.each(n.getData('stringArray'), function(s, i) {
13929       color[i] = colors[i % len];
13930       name[i] = s;
13931     });
13932         legend['name'] = name;
13933         legend['color'] = color;
13934     return legend;
13935   },
13936   
13937   /*
13938     Method: getMaxValue
13939    
13940     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13941     
13942     Example:
13943     
13944     (start code js)
13945     var ans = barChart.getMaxValue();
13946     (end code)
13947     
13948     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13949     
13950     Example:
13951     
13952     (start code js)
13953     //will return 100 for all BarChart instances,
13954     //displaying all of them with the same scale
13955     $jit.BarChart.implement({
13956       'getMaxValue': function() {
13957         return 100;
13958       }
13959     });
13960     (end code)
13961     
13962   */  
13963   getMaxValue: function() {
13964     var maxValue = 0, stacked = true;
13965     this.st.graph.eachNode(function(n) {
13966       var valArray = n.getData('valueArray'),
13967           acum = 0;
13968       if(!valArray) return;
13969       if(stacked) {
13970         $.each(valArray, function(v) { 
13971           acum += +v;
13972         });
13973       } else {
13974         acum = Math.max.apply(null, valArray);
13975       }
13976       maxValue = maxValue>acum? maxValue:acum;
13977     });
13978     return maxValue;
13979   },
13980   
13981   setBarType: function(type) {
13982     this.config.type = type;
13983     this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
13984   },
13985   
13986   normalizeDims: function() {
13987     //number of elements
13988     var root = this.st.graph.getNode(this.st.root), l=0;
13989     root.eachAdjacency(function() {
13990       l++;
13991     });
13992     var maxValue = this.getMaxValue() || 1,
13993         size = this.st.canvas.getSize(),
13994         config = this.config,
13995         margin = config.Margin,
13996         title = config.Title,
13997         subtitle = config.Subtitle,
13998         marginWidth = margin.left + margin.right,
13999         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14000         horz = config.orientation == 'horizontal',
14001         animate = config.animate,
14002         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14003
14004           - (config.showLabels && (config.Label.size + config.labelOffset)),
14005         dim1 = horz? 'height':'width',
14006         dim2 = horz? 'width':'height';
14007         
14008
14009         minWidth = size.width/8;
14010         
14011
14012
14013     this.st.graph.eachNode(function(n) {
14014       var acum = 0, animateValue = [];
14015       $.each(n.getData('valueArray'), function(v) {
14016         acum += +v;
14017         animateValue.push(0);
14018       });
14019       n.setData(dim1, minWidth);
14020             
14021       if(animate) {
14022         n.setData(dim2, acum * height / maxValue, 'end');
14023         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14024           return n * height / maxValue; 
14025         }), 'end');
14026         var dimArray = n.getData('dimArray');
14027         if(!dimArray) {
14028           n.setData('dimArray', animateValue);
14029         }
14030       } else {
14031                         n.setData(dim2, acum * height / maxValue);
14032                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14033                           return n * height / maxValue; 
14034                         }));
14035       }
14036
14037     });
14038   }
14039 });
14040
14041
14042
14043 /*
14044  * File: Options.PieChart.js
14045  *
14046 */
14047 /*
14048   Object: Options.PieChart
14049   
14050   <PieChart> options. 
14051   Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14052   
14053   Syntax:
14054   
14055   (start code js)
14056
14057   Options.PieChart = {
14058     animate: true,
14059     offset: 25,
14060     sliceOffset:0,
14061     labelOffset: 3,
14062     type: 'stacked',
14063     hoveredColor: '#9fd4ff',
14064     showLabels: true,
14065     resizeLabels: false,
14066     updateHeights: false
14067   };  
14068
14069   (end code)
14070   
14071   Example:
14072   
14073   (start code js)
14074
14075   var pie = new $jit.PieChart({
14076     animate: true,
14077     sliceOffset: 5,
14078     type: 'stacked:gradient'
14079   });  
14080
14081   (end code)
14082   
14083   Parameters:
14084   
14085   animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14086   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14087   sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14088   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14089   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14090   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14091   showLabels - (boolean) Default's *true*. Display the name of the slots.
14092   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.
14093   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.
14094
14095 */
14096 Options.PieChart = {
14097   $extend: true,
14098
14099   animate: true,
14100   offset: 25, // page offset
14101   sliceOffset:0,
14102   labelOffset: 3, // label offset
14103   type: 'stacked', // gradient
14104   labelType: 'name',
14105   hoveredColor: '#9fd4ff',
14106   Events: {
14107     enable: false,
14108     onClick: $.empty
14109   },
14110   Tips: {
14111     enable: false,
14112     onShow: $.empty,
14113     onHide: $.empty
14114   },
14115   showLabels: true,
14116   resizeLabels: false,
14117   
14118   //only valid for mono-valued datasets
14119   updateHeights: false
14120 };
14121
14122 /*
14123  * Class: Layouts.Radial
14124  * 
14125  * Implements a Radial Layout.
14126  * 
14127  * Implemented By:
14128  * 
14129  * <RGraph>, <Hypertree>
14130  * 
14131  */
14132 Layouts.Radial = new Class({
14133
14134   /*
14135    * Method: compute
14136    * 
14137    * Computes nodes' positions.
14138    * 
14139    * Parameters:
14140    * 
14141    * property - _optional_ A <Graph.Node> position property to store the new
14142    * positions. Possible values are 'pos', 'end' or 'start'.
14143    * 
14144    */
14145   compute : function(property) {
14146     var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14147     NodeDim.compute(this.graph, prop, this.config);
14148     this.graph.computeLevels(this.root, 0, "ignore");
14149     var lengthFunc = this.createLevelDistanceFunc(); 
14150     this.computeAngularWidths(prop);
14151     this.computePositions(prop, lengthFunc);
14152   },
14153
14154   /*
14155    * computePositions
14156    * 
14157    * Performs the main algorithm for computing node positions.
14158    */
14159   computePositions : function(property, getLength) {
14160     var propArray = property;
14161     var graph = this.graph;
14162     var root = graph.getNode(this.root);
14163     var parent = this.parent;
14164     var config = this.config;
14165
14166     for ( var i=0, l=propArray.length; i < l; i++) {
14167       var pi = propArray[i];
14168       root.setPos($P(0, 0), pi);
14169       root.setData('span', Math.PI * 2, pi);
14170     }
14171
14172     root.angleSpan = {
14173       begin : 0,
14174       end : 2 * Math.PI
14175     };
14176
14177     graph.eachBFS(this.root, function(elem) {
14178       var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14179       var angleInit = elem.angleSpan.begin;
14180       var len = getLength(elem);
14181       //Calculate the sum of all angular widths
14182       var totalAngularWidths = 0, subnodes = [], maxDim = {};
14183       elem.eachSubnode(function(sib) {
14184         totalAngularWidths += sib._treeAngularWidth;
14185         //get max dim
14186         for ( var i=0, l=propArray.length; i < l; i++) {
14187           var pi = propArray[i], dim = sib.getData('dim', pi);
14188           maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14189         }
14190         subnodes.push(sib);
14191       }, "ignore");
14192       //Maintain children order
14193       //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14194       if (parent && parent.id == elem.id && subnodes.length > 0
14195           && subnodes[0].dist) {
14196         subnodes.sort(function(a, b) {
14197           return (a.dist >= b.dist) - (a.dist <= b.dist);
14198         });
14199       }
14200       //Calculate nodes positions.
14201       for (var k = 0, ls=subnodes.length; k < ls; k++) {
14202         var child = subnodes[k];
14203         if (!child._flag) {
14204           var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14205           var theta = angleInit + angleProportion / 2;
14206
14207           for ( var i=0, l=propArray.length; i < l; i++) {
14208             var pi = propArray[i];
14209             child.setPos($P(theta, len), pi);
14210             child.setData('span', angleProportion, pi);
14211             child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14212           }
14213
14214           child.angleSpan = {
14215             begin : angleInit,
14216             end : angleInit + angleProportion
14217           };
14218           angleInit += angleProportion;
14219         }
14220       }
14221     }, "ignore");
14222   },
14223
14224   /*
14225    * Method: setAngularWidthForNodes
14226    * 
14227    * Sets nodes angular widths.
14228    */
14229   setAngularWidthForNodes : function(prop) {
14230     this.graph.eachBFS(this.root, function(elem, i) {
14231       var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14232       elem._angularWidth = diamValue / i;
14233     }, "ignore");
14234   },
14235
14236   /*
14237    * Method: setSubtreesAngularWidth
14238    * 
14239    * Sets subtrees angular widths.
14240    */
14241   setSubtreesAngularWidth : function() {
14242     var that = this;
14243     this.graph.eachNode(function(elem) {
14244       that.setSubtreeAngularWidth(elem);
14245     }, "ignore");
14246   },
14247
14248   /*
14249    * Method: setSubtreeAngularWidth
14250    * 
14251    * Sets the angular width for a subtree.
14252    */
14253   setSubtreeAngularWidth : function(elem) {
14254     var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14255     elem.eachSubnode(function(child) {
14256       that.setSubtreeAngularWidth(child);
14257       sumAW += child._treeAngularWidth;
14258     }, "ignore");
14259     elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14260   },
14261
14262   /*
14263    * Method: computeAngularWidths
14264    * 
14265    * Computes nodes and subtrees angular widths.
14266    */
14267   computeAngularWidths : function(prop) {
14268     this.setAngularWidthForNodes(prop);
14269     this.setSubtreesAngularWidth();
14270   }
14271
14272 });
14273
14274
14275 /*
14276  * File: Sunburst.js
14277  */
14278
14279 /*
14280    Class: Sunburst
14281       
14282    A radial space filling tree visualization.
14283    
14284    Inspired by:
14285  
14286    Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14287    
14288    Note:
14289    
14290    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.
14291    
14292   Implements:
14293   
14294   All <Loader> methods
14295   
14296    Constructor Options:
14297    
14298    Inherits options from
14299    
14300    - <Options.Canvas>
14301    - <Options.Controller>
14302    - <Options.Node>
14303    - <Options.Edge>
14304    - <Options.Label>
14305    - <Options.Events>
14306    - <Options.Tips>
14307    - <Options.NodeStyles>
14308    - <Options.Navigation>
14309    
14310    Additionally, there are other parameters and some default values changed
14311    
14312    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14313    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
14314    Node.type - Described in <Options.Node>. Default's to *multipie*.
14315    Node.height - Described in <Options.Node>. Default's *0*.
14316    Edge.type - Described in <Options.Edge>. Default's *none*.
14317    Label.textAlign - Described in <Options.Label>. Default's *start*.
14318    Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14319      
14320    Instance Properties:
14321
14322    canvas - Access a <Canvas> instance.
14323    graph - Access a <Graph> instance.
14324    op - Access a <Sunburst.Op> instance.
14325    fx - Access a <Sunburst.Plot> instance.
14326    labels - Access a <Sunburst.Label> interface implementation.   
14327
14328 */
14329
14330 $jit.Sunburst = new Class({
14331
14332   Implements: [ Loader, Extras, Layouts.Radial ],
14333
14334   initialize: function(controller) {
14335     var $Sunburst = $jit.Sunburst;
14336
14337     var config = {
14338       interpolation: 'linear',
14339       levelDistance: 100,
14340       Node: {
14341         'type': 'multipie',
14342         'height':0
14343       },
14344       Edge: {
14345         'type': 'none'
14346       },
14347       Label: {
14348         textAlign: 'start',
14349         textBaseline: 'middle'
14350       }
14351     };
14352
14353     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14354         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14355
14356     var canvasConfig = this.config;
14357     if(canvasConfig.useCanvas) {
14358       this.canvas = canvasConfig.useCanvas;
14359       this.config.labelContainer = this.canvas.id + '-label';
14360     } else {
14361       if(canvasConfig.background) {
14362         canvasConfig.background = $.merge({
14363           type: 'Fade',
14364           colorStop1: this.config.colorStop1,
14365           colorStop2: this.config.colorStop2
14366         }, canvasConfig.background);
14367       }
14368       this.canvas = new Canvas(this, canvasConfig);
14369       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14370     }
14371
14372     this.graphOptions = {
14373       'complex': false,
14374       'Node': {
14375         'selected': false,
14376         'exist': true,
14377         'drawn': true
14378       }
14379     };
14380     this.graph = new Graph(this.graphOptions, this.config.Node,
14381         this.config.Edge);
14382     this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14383     this.fx = new $Sunburst.Plot(this, $Sunburst);
14384     this.op = new $Sunburst.Op(this);
14385     this.json = null;
14386     this.root = null;
14387     this.rotated = null;
14388     this.busy = false;
14389     // initialize extras
14390     this.initializeExtras();
14391   },
14392
14393   /* 
14394   
14395     createLevelDistanceFunc 
14396   
14397     Returns the levelDistance function used for calculating a node distance 
14398     to its origin. This function returns a function that is computed 
14399     per level and not per node, such that all nodes with the same depth will have the 
14400     same distance to the origin. The resulting function gets the 
14401     parent node as parameter and returns a float.
14402
14403    */
14404   createLevelDistanceFunc: function() {
14405     var ld = this.config.levelDistance;
14406     return function(elem) {
14407       return (elem._depth + 1) * ld;
14408     };
14409   },
14410
14411   /* 
14412      Method: refresh 
14413      
14414      Computes positions and plots the tree.
14415
14416    */
14417   refresh: function() {
14418     this.compute();
14419     this.plot();
14420   },
14421
14422   /*
14423    reposition
14424   
14425    An alias for computing new positions to _endPos_
14426
14427    See also:
14428
14429    <Sunburst.compute>
14430    
14431   */
14432   reposition: function() {
14433     this.compute('end');
14434   },
14435
14436   /*
14437   Method: rotate
14438   
14439   Rotates the graph so that the selected node is horizontal on the right.
14440
14441   Parameters:
14442   
14443   node - (object) A <Graph.Node>.
14444   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14445   opt - (object) Configuration options merged with this visualization configuration options.
14446   
14447   See also:
14448
14449   <Sunburst.rotateAngle>
14450   
14451   */
14452   rotate: function(node, method, opt) {
14453     var theta = node.getPos(opt.property || 'current').getp(true).theta;
14454     this.rotated = node;
14455     this.rotateAngle(-theta, method, opt);
14456   },
14457
14458   /*
14459   Method: rotateAngle
14460   
14461   Rotates the graph of an angle theta.
14462   
14463    Parameters:
14464    
14465    node - (object) A <Graph.Node>.
14466    method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14467    opt - (object) Configuration options merged with this visualization configuration options.
14468    
14469    See also:
14470
14471    <Sunburst.rotate>
14472   
14473   */
14474   rotateAngle: function(theta, method, opt) {
14475     var that = this;
14476     var options = $.merge(this.config, opt || {}, {
14477       modes: [ 'polar' ]
14478     });
14479     var prop = opt.property || (method === "animate" ? 'end' : 'current');
14480     if(method === 'animate') {
14481       this.fx.animation.pause();
14482     }
14483     this.graph.eachNode(function(n) {
14484       var p = n.getPos(prop);
14485       p.theta += theta;
14486       if (p.theta < 0) {
14487         p.theta += Math.PI * 2;
14488       }
14489     });
14490     if (method == 'animate') {
14491       this.fx.animate(options);
14492     } else if (method == 'replot') {
14493       this.fx.plot();
14494       this.busy = false;
14495     }
14496   },
14497
14498   /*
14499    Method: plot
14500   
14501    Plots the Sunburst. This is a shortcut to *fx.plot*.
14502   */
14503   plot: function() {
14504     this.fx.plot();
14505   }
14506 });
14507
14508 $jit.Sunburst.$extend = true;
14509
14510 (function(Sunburst) {
14511
14512   /*
14513      Class: Sunburst.Op
14514
14515      Custom extension of <Graph.Op>.
14516
14517      Extends:
14518
14519      All <Graph.Op> methods
14520      
14521      See also:
14522      
14523      <Graph.Op>
14524
14525   */
14526   Sunburst.Op = new Class( {
14527
14528     Implements: Graph.Op
14529
14530   });
14531
14532   /*
14533      Class: Sunburst.Plot
14534
14535     Custom extension of <Graph.Plot>.
14536   
14537     Extends:
14538   
14539     All <Graph.Plot> methods
14540     
14541     See also:
14542     
14543     <Graph.Plot>
14544   
14545   */
14546   Sunburst.Plot = new Class( {
14547
14548     Implements: Graph.Plot
14549
14550   });
14551
14552   /*
14553     Class: Sunburst.Label
14554
14555     Custom extension of <Graph.Label>. 
14556     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14557   
14558     Extends:
14559   
14560     All <Graph.Label> methods and subclasses.
14561   
14562     See also:
14563   
14564     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14565   
14566    */
14567   Sunburst.Label = {};
14568
14569   /*
14570      Sunburst.Label.Native
14571
14572      Custom extension of <Graph.Label.Native>.
14573
14574      Extends:
14575
14576      All <Graph.Label.Native> methods
14577
14578      See also:
14579
14580      <Graph.Label.Native>
14581   */
14582   Sunburst.Label.Native = new Class( {
14583     Implements: Graph.Label.Native,
14584
14585     initialize: function(viz) {
14586       this.viz = viz;
14587       this.label = viz.config.Label;
14588       this.config = viz.config;
14589     },
14590
14591     renderLabel: function(canvas, node, controller) {
14592       var span = node.getData('span');
14593       if(span < Math.PI /2 && Math.tan(span) * 
14594           this.config.levelDistance * node._depth < 10) {
14595         return;
14596       }
14597       var ctx = canvas.getCtx();
14598       var measure = ctx.measureText(node.name);
14599       if (node.id == this.viz.root) {
14600         var x = -measure.width / 2, y = 0, thetap = 0;
14601         var ld = 0;
14602       } else {
14603         var indent = 5;
14604         var ld = controller.levelDistance - indent;
14605         var clone = node.pos.clone();
14606         clone.rho += indent;
14607         var p = clone.getp(true);
14608         var ct = clone.getc(true);
14609         var x = ct.x, y = ct.y;
14610         // get angle in degrees
14611         var pi = Math.PI;
14612         var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14613         var thetap = cond ? p.theta + pi : p.theta;
14614         if (cond) {
14615           x -= Math.abs(Math.cos(p.theta) * measure.width);
14616           y += Math.sin(p.theta) * measure.width;
14617         } else if (node.id == this.viz.root) {
14618           x -= measure.width / 2;
14619         }
14620       }
14621       ctx.save();
14622       ctx.translate(x, y);
14623       ctx.rotate(thetap);
14624       ctx.fillText(node.name, 0, 0);
14625       ctx.restore();
14626     }
14627   });
14628
14629   /*
14630      Sunburst.Label.SVG
14631
14632     Custom extension of <Graph.Label.SVG>.
14633   
14634     Extends:
14635   
14636     All <Graph.Label.SVG> methods
14637   
14638     See also:
14639   
14640     <Graph.Label.SVG>
14641   
14642   */
14643   Sunburst.Label.SVG = new Class( {
14644     Implements: Graph.Label.SVG,
14645
14646     initialize: function(viz) {
14647       this.viz = viz;
14648     },
14649
14650     /* 
14651        placeLabel
14652
14653        Overrides abstract method placeLabel in <Graph.Plot>.
14654
14655        Parameters:
14656
14657        tag - A DOM label element.
14658        node - A <Graph.Node>.
14659        controller - A configuration/controller object passed to the visualization.
14660       
14661      */
14662     placeLabel: function(tag, node, controller) {
14663       var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14664       var radius = canvas.getSize();
14665       var labelPos = {
14666         x: Math.round(pos.x + radius.width / 2),
14667         y: Math.round(pos.y + radius.height / 2)
14668       };
14669       tag.setAttribute('x', labelPos.x);
14670       tag.setAttribute('y', labelPos.y);
14671
14672       var bb = tag.getBBox();
14673       if (bb) {
14674         // center the label
14675     var x = tag.getAttribute('x');
14676     var y = tag.getAttribute('y');
14677     // get polar coordinates
14678     var p = node.pos.getp(true);
14679     // get angle in degrees
14680     var pi = Math.PI;
14681     var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14682     if (cond) {
14683       tag.setAttribute('x', x - bb.width);
14684       tag.setAttribute('y', y - bb.height);
14685     } else if (node.id == viz.root) {
14686       tag.setAttribute('x', x - bb.width / 2);
14687     }
14688
14689     var thetap = cond ? p.theta + pi : p.theta;
14690     if(node._depth)
14691       tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14692           + ' ' + y + ')');
14693   }
14694
14695   controller.onPlaceLabel(tag, node);
14696 }
14697   });
14698
14699   /*
14700      Sunburst.Label.HTML
14701
14702      Custom extension of <Graph.Label.HTML>.
14703
14704      Extends:
14705
14706      All <Graph.Label.HTML> methods.
14707
14708      See also:
14709
14710      <Graph.Label.HTML>
14711
14712   */
14713   Sunburst.Label.HTML = new Class( {
14714     Implements: Graph.Label.HTML,
14715
14716     initialize: function(viz) {
14717       this.viz = viz;
14718     },
14719     /* 
14720        placeLabel
14721
14722        Overrides abstract method placeLabel in <Graph.Plot>.
14723
14724        Parameters:
14725
14726        tag - A DOM label element.
14727        node - A <Graph.Node>.
14728        controller - A configuration/controller object passed to the visualization.
14729       
14730      */
14731     placeLabel: function(tag, node, controller) {
14732       var pos = node.pos.clone(), 
14733           canvas = this.viz.canvas,
14734           height = node.getData('height'),
14735           ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14736           radius = canvas.getSize();
14737       pos.rho += ldist;
14738       pos = pos.getc(true);
14739       
14740       var labelPos = {
14741         x: Math.round(pos.x + radius.width / 2),
14742         y: Math.round(pos.y + radius.height / 2)
14743       };
14744
14745       var style = tag.style;
14746       style.left = labelPos.x + 'px';
14747       style.top = labelPos.y + 'px';
14748       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14749
14750       controller.onPlaceLabel(tag, node);
14751     }
14752   });
14753
14754   /*
14755     Class: Sunburst.Plot.NodeTypes
14756
14757     This class contains a list of <Graph.Node> built-in types. 
14758     Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14759
14760     You can add your custom node types, customizing your visualization to the extreme.
14761
14762     Example:
14763
14764     (start code js)
14765       Sunburst.Plot.NodeTypes.implement({
14766         'mySpecialType': {
14767           'render': function(node, canvas) {
14768             //print your custom node to canvas
14769           },
14770           //optional
14771           'contains': function(node, pos) {
14772             //return true if pos is inside the node or false otherwise
14773           }
14774         }
14775       });
14776     (end code)
14777
14778   */
14779   Sunburst.Plot.NodeTypes = new Class( {
14780     'none': {
14781       'render': $.empty,
14782       'contains': $.lambda(false),
14783       'anglecontains': function(node, pos) {
14784         var span = node.getData('span') / 2, theta = node.pos.theta;
14785         var begin = theta - span, end = theta + span;
14786         if (begin < 0)
14787           begin += Math.PI * 2;
14788         var atan = Math.atan2(pos.y, pos.x);
14789         if (atan < 0)
14790           atan += Math.PI * 2;
14791         if (begin > end) {
14792           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14793         } else {
14794           return atan > begin && atan < end;
14795         }
14796       },
14797           'anglecontainsgauge': function(node, pos) {
14798         var span = node.getData('span') / 2, theta = node.pos.theta;
14799                 var config = node.getData('config');
14800         var ld = this.config.levelDistance;
14801                 var yOffset = pos.y-(ld/2);
14802                 var begin = ((theta - span)/2)+Math.PI,
14803         end = ((theta + span)/2)+Math.PI;
14804                 
14805         if (begin < 0)
14806           begin += Math.PI * 2;
14807         var atan = Math.atan2(yOffset, pos.x);
14808
14809                 
14810         if (atan < 0)
14811           atan += Math.PI * 2;
14812                   
14813                   
14814         if (begin > end) {
14815           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14816         } else {
14817           return atan > begin && atan < end;
14818         }
14819       }
14820     },
14821
14822     'pie': {
14823       'render': function(node, canvas) {
14824         var span = node.getData('span') / 2, theta = node.pos.theta;
14825         var begin = theta - span, end = theta + span;
14826         var polarNode = node.pos.getp(true);
14827         var polar = new Polar(polarNode.rho, begin);
14828         var p1coord = polar.getc(true);
14829         polar.theta = end;
14830         var p2coord = polar.getc(true);
14831
14832         var ctx = canvas.getCtx();
14833         ctx.beginPath();
14834         ctx.moveTo(0, 0);
14835         ctx.lineTo(p1coord.x, p1coord.y);
14836         ctx.moveTo(0, 0);
14837         ctx.lineTo(p2coord.x, p2coord.y);
14838         ctx.moveTo(0, 0);
14839         ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14840             false);
14841         ctx.fill();
14842       },
14843       'contains': function(node, pos) {
14844         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14845           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14846           var ld = this.config.levelDistance, d = node._depth;
14847           return (rho <= ld * d);
14848         }
14849         return false;
14850       }
14851     },
14852     'multipie': {
14853       'render': function(node, canvas) {
14854         var height = node.getData('height');
14855         var ldist = height? height : this.config.levelDistance;
14856         var span = node.getData('span') / 2, theta = node.pos.theta;
14857         var begin = theta - span, end = theta + span;
14858         var polarNode = node.pos.getp(true);
14859
14860         var polar = new Polar(polarNode.rho, begin);
14861         var p1coord = polar.getc(true);
14862
14863         polar.theta = end;
14864         var p2coord = polar.getc(true);
14865
14866         polar.rho += ldist;
14867         var p3coord = polar.getc(true);
14868
14869         polar.theta = begin;
14870         var p4coord = polar.getc(true);
14871
14872         var ctx = canvas.getCtx();
14873         ctx.moveTo(0, 0);
14874         ctx.beginPath();
14875         ctx.arc(0, 0, polarNode.rho, begin, end, false);
14876         ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
14877         ctx.moveTo(p1coord.x, p1coord.y);
14878         ctx.lineTo(p4coord.x, p4coord.y);
14879         ctx.moveTo(p2coord.x, p2coord.y);
14880         ctx.lineTo(p3coord.x, p3coord.y);
14881         ctx.fill();
14882
14883         if (node.collapsed) {
14884           ctx.save();
14885           ctx.lineWidth = 2;
14886           ctx.moveTo(0, 0);
14887           ctx.beginPath();
14888           ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
14889               true);
14890           ctx.stroke();
14891           ctx.restore();
14892         }
14893       },
14894       'contains': function(node, pos) {
14895         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14896           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14897           var height = node.getData('height');
14898           var ldist = height? height : this.config.levelDistance;
14899           var ld = this.config.levelDistance, d = node._depth;
14900           return (rho >= ld * d) && (rho <= (ld * d + ldist));
14901         }
14902         return false;
14903       }
14904     },
14905
14906     'gradient-multipie': {
14907       'render': function(node, canvas) {
14908         var ctx = canvas.getCtx();
14909         var height = node.getData('height');
14910         var ldist = height? height : this.config.levelDistance;
14911         var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
14912             0, 0, node.getPos().rho + ldist);
14913
14914         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
14915         $.each(colorArray, function(i) {
14916           ans.push(parseInt(i * 0.5, 10));
14917         });
14918         var endColor = $.rgbToHex(ans);
14919         radialGradient.addColorStop(0, endColor);
14920         radialGradient.addColorStop(1, node.getData('color'));
14921         ctx.fillStyle = radialGradient;
14922         this.nodeTypes['multipie'].render.call(this, node, canvas);
14923       },
14924       'contains': function(node, pos) {
14925         return this.nodeTypes['multipie'].contains.call(this, node, pos);
14926       }
14927     },
14928
14929     'gradient-pie': {
14930       'render': function(node, canvas) {
14931         var ctx = canvas.getCtx();
14932         var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
14933             .getPos().rho);
14934
14935         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
14936         $.each(colorArray, function(i) {
14937           ans.push(parseInt(i * 0.5, 10));
14938         });
14939         var endColor = $.rgbToHex(ans);
14940         radialGradient.addColorStop(1, endColor);
14941         radialGradient.addColorStop(0, node.getData('color'));
14942         ctx.fillStyle = radialGradient;
14943         this.nodeTypes['pie'].render.call(this, node, canvas);
14944       },
14945       'contains': function(node, pos) {
14946         return this.nodeTypes['pie'].contains.call(this, node, pos);
14947       }
14948     }
14949   });
14950
14951   /*
14952     Class: Sunburst.Plot.EdgeTypes
14953
14954     This class contains a list of <Graph.Adjacence> built-in types. 
14955     Edge types implemented are 'none', 'line' and 'arrow'.
14956   
14957     You can add your custom edge types, customizing your visualization to the extreme.
14958   
14959     Example:
14960   
14961     (start code js)
14962       Sunburst.Plot.EdgeTypes.implement({
14963         'mySpecialType': {
14964           'render': function(adj, canvas) {
14965             //print your custom edge to canvas
14966           },
14967           //optional
14968           'contains': function(adj, pos) {
14969             //return true if pos is inside the arc or false otherwise
14970           }
14971         }
14972       });
14973     (end code)
14974   
14975   */
14976   Sunburst.Plot.EdgeTypes = new Class({
14977     'none': $.empty,
14978     'line': {
14979       'render': function(adj, canvas) {
14980         var from = adj.nodeFrom.pos.getc(true),
14981             to = adj.nodeTo.pos.getc(true);
14982         this.edgeHelper.line.render(from, to, canvas);
14983       },
14984       'contains': function(adj, pos) {
14985         var from = adj.nodeFrom.pos.getc(true),
14986             to = adj.nodeTo.pos.getc(true);
14987         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
14988       }
14989     },
14990     'arrow': {
14991       'render': function(adj, canvas) {
14992         var from = adj.nodeFrom.pos.getc(true),
14993             to = adj.nodeTo.pos.getc(true),
14994             dim = adj.getData('dim'),
14995             direction = adj.data.$direction,
14996             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
14997         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
14998       },
14999       'contains': function(adj, pos) {
15000         var from = adj.nodeFrom.pos.getc(true),
15001             to = adj.nodeTo.pos.getc(true);
15002         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15003       }
15004     },
15005     'hyperline': {
15006       'render': function(adj, canvas) {
15007         var from = adj.nodeFrom.pos.getc(),
15008             to = adj.nodeTo.pos.getc(),
15009             dim = Math.max(from.norm(), to.norm());
15010         this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15011       },
15012       'contains': $.lambda(false) //TODO(nico): Implement this!
15013     }
15014   });
15015
15016 })($jit.Sunburst);
15017
15018
15019 /*
15020  * File: PieChart.js
15021  *
15022 */
15023
15024 $jit.Sunburst.Plot.NodeTypes.implement({
15025   'piechart-stacked' : {
15026     'render' : function(node, canvas) {
15027       var pos = node.pos.getp(true),
15028           dimArray = node.getData('dimArray'),
15029           valueArray = node.getData('valueArray'),
15030           colorArray = node.getData('colorArray'),
15031           colorLength = colorArray.length,
15032           stringArray = node.getData('stringArray'),
15033           span = node.getData('span') / 2,
15034           theta = node.pos.theta,
15035           begin = theta - span,
15036           end = theta + span,
15037           polar = new Polar;
15038     
15039       var ctx = canvas.getCtx(), 
15040           opt = {},
15041           gradient = node.getData('gradient'),
15042           border = node.getData('border'),
15043           config = node.getData('config'),
15044           showLabels = config.showLabels,
15045           resizeLabels = config.resizeLabels,
15046           label = config.Label;
15047
15048       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15049       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15050
15051       if (colorArray && dimArray && stringArray) {
15052         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15053           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15054           if(dimi <= 0) continue;
15055           ctx.fillStyle = ctx.strokeStyle = colori;
15056           if(gradient && dimi) {
15057             var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15058                 xpos, ypos, acum + dimi + config.sliceOffset);
15059             var colorRgb = $.hexToRgb(colori), 
15060                 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15061                 endColor = $.rgbToHex(ans);
15062
15063             radialGradient.addColorStop(0, colori);
15064             radialGradient.addColorStop(0.5, colori);
15065             radialGradient.addColorStop(1, endColor);
15066             ctx.fillStyle = radialGradient;
15067           }
15068           
15069           polar.rho = acum + config.sliceOffset;
15070           polar.theta = begin;
15071           var p1coord = polar.getc(true);
15072           polar.theta = end;
15073           var p2coord = polar.getc(true);
15074           polar.rho += dimi;
15075           var p3coord = polar.getc(true);
15076           polar.theta = begin;
15077           var p4coord = polar.getc(true);
15078
15079           ctx.beginPath();
15080           //fixing FF arc method + fill
15081           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15082           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15083           ctx.fill();
15084           if(border && border.name == stringArray[i]) {
15085             opt.acum = acum;
15086             opt.dimValue = dimArray[i];
15087             opt.begin = begin;
15088             opt.end = end;
15089           }
15090           acum += (dimi || 0);
15091           valAcum += (valueArray[i] || 0);
15092         }
15093         if(border) {
15094           ctx.save();
15095           ctx.globalCompositeOperation = "source-over";
15096           ctx.lineWidth = 2;
15097           ctx.strokeStyle = border.color;
15098           var s = begin < end? 1 : -1;
15099           ctx.beginPath();
15100           //fixing FF arc method + fill
15101           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15102           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15103           ctx.closePath();
15104           ctx.stroke();
15105           ctx.restore();
15106         }
15107         if(showLabels && label.type == 'Native') {
15108           ctx.save();
15109           ctx.fillStyle = ctx.strokeStyle = label.color;
15110           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15111               fontSize = (label.size * scale) >> 0;
15112           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15113           
15114           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15115           ctx.textBaseline = 'middle';
15116           ctx.textAlign = 'center';
15117           
15118           polar.rho = acum + config.labelOffset + config.sliceOffset;
15119           polar.theta = node.pos.theta;
15120           var cart = polar.getc(true);
15121           
15122           ctx.fillText(node.name, cart.x, cart.y);
15123           ctx.restore();
15124         }
15125       }
15126     },
15127     'contains': function(node, pos) {
15128       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15129         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15130         var ld = this.config.levelDistance, d = node._depth;
15131         var config = node.getData('config');
15132         if(rho <=ld * d + config.sliceOffset) {
15133           var dimArray = node.getData('dimArray');
15134           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15135             var dimi = dimArray[i];
15136             if(rho >= acum && rho <= acum + dimi) {
15137               return {
15138                 name: node.getData('stringArray')[i],
15139                 color: node.getData('colorArray')[i],
15140                 value: node.getData('valueArray')[i],
15141                 label: node.name
15142               };
15143             }
15144             acum += dimi;
15145           }
15146         }
15147         return false;
15148         
15149       }
15150       return false;
15151     }
15152   },
15153     'piechart-basic' : {
15154     'render' : function(node, canvas) {
15155       var pos = node.pos.getp(true),
15156           dimArray = node.getData('dimArray'),
15157           valueArray = node.getData('valueArray'),
15158           colorArray = node.getData('colorMono'),
15159           colorLength = colorArray.length,
15160           stringArray = node.getData('stringArray'),
15161                   percentage = node.getData('percentage'),
15162                   iteration = node.getData('iteration'),
15163           span = node.getData('span') / 2,
15164           theta = node.pos.theta,
15165           begin = theta - span,
15166           end = theta + span,
15167           polar = new Polar;
15168     
15169       var ctx = canvas.getCtx(), 
15170           opt = {},
15171           gradient = node.getData('gradient'),
15172           border = node.getData('border'),
15173           config = node.getData('config'),
15174           renderSubtitle = node.getData('renderSubtitle'),
15175           renderBackground = config.renderBackground,
15176           showLabels = config.showLabels,
15177           resizeLabels = config.resizeLabels,
15178           label = config.Label;
15179
15180       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15181       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15182       //background rendering for IE
15183                 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15184                         backgroundColor = config.backgroundColor,
15185                         size = canvas.getSize();
15186                         ctx.save();
15187                     ctx.fillStyle = backgroundColor;
15188                     ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15189                     
15190                     //subtitle
15191
15192                         var margin = config.Margin,
15193                         title = config.Title,
15194                         subtitle = config.Subtitle;
15195                         ctx.fillStyle = title.color;
15196                         ctx.textAlign = 'left';
15197                         
15198                         if(title.text != "") {
15199                                 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15200                                 ctx.moveTo(0,0);
15201                                 if(label.type == 'Native') {
15202                                         ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15203                                 }
15204                         }       
15205         
15206                         if(subtitle.text != "") {
15207                                 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15208                                 if(label.type == 'Native') {
15209                                         ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15210                                 } 
15211                         }
15212                         ctx.restore();          
15213                 }
15214       if (colorArray && dimArray && stringArray) {
15215         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15216           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15217           if(dimi <= 0) continue;
15218           ctx.fillStyle = ctx.strokeStyle = colori;
15219           
15220           polar.rho = acum + config.sliceOffset;
15221           polar.theta = begin;
15222           var p1coord = polar.getc(true);
15223           polar.theta = end;
15224           var p2coord = polar.getc(true);
15225           polar.rho += dimi;
15226           var p3coord = polar.getc(true);
15227           polar.theta = begin;
15228           var p4coord = polar.getc(true);
15229           
15230           if(typeof FlashCanvas == "undefined") {
15231                   //drop shadow
15232                   ctx.beginPath();
15233                   ctx.fillStyle = "rgba(0,0,0,.2)";
15234                   ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15235                   ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);    
15236                   ctx.fill();
15237                   
15238                   if(gradient && dimi) {
15239                     var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15240                         xpos, ypos, acum + dimi + config.sliceOffset);
15241                     var colorRgb = $.hexToRgb(colori), 
15242                         endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15243                         endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15244         
15245                     radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15246                     radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15247                                 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15248                     radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15249                     ctx.fillStyle = radialGradient;
15250                   }
15251           }
15252
15253           
15254           //fixing FF arc method + fill
15255           ctx.beginPath();
15256           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15257           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15258           ctx.fill();
15259           if(border && border.name == stringArray[i]) {
15260             opt.acum = acum;
15261             opt.dimValue = dimArray[i];
15262             opt.begin = begin;
15263             opt.end = end;
15264             opt.sliceValue = valueArray[i];
15265           }
15266           acum += (dimi || 0);
15267           valAcum += (valueArray[i] || 0);
15268         }
15269         if(border) {
15270           ctx.save();
15271           ctx.globalCompositeOperation = "source-over";
15272           ctx.lineWidth = 2;
15273           ctx.strokeStyle = border.color;
15274           var s = begin < end? 1 : -1;
15275           ctx.beginPath();
15276           //fixing FF arc method + fill
15277           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15278           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15279           ctx.closePath();
15280           ctx.stroke();
15281           ctx.restore();
15282         }
15283         if(showLabels && label.type == 'Native') {
15284           ctx.save();
15285           ctx.fillStyle = ctx.strokeStyle = label.color;
15286           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15287               fontSize = (label.size * scale) >> 0;
15288           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15289           
15290           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15291           ctx.textBaseline = 'middle';
15292           ctx.textAlign = 'center';
15293           pi = Math.PI;
15294           angle = theta * 360 / (2 * pi);
15295           polar.rho = acum + config.labelOffset + config.sliceOffset;
15296           polar.theta = node.pos.theta;
15297           var cart = polar.getc(true);
15298           if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15299                 
15300                 } else {
15301                   if(config.labelType == 'name') {
15302                                 ctx.fillText(node.name, cart.x, cart.y);
15303                           } else {
15304                                 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15305                           }
15306           }
15307           ctx.restore();
15308         }
15309       }
15310     },
15311     'contains': function(node, pos) {
15312       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15313         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15314         var ld = this.config.levelDistance, d = node._depth;
15315         var config = node.getData('config');
15316
15317         if(rho <=ld * d + config.sliceOffset) {
15318           var dimArray = node.getData('dimArray');
15319           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15320             var dimi = dimArray[i];
15321             if(rho >= acum && rho <= acum + dimi) {
15322                           var url = Url.decode(node.getData('linkArray')[i]);
15323               return {
15324                 name: node.getData('stringArray')[i],
15325                 link: url,
15326                 color: node.getData('colorArray')[i],
15327                 value: node.getData('valueArray')[i],
15328                 percentage: node.getData('percentage'),
15329                 valuelabel: node.getData('valuelabelsArray')[i],
15330                 label: node.name
15331               };
15332             }
15333             acum += dimi;
15334           }
15335         }
15336         return false;
15337         
15338       }
15339       return false;
15340     }
15341   }
15342 });
15343
15344 /*
15345   Class: PieChart
15346   
15347   A visualization that displays stacked bar charts.
15348   
15349   Constructor Options:
15350   
15351   See <Options.PieChart>.
15352
15353 */
15354 $jit.PieChart = new Class({
15355   sb: null,
15356   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15357   selected: {},
15358   busy: false,
15359   
15360   initialize: function(opt) {
15361     this.controller = this.config = 
15362       $.merge(Options("Canvas", "PieChart", "Label"), {
15363         Label: { type: 'Native' }
15364       }, opt);
15365     this.initializeViz();
15366   },
15367   
15368   initializeViz: function() {
15369     var config = this.config, that = this;
15370     var nodeType = config.type.split(":")[0];
15371     var sb = new $jit.Sunburst({
15372       injectInto: config.injectInto,
15373       useCanvas: config.useCanvas,
15374       withLabels: config.Label.type != 'Native',
15375       background: config.background,
15376       renderBackground: config.renderBackground,
15377       backgroundColor: config.backgroundColor,
15378       colorStop1: config.colorStop1,
15379       colorStop2: config.colorStop2,
15380       Label: {
15381         type: config.Label.type
15382       },
15383       Node: {
15384         overridable: true,
15385         type: 'piechart-' + nodeType,
15386         width: 1,
15387         height: 1
15388       },
15389       Edge: {
15390         type: 'none'
15391       },
15392       Tips: {
15393         enable: config.Tips.enable,
15394         type: 'Native',
15395         force: true,
15396         onShow: function(tip, node, contains) {
15397           var elem = contains;
15398           config.Tips.onShow(tip, elem, node);
15399                           if(elem.link != 'undefined' && elem.link != '') {
15400                                 document.body.style.cursor = 'pointer';
15401                           }
15402         },
15403                 onHide: function() {
15404                                 document.body.style.cursor = 'default';
15405         }
15406       },
15407       Events: {
15408         enable: true,
15409         type: 'Native',
15410         onClick: function(node, eventInfo, evt) {
15411           if(!config.Events.enable) return;
15412           var elem = eventInfo.getContains();
15413           config.Events.onClick(elem, eventInfo, evt);
15414         },
15415         onMouseMove: function(node, eventInfo, evt) {
15416           if(!config.hoveredColor) return;
15417           if(node) {
15418             var elem = eventInfo.getContains();
15419             that.select(node.id, elem.name, elem.index);
15420           } else {
15421             that.select(false, false, false);
15422           }
15423         }
15424       },
15425       onCreateLabel: function(domElement, node) {
15426         var labelConf = config.Label;
15427         if(config.showLabels) {
15428           var style = domElement.style;
15429           style.fontSize = labelConf.size + 'px';
15430           style.fontFamily = labelConf.family;
15431           style.color = labelConf.color;
15432           style.textAlign = 'center';
15433           if(config.labelType == 'name') {
15434                 domElement.innerHTML = node.name;
15435           } else {
15436                 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15437           }
15438           domElement.style.width = '400px';
15439         }
15440       },
15441       onPlaceLabel: function(domElement, node) {
15442         if(!config.showLabels) return;
15443         var pos = node.pos.getp(true),
15444             dimArray = node.getData('dimArray'),
15445             span = node.getData('span') / 2,
15446             theta = node.pos.theta,
15447             begin = theta - span,
15448             end = theta + span,
15449             polar = new Polar;
15450
15451         var showLabels = config.showLabels,
15452             resizeLabels = config.resizeLabels,
15453             label = config.Label;
15454         
15455         if (dimArray) {
15456           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15457             acum += dimArray[i];
15458           }
15459           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15460               fontSize = (label.size * scale) >> 0;
15461           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15462           domElement.style.fontSize = fontSize + 'px';
15463           polar.rho = acum + config.labelOffset + config.sliceOffset;
15464           polar.theta = (begin + end) / 2;
15465           var pos = polar.getc(true);
15466           var radius = that.canvas.getSize();
15467           var labelPos = {
15468             x: Math.round(pos.x + radius.width / 2),
15469             y: Math.round(pos.y + radius.height / 2)
15470           };
15471           domElement.style.left = (labelPos.x - 200) + 'px';
15472           domElement.style.top = labelPos.y + 'px';
15473         }
15474       }
15475     });
15476     
15477     var size = sb.canvas.getSize(),
15478         min = Math.min;
15479     sb.config.levelDistance = min(size.width, size.height)/2 
15480       - config.offset - config.sliceOffset;
15481     this.sb = sb;
15482     this.canvas = this.sb.canvas;
15483     this.canvas.getCtx().globalCompositeOperation = 'lighter';
15484   },
15485     renderBackground: function() {
15486                 var canvas = this.canvas,
15487                 config = this.config,
15488                 backgroundColor = config.backgroundColor,
15489                 size = canvas.getSize(),
15490                 ctx = canvas.getCtx();
15491                 ctx.globalCompositeOperation = "destination-over";
15492             ctx.fillStyle = backgroundColor;
15493             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
15494   },
15495    renderTitle: function() {
15496         var canvas = this.canvas,
15497         size = canvas.getSize(),
15498         config = this.config,
15499         margin = config.Margin,
15500         radius = this.sb.config.levelDistance,
15501         title = config.Title,
15502         label = config.Label,
15503         subtitle = config.Subtitle;
15504         ctx = canvas.getCtx();
15505         ctx.fillStyle = title.color;
15506         ctx.textAlign = 'left';
15507         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15508         ctx.moveTo(0,0);
15509         if(label.type == 'Native') {
15510                 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15511         }       
15512   },
15513   renderSubtitle: function() {
15514         var canvas = this.canvas,
15515         size = canvas.getSize(),
15516         config = this.config,
15517         margin = config.Margin,
15518         radius = this.sb.config.levelDistance,
15519         title = config.Title,
15520         label = config.Label,
15521         subtitle = config.Subtitle;
15522         ctx = canvas.getCtx();
15523         ctx.fillStyle = title.color;
15524         ctx.textAlign = 'left';
15525         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15526         ctx.moveTo(0,0);
15527         if(label.type == 'Native') {
15528                 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15529         }       
15530   },
15531   /*
15532     Method: loadJSON
15533    
15534     Loads JSON data into the visualization. 
15535     
15536     Parameters:
15537     
15538     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>.
15539     
15540     Example:
15541     (start code js)
15542     var pieChart = new $jit.PieChart(options);
15543     pieChart.loadJSON(json);
15544     (end code)
15545   */  
15546   loadJSON: function(json) {
15547     var prefix = $.time(), 
15548         ch = [], 
15549         sb = this.sb,
15550         name = $.splat(json.label),
15551         nameLength = name.length,
15552         color = $.splat(json.color || this.colors),
15553         colorLength = color.length,
15554         config = this.config,
15555         renderBackground = config.renderBackground,
15556         title = config.Title,
15557                 subtitle = config.Subtitle,
15558         gradient = !!config.type.split(":")[1],
15559         animate = config.animate,
15560         mono = nameLength == 1;
15561         totalValue = 0;
15562     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15563         var val = values[i];
15564         var valArray = $.splat(val.values);
15565         totalValue += parseInt(valArray.sum());
15566     }
15567
15568     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15569       var val = values[i];
15570       var valArray = $.splat(val.values);
15571           var percentage = (valArray.sum()/totalValue) * 100;
15572
15573       var linkArray = $.splat(val.links);
15574       var valuelabelsArray = $.splat(val.valuelabels);
15575       
15576  
15577       ch.push({
15578         'id': prefix + val.label,
15579         'name': val.label,
15580         'data': {
15581           'value': valArray,
15582           'valuelabel': valuelabelsArray,
15583           '$linkArray': linkArray,
15584           '$valuelabelsArray': valuelabelsArray,
15585           '$valueArray': valArray,
15586           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15587           '$colorMono': $.splat(color[i % colorLength]),
15588           '$stringArray': name,
15589           '$gradient': gradient,
15590           '$config': config,
15591           '$iteration': i,
15592           '$percentage': percentage.toFixed(1),
15593           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15594         },
15595         'children': []
15596       });
15597     }
15598     var root = {
15599       'id': prefix + '$root',
15600       'name': '',
15601       'data': {
15602         '$type': 'none',
15603         '$width': 1,
15604         '$height': 1
15605       },
15606       'children': ch
15607     };
15608     sb.loadJSON(root);
15609     
15610     
15611     this.normalizeDims();
15612
15613     
15614     sb.refresh();
15615     if(title.text != "") {
15616         this.renderTitle();
15617     }
15618        
15619     if(subtitle.text != "") {
15620         this.renderSubtitle();
15621     }
15622      if(renderBackground && typeof FlashCanvas == "undefined") {
15623         this.renderBackground();        
15624     }
15625     
15626     if(animate) {
15627       sb.fx.animate({
15628         modes: ['node-property:dimArray'],
15629         duration:1500
15630       });
15631     }
15632   },
15633   
15634   /*
15635     Method: updateJSON
15636    
15637     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.
15638     
15639     Parameters:
15640     
15641     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15642     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15643     
15644     Example:
15645     
15646     (start code js)
15647     pieChart.updateJSON(json, {
15648       onComplete: function() {
15649         alert('update complete!');
15650       }
15651     });
15652     (end code)
15653   */  
15654   updateJSON: function(json, onComplete) {
15655     if(this.busy) return;
15656     this.busy = true;
15657     
15658     var sb = this.sb;
15659     var graph = sb.graph;
15660     var values = json.values;
15661     var animate = this.config.animate;
15662     var that = this;
15663     $.each(values, function(v) {
15664       var n = graph.getByName(v.label),
15665           vals = $.splat(v.values);
15666       if(n) {
15667         n.setData('valueArray', vals);
15668         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15669         if(json.label) {
15670           n.setData('stringArray', $.splat(json.label));
15671         }
15672       }
15673     });
15674     this.normalizeDims();
15675     if(animate) {
15676       sb.compute('end');
15677       sb.fx.animate({
15678         modes: ['node-property:dimArray:span', 'linear'],
15679         duration:1500,
15680         onComplete: function() {
15681           that.busy = false;
15682           onComplete && onComplete.onComplete();
15683         }
15684       });
15685     } else {
15686       sb.refresh();
15687     }
15688   },
15689     
15690   //adds the little brown bar when hovering the node
15691   select: function(id, name) {
15692     if(!this.config.hoveredColor) return;
15693     var s = this.selected;
15694     if(s.id != id || s.name != name) {
15695       s.id = id;
15696       s.name = name;
15697       s.color = this.config.hoveredColor;
15698       this.sb.graph.eachNode(function(n) {
15699         if(id == n.id) {
15700           n.setData('border', s);
15701         } else {
15702           n.setData('border', false);
15703         }
15704       });
15705       this.sb.plot();
15706     }
15707   },
15708   
15709   /*
15710     Method: getLegend
15711    
15712     Returns an object containing as keys the legend names and as values hex strings with color values.
15713     
15714     Example:
15715     
15716     (start code js)
15717     var legend = pieChart.getLegend();
15718     (end code)
15719   */  
15720   getLegend: function() {
15721     var legend = new Array();
15722     var name = new Array();
15723     var color = new Array();
15724     var n;
15725     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15726       n = adj.nodeTo;
15727     });
15728     var colors = n.getData('colorArray'),
15729         len = colors.length;
15730     $.each(n.getData('stringArray'), function(s, i) {
15731       color[i] = colors[i % len];
15732       name[i] = s;
15733     });
15734         legend['name'] = name;
15735         legend['color'] = color;
15736     return legend;
15737   },
15738   
15739   /*
15740     Method: getMaxValue
15741    
15742     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15743     
15744     Example:
15745     
15746     (start code js)
15747     var ans = pieChart.getMaxValue();
15748     (end code)
15749     
15750     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15751     
15752     Example:
15753     
15754     (start code js)
15755     //will return 100 for all PieChart instances,
15756     //displaying all of them with the same scale
15757     $jit.PieChart.implement({
15758       'getMaxValue': function() {
15759         return 100;
15760       }
15761     });
15762     (end code)
15763     
15764   */  
15765   getMaxValue: function() {
15766     var maxValue = 0;
15767     this.sb.graph.eachNode(function(n) {
15768       var valArray = n.getData('valueArray'),
15769           acum = 0;
15770       $.each(valArray, function(v) { 
15771         acum += +v;
15772       });
15773       maxValue = maxValue>acum? maxValue:acum;
15774     });
15775     return maxValue;
15776   },
15777   
15778   normalizeDims: function() {
15779     //number of elements
15780     var root = this.sb.graph.getNode(this.sb.root), l=0;
15781     root.eachAdjacency(function() {
15782       l++;
15783     });
15784     var maxValue = this.getMaxValue() || 1,
15785         config = this.config,
15786         animate = config.animate,
15787         rho = this.sb.config.levelDistance;
15788     this.sb.graph.eachNode(function(n) {
15789       var acum = 0, animateValue = [];
15790       $.each(n.getData('valueArray'), function(v) {
15791         acum += +v;
15792         animateValue.push(1);
15793       });
15794       var stat = (animateValue.length == 1) && !config.updateHeights;
15795       if(animate) {
15796         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15797           return stat? rho: (n * rho / maxValue); 
15798         }), 'end');
15799         var dimArray = n.getData('dimArray');
15800         if(!dimArray) {
15801           n.setData('dimArray', animateValue);
15802         }
15803       } else {
15804         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15805           return stat? rho : (n * rho / maxValue); 
15806         }));
15807       }
15808       n.setData('normalizedDim', acum / maxValue);
15809     });
15810   }
15811 });
15812
15813
15814 //Gauge Chart
15815
15816 Options.GaugeChart = {
15817   $extend: true,
15818
15819   animate: true,
15820   offset: 25, // page offset
15821   sliceOffset:0,
15822   labelOffset: 3, // label offset
15823   type: 'stacked', // gradient
15824   labelType: 'name',
15825   hoveredColor: '#9fd4ff',
15826   Events: {
15827     enable: false,
15828     onClick: $.empty
15829   },
15830   Tips: {
15831     enable: false,
15832     onShow: $.empty,
15833     onHide: $.empty
15834   },
15835   showLabels: true,
15836   resizeLabels: false,
15837   
15838   //only valid for mono-valued datasets
15839   updateHeights: false
15840 };
15841
15842
15843
15844 $jit.Sunburst.Plot.NodeTypes.implement({
15845     'gaugechart-basic' : {
15846     'render' : function(node, canvas) {
15847       var pos = node.pos.getp(true),
15848           dimArray = node.getData('dimArray'),
15849           valueArray = node.getData('valueArray'),
15850           valuelabelsArray = node.getData('valuelabelsArray'),
15851           gaugeTarget = node.getData('gaugeTarget'),
15852           nodeIteration = node.getData('nodeIteration'),
15853           nodeLength = node.getData('nodeLength'),
15854           colorArray = node.getData('colorMono'),
15855           colorLength = colorArray.length,
15856           stringArray = node.getData('stringArray'),
15857           span = node.getData('span') / 2,
15858           theta = node.pos.theta,
15859           begin = ((theta - span)/2)+Math.PI,
15860           end = ((theta + span)/2)+Math.PI,
15861           polar = new Polar;
15862
15863   
15864       var ctx = canvas.getCtx(), 
15865           opt = {},
15866           gradient = node.getData('gradient'),
15867           border = node.getData('border'),
15868           config = node.getData('config'),
15869           showLabels = config.showLabels,
15870           resizeLabels = config.resizeLabels,
15871           label = config.Label;
15872
15873       var xpos = Math.cos((begin + end) /2);
15874       var ypos = Math.sin((begin + end) /2);
15875
15876       if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
15877         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15878           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15879           if(dimi <= 0) continue;
15880           ctx.fillStyle = ctx.strokeStyle = colori;
15881           if(gradient && dimi) {
15882             var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
15883                 xpos, (ypos + dimi/2), acum + dimi);
15884             var colorRgb = $.hexToRgb(colori), 
15885                 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
15886                 endColor = $.rgbToHex(ans);
15887
15888             radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15889             radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
15890             radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
15891             radialGradient.addColorStop(1,  'rgba('+ans+',1)');
15892             ctx.fillStyle = radialGradient;
15893           }
15894           
15895           polar.rho = acum;
15896           polar.theta = begin;
15897           var p1coord = polar.getc(true);
15898           polar.theta = end;
15899           var p2coord = polar.getc(true);
15900           polar.rho += dimi;
15901           var p3coord = polar.getc(true);
15902           polar.theta = begin;
15903           var p4coord = polar.getc(true);
15904
15905                   
15906           ctx.beginPath();
15907           //fixing FF arc method + fill
15908           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
15909           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
15910           ctx.fill();
15911                   
15912
15913           acum += (dimi || 0);
15914           valAcum += (valueArray[i] || 0);                
15915         }
15916                 
15917                 if(showLabels && label.type == 'Native') {
15918                           ctx.save();
15919                           ctx.fillStyle = ctx.strokeStyle = label.color;
15920
15921                           
15922                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
15923                           ctx.textBaseline = 'bottom';
15924                           ctx.textAlign = 'center';
15925
15926                           polar.rho = acum * .65;
15927                           polar.theta = begin;
15928                           var cart = polar.getc(true);
15929                           
15930                           //changes y pos of first label
15931                           if(nodeIteration == 1) {
15932                                 textY = cart.y - (label.size/2) + acum /2;
15933                           } else {
15934                                 textY = cart.y + acum/2;
15935                           }
15936                           
15937                           if(config.labelType == 'name') {
15938                                 ctx.fillText(node.name, cart.x, textY);
15939                           } else {
15940                                 ctx.fillText(valuelabelsArray[0], cart.x, textY);
15941                           }
15942                           
15943                           //adds final label
15944                           if(nodeIteration == nodeLength) {
15945                                 polar.theta = end;
15946                                 var cart = polar.getc(true);
15947                                 if(config.labelType == 'name') {
15948                                         ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
15949                                 } else {
15950                                         ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
15951                                 }
15952                                 
15953                           }
15954                           ctx.restore();
15955                 }
15956
15957       }
15958       },
15959     'contains': function(node, pos) {
15960                 
15961                 
15962                 
15963       if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
15964                 var config = node.getData('config');
15965         var ld = this.config.levelDistance , d = node._depth;
15966                 var yOffset = pos.y - (ld/2);
15967                 var xOffset = pos.x;
15968         var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
15969         if(rho <=parseInt(ld * d)) {
15970           var dimArray = node.getData('dimArray');
15971           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15972                         var dimi = dimArray[i];
15973             if(rho >= ld * .8 && rho <= acum + dimi) {
15974                 
15975                           var url = Url.decode(node.getData('linkArray')[i]);
15976               return {
15977                 name: node.getData('stringArray')[i],
15978                 link: url,
15979                 color: node.getData('colorArray')[i],
15980                 value: node.getData('valueArray')[i],
15981                 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
15982                 label: node.name
15983               };
15984             }
15985             acum += dimi;
15986                         
15987                         
15988           }
15989         }
15990         return false;
15991         
15992       }
15993       return false;
15994     }
15995   }
15996 });
15997
15998 /*
15999   Class: GaugeChart
16000   
16001   A visualization that displays gauge charts
16002   
16003   Constructor Options:
16004   
16005   See <Options.Gauge>.
16006
16007 */
16008 $jit.GaugeChart = new Class({
16009   sb: null,
16010   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16011   selected: {},
16012   busy: false,
16013   
16014   initialize: function(opt) {
16015     this.controller = this.config = 
16016       $.merge(Options("Canvas", "GaugeChart", "Label"), {
16017         Label: { type: 'Native' }
16018       }, opt);
16019     this.initializeViz();
16020   },
16021   
16022   initializeViz: function() {
16023     var config = this.config, that = this;
16024     var nodeType = config.type.split(":")[0];
16025     var sb = new $jit.Sunburst({
16026       injectInto: config.injectInto,
16027       useCanvas: config.useCanvas,
16028       withLabels: config.Label.type != 'Native',
16029       background: config.background,
16030       renderBackground: config.renderBackground,
16031       backgroundColor: config.backgroundColor,
16032       colorStop1: config.colorStop1,
16033       colorStop2: config.colorStop2,
16034       Label: {
16035         type: config.Label.type
16036       },
16037       Node: {
16038         overridable: true,
16039         type: 'gaugechart-' + nodeType,
16040         width: 1,
16041         height: 1
16042       },
16043       Edge: {
16044         type: 'none'
16045       },
16046       Tips: {
16047         enable: config.Tips.enable,
16048         type: 'Native',
16049         force: true,
16050         onShow: function(tip, node, contains) {
16051           var elem = contains;
16052           config.Tips.onShow(tip, elem, node);
16053                           if(elem.link != 'undefined' && elem.link != '') {
16054                                 document.body.style.cursor = 'pointer';
16055                           }
16056         },
16057                 onHide: function() {
16058                                 document.body.style.cursor = 'default';
16059         }
16060       },
16061       Events: {
16062         enable: true,
16063         type: 'Native',
16064         onClick: function(node, eventInfo, evt) {
16065           if(!config.Events.enable) return;
16066           var elem = eventInfo.getContains();
16067           config.Events.onClick(elem, eventInfo, evt);
16068         }
16069         },
16070       onCreateLabel: function(domElement, node) {
16071         var labelConf = config.Label;
16072         if(config.showLabels) {
16073           var style = domElement.style;
16074           style.fontSize = labelConf.size + 'px';
16075           style.fontFamily = labelConf.family;
16076           style.color = labelConf.color;
16077           style.textAlign = 'center';
16078           valuelabelsArray = node.getData('valuelabelsArray'),
16079           nodeIteration = node.getData('nodeIteration'),
16080           nodeLength = node.getData('nodeLength'),
16081           canvas = sb.canvas,
16082           prefix = $.time();
16083           
16084           if(config.labelType == 'name') {
16085                 domElement.innerHTML = node.name;
16086           } else {
16087                 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16088           }
16089           
16090           domElement.style.width = '400px';
16091           
16092           //adds final label
16093                   if(nodeIteration == nodeLength && nodeLength != 0) {
16094                   idLabel = canvas.id + "-label";
16095                   container = document.getElementById(idLabel);
16096                   finalLabel = document.createElement('div');
16097                   finalLabelStyle = finalLabel.style;
16098                   finalLabel.id = prefix + "finalLabel";
16099                   finalLabelStyle.position = "absolute";
16100                   finalLabelStyle.width = "400px";
16101                   finalLabelStyle.left = "0px";
16102                   container.appendChild(finalLabel);
16103                         if(config.labelType == 'name') {
16104                                 finalLabel.innerHTML = node.name;
16105                         } else {
16106                                 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16107                         }
16108                         
16109                   }
16110         }
16111       },
16112       onPlaceLabel: function(domElement, node) {
16113         if(!config.showLabels) return;
16114         var pos = node.pos.getp(true),
16115             dimArray = node.getData('dimArray'),
16116             nodeIteration = node.getData('nodeIteration'),
16117             nodeLength = node.getData('nodeLength'),
16118             span = node.getData('span') / 2,
16119             theta = node.pos.theta,
16120             begin = ((theta - span)/2)+Math.PI,
16121             end = ((theta + span)/2)+Math.PI,
16122             polar = new Polar;
16123
16124         var showLabels = config.showLabels,
16125             resizeLabels = config.resizeLabels,
16126             label = config.Label,
16127             radiusOffset = sb.config.levelDistance;
16128
16129         if (dimArray) {
16130           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16131             acum += dimArray[i];
16132           }
16133           var scale = resizeLabels? node.getData('normalizedDim') : 1,
16134               fontSize = (label.size * scale) >> 0;
16135           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16136           domElement.style.fontSize = fontSize + 'px';
16137           polar.rho = acum * .65;
16138           polar.theta = begin;
16139           var pos = polar.getc(true);
16140           var radius = that.canvas.getSize();
16141           var labelPos = {
16142             x: Math.round(pos.x + radius.width / 2),
16143             y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16144           };
16145           
16146
16147           
16148           domElement.style.left = (labelPos.x - 200) + 'px';
16149           domElement.style.top = labelPos.y + 'px';
16150           
16151           //reposition first label
16152           if(nodeIteration == 1) {
16153                  domElement.style.top = labelPos.y - label.size + 'px';
16154           }
16155           
16156           
16157           //position final label
16158                 if(nodeIteration == nodeLength && nodeLength != 0) {
16159                         polar.theta = end;
16160                         var final = polar.getc(true);
16161                         var finalPos = {
16162                                         x: Math.round(final.x + radius.width / 2),
16163                                 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16164                                 };
16165                         finalLabel.style.left = (finalPos.x - 200) + "px";
16166                         finalLabel.style.top = finalPos.y - label.size + "px";
16167                     }
16168           
16169         }
16170       }
16171    
16172     });
16173     this.sb = sb;
16174     this.canvas = this.sb.canvas;
16175     var size = sb.canvas.getSize(),
16176         min = Math.min;
16177         sb.config.levelDistance = min(size.width, size.height)/2 
16178       - config.offset - config.sliceOffset;
16179
16180
16181   },
16182         
16183   renderBackground: function() {
16184         var canvas = this.sb.canvas,
16185         config = this.config,
16186         style = config.gaugeStyle,
16187         ctx = canvas.getCtx(),
16188         size = canvas.getSize(),
16189         radius = this.sb.config.levelDistance,
16190         startAngle = (Math.PI/180)*1,
16191         endAngle = (Math.PI/180)*179;
16192         
16193
16194         //background border
16195         ctx.fillStyle = style.borderColor;      
16196         ctx.beginPath();
16197         ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true); 
16198         ctx.fill(); 
16199         
16200         
16201         var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16202         radialGradient.addColorStop(0, '#ffffff');  
16203         radialGradient.addColorStop(0.3, style.backgroundColor);  
16204         radialGradient.addColorStop(0.6, style.backgroundColor);  
16205         radialGradient.addColorStop(1, '#FFFFFF'); 
16206         ctx.fillStyle = radialGradient;
16207         
16208         //background
16209         startAngle = (Math.PI/180)*0;
16210         endAngle = (Math.PI/180)*180;
16211         ctx.beginPath();
16212         ctx.arc(0,radius/2,radius,startAngle,endAngle, true); 
16213         ctx.fill();     
16214         
16215         
16216  
16217   },
16218   
16219   
16220   renderNeedle: function(gaugePosition,target) {
16221         var canvas = this.sb.canvas,
16222         config = this.config,
16223         style = config.gaugeStyle,
16224         ctx = canvas.getCtx(),
16225         size = canvas.getSize(),
16226         radius = this.sb.config.levelDistance;
16227         gaugeCenter = (radius/2);
16228         startAngle = 0;
16229         endAngle = (Math.PI/180)*180;
16230         
16231         
16232         // needle
16233         ctx.fillStyle = style.needleColor;
16234         var segments = 180/target;
16235         needleAngle = gaugePosition * segments;
16236         ctx.translate(0, gaugeCenter);
16237         ctx.save();
16238         ctx.rotate(needleAngle * Math.PI / 180);  
16239         ctx.beginPath();
16240         ctx.moveTo(0,0); 
16241         ctx.lineTo(0,-4);  
16242         ctx.lineTo(-radius*.9,-1);  
16243         ctx.lineTo(-radius*.9,1);  
16244         ctx.lineTo(0,4);  
16245         ctx.lineTo(0,0);  
16246         ctx.closePath(); 
16247         ctx.fill();
16248         ctx.restore(); 
16249         
16250         
16251         // stroke needle
16252         ctx.lineWidth = 1;
16253         ctx.strokeStyle = '#aa0000';
16254         ctx.save();
16255         ctx.rotate(needleAngle * Math.PI / 180);  
16256         ctx.beginPath();
16257         ctx.moveTo(0,0); 
16258         ctx.lineTo(0,-4);  
16259         ctx.lineTo(-radius*.8,-1);  
16260         ctx.lineTo(-radius*.8,1);  
16261         ctx.lineTo(0,4);  
16262         ctx.lineTo(0,0);  
16263         ctx.closePath(); 
16264         ctx.stroke();
16265         ctx.restore(); 
16266
16267         //needle cap
16268         ctx.fillStyle = "#000000";
16269         ctx.lineWidth = style.borderSize;
16270     ctx.strokeStyle = style.borderColor;
16271         var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16272         radialGradient.addColorStop(0, '#666666');  
16273         radialGradient.addColorStop(0.8, '#444444');  
16274         radialGradient.addColorStop(1, 'rgba(0,0,0,0)'); 
16275         ctx.fillStyle = radialGradient;
16276         ctx.translate(0,5);
16277         ctx.save();
16278         ctx.beginPath();
16279         ctx.arc(0,0,radius*.2,startAngle,endAngle, true); 
16280         ctx.fill();     
16281         ctx.restore();
16282
16283         
16284   },
16285   
16286   renderTicks: function(values) {
16287         var canvas = this.sb.canvas,
16288         config = this.config,
16289         style = config.gaugeStyle,
16290         ctx = canvas.getCtx(),
16291         size = canvas.getSize(),
16292         radius = this.sb.config.levelDistance,
16293         gaugeCenter = (radius/2);
16294         
16295         
16296         ctx.strokeStyle = style.borderColor;
16297         ctx.lineWidth = 5;
16298         ctx.lineCap = "round";
16299                 for(var i=0, total = 0, l=values.length; i<l; i++) {
16300                         var val = values[i];
16301                         if(val.label != 'GaugePosition') {
16302                         total += (parseInt(val.values) || 0);
16303                         }
16304                 }
16305         
16306                 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16307                         var val = values[i];
16308                         if(val.label != 'GaugePosition') {
16309                         acum += (parseInt(val.values) || 0);
16310
16311                            var segments = 180/total;
16312                         angle = acum * segments;
16313
16314                           //alert(acum);
16315                                  ctx.save();
16316                                  ctx.translate(0, gaugeCenter);
16317                                  ctx.beginPath();
16318                                 ctx.rotate(angle * (Math.PI/180));
16319                                 ctx.moveTo(-radius,0);
16320                                 ctx.lineTo(-radius*.75,0);
16321                                 ctx.stroke();
16322                                  ctx.restore();
16323                         
16324                         }
16325                 }
16326         },
16327         
16328         renderPositionLabel: function(position) {
16329                 var canvas = this.sb.canvas,
16330                 config = this.config,
16331                 label = config.Label,
16332                 style = config.gaugeStyle,
16333                 ctx = canvas.getCtx(),
16334                 size = canvas.getSize(),
16335                 radius = this.sb.config.levelDistance,
16336                 gaugeCenter = (radius/2);
16337                 ctx.textBaseline = 'middle';
16338                 ctx.textAlign = 'center';
16339                 ctx.font = style.positionFontSize + 'px ' + label.family;
16340                 ctx.fillStyle = "#ffffff";
16341                 ctx.lineWidth = 2;
16342                 height = style.positionFontSize + 10,
16343                 cornerRadius = 8,
16344                 idLabel = canvas.id + "-label";
16345                 container = document.getElementById(idLabel);
16346                 if(label.type == 'Native') {
16347                         var m = ctx.measureText(position),
16348                         width = m.width + 40;
16349                 } else {
16350                         var width = 70;
16351                 }
16352                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16353                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16354                 if(label.type == 'Native') {
16355                         ctx.fillStyle = label.color;
16356                         ctx.fillText(position, 0, (height/2) + style.positionOffset);
16357                 } else {
16358                         var labelDiv =  document.createElement('div');
16359                         labelDivStyle = labelDiv.style;
16360                         labelDivStyle.color =  label.color;
16361                         labelDivStyle.fontSize =  style.positionFontSize + "px";
16362                         labelDivStyle.position = "absolute";
16363                         labelDivStyle.width = width + "px";
16364                         labelDivStyle.left = (size.width/2) - (width/2) + "px";
16365                         labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16366                         labelDiv.innerHTML = position;
16367                         container.appendChild(labelDiv);
16368                 }
16369         
16370     },
16371     
16372    renderSubtitle: function() {
16373         var canvas = this.canvas,
16374         size = canvas.getSize(),
16375         config = this.config,
16376         margin = config.Margin,
16377         radius = this.sb.config.levelDistance,
16378         title = config.Title,
16379         label = config.Label,
16380         subtitle = config.Subtitle;
16381         ctx = canvas.getCtx();
16382         ctx.fillStyle = title.color;
16383         ctx.textAlign = 'left';
16384         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16385         ctx.moveTo(0,0);
16386         if(label.type == 'Native') {
16387                 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2)); 
16388         }
16389   },
16390   
16391   renderChartBackground: function() {
16392                 var canvas = this.canvas,
16393                 config = this.config,
16394                 backgroundColor = config.backgroundColor,
16395                 size = canvas.getSize(),
16396                 ctx = canvas.getCtx();
16397                 //ctx.globalCompositeOperation = "destination-over";
16398             ctx.fillStyle = backgroundColor;
16399             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
16400   },
16401   
16402   loadJSON: function(json) {
16403   
16404      var prefix = $.time(), 
16405         ch = [], 
16406         sb = this.sb,
16407         name = $.splat(json.label),
16408         nameLength = name.length,
16409         color = $.splat(json.color || this.colors),
16410         colorLength = color.length,
16411         config = this.config,
16412         renderBackground = config.renderBackground,
16413         gradient = !!config.type.split(":")[1],
16414         animate = config.animate,
16415         mono = nameLength == 1;
16416                 var props = $.splat(json.properties)[0];
16417
16418     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16419         
16420       var val = values[i];
16421           if(val.label != 'GaugePosition') {
16422                   var valArray = $.splat(val.values);
16423                   var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16424                   var valuelabelsArray = $.splat(val.valuelabels);
16425
16426                   ch.push({
16427                         'id': prefix + val.label,
16428                         'name': val.label,
16429                         'data': {
16430                           'value': valArray,
16431                           'valuelabel': valuelabelsArray,
16432                           '$linkArray': linkArray,
16433                           '$valuelabelsArray': valuelabelsArray,
16434                           '$valueArray': valArray,
16435                           '$nodeIteration': i,
16436                           '$nodeLength': l-1,
16437                           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16438                           '$colorMono': $.splat(color[i % colorLength]),
16439                           '$stringArray': name,
16440                           '$gradient': gradient,
16441                           '$config': config,
16442                           '$gaugeTarget': props['gaugeTarget'],
16443                           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16444                         },
16445                         'children': []
16446                   });
16447           } else {
16448                 var gaugePosition = val.gvalue;
16449                 var gaugePositionLabel = val.gvaluelabel;
16450           }
16451     }
16452     var root = {
16453       'id': prefix + '$root',
16454       'name': '',
16455       'data': {
16456         '$type': 'none',
16457         '$width': 1,
16458         '$height': 1
16459       },
16460       'children': ch
16461     };
16462         
16463         
16464     sb.loadJSON(root);
16465     
16466     if(renderBackground) {
16467         this.renderChartBackground();   
16468     }
16469     
16470     this.renderBackground();
16471     this.renderSubtitle();
16472     
16473     this.normalizeDims();
16474         
16475     sb.refresh();
16476     if(animate) {
16477       sb.fx.animate({
16478         modes: ['node-property:dimArray'],
16479         duration:1500
16480       });
16481     }
16482         
16483
16484         this.renderPositionLabel(gaugePositionLabel);
16485         if (props['gaugeTarget'] != 0) {
16486                 this.renderTicks(json.values);
16487                 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16488         }
16489         
16490         
16491
16492   },
16493   
16494   updateJSON: function(json, onComplete) {
16495     if(this.busy) return;
16496     this.busy = true;
16497     
16498     var sb = this.sb;
16499     var graph = sb.graph;
16500     var values = json.values;
16501     var animate = this.config.animate;
16502     var that = this;
16503     $.each(values, function(v) {
16504       var n = graph.getByName(v.label),
16505           vals = $.splat(v.values);
16506       if(n) {
16507         n.setData('valueArray', vals);
16508         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16509         if(json.label) {
16510           n.setData('stringArray', $.splat(json.label));
16511         }
16512       }
16513     });
16514     this.normalizeDims();
16515     if(animate) {
16516       sb.compute('end');
16517       sb.fx.animate({
16518         modes: ['node-property:dimArray:span', 'linear'],
16519         duration:1500,
16520         onComplete: function() {
16521           that.busy = false;
16522           onComplete && onComplete.onComplete();
16523         }
16524       });
16525     } else {
16526       sb.refresh();
16527     }
16528   },
16529     
16530   //adds the little brown bar when hovering the node
16531   select: function(id, name) {
16532     if(!this.config.hoveredColor) return;
16533     var s = this.selected;
16534     if(s.id != id || s.name != name) {
16535       s.id = id;
16536       s.name = name;
16537       s.color = this.config.hoveredColor;
16538       this.sb.graph.eachNode(function(n) {
16539         if(id == n.id) {
16540           n.setData('border', s);
16541         } else {
16542           n.setData('border', false);
16543         }
16544       });
16545       this.sb.plot();
16546     }
16547   },
16548   
16549   /*
16550     Method: getLegend
16551    
16552     Returns an object containing as keys the legend names and as values hex strings with color values.
16553     
16554     Example:
16555     
16556     (start code js)
16557     var legend = pieChart.getLegend();
16558     (end code)
16559   */  
16560   getLegend: function() {
16561     var legend = new Array();
16562     var name = new Array();
16563     var color = new Array();
16564     var n;
16565     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16566       n = adj.nodeTo;
16567     });
16568     var colors = n.getData('colorArray'),
16569         len = colors.length;
16570     $.each(n.getData('stringArray'), function(s, i) {
16571       color[i] = colors[i % len];
16572       name[i] = s;
16573     });
16574         legend['name'] = name;
16575         legend['color'] = color;
16576     return legend;
16577   },
16578   
16579   /*
16580     Method: getMaxValue
16581    
16582     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16583     
16584     Example:
16585     
16586     (start code js)
16587     var ans = pieChart.getMaxValue();
16588     (end code)
16589     
16590     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16591     
16592     Example:
16593     
16594     (start code js)
16595     //will return 100 for all PieChart instances,
16596     //displaying all of them with the same scale
16597     $jit.PieChart.implement({
16598       'getMaxValue': function() {
16599         return 100;
16600       }
16601     });
16602     (end code)
16603     
16604   */  
16605   getMaxValue: function() {
16606     var maxValue = 0;
16607     this.sb.graph.eachNode(function(n) {
16608       var valArray = n.getData('valueArray'),
16609           acum = 0;
16610       $.each(valArray, function(v) { 
16611         acum += +v;
16612       });
16613       maxValue = maxValue>acum? maxValue:acum;
16614     });
16615     return maxValue;
16616   },
16617   
16618   normalizeDims: function() {
16619     //number of elements
16620     var root = this.sb.graph.getNode(this.sb.root), l=0;
16621     root.eachAdjacency(function() {
16622       l++;
16623     });
16624     var maxValue = this.getMaxValue() || 1,
16625         config = this.config,
16626         animate = config.animate,
16627         rho = this.sb.config.levelDistance;
16628     this.sb.graph.eachNode(function(n) {
16629       var acum = 0, animateValue = [];
16630       $.each(n.getData('valueArray'), function(v) {
16631         acum += +v;
16632         animateValue.push(1);
16633       });
16634       var stat = (animateValue.length == 1) && !config.updateHeights;
16635       if(animate) {
16636         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16637           return stat? rho: (n * rho / maxValue); 
16638         }), 'end');
16639         var dimArray = n.getData('dimArray');
16640         if(!dimArray) {
16641           n.setData('dimArray', animateValue);
16642         }
16643       } else {
16644         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16645           return stat? rho : (n * rho / maxValue); 
16646         }));
16647       }
16648       n.setData('normalizedDim', acum / maxValue);
16649     });
16650   }
16651 });
16652
16653
16654 /*
16655  * Class: Layouts.TM
16656  * 
16657  * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16658  * 
16659  * Implemented By:
16660  * 
16661  * <TM>
16662  * 
16663  */
16664 Layouts.TM = {};
16665
16666 Layouts.TM.SliceAndDice = new Class({
16667   compute: function(prop) {
16668     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16669     this.controller.onBeforeCompute(root);
16670     var size = this.canvas.getSize(),
16671         config = this.config,
16672         width = size.width,
16673         height = size.height;
16674     this.graph.computeLevels(this.root, 0, "ignore");
16675     //set root position and dimensions
16676     root.getPos(prop).setc(-width/2, -height/2);
16677     root.setData('width', width, prop);
16678     root.setData('height', height + config.titleHeight, prop);
16679     this.computePositions(root, root, this.layout.orientation, prop);
16680     this.controller.onAfterCompute(root);
16681   },
16682   
16683   computePositions: function(par, ch, orn, prop) {
16684     //compute children areas
16685     var totalArea = 0;
16686     par.eachSubnode(function(n) {
16687       totalArea += n.getData('area', prop);
16688     });
16689     
16690     var config = this.config,
16691         offst = config.offset,
16692         width  = par.getData('width', prop),
16693         height = par.getData('height', prop) - config.titleHeight,
16694         fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16695     
16696     var otherSize, size, dim, pos, pos2, posth, pos2th;
16697     var horizontal = (orn == "h");
16698     if(horizontal) {
16699       orn = 'v';    
16700       otherSize = height;
16701       size = width * fact;
16702       dim = 'height';
16703       pos = 'y';
16704       pos2 = 'x';
16705       posth = config.titleHeight;
16706       pos2th = 0;
16707     } else {
16708       orn = 'h';    
16709       otherSize = height * fact;
16710       size = width;
16711       dim = 'width';
16712       pos = 'x';
16713       pos2 = 'y';
16714       posth = 0;
16715       pos2th = config.titleHeight;
16716     }
16717     var cpos = ch.getPos(prop);
16718     ch.setData('width', size, prop);
16719     ch.setData('height', otherSize, prop);
16720     var offsetSize = 0, tm = this;
16721     ch.eachSubnode(function(n) {
16722       var p = n.getPos(prop);
16723       p[pos] = offsetSize + cpos[pos] + posth;
16724       p[pos2] = cpos[pos2] + pos2th;
16725       tm.computePositions(ch, n, orn, prop);
16726       offsetSize += n.getData(dim, prop);
16727     });
16728   }
16729
16730 });
16731
16732 Layouts.TM.Area = {
16733  /*
16734     Method: compute
16735  
16736    Called by loadJSON to calculate recursively all node positions and lay out the tree.
16737  
16738     Parameters:
16739
16740        json - A JSON tree. See also <Loader.loadJSON>.
16741        coord - A coordinates object specifying width, height, left and top style properties.
16742  */
16743  compute: function(prop) {
16744     prop = prop || "current";
16745     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16746     this.controller.onBeforeCompute(root);
16747     var config = this.config,
16748         size = this.canvas.getSize(),
16749         width = size.width,
16750         height = size.height,
16751         offst = config.offset,
16752         offwdth = width - offst,
16753         offhght = height - offst;
16754     this.graph.computeLevels(this.root, 0, "ignore");
16755     //set root position and dimensions
16756     root.getPos(prop).setc(-width/2, -height/2);
16757     root.setData('width', width, prop);
16758     root.setData('height', height, prop);
16759     //create a coordinates object
16760     var coord = {
16761         'top': -height/2 + config.titleHeight,
16762         'left': -width/2,
16763         'width': offwdth,
16764         'height': offhght - config.titleHeight
16765     };
16766     this.computePositions(root, coord, prop);
16767     this.controller.onAfterCompute(root);
16768  }, 
16769  
16770  /*
16771     Method: computeDim
16772  
16773    Computes dimensions and positions of a group of nodes
16774    according to a custom layout row condition. 
16775  
16776     Parameters:
16777
16778        tail - An array of nodes.  
16779        initElem - An array of nodes (containing the initial node to be laid).
16780        w - A fixed dimension where nodes will be layed out.
16781        coord - A coordinates object specifying width, height, left and top style properties.
16782        comp - A custom comparison function
16783  */
16784  computeDim: function(tail, initElem, w, coord, comp, prop) {
16785    if(tail.length + initElem.length == 1) {
16786      var l = (tail.length == 1)? tail : initElem;
16787      this.layoutLast(l, w, coord, prop);
16788      return;
16789    }
16790    if(tail.length >= 2 && initElem.length == 0) {
16791      initElem = [tail.shift()];
16792    }
16793    if(tail.length == 0) {
16794      if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
16795      return;
16796    }
16797    var c = tail[0];
16798    if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
16799      this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
16800    } else {
16801      var newCoords = this.layoutRow(initElem, w, coord, prop);
16802      this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
16803    }
16804  },
16805
16806  
16807  /*
16808     Method: worstAspectRatio
16809  
16810    Calculates the worst aspect ratio of a group of rectangles. 
16811        
16812     See also:
16813        
16814        <http://en.wikipedia.org/wiki/Aspect_ratio>
16815    
16816     Parameters:
16817
16818      ch - An array of nodes.  
16819      w  - The fixed dimension where rectangles are being laid out.
16820
16821     Returns:
16822  
16823         The worst aspect ratio.
16824
16825
16826  */
16827  worstAspectRatio: function(ch, w) {
16828    if(!ch || ch.length == 0) return Number.MAX_VALUE;
16829    var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
16830    for(var i=0, l=ch.length; i<l; i++) {
16831      var area = ch[i]._area;
16832      areaSum += area; 
16833      minArea = minArea < area? minArea : area;
16834      maxArea = maxArea > area? maxArea : area; 
16835    }
16836    var sqw = w * w, sqAreaSum = areaSum * areaSum;
16837    return Math.max(sqw * maxArea / sqAreaSum,
16838            sqAreaSum / (sqw * minArea));
16839  },
16840  
16841  /*
16842     Method: avgAspectRatio
16843  
16844    Calculates the average aspect ratio of a group of rectangles. 
16845        
16846        See also:
16847        
16848        <http://en.wikipedia.org/wiki/Aspect_ratio>
16849    
16850     Parameters:
16851
16852      ch - An array of nodes.  
16853        w - The fixed dimension where rectangles are being laid out.
16854
16855     Returns:
16856  
16857         The average aspect ratio.
16858
16859
16860  */
16861  avgAspectRatio: function(ch, w) {
16862    if(!ch || ch.length == 0) return Number.MAX_VALUE;
16863    var arSum = 0;
16864    for(var i=0, l=ch.length; i<l; i++) {
16865      var area = ch[i]._area;
16866      var h = area / w;
16867      arSum += w > h? w / h : h / w;
16868    }
16869    return arSum / l;
16870  },
16871
16872  /*
16873     layoutLast
16874  
16875    Performs the layout of the last computed sibling.
16876  
16877     Parameters:
16878
16879        ch - An array of nodes.  
16880        w - A fixed dimension where nodes will be layed out.
16881      coord - A coordinates object specifying width, height, left and top style properties.
16882  */
16883  layoutLast: function(ch, w, coord, prop) {
16884    var child = ch[0];
16885    child.getPos(prop).setc(coord.left, coord.top);
16886    child.setData('width', coord.width, prop);
16887    child.setData('height', coord.height, prop);
16888  }
16889 };
16890
16891
16892 Layouts.TM.Squarified = new Class({
16893  Implements: Layouts.TM.Area,
16894  
16895  computePositions: function(node, coord, prop) {
16896    var config = this.config;
16897    
16898    if (coord.width >= coord.height) 
16899      this.layout.orientation = 'h';
16900    else
16901      this.layout.orientation = 'v';
16902    
16903    var ch = node.getSubnodes([1, 1], "ignore");
16904    if(ch.length > 0) {
16905      this.processChildrenLayout(node, ch, coord, prop);
16906      for(var i=0, l=ch.length; i<l; i++) {
16907        var chi = ch[i]; 
16908        var offst = config.offset,
16909            height = chi.getData('height', prop) - offst - config.titleHeight,
16910            width = chi.getData('width', prop) - offst;
16911        var chipos = chi.getPos(prop);
16912        coord = {
16913          'width': width,
16914          'height': height,
16915          'top': chipos.y + config.titleHeight,
16916          'left': chipos.x
16917        };
16918        this.computePositions(chi, coord, prop);
16919      }
16920    }
16921  },
16922
16923  /*
16924     Method: processChildrenLayout
16925  
16926    Computes children real areas and other useful parameters for performing the Squarified algorithm.
16927  
16928     Parameters:
16929
16930        par - The parent node of the json subtree.  
16931        ch - An Array of nodes
16932      coord - A coordinates object specifying width, height, left and top style properties.
16933  */
16934  processChildrenLayout: function(par, ch, coord, prop) {
16935    //compute children real areas
16936    var parentArea = coord.width * coord.height;
16937    var i, l=ch.length, totalChArea=0, chArea = [];
16938    for(i=0; i<l; i++) {
16939      chArea[i] = parseFloat(ch[i].getData('area', prop));
16940      totalChArea += chArea[i];
16941    }
16942    for(i=0; i<l; i++) {
16943      ch[i]._area = parentArea * chArea[i] / totalChArea;
16944    }
16945    var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
16946    ch.sort(function(a, b) { 
16947      var diff = b._area - a._area; 
16948      return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1)); 
16949    });
16950    var initElem = [ch[0]];
16951    var tail = ch.slice(1);
16952    this.squarify(tail, initElem, minimumSideValue, coord, prop);
16953  },
16954
16955  /*
16956    Method: squarify
16957  
16958    Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
16959  
16960     Parameters:
16961
16962        tail - An array of nodes.  
16963        initElem - An array of nodes, containing the initial node to be laid out.
16964        w - A fixed dimension where nodes will be laid out.
16965        coord - A coordinates object specifying width, height, left and top style properties.
16966  */
16967  squarify: function(tail, initElem, w, coord, prop) {
16968    this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
16969  },
16970  
16971  /*
16972     Method: layoutRow
16973  
16974    Performs the layout of an array of nodes.
16975  
16976     Parameters:
16977
16978        ch - An array of nodes.  
16979        w - A fixed dimension where nodes will be laid out.
16980        coord - A coordinates object specifying width, height, left and top style properties.
16981  */
16982  layoutRow: function(ch, w, coord, prop) {
16983    if(this.layout.horizontal()) {
16984      return this.layoutV(ch, w, coord, prop);
16985    } else {
16986      return this.layoutH(ch, w, coord, prop);
16987    }
16988  },
16989  
16990  layoutV: function(ch, w, coord, prop) {
16991    var totalArea = 0, rnd = function(x) { return x; }; 
16992    $.each(ch, function(elem) { totalArea += elem._area; });
16993    var width = rnd(totalArea / w), top =  0; 
16994    for(var i=0, l=ch.length; i<l; i++) {
16995      var h = rnd(ch[i]._area / width);
16996      var chi = ch[i];
16997      chi.getPos(prop).setc(coord.left, coord.top + top);
16998      chi.setData('width', width, prop);
16999      chi.setData('height', h, prop);
17000      top += h;
17001    }
17002    var ans = {
17003      'height': coord.height,
17004      'width': coord.width - width,
17005      'top': coord.top,
17006      'left': coord.left + width
17007    };
17008    //take minimum side value.
17009    ans.dim = Math.min(ans.width, ans.height);
17010    if(ans.dim != ans.height) this.layout.change();
17011    return ans;
17012  },
17013  
17014  layoutH: function(ch, w, coord, prop) {
17015    var totalArea = 0; 
17016    $.each(ch, function(elem) { totalArea += elem._area; });
17017    var height = totalArea / w,
17018        top = coord.top, 
17019        left = 0;
17020    
17021    for(var i=0, l=ch.length; i<l; i++) {
17022      var chi = ch[i];
17023      var w = chi._area / height;
17024      chi.getPos(prop).setc(coord.left + left, top);
17025      chi.setData('width', w, prop);
17026      chi.setData('height', height, prop);
17027      left += w;
17028    }
17029    var ans = {
17030      'height': coord.height - height,
17031      'width': coord.width,
17032      'top': coord.top + height,
17033      'left': coord.left
17034    };
17035    ans.dim = Math.min(ans.width, ans.height);
17036    if(ans.dim != ans.width) this.layout.change();
17037    return ans;
17038  }
17039 });
17040
17041 Layouts.TM.Strip = new Class({
17042   Implements: Layouts.TM.Area,
17043
17044     /*
17045       Method: compute
17046     
17047      Called by loadJSON to calculate recursively all node positions and lay out the tree.
17048     
17049       Parameters:
17050     
17051          json - A JSON subtree. See also <Loader.loadJSON>. 
17052        coord - A coordinates object specifying width, height, left and top style properties.
17053     */
17054     computePositions: function(node, coord, prop) {
17055      var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17056      if(ch.length > 0) {
17057        this.processChildrenLayout(node, ch, coord, prop);
17058        for(var i=0, l=ch.length; i<l; i++) {
17059          var chi = ch[i];
17060          var offst = config.offset,
17061              height = chi.getData('height', prop) - offst - config.titleHeight,
17062              width  = chi.getData('width', prop)  - offst;
17063          var chipos = chi.getPos(prop);
17064          coord = {
17065            'width': width,
17066            'height': height,
17067            'top': chipos.y + config.titleHeight,
17068            'left': chipos.x
17069          };
17070          this.computePositions(chi, coord, prop);
17071        }
17072      }
17073     },
17074     
17075     /*
17076       Method: processChildrenLayout
17077     
17078      Computes children real areas and other useful parameters for performing the Strip algorithm.
17079     
17080       Parameters:
17081     
17082          par - The parent node of the json subtree.  
17083          ch - An Array of nodes
17084          coord - A coordinates object specifying width, height, left and top style properties.
17085     */
17086     processChildrenLayout: function(par, ch, coord, prop) {
17087      //compute children real areas
17088       var parentArea = coord.width * coord.height;
17089       var i, l=ch.length, totalChArea=0, chArea = [];
17090       for(i=0; i<l; i++) {
17091         chArea[i] = +ch[i].getData('area', prop);
17092         totalChArea += chArea[i];
17093       }
17094       for(i=0; i<l; i++) {
17095         ch[i]._area = parentArea * chArea[i] / totalChArea;
17096       }
17097      var side = this.layout.horizontal()? coord.width : coord.height;
17098      var initElem = [ch[0]];
17099      var tail = ch.slice(1);
17100      this.stripify(tail, initElem, side, coord, prop);
17101     },
17102     
17103     /*
17104       Method: stripify
17105     
17106      Performs an heuristic method to calculate div elements sizes in order to have 
17107      a good compromise between aspect ratio and order.
17108     
17109       Parameters:
17110     
17111          tail - An array of nodes.  
17112          initElem - An array of nodes.
17113          w - A fixed dimension where nodes will be layed out.
17114        coord - A coordinates object specifying width, height, left and top style properties.
17115     */
17116     stripify: function(tail, initElem, w, coord, prop) {
17117      this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17118     },
17119     
17120     /*
17121       Method: layoutRow
17122     
17123      Performs the layout of an array of nodes.
17124     
17125       Parameters:
17126     
17127          ch - An array of nodes.  
17128          w - A fixed dimension where nodes will be laid out.
17129          coord - A coordinates object specifying width, height, left and top style properties.
17130     */
17131     layoutRow: function(ch, w, coord, prop) {
17132      if(this.layout.horizontal()) {
17133        return this.layoutH(ch, w, coord, prop);
17134      } else {
17135        return this.layoutV(ch, w, coord, prop);
17136      }
17137     },
17138     
17139     layoutV: function(ch, w, coord, prop) {
17140      var totalArea = 0; 
17141      $.each(ch, function(elem) { totalArea += elem._area; });
17142      var width = totalArea / w, top =  0; 
17143      for(var i=0, l=ch.length; i<l; i++) {
17144        var chi = ch[i];
17145        var h = chi._area / width;
17146        chi.getPos(prop).setc(coord.left, 
17147            coord.top + (w - h - top));
17148        chi.setData('width', width, prop);
17149        chi.setData('height', h, prop);
17150        top += h;
17151      }
17152     
17153      return {
17154        'height': coord.height,
17155        'width': coord.width - width,
17156        'top': coord.top,
17157        'left': coord.left + width,
17158        'dim': w
17159      };
17160     },
17161     
17162     layoutH: function(ch, w, coord, prop) {
17163      var totalArea = 0; 
17164      $.each(ch, function(elem) { totalArea += elem._area; });
17165      var height = totalArea / w,
17166          top = coord.height - height, 
17167          left = 0;
17168      
17169      for(var i=0, l=ch.length; i<l; i++) {
17170        var chi = ch[i];
17171        var s = chi._area / height;
17172        chi.getPos(prop).setc(coord.left + left, coord.top + top);
17173        chi.setData('width', s, prop);
17174        chi.setData('height', height, prop);
17175        left += s;
17176      }
17177      return {
17178        'height': coord.height - height,
17179        'width': coord.width,
17180        'top': coord.top,
17181        'left': coord.left,
17182        'dim': w
17183      };
17184     }
17185  });
17186
17187 /*
17188  * Class: Layouts.Icicle
17189  *
17190  * Implements the icicle tree layout.
17191  *
17192  * Implemented By:
17193  *
17194  * <Icicle>
17195  *
17196  */
17197
17198 Layouts.Icicle = new Class({
17199  /*
17200   * Method: compute
17201   *
17202   * Called by loadJSON to calculate all node positions.
17203   *
17204   * Parameters:
17205   *
17206   * posType - The nodes' position to compute. Either "start", "end" or
17207   *            "current". Defaults to "current".
17208   */
17209   compute: function(posType) {
17210     posType = posType || "current";
17211     var root = this.graph.getNode(this.root),
17212         config = this.config,
17213         size = this.canvas.getSize(),
17214         width = size.width,
17215         height = size.height,
17216         offset = config.offset,
17217         levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17218
17219     this.controller.onBeforeCompute(root);
17220
17221     Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17222
17223     var treeDepth = 0;
17224
17225     Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17226
17227     var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17228     var maxDepth = Math.min(treeDepth, levelsToShow-1);
17229     var initialDepth = startNode._depth;
17230     if(this.layout.horizontal()) {
17231       this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17232     } else {
17233       this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17234     }
17235   },
17236
17237   computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17238     root.getPos(posType).setc(x, y);
17239     root.setData('width', width, posType);
17240     root.setData('height', height, posType);
17241
17242     var nodeLength, prevNodeLength = 0, totalDim = 0;
17243     var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17244
17245     if(!children.length)
17246       return;
17247
17248     $.each(children, function(e) { totalDim += e.getData('dim'); });
17249
17250     for(var i=0, l=children.length; i < l; i++) {
17251       if(this.layout.horizontal()) {
17252         nodeLength = height * children[i].getData('dim') / totalDim;
17253         this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17254         y += nodeLength;
17255       } else {
17256         nodeLength = width * children[i].getData('dim') / totalDim;
17257         this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17258         x += nodeLength;
17259       }
17260     }
17261   }
17262 });
17263
17264
17265
17266 /*
17267  * File: Icicle.js
17268  *
17269 */
17270
17271 /*
17272   Class: Icicle
17273   
17274   Icicle space filling visualization.
17275   
17276   Implements:
17277   
17278   All <Loader> methods
17279   
17280   Constructor Options:
17281   
17282   Inherits options from
17283   
17284   - <Options.Canvas>
17285   - <Options.Controller>
17286   - <Options.Node>
17287   - <Options.Edge>
17288   - <Options.Label>
17289   - <Options.Events>
17290   - <Options.Tips>
17291   - <Options.NodeStyles>
17292   - <Options.Navigation>
17293   
17294   Additionally, there are other parameters and some default values changed
17295
17296   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17297   offset - (number) Default's *2*. Boxes offset.
17298   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17299   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17300   animate - (boolean) Default's *false*. Whether to animate transitions.
17301   Node.type - Described in <Options.Node>. Default's *rectangle*.
17302   Label.type - Described in <Options.Label>. Default's *Native*.
17303   duration - Described in <Options.Fx>. Default's *700*.
17304   fps - Described in <Options.Fx>. Default's *45*.
17305   
17306   Instance Properties:
17307   
17308   canvas - Access a <Canvas> instance.
17309   graph - Access a <Graph> instance.
17310   op - Access a <Icicle.Op> instance.
17311   fx - Access a <Icicle.Plot> instance.
17312   labels - Access a <Icicle.Label> interface implementation.
17313
17314 */
17315
17316 $jit.Icicle = new Class({
17317   Implements: [ Loader, Extras, Layouts.Icicle ],
17318
17319   layout: {
17320     orientation: "h",
17321     vertical: function(){
17322       return this.orientation == "v";
17323     },
17324     horizontal: function(){
17325       return this.orientation == "h";
17326     },
17327     change: function(){
17328       this.orientation = this.vertical()? "h" : "v";
17329     }
17330   },
17331
17332   initialize: function(controller) {
17333     var config = {
17334       animate: false,
17335       orientation: "h",
17336       offset: 2,
17337       levelsToShow: Number.MAX_VALUE,
17338       constrained: false,
17339       Node: {
17340         type: 'rectangle',
17341         overridable: true
17342       },
17343       Edge: {
17344         type: 'none'
17345       },
17346       Label: {
17347         type: 'Native'
17348       },
17349       duration: 700,
17350       fps: 45
17351     };
17352
17353     var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17354                        "Events", "Navigation", "Controller", "Label");
17355     this.controller = this.config = $.merge(opts, config, controller);
17356     this.layout.orientation = this.config.orientation;
17357
17358     var canvasConfig = this.config;
17359     if (canvasConfig.useCanvas) {
17360       this.canvas = canvasConfig.useCanvas;
17361       this.config.labelContainer = this.canvas.id + '-label';
17362     } else {
17363       this.canvas = new Canvas(this, canvasConfig);
17364       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17365     }
17366
17367     this.graphOptions = {
17368       'complex': true,
17369       'Node': {
17370         'selected': false,
17371         'exist': true,
17372         'drawn': true
17373       }
17374     };
17375
17376     this.graph = new Graph(
17377       this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17378
17379     this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17380     this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17381     this.op = new $jit.Icicle.Op(this);
17382     this.group = new $jit.Icicle.Group(this);
17383     this.clickedNode = null;
17384
17385     this.initializeExtras();
17386   },
17387
17388   /* 
17389     Method: refresh 
17390     
17391     Computes positions and plots the tree.
17392   */
17393   refresh: function(){
17394     var labelType = this.config.Label.type;
17395     if(labelType != 'Native') {
17396       var that = this;
17397       this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17398     }
17399     this.compute();
17400     this.plot();
17401   },
17402
17403   /* 
17404     Method: plot 
17405     
17406     Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
17407   
17408    */
17409   plot: function(){
17410     this.fx.plot(this.config);
17411   },
17412
17413   /* 
17414     Method: enter 
17415     
17416     Sets the node as root.
17417     
17418      Parameters:
17419      
17420      node - (object) A <Graph.Node>.
17421   
17422    */
17423   enter: function (node) {
17424     if (this.busy)
17425       return;
17426     this.busy = true;
17427
17428     var that = this,
17429         config = this.config;
17430
17431     var callback = {
17432       onComplete: function() {
17433         //compute positions of newly inserted nodes
17434         if(config.request)
17435           that.compute();
17436
17437         if(config.animate) {
17438           that.graph.nodeList.setDataset(['current', 'end'], {
17439             'alpha': [1, 0] //fade nodes
17440           });
17441
17442           Graph.Util.eachSubgraph(node, function(n) {
17443             n.setData('alpha', 1, 'end');
17444           }, "ignore");
17445
17446           that.fx.animate({
17447             duration: 500,
17448             modes:['node-property:alpha'],
17449             onComplete: function() {
17450               that.clickedNode = node;
17451               that.compute('end');
17452
17453               that.fx.animate({
17454                 modes:['linear', 'node-property:width:height'],
17455                 duration: 1000,
17456                 onComplete: function() {
17457                   that.busy = false;
17458                   that.clickedNode = node;
17459                 }
17460               });
17461             }
17462           });
17463         } else {
17464           that.clickedNode = node;
17465           that.busy = false;
17466           that.refresh();
17467         }
17468       }
17469     };
17470
17471     if(config.request) {
17472       this.requestNodes(clickedNode, callback);
17473     } else {
17474       callback.onComplete();
17475     }
17476   },
17477
17478   /* 
17479     Method: out 
17480     
17481     Sets the parent node of the current selected node as root.
17482   
17483    */
17484   out: function(){
17485     if(this.busy)
17486       return;
17487
17488     var that = this,
17489         GUtil = Graph.Util,
17490         config = this.config,
17491         graph = this.graph,
17492         parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17493         parent = parents[0],
17494         clickedNode = parent,
17495         previousClickedNode = this.clickedNode;
17496
17497     this.busy = true;
17498     this.events.hoveredNode = false;
17499
17500     if(!parent) {
17501       this.busy = false;
17502       return;
17503     }
17504
17505     //final plot callback
17506     callback = {
17507       onComplete: function() {
17508         that.clickedNode = parent;
17509         if(config.request) {
17510           that.requestNodes(parent, {
17511             onComplete: function() {
17512               that.compute();
17513               that.plot();
17514               that.busy = false;
17515             }
17516           });
17517         } else {
17518           that.compute();
17519           that.plot();
17520           that.busy = false;
17521         }
17522       }
17523     };
17524
17525     //animate node positions
17526     if(config.animate) {
17527       this.clickedNode = clickedNode;
17528       this.compute('end');
17529       //animate the visible subtree only
17530       this.clickedNode = previousClickedNode;
17531       this.fx.animate({
17532         modes:['linear', 'node-property:width:height'],
17533         duration: 1000,
17534         onComplete: function() {
17535           //animate the parent subtree
17536           that.clickedNode = clickedNode;
17537           //change nodes alpha
17538           graph.nodeList.setDataset(['current', 'end'], {
17539             'alpha': [0, 1]
17540           });
17541           GUtil.eachSubgraph(previousClickedNode, function(node) {
17542             node.setData('alpha', 1);
17543           }, "ignore");
17544           that.fx.animate({
17545             duration: 500,
17546             modes:['node-property:alpha'],
17547             onComplete: function() {
17548               callback.onComplete();
17549             }
17550           });
17551         }
17552       });
17553     } else {
17554       callback.onComplete();
17555     }
17556   },
17557   requestNodes: function(node, onComplete){
17558     var handler = $.merge(this.controller, onComplete),
17559         levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17560
17561     if (handler.request) {
17562       var leaves = [], d = node._depth;
17563       Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17564         if (n.drawn && !Graph.Util.anySubnode(n)) {
17565           leaves.push(n);
17566           n._level = n._depth - d;
17567           if (this.config.constrained)
17568             n._level = levelsToShow - n._level;
17569
17570         }
17571       });
17572       this.group.requestNodes(leaves, handler);
17573     } else {
17574       handler.onComplete();
17575     }
17576   }
17577 });
17578
17579 /*
17580   Class: Icicle.Op
17581   
17582   Custom extension of <Graph.Op>.
17583   
17584   Extends:
17585   
17586   All <Graph.Op> methods
17587   
17588   See also:
17589   
17590   <Graph.Op>
17591   
17592   */
17593 $jit.Icicle.Op = new Class({
17594
17595   Implements: Graph.Op
17596
17597 });
17598
17599 /*
17600  * Performs operations on group of nodes.
17601  */
17602 $jit.Icicle.Group = new Class({
17603
17604   initialize: function(viz){
17605     this.viz = viz;
17606     this.canvas = viz.canvas;
17607     this.config = viz.config;
17608   },
17609
17610   /*
17611    * Calls the request method on the controller to request a subtree for each node.
17612    */
17613   requestNodes: function(nodes, controller){
17614     var counter = 0, len = nodes.length, nodeSelected = {};
17615     var complete = function(){
17616       controller.onComplete();
17617     };
17618     var viz = this.viz;
17619     if (len == 0)
17620       complete();
17621     for(var i = 0; i < len; i++) {
17622       nodeSelected[nodes[i].id] = nodes[i];
17623       controller.request(nodes[i].id, nodes[i]._level, {
17624         onComplete: function(nodeId, data){
17625           if (data && data.children) {
17626             data.id = nodeId;
17627             viz.op.sum(data, {
17628               type: 'nothing'
17629             });
17630           }
17631           if (++counter == len) {
17632             Graph.Util.computeLevels(viz.graph, viz.root, 0);
17633             complete();
17634           }
17635         }
17636       });
17637     }
17638   }
17639 });
17640
17641 /*
17642   Class: Icicle.Plot
17643   
17644   Custom extension of <Graph.Plot>.
17645   
17646   Extends:
17647   
17648   All <Graph.Plot> methods
17649   
17650   See also:
17651   
17652   <Graph.Plot>
17653   
17654   */
17655 $jit.Icicle.Plot = new Class({
17656   Implements: Graph.Plot,
17657
17658   plot: function(opt, animating){
17659     opt = opt || this.viz.controller;
17660     var viz = this.viz,
17661         graph = viz.graph,
17662         root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17663         initialDepth = root._depth;
17664
17665     viz.canvas.clear();
17666     this.plotTree(root, $.merge(opt, {
17667       'withLabels': true,
17668       'hideLabels': false,
17669       'plotSubtree': function(root, node) {
17670         return !viz.config.constrained ||
17671                (node._depth - initialDepth < viz.config.levelsToShow);
17672       }
17673     }), animating);
17674   }
17675 });
17676
17677 /*
17678   Class: Icicle.Label
17679   
17680   Custom extension of <Graph.Label>. 
17681   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17682   
17683   Extends:
17684   
17685   All <Graph.Label> methods and subclasses.
17686   
17687   See also:
17688   
17689   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17690   
17691   */
17692 $jit.Icicle.Label = {};
17693
17694 /*
17695   Icicle.Label.Native
17696   
17697   Custom extension of <Graph.Label.Native>.
17698   
17699   Extends:
17700   
17701   All <Graph.Label.Native> methods
17702   
17703   See also:
17704   
17705   <Graph.Label.Native>
17706
17707   */
17708 $jit.Icicle.Label.Native = new Class({
17709   Implements: Graph.Label.Native,
17710
17711   renderLabel: function(canvas, node, controller) {
17712     var ctx = canvas.getCtx(),
17713         width = node.getData('width'),
17714         height = node.getData('height'),
17715         size = node.getLabelData('size'),
17716         m = ctx.measureText(node.name);
17717
17718     // Guess as much as possible if the label will fit in the node
17719     if(height < (size * 1.5) || width < m.width)
17720       return;
17721
17722     var pos = node.pos.getc(true);
17723     ctx.fillText(node.name,
17724                  pos.x + width / 2,
17725                  pos.y + height / 2);
17726   }
17727 });
17728
17729 /*
17730   Icicle.Label.SVG
17731   
17732   Custom extension of <Graph.Label.SVG>.
17733   
17734   Extends:
17735   
17736   All <Graph.Label.SVG> methods
17737   
17738   See also:
17739   
17740   <Graph.Label.SVG>
17741 */
17742 $jit.Icicle.Label.SVG = new Class( {
17743   Implements: Graph.Label.SVG,
17744
17745   initialize: function(viz){
17746     this.viz = viz;
17747   },
17748
17749   /*
17750     placeLabel
17751    
17752     Overrides abstract method placeLabel in <Graph.Plot>.
17753    
17754     Parameters:
17755    
17756     tag - A DOM label element.
17757     node - A <Graph.Node>.
17758     controller - A configuration/controller object passed to the visualization.
17759    */
17760   placeLabel: function(tag, node, controller){
17761     var pos = node.pos.getc(true), canvas = this.viz.canvas;
17762     var radius = canvas.getSize();
17763     var labelPos = {
17764       x: Math.round(pos.x + radius.width / 2),
17765       y: Math.round(pos.y + radius.height / 2)
17766     };
17767     tag.setAttribute('x', labelPos.x);
17768     tag.setAttribute('y', labelPos.y);
17769
17770     controller.onPlaceLabel(tag, node);
17771   }
17772 });
17773
17774 /*
17775   Icicle.Label.HTML
17776   
17777   Custom extension of <Graph.Label.HTML>.
17778   
17779   Extends:
17780   
17781   All <Graph.Label.HTML> methods.
17782   
17783   See also:
17784   
17785   <Graph.Label.HTML>
17786   
17787   */
17788 $jit.Icicle.Label.HTML = new Class( {
17789   Implements: Graph.Label.HTML,
17790
17791   initialize: function(viz){
17792     this.viz = viz;
17793   },
17794
17795   /*
17796     placeLabel
17797    
17798     Overrides abstract method placeLabel in <Graph.Plot>.
17799    
17800     Parameters:
17801    
17802     tag - A DOM label element.
17803     node - A <Graph.Node>.
17804     controller - A configuration/controller object passed to the visualization.
17805    */
17806   placeLabel: function(tag, node, controller){
17807     var pos = node.pos.getc(true), canvas = this.viz.canvas;
17808     var radius = canvas.getSize();
17809     var labelPos = {
17810       x: Math.round(pos.x + radius.width / 2),
17811       y: Math.round(pos.y + radius.height / 2)
17812     };
17813
17814     var style = tag.style;
17815     style.left = labelPos.x + 'px';
17816     style.top = labelPos.y + 'px';
17817     style.display = '';
17818
17819     controller.onPlaceLabel(tag, node);
17820   }
17821 });
17822
17823 /*
17824   Class: Icicle.Plot.NodeTypes
17825   
17826   This class contains a list of <Graph.Node> built-in types. 
17827   Node types implemented are 'none', 'rectangle'.
17828   
17829   You can add your custom node types, customizing your visualization to the extreme.
17830   
17831   Example:
17832   
17833   (start code js)
17834     Icicle.Plot.NodeTypes.implement({
17835       'mySpecialType': {
17836         'render': function(node, canvas) {
17837           //print your custom node to canvas
17838         },
17839         //optional
17840         'contains': function(node, pos) {
17841           //return true if pos is inside the node or false otherwise
17842         }
17843       }
17844     });
17845   (end code)
17846   
17847   */
17848 $jit.Icicle.Plot.NodeTypes = new Class( {
17849   'none': {
17850     'render': $.empty
17851   },
17852
17853   'rectangle': {
17854     'render': function(node, canvas, animating) {
17855       var config = this.viz.config;
17856       var offset = config.offset;
17857       var width = node.getData('width');
17858       var height = node.getData('height');
17859       var border = node.getData('border');
17860       var pos = node.pos.getc(true);
17861       var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
17862       var ctx = canvas.getCtx();
17863       
17864       if(width - offset < 2 || height - offset < 2) return;
17865       
17866       if(config.cushion) {
17867         var color = node.getData('color');
17868         var lg = ctx.createRadialGradient(posx + (width - offset)/2, 
17869                                           posy + (height - offset)/2, 1, 
17870                                           posx + (width-offset)/2, posy + (height-offset)/2, 
17871                                           width < height? height : width);
17872         var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
17873             function(r) { return r * 0.3 >> 0; }));
17874         lg.addColorStop(0, color);
17875         lg.addColorStop(1, colorGrad);
17876         ctx.fillStyle = lg;
17877       }
17878
17879       if (border) {
17880         ctx.strokeStyle = border;
17881         ctx.lineWidth = 3;
17882       }
17883
17884       ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
17885       border && ctx.strokeRect(pos.x, pos.y, width, height);
17886     },
17887
17888     'contains': function(node, pos) {
17889       if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
17890       var npos = node.pos.getc(true),
17891           width = node.getData('width'),
17892           height = node.getData('height');
17893       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
17894     }
17895   }
17896 });
17897
17898 $jit.Icicle.Plot.EdgeTypes = new Class( {
17899   'none': $.empty
17900 });
17901
17902
17903
17904 /*
17905  * File: Layouts.ForceDirected.js
17906  *
17907 */
17908
17909 /*
17910  * Class: Layouts.ForceDirected
17911  * 
17912  * Implements a Force Directed Layout.
17913  * 
17914  * Implemented By:
17915  * 
17916  * <ForceDirected>
17917  * 
17918  * Credits:
17919  * 
17920  * Marcus Cobden <http://marcuscobden.co.uk>
17921  * 
17922  */
17923 Layouts.ForceDirected = new Class({
17924
17925   getOptions: function(random) {
17926     var s = this.canvas.getSize();
17927     var w = s.width, h = s.height;
17928     //count nodes
17929     var count = 0;
17930     this.graph.eachNode(function(n) { 
17931       count++;
17932     });
17933     var k2 = w * h / count, k = Math.sqrt(k2);
17934     var l = this.config.levelDistance;
17935     
17936     return {
17937       width: w,
17938       height: h,
17939       tstart: w * 0.1,
17940       nodef: function(x) { return k2 / (x || 1); },
17941       edgef: function(x) { return /* x * x / k; */ k * (x - l); }
17942     };
17943   },
17944   
17945   compute: function(property, incremental) {
17946     var prop = $.splat(property || ['current', 'start', 'end']);
17947     var opt = this.getOptions();
17948     NodeDim.compute(this.graph, prop, this.config);
17949     this.graph.computeLevels(this.root, 0, "ignore");
17950     this.graph.eachNode(function(n) {
17951       $.each(prop, function(p) {
17952         var pos = n.getPos(p);
17953         if(pos.equals(Complex.KER)) {
17954           pos.x = opt.width/5 * (Math.random() - 0.5);
17955           pos.y = opt.height/5 * (Math.random() - 0.5);
17956         }
17957         //initialize disp vector
17958         n.disp = {};
17959         $.each(prop, function(p) {
17960           n.disp[p] = $C(0, 0);
17961         });
17962       });
17963     });
17964     this.computePositions(prop, opt, incremental);
17965   },
17966   
17967   computePositions: function(property, opt, incremental) {
17968     var times = this.config.iterations, i = 0, that = this;
17969     if(incremental) {
17970       (function iter() {
17971         for(var total=incremental.iter, j=0; j<total; j++) {
17972           opt.t = opt.tstart * (1 - i++/(times -1));
17973           that.computePositionStep(property, opt);
17974           if(i >= times) {
17975             incremental.onComplete();
17976             return;
17977           }
17978         }
17979         incremental.onStep(Math.round(i / (times -1) * 100));
17980         setTimeout(iter, 1);
17981       })();
17982     } else {
17983       for(; i < times; i++) {
17984         opt.t = opt.tstart * (1 - i/(times -1));
17985         this.computePositionStep(property, opt);
17986       }
17987     }
17988   },
17989   
17990   computePositionStep: function(property, opt) {
17991     var graph = this.graph;
17992     var min = Math.min, max = Math.max;
17993     var dpos = $C(0, 0);
17994     //calculate repulsive forces
17995     graph.eachNode(function(v) {
17996       //initialize disp
17997       $.each(property, function(p) {
17998         v.disp[p].x = 0; v.disp[p].y = 0;
17999       });
18000       graph.eachNode(function(u) {
18001         if(u.id != v.id) {
18002           $.each(property, function(p) {
18003             var vp = v.getPos(p), up = u.getPos(p);
18004             dpos.x = vp.x - up.x;
18005             dpos.y = vp.y - up.y;
18006             var norm = dpos.norm() || 1;
18007             v.disp[p].$add(dpos
18008                 .$scale(opt.nodef(norm) / norm));
18009           });
18010         }
18011       });
18012     });
18013     //calculate attractive forces
18014     var T = !!graph.getNode(this.root).visited;
18015     graph.eachNode(function(node) {
18016       node.eachAdjacency(function(adj) {
18017         var nodeTo = adj.nodeTo;
18018         if(!!nodeTo.visited === T) {
18019           $.each(property, function(p) {
18020             var vp = node.getPos(p), up = nodeTo.getPos(p);
18021             dpos.x = vp.x - up.x;
18022             dpos.y = vp.y - up.y;
18023             var norm = dpos.norm() || 1;
18024             node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18025             nodeTo.disp[p].$add(dpos.$scale(-1));
18026           });
18027         }
18028       });
18029       node.visited = !T;
18030     });
18031     //arrange positions to fit the canvas
18032     var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18033     graph.eachNode(function(u) {
18034       $.each(property, function(p) {
18035         var disp = u.disp[p];
18036         var norm = disp.norm() || 1;
18037         var p = u.getPos(p);
18038         p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
18039             disp.y * min(Math.abs(disp.y), t) / norm));
18040         p.x = min(w2, max(-w2, p.x));
18041         p.y = min(h2, max(-h2, p.y));
18042       });
18043     });
18044   }
18045 });
18046
18047 /*
18048  * File: ForceDirected.js
18049  */
18050
18051 /*
18052    Class: ForceDirected
18053       
18054    A visualization that lays graphs using a Force-Directed layout algorithm.
18055    
18056    Inspired by:
18057   
18058    Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18059    
18060   Implements:
18061   
18062   All <Loader> methods
18063   
18064    Constructor Options:
18065    
18066    Inherits options from
18067    
18068    - <Options.Canvas>
18069    - <Options.Controller>
18070    - <Options.Node>
18071    - <Options.Edge>
18072    - <Options.Label>
18073    - <Options.Events>
18074    - <Options.Tips>
18075    - <Options.NodeStyles>
18076    - <Options.Navigation>
18077    
18078    Additionally, there are two parameters
18079    
18080    levelDistance - (number) Default's *50*. The natural length desired for the edges.
18081    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*. 
18082      
18083    Instance Properties:
18084
18085    canvas - Access a <Canvas> instance.
18086    graph - Access a <Graph> instance.
18087    op - Access a <ForceDirected.Op> instance.
18088    fx - Access a <ForceDirected.Plot> instance.
18089    labels - Access a <ForceDirected.Label> interface implementation.
18090
18091 */
18092
18093 $jit.ForceDirected = new Class( {
18094
18095   Implements: [ Loader, Extras, Layouts.ForceDirected ],
18096
18097   initialize: function(controller) {
18098     var $ForceDirected = $jit.ForceDirected;
18099
18100     var config = {
18101       iterations: 50,
18102       levelDistance: 50
18103     };
18104
18105     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18106         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18107
18108     var canvasConfig = this.config;
18109     if(canvasConfig.useCanvas) {
18110       this.canvas = canvasConfig.useCanvas;
18111       this.config.labelContainer = this.canvas.id + '-label';
18112     } else {
18113       if(canvasConfig.background) {
18114         canvasConfig.background = $.merge({
18115           type: 'Circles'
18116         }, canvasConfig.background);
18117       }
18118       this.canvas = new Canvas(this, canvasConfig);
18119       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18120     }
18121
18122     this.graphOptions = {
18123       'complex': true,
18124       'Node': {
18125         'selected': false,
18126         'exist': true,
18127         'drawn': true
18128       }
18129     };
18130     this.graph = new Graph(this.graphOptions, this.config.Node,
18131         this.config.Edge);
18132     this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18133     this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18134     this.op = new $ForceDirected.Op(this);
18135     this.json = null;
18136     this.busy = false;
18137     // initialize extras
18138     this.initializeExtras();
18139   },
18140
18141   /* 
18142     Method: refresh 
18143     
18144     Computes positions and plots the tree.
18145   */
18146   refresh: function() {
18147     this.compute();
18148     this.plot();
18149   },
18150
18151   reposition: function() {
18152     this.compute('end');
18153   },
18154
18155 /*
18156   Method: computeIncremental
18157   
18158   Performs the Force Directed algorithm incrementally.
18159   
18160   Description:
18161   
18162   ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
18163   This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
18164   avoiding browser messages such as "This script is taking too long to complete".
18165   
18166   Parameters:
18167   
18168   opt - (object) The object properties are described below
18169   
18170   iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
18171   of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18172   
18173   property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
18174   You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
18175   computations for final animation positions then you can just choose 'end'.
18176   
18177   onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
18178   parameter a percentage value.
18179   
18180   onComplete - A callback function called when the algorithm completed.
18181   
18182   Example:
18183   
18184   In this example I calculate the end positions and then animate the graph to those positions
18185   
18186   (start code js)
18187   var fd = new $jit.ForceDirected(...);
18188   fd.computeIncremental({
18189     iter: 20,
18190     property: 'end',
18191     onStep: function(perc) {
18192       Log.write("loading " + perc + "%");
18193     },
18194     onComplete: function() {
18195       Log.write("done");
18196       fd.animate();
18197     }
18198   });
18199   (end code)
18200   
18201   In this example I calculate all positions and (re)plot the graph
18202   
18203   (start code js)
18204   var fd = new ForceDirected(...);
18205   fd.computeIncremental({
18206     iter: 20,
18207     property: ['end', 'start', 'current'],
18208     onStep: function(perc) {
18209       Log.write("loading " + perc + "%");
18210     },
18211     onComplete: function() {
18212       Log.write("done");
18213       fd.plot();
18214     }
18215   });
18216   (end code)
18217   
18218   */
18219   computeIncremental: function(opt) {
18220     opt = $.merge( {
18221       iter: 20,
18222       property: 'end',
18223       onStep: $.empty,
18224       onComplete: $.empty
18225     }, opt || {});
18226
18227     this.config.onBeforeCompute(this.graph.getNode(this.root));
18228     this.compute(opt.property, opt);
18229   },
18230
18231   /*
18232     Method: plot
18233    
18234     Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18235    */
18236   plot: function() {
18237     this.fx.plot();
18238   },
18239
18240   /*
18241      Method: animate
18242     
18243      Animates the graph from the current positions to the 'end' node positions.
18244   */
18245   animate: function(opt) {
18246     this.fx.animate($.merge( {
18247       modes: [ 'linear' ]
18248     }, opt || {}));
18249   }
18250 });
18251
18252 $jit.ForceDirected.$extend = true;
18253
18254 (function(ForceDirected) {
18255
18256   /*
18257      Class: ForceDirected.Op
18258      
18259      Custom extension of <Graph.Op>.
18260
18261      Extends:
18262
18263      All <Graph.Op> methods
18264      
18265      See also:
18266      
18267      <Graph.Op>
18268
18269   */
18270   ForceDirected.Op = new Class( {
18271
18272     Implements: Graph.Op
18273
18274   });
18275
18276   /*
18277     Class: ForceDirected.Plot
18278     
18279     Custom extension of <Graph.Plot>.
18280   
18281     Extends:
18282   
18283     All <Graph.Plot> methods
18284     
18285     See also:
18286     
18287     <Graph.Plot>
18288   
18289   */
18290   ForceDirected.Plot = new Class( {
18291
18292     Implements: Graph.Plot
18293
18294   });
18295
18296   /*
18297     Class: ForceDirected.Label
18298     
18299     Custom extension of <Graph.Label>. 
18300     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18301   
18302     Extends:
18303   
18304     All <Graph.Label> methods and subclasses.
18305   
18306     See also:
18307   
18308     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18309   
18310   */
18311   ForceDirected.Label = {};
18312
18313   /*
18314      ForceDirected.Label.Native
18315      
18316      Custom extension of <Graph.Label.Native>.
18317
18318      Extends:
18319
18320      All <Graph.Label.Native> methods
18321
18322      See also:
18323
18324      <Graph.Label.Native>
18325
18326   */
18327   ForceDirected.Label.Native = new Class( {
18328     Implements: Graph.Label.Native
18329   });
18330
18331   /*
18332     ForceDirected.Label.SVG
18333     
18334     Custom extension of <Graph.Label.SVG>.
18335   
18336     Extends:
18337   
18338     All <Graph.Label.SVG> methods
18339   
18340     See also:
18341   
18342     <Graph.Label.SVG>
18343   
18344   */
18345   ForceDirected.Label.SVG = new Class( {
18346     Implements: Graph.Label.SVG,
18347
18348     initialize: function(viz) {
18349       this.viz = viz;
18350     },
18351
18352     /* 
18353        placeLabel
18354
18355        Overrides abstract method placeLabel in <Graph.Label>.
18356
18357        Parameters:
18358
18359        tag - A DOM label element.
18360        node - A <Graph.Node>.
18361        controller - A configuration/controller object passed to the visualization.
18362       
18363      */
18364     placeLabel: function(tag, node, controller) {
18365       var pos = node.pos.getc(true), 
18366           canvas = this.viz.canvas,
18367           ox = canvas.translateOffsetX,
18368           oy = canvas.translateOffsetY,
18369           sx = canvas.scaleOffsetX,
18370           sy = canvas.scaleOffsetY,
18371           radius = canvas.getSize();
18372       var labelPos = {
18373         x: Math.round(pos.x * sx + ox + radius.width / 2),
18374         y: Math.round(pos.y * sy + oy + radius.height / 2)
18375       };
18376       tag.setAttribute('x', labelPos.x);
18377       tag.setAttribute('y', labelPos.y);
18378
18379       controller.onPlaceLabel(tag, node);
18380     }
18381   });
18382
18383   /*
18384      ForceDirected.Label.HTML
18385      
18386      Custom extension of <Graph.Label.HTML>.
18387
18388      Extends:
18389
18390      All <Graph.Label.HTML> methods.
18391
18392      See also:
18393
18394      <Graph.Label.HTML>
18395
18396   */
18397   ForceDirected.Label.HTML = new Class( {
18398     Implements: Graph.Label.HTML,
18399
18400     initialize: function(viz) {
18401       this.viz = viz;
18402     },
18403     /* 
18404        placeLabel
18405
18406        Overrides abstract method placeLabel in <Graph.Plot>.
18407
18408        Parameters:
18409
18410        tag - A DOM label element.
18411        node - A <Graph.Node>.
18412        controller - A configuration/controller object passed to the visualization.
18413       
18414      */
18415     placeLabel: function(tag, node, controller) {
18416       var pos = node.pos.getc(true), 
18417           canvas = this.viz.canvas,
18418           ox = canvas.translateOffsetX,
18419           oy = canvas.translateOffsetY,
18420           sx = canvas.scaleOffsetX,
18421           sy = canvas.scaleOffsetY,
18422           radius = canvas.getSize();
18423       var labelPos = {
18424         x: Math.round(pos.x * sx + ox + radius.width / 2),
18425         y: Math.round(pos.y * sy + oy + radius.height / 2)
18426       };
18427       var style = tag.style;
18428       style.left = labelPos.x + 'px';
18429       style.top = labelPos.y + 'px';
18430       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18431
18432       controller.onPlaceLabel(tag, node);
18433     }
18434   });
18435
18436   /*
18437     Class: ForceDirected.Plot.NodeTypes
18438
18439     This class contains a list of <Graph.Node> built-in types. 
18440     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18441
18442     You can add your custom node types, customizing your visualization to the extreme.
18443
18444     Example:
18445
18446     (start code js)
18447       ForceDirected.Plot.NodeTypes.implement({
18448         'mySpecialType': {
18449           'render': function(node, canvas) {
18450             //print your custom node to canvas
18451           },
18452           //optional
18453           'contains': function(node, pos) {
18454             //return true if pos is inside the node or false otherwise
18455           }
18456         }
18457       });
18458     (end code)
18459
18460   */
18461   ForceDirected.Plot.NodeTypes = new Class({
18462     'none': {
18463       'render': $.empty,
18464       'contains': $.lambda(false)
18465     },
18466     'circle': {
18467       'render': function(node, canvas){
18468         var pos = node.pos.getc(true), 
18469             dim = node.getData('dim');
18470         this.nodeHelper.circle.render('fill', pos, dim, canvas);
18471       },
18472       'contains': function(node, pos){
18473         var npos = node.pos.getc(true), 
18474             dim = node.getData('dim');
18475         return this.nodeHelper.circle.contains(npos, pos, dim);
18476       }
18477     },
18478     'ellipse': {
18479       'render': function(node, canvas){
18480         var pos = node.pos.getc(true), 
18481             width = node.getData('width'), 
18482             height = node.getData('height');
18483         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18484         },
18485       // TODO(nico): be more precise...
18486       'contains': function(node, pos){
18487         var npos = node.pos.getc(true), 
18488             width = node.getData('width'), 
18489             height = node.getData('height');
18490         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18491       }
18492     },
18493     'square': {
18494       'render': function(node, canvas){
18495         var pos = node.pos.getc(true), 
18496             dim = node.getData('dim');
18497         this.nodeHelper.square.render('fill', pos, dim, canvas);
18498       },
18499       'contains': function(node, pos){
18500         var npos = node.pos.getc(true), 
18501             dim = node.getData('dim');
18502         return this.nodeHelper.square.contains(npos, pos, dim);
18503       }
18504     },
18505     'rectangle': {
18506       'render': function(node, canvas){
18507         var pos = node.pos.getc(true), 
18508             width = node.getData('width'), 
18509             height = node.getData('height');
18510         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18511       },
18512       'contains': function(node, pos){
18513         var npos = node.pos.getc(true), 
18514             width = node.getData('width'), 
18515             height = node.getData('height');
18516         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18517       }
18518     },
18519     'triangle': {
18520       'render': function(node, canvas){
18521         var pos = node.pos.getc(true), 
18522             dim = node.getData('dim');
18523         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18524       },
18525       'contains': function(node, pos) {
18526         var npos = node.pos.getc(true), 
18527             dim = node.getData('dim');
18528         return this.nodeHelper.triangle.contains(npos, pos, dim);
18529       }
18530     },
18531     'star': {
18532       'render': function(node, canvas){
18533         var pos = node.pos.getc(true),
18534             dim = node.getData('dim');
18535         this.nodeHelper.star.render('fill', pos, dim, canvas);
18536       },
18537       'contains': function(node, pos) {
18538         var npos = node.pos.getc(true),
18539             dim = node.getData('dim');
18540         return this.nodeHelper.star.contains(npos, pos, dim);
18541       }
18542     }
18543   });
18544
18545   /*
18546     Class: ForceDirected.Plot.EdgeTypes
18547   
18548     This class contains a list of <Graph.Adjacence> built-in types. 
18549     Edge types implemented are 'none', 'line' and 'arrow'.
18550   
18551     You can add your custom edge types, customizing your visualization to the extreme.
18552   
18553     Example:
18554   
18555     (start code js)
18556       ForceDirected.Plot.EdgeTypes.implement({
18557         'mySpecialType': {
18558           'render': function(adj, canvas) {
18559             //print your custom edge to canvas
18560           },
18561           //optional
18562           'contains': function(adj, pos) {
18563             //return true if pos is inside the arc or false otherwise
18564           }
18565         }
18566       });
18567     (end code)
18568   
18569   */
18570   ForceDirected.Plot.EdgeTypes = new Class({
18571     'none': $.empty,
18572     'line': {
18573       'render': function(adj, canvas) {
18574         var from = adj.nodeFrom.pos.getc(true),
18575             to = adj.nodeTo.pos.getc(true);
18576         this.edgeHelper.line.render(from, to, canvas);
18577       },
18578       'contains': function(adj, pos) {
18579         var from = adj.nodeFrom.pos.getc(true),
18580             to = adj.nodeTo.pos.getc(true);
18581         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18582       }
18583     },
18584     'arrow': {
18585       'render': function(adj, canvas) {
18586         var from = adj.nodeFrom.pos.getc(true),
18587             to = adj.nodeTo.pos.getc(true),
18588             dim = adj.getData('dim'),
18589             direction = adj.data.$direction,
18590             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18591         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18592       },
18593       'contains': function(adj, pos) {
18594         var from = adj.nodeFrom.pos.getc(true),
18595             to = adj.nodeTo.pos.getc(true);
18596         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18597       }
18598     }
18599   });
18600
18601 })($jit.ForceDirected);
18602
18603
18604 /*
18605  * File: Treemap.js
18606  *
18607 */
18608
18609 $jit.TM = {};
18610
18611 var TM = $jit.TM;
18612
18613 $jit.TM.$extend = true;
18614
18615 /*
18616   Class: TM.Base
18617   
18618   Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18619   
18620   Implements:
18621   
18622   All <Loader> methods
18623   
18624   Constructor Options:
18625   
18626   Inherits options from
18627   
18628   - <Options.Canvas>
18629   - <Options.Controller>
18630   - <Options.Node>
18631   - <Options.Edge>
18632   - <Options.Label>
18633   - <Options.Events>
18634   - <Options.Tips>
18635   - <Options.NodeStyles>
18636   - <Options.Navigation>
18637   
18638   Additionally, there are other parameters and some default values changed
18639
18640   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18641   titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18642   offset - (number) Default's *2*. Boxes offset.
18643   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18644   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18645   animate - (boolean) Default's *false*. Whether to animate transitions.
18646   Node.type - Described in <Options.Node>. Default's *rectangle*.
18647   duration - Described in <Options.Fx>. Default's *700*.
18648   fps - Described in <Options.Fx>. Default's *45*.
18649   
18650   Instance Properties:
18651   
18652   canvas - Access a <Canvas> instance.
18653   graph - Access a <Graph> instance.
18654   op - Access a <TM.Op> instance.
18655   fx - Access a <TM.Plot> instance.
18656   labels - Access a <TM.Label> interface implementation.
18657
18658   Inspired by:
18659   
18660   Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18661   
18662   Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18663   
18664    Note:
18665    
18666    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.
18667
18668 */
18669 TM.Base = {
18670   layout: {
18671     orientation: "h",
18672     vertical: function(){
18673       return this.orientation == "v";
18674     },
18675     horizontal: function(){
18676       return this.orientation == "h";
18677     },
18678     change: function(){
18679       this.orientation = this.vertical()? "h" : "v";
18680     }
18681   },
18682
18683   initialize: function(controller){
18684     var config = {
18685       orientation: "h",
18686       titleHeight: 13,
18687       offset: 2,
18688       levelsToShow: 0,
18689       constrained: false,
18690       animate: false,
18691       Node: {
18692         type: 'rectangle',
18693         overridable: true,
18694         //we all know why this is not zero,
18695         //right, Firefox?
18696         width: 3,
18697         height: 3,
18698         color: '#444'
18699       },
18700       Label: {
18701         textAlign: 'center',
18702         textBaseline: 'top'
18703       },
18704       Edge: {
18705         type: 'none'
18706       },
18707       duration: 700,
18708       fps: 45
18709     };
18710
18711     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18712         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18713     this.layout.orientation = this.config.orientation;
18714
18715     var canvasConfig = this.config;
18716     if (canvasConfig.useCanvas) {
18717       this.canvas = canvasConfig.useCanvas;
18718       this.config.labelContainer = this.canvas.id + '-label';
18719     } else {
18720       if(canvasConfig.background) {
18721         canvasConfig.background = $.merge({
18722           type: 'Circles'
18723         }, canvasConfig.background);
18724       }
18725       this.canvas = new Canvas(this, canvasConfig);
18726       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18727     }
18728
18729     this.graphOptions = {
18730       'complex': true,
18731       'Node': {
18732         'selected': false,
18733         'exist': true,
18734         'drawn': true
18735       }
18736     };
18737     this.graph = new Graph(this.graphOptions, this.config.Node,
18738         this.config.Edge);
18739     this.labels = new TM.Label[canvasConfig.Label.type](this);
18740     this.fx = new TM.Plot(this);
18741     this.op = new TM.Op(this);
18742     this.group = new TM.Group(this);
18743     this.geom = new TM.Geom(this);
18744     this.clickedNode = null;
18745     this.busy = false;
18746     // initialize extras
18747     this.initializeExtras();
18748   },
18749
18750   /* 
18751     Method: refresh 
18752     
18753     Computes positions and plots the tree.
18754   */
18755   refresh: function(){
18756     if(this.busy) return;
18757     this.busy = true;
18758     var that = this;
18759     if(this.config.animate) {
18760       this.compute('end');
18761       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18762           && this.clickedNode.id || this.root));
18763       this.fx.animate($.merge(this.config, {
18764         modes: ['linear', 'node-property:width:height'],
18765         onComplete: function() {
18766           that.busy = false;
18767         }
18768       }));
18769     } else {
18770       var labelType = this.config.Label.type;
18771       if(labelType != 'Native') {
18772         var that = this;
18773         this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18774       }
18775       this.busy = false;
18776       this.compute();
18777       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18778           && this.clickedNode.id || this.root));
18779       this.plot();
18780     }
18781   },
18782
18783   /* 
18784     Method: plot 
18785     
18786     Plots the TreeMap. This is a shortcut to *fx.plot*. 
18787   
18788    */
18789   plot: function(){
18790     this.fx.plot();
18791   },
18792
18793   /* 
18794   Method: leaf 
18795   
18796   Returns whether the node is a leaf.
18797   
18798    Parameters:
18799    
18800    n - (object) A <Graph.Node>.
18801
18802  */
18803   leaf: function(n){
18804     return n.getSubnodes([
18805         1, 1
18806     ], "ignore").length == 0;
18807   },
18808   
18809   /* 
18810   Method: enter 
18811   
18812   Sets the node as root.
18813   
18814    Parameters:
18815    
18816    n - (object) A <Graph.Node>.
18817
18818  */
18819   enter: function(n){
18820     if(this.busy) return;
18821     this.busy = true;
18822     
18823     var that = this,
18824         config = this.config,
18825         graph = this.graph,
18826         clickedNode = n,
18827         previousClickedNode = this.clickedNode;
18828
18829     var callback = {
18830       onComplete: function() {
18831         //ensure that nodes are shown for that level
18832         if(config.levelsToShow > 0) {
18833           that.geom.setRightLevelToShow(n);
18834         }
18835         //compute positions of newly inserted nodes
18836         if(config.levelsToShow > 0 || config.request) that.compute();
18837         if(config.animate) {
18838           //fade nodes
18839           graph.nodeList.setData('alpha', 0, 'end');
18840           n.eachSubgraph(function(n) {
18841             n.setData('alpha', 1, 'end');
18842           }, "ignore");
18843           that.fx.animate({
18844             duration: 500,
18845             modes:['node-property:alpha'],
18846             onComplete: function() {
18847               //compute end positions
18848               that.clickedNode = clickedNode;
18849               that.compute('end');
18850               //animate positions
18851               //TODO(nico) commenting this line didn't seem to throw errors...
18852               that.clickedNode = previousClickedNode;
18853               that.fx.animate({
18854                 modes:['linear', 'node-property:width:height'],
18855                 duration: 1000,
18856                 onComplete: function() { 
18857                   that.busy = false;
18858                   //TODO(nico) check comment above
18859                   that.clickedNode = clickedNode;
18860                 }
18861               });
18862             }
18863           });
18864         } else {
18865           that.busy = false;
18866           that.clickedNode = n;
18867           that.refresh();
18868         }
18869       }
18870     };
18871     if(config.request) {
18872       this.requestNodes(clickedNode, callback);
18873     } else {
18874       callback.onComplete();
18875     }
18876   },
18877
18878   /* 
18879   Method: out 
18880   
18881   Sets the parent node of the current selected node as root.
18882
18883  */
18884   out: function(){
18885     if(this.busy) return;
18886     this.busy = true;
18887     this.events.hoveredNode = false;
18888     var that = this,
18889         config = this.config,
18890         graph = this.graph,
18891         parents = graph.getNode(this.clickedNode 
18892             && this.clickedNode.id || this.root).getParents(),
18893         parent = parents[0],
18894         clickedNode = parent,
18895         previousClickedNode = this.clickedNode;
18896     
18897     //if no parents return
18898     if(!parent) {
18899       this.busy = false;
18900       return;
18901     }
18902     //final plot callback
18903     callback = {
18904       onComplete: function() {
18905         that.clickedNode = parent;
18906         if(config.request) {
18907           that.requestNodes(parent, {
18908             onComplete: function() {
18909               that.compute();
18910               that.plot();
18911               that.busy = false;
18912             }
18913           });
18914         } else {
18915           that.compute();
18916           that.plot();
18917           that.busy = false;
18918         }
18919       }
18920     };
18921     //prune tree
18922     if (config.levelsToShow > 0)
18923       this.geom.setRightLevelToShow(parent);
18924     //animate node positions
18925     if(config.animate) {
18926       this.clickedNode = clickedNode;
18927       this.compute('end');
18928       //animate the visible subtree only
18929       this.clickedNode = previousClickedNode;
18930       this.fx.animate({
18931         modes:['linear', 'node-property:width:height'],
18932         duration: 1000,
18933         onComplete: function() {
18934           //animate the parent subtree
18935           that.clickedNode = clickedNode;
18936           //change nodes alpha
18937           graph.eachNode(function(n) {
18938             n.setDataset(['current', 'end'], {
18939               'alpha': [0, 1]
18940             });
18941           }, "ignore");
18942           previousClickedNode.eachSubgraph(function(node) {
18943             node.setData('alpha', 1);
18944           }, "ignore");
18945           that.fx.animate({
18946             duration: 500,
18947             modes:['node-property:alpha'],
18948             onComplete: function() {
18949               callback.onComplete();
18950             }
18951           });
18952         }
18953       });
18954     } else {
18955       callback.onComplete();
18956     }
18957   },
18958
18959   requestNodes: function(node, onComplete){
18960     var handler = $.merge(this.controller, onComplete), 
18961         lev = this.config.levelsToShow;
18962     if (handler.request) {
18963       var leaves = [], d = node._depth;
18964       node.eachLevel(0, lev, function(n){
18965         var nodeLevel = lev - (n._depth - d);
18966         if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
18967           leaves.push(n);
18968           n._level = nodeLevel;
18969         }
18970       });
18971       this.group.requestNodes(leaves, handler);
18972     } else {
18973       handler.onComplete();
18974     }
18975   }
18976 };
18977
18978 /*
18979   Class: TM.Op
18980   
18981   Custom extension of <Graph.Op>.
18982   
18983   Extends:
18984   
18985   All <Graph.Op> methods
18986   
18987   See also:
18988   
18989   <Graph.Op>
18990   
18991   */
18992 TM.Op = new Class({
18993   Implements: Graph.Op,
18994
18995   initialize: function(viz){
18996     this.viz = viz;
18997   }
18998 });
18999
19000 //extend level methods of Graph.Geom
19001 TM.Geom = new Class({
19002   Implements: Graph.Geom,
19003   
19004   getRightLevelToShow: function() {
19005     return this.viz.config.levelsToShow;
19006   },
19007   
19008   setRightLevelToShow: function(node) {
19009     var level = this.getRightLevelToShow(), 
19010         fx = this.viz.labels;
19011     node.eachLevel(0, level+1, function(n) {
19012       var d = n._depth - node._depth;
19013       if(d > level) {
19014         n.drawn = false; 
19015         n.exist = false;
19016         n.ignore = true;
19017         fx.hideLabel(n, false);
19018       } else {
19019         n.drawn = true;
19020         n.exist = true;
19021         delete n.ignore;
19022       }
19023     });
19024     node.drawn = true;
19025     delete node.ignore;
19026   }
19027 });
19028
19029 /*
19030
19031 Performs operations on group of nodes.
19032
19033 */
19034 TM.Group = new Class( {
19035
19036   initialize: function(viz){
19037     this.viz = viz;
19038     this.canvas = viz.canvas;
19039     this.config = viz.config;
19040   },
19041
19042   /*
19043   
19044     Calls the request method on the controller to request a subtree for each node. 
19045   */
19046   requestNodes: function(nodes, controller){
19047     var counter = 0, len = nodes.length, nodeSelected = {};
19048     var complete = function(){
19049       controller.onComplete();
19050     };
19051     var viz = this.viz;
19052     if (len == 0)
19053       complete();
19054     for ( var i = 0; i < len; i++) {
19055       nodeSelected[nodes[i].id] = nodes[i];
19056       controller.request(nodes[i].id, nodes[i]._level, {
19057         onComplete: function(nodeId, data){
19058           if (data && data.children) {
19059             data.id = nodeId;
19060             viz.op.sum(data, {
19061               type: 'nothing'
19062             });
19063           }
19064           if (++counter == len) {
19065             viz.graph.computeLevels(viz.root, 0);
19066             complete();
19067           }
19068         }
19069       });
19070     }
19071   }
19072 });
19073
19074 /*
19075   Class: TM.Plot
19076   
19077   Custom extension of <Graph.Plot>.
19078   
19079   Extends:
19080   
19081   All <Graph.Plot> methods
19082   
19083   See also:
19084   
19085   <Graph.Plot>
19086   
19087   */
19088 TM.Plot = new Class({
19089
19090   Implements: Graph.Plot,
19091
19092   initialize: function(viz){
19093     this.viz = viz;
19094     this.config = viz.config;
19095     this.node = this.config.Node;
19096     this.edge = this.config.Edge;
19097     this.animation = new Animation;
19098     this.nodeTypes = new TM.Plot.NodeTypes;
19099     this.edgeTypes = new TM.Plot.EdgeTypes;
19100     this.labels = viz.labels;
19101   },
19102
19103   plot: function(opt, animating){
19104     var viz = this.viz, 
19105         graph = viz.graph;
19106     viz.canvas.clear();
19107     this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19108       'withLabels': true,
19109       'hideLabels': false,
19110       'plotSubtree': function(n, ch){
19111         return n.anySubnode("exist");
19112       }
19113     }), animating);
19114   }
19115 });
19116
19117 /*
19118   Class: TM.Label
19119   
19120   Custom extension of <Graph.Label>. 
19121   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19122
19123   Extends:
19124
19125   All <Graph.Label> methods and subclasses.
19126
19127   See also:
19128
19129   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19130   
19131 */
19132 TM.Label = {};
19133
19134 /*
19135  TM.Label.Native
19136
19137  Custom extension of <Graph.Label.Native>.
19138
19139  Extends:
19140
19141  All <Graph.Label.Native> methods
19142
19143  See also:
19144
19145  <Graph.Label.Native>
19146 */
19147 TM.Label.Native = new Class({
19148   Implements: Graph.Label.Native,
19149
19150   initialize: function(viz) {
19151     this.config = viz.config;
19152     this.leaf = viz.leaf;
19153   },
19154   
19155   renderLabel: function(canvas, node, controller){
19156     if(!this.leaf(node) && !this.config.titleHeight) return;
19157     var pos = node.pos.getc(true), 
19158         ctx = canvas.getCtx(),
19159         width = node.getData('width'),
19160         height = node.getData('height'),
19161         x = pos.x + width/2,
19162         y = pos.y;
19163         
19164     ctx.fillText(node.name, x, y, width);
19165   }
19166 });
19167
19168 /*
19169  TM.Label.SVG
19170
19171   Custom extension of <Graph.Label.SVG>.
19172
19173   Extends:
19174
19175   All <Graph.Label.SVG> methods
19176
19177   See also:
19178
19179   <Graph.Label.SVG>
19180 */
19181 TM.Label.SVG = new Class( {
19182   Implements: Graph.Label.SVG,
19183
19184   initialize: function(viz){
19185     this.viz = viz;
19186     this.leaf = viz.leaf;
19187     this.config = viz.config;
19188   },
19189
19190   /* 
19191   placeLabel
19192
19193   Overrides abstract method placeLabel in <Graph.Plot>.
19194
19195   Parameters:
19196
19197   tag - A DOM label element.
19198   node - A <Graph.Node>.
19199   controller - A configuration/controller object passed to the visualization.
19200   
19201   */
19202   placeLabel: function(tag, node, controller){
19203     var pos = node.pos.getc(true), 
19204         canvas = this.viz.canvas,
19205         ox = canvas.translateOffsetX,
19206         oy = canvas.translateOffsetY,
19207         sx = canvas.scaleOffsetX,
19208         sy = canvas.scaleOffsetY,
19209         radius = canvas.getSize();
19210     var labelPos = {
19211       x: Math.round(pos.x * sx + ox + radius.width / 2),
19212       y: Math.round(pos.y * sy + oy + radius.height / 2)
19213     };
19214     tag.setAttribute('x', labelPos.x);
19215     tag.setAttribute('y', labelPos.y);
19216
19217     if(!this.leaf(node) && !this.config.titleHeight) {
19218       tag.style.display = 'none';
19219     }
19220     controller.onPlaceLabel(tag, node);
19221   }
19222 });
19223
19224 /*
19225  TM.Label.HTML
19226
19227  Custom extension of <Graph.Label.HTML>.
19228
19229  Extends:
19230
19231  All <Graph.Label.HTML> methods.
19232
19233  See also:
19234
19235  <Graph.Label.HTML>
19236
19237 */
19238 TM.Label.HTML = new Class( {
19239   Implements: Graph.Label.HTML,
19240
19241   initialize: function(viz){
19242     this.viz = viz;
19243     this.leaf = viz.leaf;
19244     this.config = viz.config;
19245   },
19246
19247   /* 
19248     placeLabel
19249   
19250     Overrides abstract method placeLabel in <Graph.Plot>.
19251   
19252     Parameters:
19253   
19254     tag - A DOM label element.
19255     node - A <Graph.Node>.
19256     controller - A configuration/controller object passed to the visualization.
19257   
19258   */
19259   placeLabel: function(tag, node, controller){
19260     var pos = node.pos.getc(true), 
19261         canvas = this.viz.canvas,
19262         ox = canvas.translateOffsetX,
19263         oy = canvas.translateOffsetY,
19264         sx = canvas.scaleOffsetX,
19265         sy = canvas.scaleOffsetY,
19266         radius = canvas.getSize();
19267     var labelPos = {
19268       x: Math.round(pos.x * sx + ox + radius.width / 2),
19269       y: Math.round(pos.y * sy + oy + radius.height / 2)
19270     };
19271
19272     var style = tag.style;
19273     style.left = labelPos.x + 'px';
19274     style.top = labelPos.y + 'px';
19275     style.width = node.getData('width') * sx + 'px';
19276     style.height = node.getData('height') * sy + 'px';
19277     style.zIndex = node._depth * 100;
19278     style.display = '';
19279
19280     if(!this.leaf(node) && !this.config.titleHeight) {
19281       tag.style.display = 'none';
19282     }
19283     controller.onPlaceLabel(tag, node);
19284   }
19285 });
19286
19287 /*
19288   Class: TM.Plot.NodeTypes
19289
19290   This class contains a list of <Graph.Node> built-in types. 
19291   Node types implemented are 'none', 'rectangle'.
19292
19293   You can add your custom node types, customizing your visualization to the extreme.
19294
19295   Example:
19296
19297   (start code js)
19298     TM.Plot.NodeTypes.implement({
19299       'mySpecialType': {
19300         'render': function(node, canvas) {
19301           //print your custom node to canvas
19302         },
19303         //optional
19304         'contains': function(node, pos) {
19305           //return true if pos is inside the node or false otherwise
19306         }
19307       }
19308     });
19309   (end code)
19310
19311 */
19312 TM.Plot.NodeTypes = new Class( {
19313   'none': {
19314     'render': $.empty
19315   },
19316
19317   'rectangle': {
19318     'render': function(node, canvas, animating){
19319       var leaf = this.viz.leaf(node),
19320           config = this.config,
19321           offst = config.offset,
19322           titleHeight = config.titleHeight,
19323           pos = node.pos.getc(true),
19324           width = node.getData('width'),
19325           height = node.getData('height'),
19326           border = node.getData('border'),
19327           ctx = canvas.getCtx(),
19328           posx = pos.x + offst / 2, 
19329           posy = pos.y + offst / 2;
19330       if(width <= offst || height <= offst) return;
19331       if (leaf) {
19332         if(config.cushion) {
19333           var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1, 
19334               posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19335           var color = node.getData('color');
19336           var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
19337               function(r) { return r * 0.2 >> 0; }));
19338           lg.addColorStop(0, color);
19339           lg.addColorStop(1, colorGrad);
19340           ctx.fillStyle = lg;
19341         }
19342         ctx.fillRect(posx, posy, width - offst, height - offst);
19343         if(border) {
19344           ctx.save();
19345           ctx.strokeStyle = border;
19346           ctx.strokeRect(posx, posy, width - offst, height - offst);
19347           ctx.restore();
19348         }
19349       } else if(titleHeight > 0){
19350         ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19351             titleHeight - offst);
19352         if(border) {
19353           ctx.save();
19354           ctx.strokeStyle = border;
19355           ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19356               height - offst);
19357           ctx.restore();
19358         }
19359       }
19360     },
19361     'contains': function(node, pos) {
19362       if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19363       var npos = node.pos.getc(true),
19364           width = node.getData('width'), 
19365           leaf = this.viz.leaf(node),
19366           height = leaf? node.getData('height') : this.config.titleHeight;
19367       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19368     }
19369   }
19370 });
19371
19372 TM.Plot.EdgeTypes = new Class( {
19373   'none': $.empty
19374 });
19375
19376 /*
19377   Class: TM.SliceAndDice
19378   
19379   A slice and dice TreeMap visualization.
19380   
19381   Implements:
19382   
19383   All <TM.Base> methods and properties.
19384 */
19385 TM.SliceAndDice = new Class( {
19386   Implements: [
19387       Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19388   ]
19389 });
19390
19391 /*
19392   Class: TM.Squarified
19393   
19394   A squarified TreeMap visualization.
19395
19396   Implements:
19397   
19398   All <TM.Base> methods and properties.
19399 */
19400 TM.Squarified = new Class( {
19401   Implements: [
19402       Loader, Extras, TM.Base, Layouts.TM.Squarified
19403   ]
19404 });
19405
19406 /*
19407   Class: TM.Strip
19408   
19409   A strip TreeMap visualization.
19410
19411   Implements:
19412   
19413   All <TM.Base> methods and properties.
19414 */
19415 TM.Strip = new Class( {
19416   Implements: [
19417       Loader, Extras, TM.Base, Layouts.TM.Strip
19418   ]
19419 });
19420
19421
19422 /*
19423  * File: RGraph.js
19424  *
19425  */
19426
19427 /*
19428    Class: RGraph
19429    
19430    A radial graph visualization with advanced animations.
19431    
19432    Inspired by:
19433  
19434    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>
19435    
19436    Note:
19437    
19438    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.
19439    
19440   Implements:
19441   
19442   All <Loader> methods
19443   
19444    Constructor Options:
19445    
19446    Inherits options from
19447    
19448    - <Options.Canvas>
19449    - <Options.Controller>
19450    - <Options.Node>
19451    - <Options.Edge>
19452    - <Options.Label>
19453    - <Options.Events>
19454    - <Options.Tips>
19455    - <Options.NodeStyles>
19456    - <Options.Navigation>
19457    
19458    Additionally, there are other parameters and some default values changed
19459    
19460    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19461    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
19462      
19463    Instance Properties:
19464
19465    canvas - Access a <Canvas> instance.
19466    graph - Access a <Graph> instance.
19467    op - Access a <RGraph.Op> instance.
19468    fx - Access a <RGraph.Plot> instance.
19469    labels - Access a <RGraph.Label> interface implementation.   
19470 */
19471
19472 $jit.RGraph = new Class( {
19473
19474   Implements: [
19475       Loader, Extras, Layouts.Radial
19476   ],
19477
19478   initialize: function(controller){
19479     var $RGraph = $jit.RGraph;
19480
19481     var config = {
19482       interpolation: 'linear',
19483       levelDistance: 100
19484     };
19485
19486     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19487         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19488
19489     var canvasConfig = this.config;
19490     if(canvasConfig.useCanvas) {
19491       this.canvas = canvasConfig.useCanvas;
19492       this.config.labelContainer = this.canvas.id + '-label';
19493     } else {
19494       if(canvasConfig.background) {
19495         canvasConfig.background = $.merge({
19496           type: 'Circles'
19497         }, canvasConfig.background);
19498       }
19499       this.canvas = new Canvas(this, canvasConfig);
19500       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19501     }
19502
19503     this.graphOptions = {
19504       'complex': false,
19505       'Node': {
19506         'selected': false,
19507         'exist': true,
19508         'drawn': true
19509       }
19510     };
19511     this.graph = new Graph(this.graphOptions, this.config.Node,
19512         this.config.Edge);
19513     this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19514     this.fx = new $RGraph.Plot(this, $RGraph);
19515     this.op = new $RGraph.Op(this);
19516     this.json = null;
19517     this.root = null;
19518     this.busy = false;
19519     this.parent = false;
19520     // initialize extras
19521     this.initializeExtras();
19522   },
19523
19524   /* 
19525   
19526     createLevelDistanceFunc 
19527   
19528     Returns the levelDistance function used for calculating a node distance 
19529     to its origin. This function returns a function that is computed 
19530     per level and not per node, such that all nodes with the same depth will have the 
19531     same distance to the origin. The resulting function gets the 
19532     parent node as parameter and returns a float.
19533
19534    */
19535   createLevelDistanceFunc: function(){
19536     var ld = this.config.levelDistance;
19537     return function(elem){
19538       return (elem._depth + 1) * ld;
19539     };
19540   },
19541
19542   /* 
19543      Method: refresh 
19544      
19545      Computes positions and plots the tree.
19546
19547    */
19548   refresh: function(){
19549     this.compute();
19550     this.plot();
19551   },
19552
19553   reposition: function(){
19554     this.compute('end');
19555   },
19556
19557   /*
19558    Method: plot
19559   
19560    Plots the RGraph. This is a shortcut to *fx.plot*.
19561   */
19562   plot: function(){
19563     this.fx.plot();
19564   },
19565   /*
19566    getNodeAndParentAngle
19567   
19568    Returns the _parent_ of the given node, also calculating its angle span.
19569   */
19570   getNodeAndParentAngle: function(id){
19571     var theta = false;
19572     var n = this.graph.getNode(id);
19573     var ps = n.getParents();
19574     var p = (ps.length > 0)? ps[0] : false;
19575     if (p) {
19576       var posParent = p.pos.getc(), posChild = n.pos.getc();
19577       var newPos = posParent.add(posChild.scale(-1));
19578       theta = Math.atan2(newPos.y, newPos.x);
19579       if (theta < 0)
19580         theta += 2 * Math.PI;
19581     }
19582     return {
19583       parent: p,
19584       theta: theta
19585     };
19586   },
19587   /*
19588    tagChildren
19589   
19590    Enumerates the children in order to maintain child ordering (second constraint of the paper).
19591   */
19592   tagChildren: function(par, id){
19593     if (par.angleSpan) {
19594       var adjs = [];
19595       par.eachAdjacency(function(elem){
19596         adjs.push(elem.nodeTo);
19597       }, "ignore");
19598       var len = adjs.length;
19599       for ( var i = 0; i < len && id != adjs[i].id; i++)
19600         ;
19601       for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19602         adjs[j].dist = k++;
19603       }
19604     }
19605   },
19606   /* 
19607   Method: onClick 
19608   
19609   Animates the <RGraph> to center the node specified by *id*.
19610
19611    Parameters:
19612
19613    id - A <Graph.Node> id.
19614    opt - (optional|object) An object containing some extra properties described below
19615    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19616
19617    Example:
19618
19619    (start code js)
19620      rgraph.onClick('someid');
19621      //or also...
19622      rgraph.onClick('someid', {
19623       hideLabels: false
19624      });
19625     (end code)
19626     
19627   */
19628   onClick: function(id, opt){
19629     if (this.root != id && !this.busy) {
19630       this.busy = true;
19631       this.root = id;
19632       that = this;
19633       this.controller.onBeforeCompute(this.graph.getNode(id));
19634       var obj = this.getNodeAndParentAngle(id);
19635
19636       // second constraint
19637       this.tagChildren(obj.parent, id);
19638       this.parent = obj.parent;
19639       this.compute('end');
19640
19641       // first constraint
19642       var thetaDiff = obj.theta - obj.parent.endPos.theta;
19643       this.graph.eachNode(function(elem){
19644         elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19645       });
19646
19647       var mode = this.config.interpolation;
19648       opt = $.merge( {
19649         onComplete: $.empty
19650       }, opt || {});
19651
19652       this.fx.animate($.merge( {
19653         hideLabels: true,
19654         modes: [
19655           mode
19656         ]
19657       }, opt, {
19658         onComplete: function(){
19659           that.busy = false;
19660           opt.onComplete();
19661         }
19662       }));
19663     }
19664   }
19665 });
19666
19667 $jit.RGraph.$extend = true;
19668
19669 (function(RGraph){
19670
19671   /*
19672      Class: RGraph.Op
19673      
19674      Custom extension of <Graph.Op>.
19675
19676      Extends:
19677
19678      All <Graph.Op> methods
19679      
19680      See also:
19681      
19682      <Graph.Op>
19683
19684   */
19685   RGraph.Op = new Class( {
19686
19687     Implements: Graph.Op
19688
19689   });
19690
19691   /*
19692      Class: RGraph.Plot
19693     
19694     Custom extension of <Graph.Plot>.
19695   
19696     Extends:
19697   
19698     All <Graph.Plot> methods
19699     
19700     See also:
19701     
19702     <Graph.Plot>
19703   
19704   */
19705   RGraph.Plot = new Class( {
19706
19707     Implements: Graph.Plot
19708
19709   });
19710
19711   /*
19712     Object: RGraph.Label
19713
19714     Custom extension of <Graph.Label>. 
19715     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19716   
19717     Extends:
19718   
19719     All <Graph.Label> methods and subclasses.
19720   
19721     See also:
19722   
19723     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19724   
19725    */
19726   RGraph.Label = {};
19727
19728   /*
19729      RGraph.Label.Native
19730
19731      Custom extension of <Graph.Label.Native>.
19732
19733      Extends:
19734
19735      All <Graph.Label.Native> methods
19736
19737      See also:
19738
19739      <Graph.Label.Native>
19740
19741   */
19742   RGraph.Label.Native = new Class( {
19743     Implements: Graph.Label.Native
19744   });
19745
19746   /*
19747      RGraph.Label.SVG
19748     
19749     Custom extension of <Graph.Label.SVG>.
19750   
19751     Extends:
19752   
19753     All <Graph.Label.SVG> methods
19754   
19755     See also:
19756   
19757     <Graph.Label.SVG>
19758   
19759   */
19760   RGraph.Label.SVG = new Class( {
19761     Implements: Graph.Label.SVG,
19762
19763     initialize: function(viz){
19764       this.viz = viz;
19765     },
19766
19767     /* 
19768        placeLabel
19769
19770        Overrides abstract method placeLabel in <Graph.Plot>.
19771
19772        Parameters:
19773
19774        tag - A DOM label element.
19775        node - A <Graph.Node>.
19776        controller - A configuration/controller object passed to the visualization.
19777       
19778      */
19779     placeLabel: function(tag, node, controller){
19780       var pos = node.pos.getc(true), 
19781           canvas = this.viz.canvas,
19782           ox = canvas.translateOffsetX,
19783           oy = canvas.translateOffsetY,
19784           sx = canvas.scaleOffsetX,
19785           sy = canvas.scaleOffsetY,
19786           radius = canvas.getSize();
19787       var labelPos = {
19788         x: Math.round(pos.x * sx + ox + radius.width / 2),
19789         y: Math.round(pos.y * sy + oy + radius.height / 2)
19790       };
19791       tag.setAttribute('x', labelPos.x);
19792       tag.setAttribute('y', labelPos.y);
19793
19794       controller.onPlaceLabel(tag, node);
19795     }
19796   });
19797
19798   /*
19799      RGraph.Label.HTML
19800
19801      Custom extension of <Graph.Label.HTML>.
19802
19803      Extends:
19804
19805      All <Graph.Label.HTML> methods.
19806
19807      See also:
19808
19809      <Graph.Label.HTML>
19810
19811   */
19812   RGraph.Label.HTML = new Class( {
19813     Implements: Graph.Label.HTML,
19814
19815     initialize: function(viz){
19816       this.viz = viz;
19817     },
19818     /* 
19819        placeLabel
19820
19821        Overrides abstract method placeLabel in <Graph.Plot>.
19822
19823        Parameters:
19824
19825        tag - A DOM label element.
19826        node - A <Graph.Node>.
19827        controller - A configuration/controller object passed to the visualization.
19828       
19829      */
19830     placeLabel: function(tag, node, controller){
19831       var pos = node.pos.getc(true), 
19832           canvas = this.viz.canvas,
19833           ox = canvas.translateOffsetX,
19834           oy = canvas.translateOffsetY,
19835           sx = canvas.scaleOffsetX,
19836           sy = canvas.scaleOffsetY,
19837           radius = canvas.getSize();
19838       var labelPos = {
19839         x: Math.round(pos.x * sx + ox + radius.width / 2),
19840         y: Math.round(pos.y * sy + oy + radius.height / 2)
19841       };
19842
19843       var style = tag.style;
19844       style.left = labelPos.x + 'px';
19845       style.top = labelPos.y + 'px';
19846       style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
19847
19848       controller.onPlaceLabel(tag, node);
19849     }
19850   });
19851
19852   /*
19853     Class: RGraph.Plot.NodeTypes
19854
19855     This class contains a list of <Graph.Node> built-in types. 
19856     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
19857
19858     You can add your custom node types, customizing your visualization to the extreme.
19859
19860     Example:
19861
19862     (start code js)
19863       RGraph.Plot.NodeTypes.implement({
19864         'mySpecialType': {
19865           'render': function(node, canvas) {
19866             //print your custom node to canvas
19867           },
19868           //optional
19869           'contains': function(node, pos) {
19870             //return true if pos is inside the node or false otherwise
19871           }
19872         }
19873       });
19874     (end code)
19875
19876   */
19877   RGraph.Plot.NodeTypes = new Class({
19878     'none': {
19879       'render': $.empty,
19880       'contains': $.lambda(false)
19881     },
19882     'circle': {
19883       'render': function(node, canvas){
19884         var pos = node.pos.getc(true), 
19885             dim = node.getData('dim');
19886         this.nodeHelper.circle.render('fill', pos, dim, canvas);
19887       },
19888       'contains': function(node, pos){
19889         var npos = node.pos.getc(true), 
19890             dim = node.getData('dim');
19891         return this.nodeHelper.circle.contains(npos, pos, dim);
19892       }
19893     },
19894     'ellipse': {
19895       'render': function(node, canvas){
19896         var pos = node.pos.getc(true), 
19897             width = node.getData('width'), 
19898             height = node.getData('height');
19899         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
19900         },
19901       // TODO(nico): be more precise...
19902       'contains': function(node, pos){
19903         var npos = node.pos.getc(true), 
19904             width = node.getData('width'), 
19905             height = node.getData('height');
19906         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
19907       }
19908     },
19909     'square': {
19910       'render': function(node, canvas){
19911         var pos = node.pos.getc(true), 
19912             dim = node.getData('dim');
19913         this.nodeHelper.square.render('fill', pos, dim, canvas);
19914       },
19915       'contains': function(node, pos){
19916         var npos = node.pos.getc(true), 
19917             dim = node.getData('dim');
19918         return this.nodeHelper.square.contains(npos, pos, dim);
19919       }
19920     },
19921     'rectangle': {
19922       'render': function(node, canvas){
19923         var pos = node.pos.getc(true), 
19924             width = node.getData('width'), 
19925             height = node.getData('height');
19926         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
19927       },
19928       'contains': function(node, pos){
19929         var npos = node.pos.getc(true), 
19930             width = node.getData('width'), 
19931             height = node.getData('height');
19932         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
19933       }
19934     },
19935     'triangle': {
19936       'render': function(node, canvas){
19937         var pos = node.pos.getc(true), 
19938             dim = node.getData('dim');
19939         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
19940       },
19941       'contains': function(node, pos) {
19942         var npos = node.pos.getc(true), 
19943             dim = node.getData('dim');
19944         return this.nodeHelper.triangle.contains(npos, pos, dim);
19945       }
19946     },
19947     'star': {
19948       'render': function(node, canvas){
19949         var pos = node.pos.getc(true),
19950             dim = node.getData('dim');
19951         this.nodeHelper.star.render('fill', pos, dim, canvas);
19952       },
19953       'contains': function(node, pos) {
19954         var npos = node.pos.getc(true),
19955             dim = node.getData('dim');
19956         return this.nodeHelper.star.contains(npos, pos, dim);
19957       }
19958     }
19959   });
19960
19961   /*
19962     Class: RGraph.Plot.EdgeTypes
19963
19964     This class contains a list of <Graph.Adjacence> built-in types. 
19965     Edge types implemented are 'none', 'line' and 'arrow'.
19966   
19967     You can add your custom edge types, customizing your visualization to the extreme.
19968   
19969     Example:
19970   
19971     (start code js)
19972       RGraph.Plot.EdgeTypes.implement({
19973         'mySpecialType': {
19974           'render': function(adj, canvas) {
19975             //print your custom edge to canvas
19976           },
19977           //optional
19978           'contains': function(adj, pos) {
19979             //return true if pos is inside the arc or false otherwise
19980           }
19981         }
19982       });
19983     (end code)
19984   
19985   */
19986   RGraph.Plot.EdgeTypes = new Class({
19987     'none': $.empty,
19988     'line': {
19989       'render': function(adj, canvas) {
19990         var from = adj.nodeFrom.pos.getc(true),
19991             to = adj.nodeTo.pos.getc(true);
19992         this.edgeHelper.line.render(from, to, canvas);
19993       },
19994       'contains': function(adj, pos) {
19995         var from = adj.nodeFrom.pos.getc(true),
19996             to = adj.nodeTo.pos.getc(true);
19997         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
19998       }
19999     },
20000     'arrow': {
20001       'render': function(adj, canvas) {
20002         var from = adj.nodeFrom.pos.getc(true),
20003             to = adj.nodeTo.pos.getc(true),
20004             dim = adj.getData('dim'),
20005             direction = adj.data.$direction,
20006             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20007         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20008       },
20009       'contains': function(adj, pos) {
20010         var from = adj.nodeFrom.pos.getc(true),
20011             to = adj.nodeTo.pos.getc(true);
20012         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20013       }
20014     }
20015   });
20016
20017 })($jit.RGraph);
20018
20019
20020 /*
20021  * File: Hypertree.js
20022  * 
20023 */
20024
20025 /* 
20026      Complex 
20027      
20028      A multi-purpose Complex Class with common methods. Extended for the Hypertree. 
20029  
20030 */
20031 /* 
20032    moebiusTransformation 
20033  
20034    Calculates a moebius transformation for this point / complex. 
20035     For more information go to: 
20036         http://en.wikipedia.org/wiki/Moebius_transformation. 
20037  
20038    Parameters: 
20039  
20040       c - An initialized Complex instance representing a translation Vector. 
20041 */
20042
20043 Complex.prototype.moebiusTransformation = function(c) {
20044   var num = this.add(c);
20045   var den = c.$conjugate().$prod(this);
20046   den.x++;
20047   return num.$div(den);
20048 };
20049
20050 /* 
20051     moebiusTransformation 
20052      
20053     Calculates a moebius transformation for the hyperbolic tree. 
20054      
20055     <http://en.wikipedia.org/wiki/Moebius_transformation> 
20056       
20057      Parameters: 
20058      
20059         graph - A <Graph> instance.
20060         pos - A <Complex>.
20061         prop - A property array.
20062         theta - Rotation angle. 
20063         startPos - _optional_ start position. 
20064 */
20065 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20066   this.eachNode(graph, function(elem) {
20067     for ( var i = 0; i < prop.length; i++) {
20068       var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20069       elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20070     }
20071   }, flags);
20072 };
20073
20074 /* 
20075    Class: Hypertree 
20076    
20077    A Hyperbolic Tree/Graph visualization.
20078    
20079    Inspired by:
20080  
20081    A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli). 
20082    <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20083  
20084   Note:
20085  
20086   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.
20087
20088   Implements:
20089   
20090   All <Loader> methods
20091   
20092   Constructor Options:
20093   
20094   Inherits options from
20095   
20096   - <Options.Canvas>
20097   - <Options.Controller>
20098   - <Options.Node>
20099   - <Options.Edge>
20100   - <Options.Label>
20101   - <Options.Events>
20102   - <Options.Tips>
20103   - <Options.NodeStyles>
20104   - <Options.Navigation>
20105   
20106   Additionally, there are other parameters and some default values changed
20107   
20108   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*.
20109   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.
20110   fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20111   duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20112   Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
20113   
20114   Instance Properties:
20115   
20116   canvas - Access a <Canvas> instance.
20117   graph - Access a <Graph> instance.
20118   op - Access a <Hypertree.Op> instance.
20119   fx - Access a <Hypertree.Plot> instance.
20120   labels - Access a <Hypertree.Label> interface implementation.
20121
20122 */
20123
20124 $jit.Hypertree = new Class( {
20125
20126   Implements: [ Loader, Extras, Layouts.Radial ],
20127
20128   initialize: function(controller) {
20129     var $Hypertree = $jit.Hypertree;
20130
20131     var config = {
20132       radius: "auto",
20133       offset: 0,
20134       Edge: {
20135         type: 'hyperline'
20136       },
20137       duration: 1500,
20138       fps: 35
20139     };
20140     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20141         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20142
20143     var canvasConfig = this.config;
20144     if(canvasConfig.useCanvas) {
20145       this.canvas = canvasConfig.useCanvas;
20146       this.config.labelContainer = this.canvas.id + '-label';
20147     } else {
20148       if(canvasConfig.background) {
20149         canvasConfig.background = $.merge({
20150           type: 'Circles'
20151         }, canvasConfig.background);
20152       }
20153       this.canvas = new Canvas(this, canvasConfig);
20154       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20155     }
20156
20157     this.graphOptions = {
20158       'complex': false,
20159       'Node': {
20160         'selected': false,
20161         'exist': true,
20162         'drawn': true
20163       }
20164     };
20165     this.graph = new Graph(this.graphOptions, this.config.Node,
20166         this.config.Edge);
20167     this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20168     this.fx = new $Hypertree.Plot(this, $Hypertree);
20169     this.op = new $Hypertree.Op(this);
20170     this.json = null;
20171     this.root = null;
20172     this.busy = false;
20173     // initialize extras
20174     this.initializeExtras();
20175   },
20176
20177   /* 
20178   
20179   createLevelDistanceFunc 
20180
20181   Returns the levelDistance function used for calculating a node distance 
20182   to its origin. This function returns a function that is computed 
20183   per level and not per node, such that all nodes with the same depth will have the 
20184   same distance to the origin. The resulting function gets the 
20185   parent node as parameter and returns a float.
20186
20187   */
20188   createLevelDistanceFunc: function() {
20189     // get max viz. length.
20190     var r = this.getRadius();
20191     // get max depth.
20192     var depth = 0, max = Math.max, config = this.config;
20193     this.graph.eachNode(function(node) {
20194       depth = max(node._depth, depth);
20195     }, "ignore");
20196     depth++;
20197     // node distance generator
20198     var genDistFunc = function(a) {
20199       return function(node) {
20200         node.scale = r;
20201         var d = node._depth + 1;
20202         var acum = 0, pow = Math.pow;
20203         while (d) {
20204           acum += pow(a, d--);
20205         }
20206         return acum - config.offset;
20207       };
20208     };
20209     // estimate better edge length.
20210     for ( var i = 0.51; i <= 1; i += 0.01) {
20211       var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20212       if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20213     }
20214     return genDistFunc(0.75);
20215   },
20216
20217   /* 
20218     Method: getRadius 
20219     
20220     Returns the current radius of the visualization. If *config.radius* is *auto* then it 
20221     calculates the radius by taking the smaller size of the <Canvas> widget.
20222     
20223     See also:
20224     
20225     <Canvas.getSize>
20226    
20227   */
20228   getRadius: function() {
20229     var rad = this.config.radius;
20230     if (rad !== "auto") { return rad; }
20231     var s = this.canvas.getSize();
20232     return Math.min(s.width, s.height) / 2;
20233   },
20234
20235   /* 
20236     Method: refresh 
20237     
20238     Computes positions and plots the tree.
20239
20240     Parameters:
20241
20242     reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20243
20244    */
20245   refresh: function(reposition) {
20246     if (reposition) {
20247       this.reposition();
20248       this.graph.eachNode(function(node) {
20249         node.startPos.rho = node.pos.rho = node.endPos.rho;
20250         node.startPos.theta = node.pos.theta = node.endPos.theta;
20251       });
20252     } else {
20253       this.compute();
20254     }
20255     this.plot();
20256   },
20257
20258   /* 
20259    reposition 
20260    
20261    Computes nodes' positions and restores the tree to its previous position.
20262
20263    For calculating nodes' positions the root must be placed on its origin. This method does this 
20264      and then attemps to restore the hypertree to its previous position.
20265     
20266   */
20267   reposition: function() {
20268     this.compute('end');
20269     var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20270     Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20271         'end', "ignore");
20272     this.graph.eachNode(function(node) {
20273       if (node.ignore) {
20274         node.endPos.rho = node.pos.rho;
20275         node.endPos.theta = node.pos.theta;
20276       }
20277     });
20278   },
20279
20280   /* 
20281    Method: plot 
20282    
20283    Plots the <Hypertree>. This is a shortcut to *fx.plot*. 
20284
20285   */
20286   plot: function() {
20287     this.fx.plot();
20288   },
20289
20290   /* 
20291    Method: onClick 
20292    
20293    Animates the <Hypertree> to center the node specified by *id*.
20294
20295    Parameters:
20296
20297    id - A <Graph.Node> id.
20298    opt - (optional|object) An object containing some extra properties described below
20299    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20300
20301    Example:
20302
20303    (start code js)
20304      ht.onClick('someid');
20305      //or also...
20306      ht.onClick('someid', {
20307       hideLabels: false
20308      });
20309     (end code)
20310     
20311   */
20312   onClick: function(id, opt) {
20313     var pos = this.graph.getNode(id).pos.getc(true);
20314     this.move(pos, opt);
20315   },
20316
20317   /* 
20318    Method: move 
20319
20320    Translates the tree to the given position. 
20321
20322    Parameters:
20323
20324    pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20325    opt - This object has been defined in <Hypertree.onClick>
20326    
20327    Example:
20328    
20329    (start code js)
20330      ht.move({ x: 0, y: 0.7 }, {
20331        hideLabels: false
20332      });
20333    (end code)
20334
20335   */
20336   move: function(pos, opt) {
20337     var versor = $C(pos.x, pos.y);
20338     if (this.busy === false && versor.norm() < 1) {
20339       this.busy = true;
20340       var root = this.graph.getClosestNodeToPos(versor), that = this;
20341       this.graph.computeLevels(root.id, 0);
20342       this.controller.onBeforeCompute(root);
20343       opt = $.merge( {
20344         onComplete: $.empty
20345       }, opt || {});
20346       this.fx.animate($.merge( {
20347         modes: [ 'moebius' ],
20348         hideLabels: true
20349       }, opt, {
20350         onComplete: function() {
20351           that.busy = false;
20352           opt.onComplete();
20353         }
20354       }), versor);
20355     }
20356   }
20357 });
20358
20359 $jit.Hypertree.$extend = true;
20360
20361 (function(Hypertree) {
20362
20363   /* 
20364      Class: Hypertree.Op 
20365    
20366      Custom extension of <Graph.Op>.
20367
20368      Extends:
20369
20370      All <Graph.Op> methods
20371      
20372      See also:
20373      
20374      <Graph.Op>
20375
20376   */
20377   Hypertree.Op = new Class( {
20378
20379     Implements: Graph.Op
20380
20381   });
20382
20383   /* 
20384      Class: Hypertree.Plot 
20385    
20386     Custom extension of <Graph.Plot>.
20387   
20388     Extends:
20389   
20390     All <Graph.Plot> methods
20391     
20392     See also:
20393     
20394     <Graph.Plot>
20395   
20396   */
20397   Hypertree.Plot = new Class( {
20398
20399     Implements: Graph.Plot
20400
20401   });
20402
20403   /*
20404     Object: Hypertree.Label
20405
20406     Custom extension of <Graph.Label>. 
20407     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20408   
20409     Extends:
20410   
20411     All <Graph.Label> methods and subclasses.
20412   
20413     See also:
20414   
20415     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20416
20417    */
20418   Hypertree.Label = {};
20419
20420   /*
20421      Hypertree.Label.Native
20422
20423      Custom extension of <Graph.Label.Native>.
20424
20425      Extends:
20426
20427      All <Graph.Label.Native> methods
20428
20429      See also:
20430
20431      <Graph.Label.Native>
20432
20433   */
20434   Hypertree.Label.Native = new Class( {
20435     Implements: Graph.Label.Native,
20436
20437     initialize: function(viz) {
20438       this.viz = viz;
20439     },
20440
20441     renderLabel: function(canvas, node, controller) {
20442       var ctx = canvas.getCtx();
20443       var coord = node.pos.getc(true);
20444       var s = this.viz.getRadius();
20445       ctx.fillText(node.name, coord.x * s, coord.y * s);
20446     }
20447   });
20448
20449   /*
20450      Hypertree.Label.SVG
20451
20452     Custom extension of <Graph.Label.SVG>.
20453   
20454     Extends:
20455   
20456     All <Graph.Label.SVG> methods
20457   
20458     See also:
20459   
20460     <Graph.Label.SVG>
20461   
20462   */
20463   Hypertree.Label.SVG = new Class( {
20464     Implements: Graph.Label.SVG,
20465
20466     initialize: function(viz) {
20467       this.viz = viz;
20468     },
20469
20470     /* 
20471        placeLabel
20472
20473        Overrides abstract method placeLabel in <Graph.Plot>.
20474
20475        Parameters:
20476
20477        tag - A DOM label element.
20478        node - A <Graph.Node>.
20479        controller - A configuration/controller object passed to the visualization.
20480       
20481      */
20482     placeLabel: function(tag, node, controller) {
20483       var pos = node.pos.getc(true), 
20484           canvas = this.viz.canvas,
20485           ox = canvas.translateOffsetX,
20486           oy = canvas.translateOffsetY,
20487           sx = canvas.scaleOffsetX,
20488           sy = canvas.scaleOffsetY,
20489           radius = canvas.getSize(),
20490           r = this.viz.getRadius();
20491       var labelPos = {
20492         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20493         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20494       };
20495       tag.setAttribute('x', labelPos.x);
20496       tag.setAttribute('y', labelPos.y);
20497       controller.onPlaceLabel(tag, node);
20498     }
20499   });
20500
20501   /*
20502      Hypertree.Label.HTML
20503
20504      Custom extension of <Graph.Label.HTML>.
20505
20506      Extends:
20507
20508      All <Graph.Label.HTML> methods.
20509
20510      See also:
20511
20512      <Graph.Label.HTML>
20513
20514   */
20515   Hypertree.Label.HTML = new Class( {
20516     Implements: Graph.Label.HTML,
20517
20518     initialize: function(viz) {
20519       this.viz = viz;
20520     },
20521     /* 
20522        placeLabel
20523
20524        Overrides abstract method placeLabel in <Graph.Plot>.
20525
20526        Parameters:
20527
20528        tag - A DOM label element.
20529        node - A <Graph.Node>.
20530        controller - A configuration/controller object passed to the visualization.
20531       
20532      */
20533     placeLabel: function(tag, node, controller) {
20534       var pos = node.pos.getc(true), 
20535           canvas = this.viz.canvas,
20536           ox = canvas.translateOffsetX,
20537           oy = canvas.translateOffsetY,
20538           sx = canvas.scaleOffsetX,
20539           sy = canvas.scaleOffsetY,
20540           radius = canvas.getSize(),
20541           r = this.viz.getRadius();
20542       var labelPos = {
20543         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20544         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20545       };
20546       var style = tag.style;
20547       style.left = labelPos.x + 'px';
20548       style.top = labelPos.y + 'px';
20549       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20550
20551       controller.onPlaceLabel(tag, node);
20552     }
20553   });
20554
20555   /*
20556     Class: Hypertree.Plot.NodeTypes
20557
20558     This class contains a list of <Graph.Node> built-in types. 
20559     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20560
20561     You can add your custom node types, customizing your visualization to the extreme.
20562
20563     Example:
20564
20565     (start code js)
20566       Hypertree.Plot.NodeTypes.implement({
20567         'mySpecialType': {
20568           'render': function(node, canvas) {
20569             //print your custom node to canvas
20570           },
20571           //optional
20572           'contains': function(node, pos) {
20573             //return true if pos is inside the node or false otherwise
20574           }
20575         }
20576       });
20577     (end code)
20578
20579   */
20580   Hypertree.Plot.NodeTypes = new Class({
20581     'none': {
20582       'render': $.empty,
20583       'contains': $.lambda(false)
20584     },
20585     'circle': {
20586       'render': function(node, canvas) {
20587         var nconfig = this.node,
20588             dim = node.getData('dim'),
20589             p = node.pos.getc();
20590         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20591         p.$scale(node.scale);
20592         if (dim > 0.2) {
20593           this.nodeHelper.circle.render('fill', p, dim, canvas);
20594         }
20595       },
20596       'contains': function(node, pos) {
20597         var dim = node.getData('dim'),
20598             npos = node.pos.getc().$scale(node.scale);
20599         return this.nodeHelper.circle.contains(npos, pos, dim);
20600       }
20601     },
20602     'ellipse': {
20603       'render': function(node, canvas) {
20604         var pos = node.pos.getc().$scale(node.scale),
20605             width = node.getData('width'),
20606             height = node.getData('height');
20607         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20608       },
20609       'contains': function(node, pos) {
20610         var width = node.getData('width'),
20611             height = node.getData('height'),
20612             npos = node.pos.getc().$scale(node.scale);
20613         return this.nodeHelper.circle.contains(npos, pos, width, height);
20614       }
20615     },
20616     'square': {
20617       'render': function(node, canvas) {
20618         var nconfig = this.node,
20619             dim = node.getData('dim'),
20620             p = node.pos.getc();
20621         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20622         p.$scale(node.scale);
20623         if (dim > 0.2) {
20624           this.nodeHelper.square.render('fill', p, dim, canvas);
20625         }
20626       },
20627       'contains': function(node, pos) {
20628         var dim = node.getData('dim'),
20629             npos = node.pos.getc().$scale(node.scale);
20630         return this.nodeHelper.square.contains(npos, pos, dim);
20631       }
20632     },
20633     'rectangle': {
20634       'render': function(node, canvas) {
20635         var nconfig = this.node,
20636             width = node.getData('width'),
20637             height = node.getData('height'),
20638             pos = node.pos.getc();
20639         width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20640         height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20641         pos.$scale(node.scale);
20642         if (width > 0.2 && height > 0.2) {
20643           this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20644         }
20645       },
20646       'contains': function(node, pos) {
20647         var width = node.getData('width'),
20648             height = node.getData('height'),
20649             npos = node.pos.getc().$scale(node.scale);
20650         return this.nodeHelper.square.contains(npos, pos, width, height);
20651       }
20652     },
20653     'triangle': {
20654       'render': function(node, canvas) {
20655         var nconfig = this.node,
20656             dim = node.getData('dim'),
20657             p = node.pos.getc();
20658         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20659         p.$scale(node.scale);
20660         if (dim > 0.2) {
20661           this.nodeHelper.triangle.render('fill', p, dim, canvas);
20662         }
20663       },
20664       'contains': function(node, pos) {
20665         var dim = node.getData('dim'),
20666             npos = node.pos.getc().$scale(node.scale);
20667         return this.nodeHelper.triangle.contains(npos, pos, dim);
20668       }
20669     },
20670     'star': {
20671       'render': function(node, canvas) {
20672         var nconfig = this.node,
20673             dim = node.getData('dim'),
20674             p = node.pos.getc();
20675         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20676         p.$scale(node.scale);
20677         if (dim > 0.2) {
20678           this.nodeHelper.star.render('fill', p, dim, canvas);
20679         }
20680       },
20681       'contains': function(node, pos) {
20682         var dim = node.getData('dim'),
20683             npos = node.pos.getc().$scale(node.scale);
20684         return this.nodeHelper.star.contains(npos, pos, dim);
20685       }
20686     }
20687   });
20688
20689   /*
20690    Class: Hypertree.Plot.EdgeTypes
20691
20692     This class contains a list of <Graph.Adjacence> built-in types. 
20693     Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20694   
20695     You can add your custom edge types, customizing your visualization to the extreme.
20696   
20697     Example:
20698   
20699     (start code js)
20700       Hypertree.Plot.EdgeTypes.implement({
20701         'mySpecialType': {
20702           'render': function(adj, canvas) {
20703             //print your custom edge to canvas
20704           },
20705           //optional
20706           'contains': function(adj, pos) {
20707             //return true if pos is inside the arc or false otherwise
20708           }
20709         }
20710       });
20711     (end code)
20712   
20713   */
20714   Hypertree.Plot.EdgeTypes = new Class({
20715     'none': $.empty,
20716     'line': {
20717       'render': function(adj, canvas) {
20718         var from = adj.nodeFrom.pos.getc(true),
20719           to = adj.nodeTo.pos.getc(true),
20720           r = adj.nodeFrom.scale;
20721           this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20722       },
20723       'contains': function(adj, pos) {
20724         var from = adj.nodeFrom.pos.getc(true),
20725             to = adj.nodeTo.pos.getc(true),
20726             r = adj.nodeFrom.scale;
20727             this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20728       }
20729     },
20730     'arrow': {
20731       'render': function(adj, canvas) {
20732         var from = adj.nodeFrom.pos.getc(true),
20733             to = adj.nodeTo.pos.getc(true),
20734             r = adj.nodeFrom.scale,
20735             dim = adj.getData('dim'),
20736             direction = adj.data.$direction,
20737             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20738         this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20739       },
20740       'contains': function(adj, pos) {
20741         var from = adj.nodeFrom.pos.getc(true),
20742             to = adj.nodeTo.pos.getc(true),
20743             r = adj.nodeFrom.scale;
20744         this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20745       }
20746     },
20747     'hyperline': {
20748       'render': function(adj, canvas) {
20749         var from = adj.nodeFrom.pos.getc(),
20750             to = adj.nodeTo.pos.getc(),
20751             dim = this.viz.getRadius();
20752         this.edgeHelper.hyperline.render(from, to, dim, canvas);
20753       },
20754       'contains': $.lambda(false)
20755     }
20756   });
20757
20758 })($jit.Hypertree);
20759
20760
20761
20762
20763  })();