]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/SugarCharts/Jit/js/Jit/jit.js
Release 6.2.0beta4
[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                 }       
221                 var handleSuccess = function(o){
222                 }                       
223                 var callback =
224                 {
225                   success:handleSuccess,
226                   failure:handleFailure,
227                   argument: { foo:'foo', bar:''}
228                 };
229                 var path = "index.php?action=DynamicAction&DynamicAction=saveImage&module=Charts&to_pdf=1";
230                 var postData = "imageStr=" + strDataURI + "&filename=" + filename;
231                 var request = YAHOO.util.Connect.asyncRequest('POST', path, callback, postData);
232         }
233 };
234
235 $.saveImageTest = function (id,jsonfilename,imageExt) {
236                 if(typeof FlashCanvas != "undefined") {
237                         setTimeout(function(){$.saveImageFile(id,jsonfilename,imageExt)},10000);
238                 } else {
239                         $.saveImageFile(id,jsonfilename,imageExt);
240                 }
241         };
242 /*
243   Method: extend
244   
245   Augment an object by appending another object's properties.
246   
247   Parameters:
248   
249   original - (object) The object to be extended.
250   extended - (object) An object which properties are going to be appended to the original object.
251   
252   Example:
253   (start code js)
254   $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
255   (end code)
256 */
257 $.extend = function(original, extended) {
258   for ( var key in (extended || {}))
259     original[key] = extended[key];
260   return original;
261 };
262
263 $.lambda = function(value) {
264   return (typeof value == 'function') ? value : function() {
265     return value;
266   };
267 };
268
269 $.time = Date.now || function() {
270   return +new Date;
271 };
272
273 /*
274   Method: splat
275   
276   Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
277   
278   Parameters:
279   
280   obj - (mixed) The object to be wrapped in an array.
281   
282   Example:
283   (start code js)
284   $jit.util.splat(3);   //[3]
285   $jit.util.splat([3]); //[3]
286   (end code)
287 */
288 $.splat = function(obj) {
289   var type = $.type(obj);
290   return type ? ((type != 'array') ? [ obj ] : obj) : [];
291 };
292
293 $.type = function(elem) {
294   var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
295   if(type != 'object') return type;
296   if(elem && elem.$$family) return elem.$$family;
297   return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
298 };
299 $.type.s = Object.prototype.toString;
300
301 /*
302   Method: each
303   
304   Iterates through an iterable applying *f*.
305   
306   Parameters:
307   
308   iterable - (array) The original array.
309   fn - (function) The function to apply to the array elements.
310   
311   Example:
312   (start code js)
313   $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
314   (end code)
315 */
316 $.each = function(iterable, fn) {
317   var type = $.type(iterable);
318   if (type == 'object') {
319     for ( var key in iterable)
320       fn(iterable[key], key);
321   } else {
322     for ( var i = 0, l = iterable.length; i < l; i++)
323       fn(iterable[i], i);
324   }
325 };
326
327 $.indexOf = function(array, item) {
328   if(Array.indexOf) return array.indexOf(item);
329   for(var i=0,l=array.length; i<l; i++) {
330     if(array[i] === item) return i;
331   }
332   return -1;
333 };
334
335 /*
336   Method: map
337   
338   Maps or collects an array by applying *f*.
339   
340   Parameters:
341   
342   array - (array) The original array.
343   f - (function) The function to apply to the array elements.
344   
345   Example:
346   (start code js)
347   $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
348   (end code)
349 */
350 $.map = function(array, f) {
351   var ans = [];
352   $.each(array, function(elem, i) {
353     ans.push(f(elem, i));
354   });
355   return ans;
356 };
357
358 /*
359   Method: reduce
360   
361   Iteratively applies the binary function *f* storing the result in an accumulator.
362   
363   Parameters:
364   
365   array - (array) The original array.
366   f - (function) The function to apply to the array elements.
367   opt - (optional|mixed) The starting value for the acumulator.
368   
369   Example:
370   (start code js)
371   $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
372   (end code)
373 */
374 $.reduce = function(array, f, opt) {
375   var l = array.length;
376   if(l==0) return opt;
377   var acum = arguments.length == 3? opt : array[--l];
378   while(l--) {
379     acum = f(acum, array[l]);
380   }
381   return acum;
382 };
383
384 /*
385   Method: merge
386   
387   Merges n-objects and their sub-objects creating a new, fresh object.
388   
389   Parameters:
390   
391   An arbitrary number of objects.
392   
393   Example:
394   (start code js)
395   $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
396   (end code)
397 */
398 $.merge = function() {
399   var mix = {};
400   for ( var i = 0, l = arguments.length; i < l; i++) {
401     var object = arguments[i];
402     if ($.type(object) != 'object')
403       continue;
404     for ( var key in object) {
405       var op = object[key], mp = mix[key];
406       mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
407           .merge(mp, op) : $.unlink(op);
408     }
409   }
410   return mix;
411 };
412
413 $.unlink = function(object) {
414   var unlinked;
415   switch ($.type(object)) {
416   case 'object':
417     unlinked = {};
418     for ( var p in object)
419       unlinked[p] = $.unlink(object[p]);
420     break;
421   case 'array':
422     unlinked = [];
423     for ( var i = 0, l = object.length; i < l; i++)
424       unlinked[i] = $.unlink(object[i]);
425     break;
426   default:
427     return object;
428   }
429   return unlinked;
430 };
431
432 $.zip = function() {
433   if(arguments.length === 0) return [];
434   for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
435     for(var i=0, row=[]; i<l; i++) {
436       row.push(arguments[i][j]);
437     }
438     ans.push(row);
439   }
440   return ans;
441 };
442
443 /*
444   Method: rgbToHex
445   
446   Converts an RGB array into a Hex string.
447   
448   Parameters:
449   
450   srcArray - (array) An array with R, G and B values
451   
452   Example:
453   (start code js)
454   $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
455   (end code)
456 */
457 $.rgbToHex = function(srcArray, array) {
458   if (srcArray.length < 3)
459     return null;
460   if (srcArray.length == 4 && srcArray[3] == 0 && !array)
461     return 'transparent';
462   var hex = [];
463   for ( var i = 0; i < 3; i++) {
464     var bit = (srcArray[i] - 0).toString(16);
465     hex.push(bit.length == 1 ? '0' + bit : bit);
466   }
467   return array ? hex : '#' + hex.join('');
468 };
469
470 /*
471   Method: hexToRgb
472   
473   Converts an Hex color string into an RGB array.
474   
475   Parameters:
476   
477   hex - (string) A color hex string.
478   
479   Example:
480   (start code js)
481   $jit.util.hexToRgb('#fff'); //[255, 255, 255]
482   (end code)
483 */
484 $.hexToRgb = function(hex) {
485   if (hex.length != 7) {
486     hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
487     hex.shift();
488     if (hex.length != 3)
489       return null;
490     var rgb = [];
491     for ( var i = 0; i < 3; i++) {
492       var value = hex[i];
493       if (value.length == 1)
494         value += value;
495       rgb.push(parseInt(value, 16));
496     }
497     return rgb;
498   } else {
499     hex = parseInt(hex.slice(1), 16);
500     return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
501   }
502 };
503
504 $.destroy = function(elem) {
505   $.clean(elem);
506   if (elem.parentNode)
507     elem.parentNode.removeChild(elem);
508   if (elem.clearAttributes)
509     elem.clearAttributes();
510 };
511
512 $.clean = function(elem) {
513   for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
514     $.destroy(ch[i]);
515   }
516 };
517
518 /*
519   Method: addEvent
520   
521   Cross-browser add event listener.
522   
523   Parameters:
524   
525   obj - (obj) The Element to attach the listener to.
526   type - (string) The listener type. For example 'click', or 'mousemove'.
527   fn - (function) The callback function to be used when the event is fired.
528   
529   Example:
530   (start code js)
531   $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
532   (end code)
533 */
534 $.addEvent = function(obj, type, fn) {
535   if (obj.addEventListener)
536     obj.addEventListener(type, fn, false);
537   else
538     obj.attachEvent('on' + type, fn);
539 };
540
541 $.addEvents = function(obj, typeObj) {
542   for(var type in typeObj) {
543     $.addEvent(obj, type, typeObj[type]);
544   }
545 };
546
547 $.hasClass = function(obj, klass) {
548   return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
549 };
550
551 $.addClass = function(obj, klass) {
552   if (!$.hasClass(obj, klass))
553     obj.className = (obj.className + " " + klass);
554 };
555
556 $.removeClass = function(obj, klass) {
557   obj.className = obj.className.replace(new RegExp(
558       '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
559 };
560
561 $.getPos = function(elem) {
562   var offset = getOffsets(elem);
563   var scroll = getScrolls(elem);
564   return {
565     x: offset.x - scroll.x,
566     y: offset.y - scroll.y
567   };
568
569   function getOffsets(elem) {
570     var position = {
571       x: 0,
572       y: 0
573     };
574     while (elem && !isBody(elem)) {
575       position.x += elem.offsetLeft;
576       position.y += elem.offsetTop;
577       elem = elem.offsetParent;
578     }
579     return position;
580   }
581
582   function getScrolls(elem) {
583     var position = {
584       x: 0,
585       y: 0
586     };
587     while (elem && !isBody(elem)) {
588       position.x += elem.scrollLeft;
589       position.y += elem.scrollTop;
590       elem = elem.parentNode;
591     }
592     return position;
593   }
594
595   function isBody(element) {
596     return (/^(?:body|html)$/i).test(element.tagName);
597   }
598 };
599
600 $.event = {
601   get: function(e, win) {
602     win = win || window;
603     return e || win.event;
604   },
605   getWheel: function(e) {
606     return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
607   },
608   isRightClick: function(e) {
609     return (e.which == 3 || e.button == 2);
610   },
611   getPos: function(e, win) {
612     // get mouse position
613     win = win || window;
614     e = e || win.event;
615     var doc = win.document;
616     doc = doc.documentElement || doc.body;
617     //TODO(nico): make touch event handling better
618     if(e.touches && e.touches.length) {
619       e = e.touches[0];
620     }
621     var page = {
622       x: e.pageX || (e.clientX + doc.scrollLeft),
623       y: e.pageY || (e.clientY + doc.scrollTop)
624     };
625     return page;
626   },
627   stop: function(e) {
628     if (e.stopPropagation) e.stopPropagation();
629     e.cancelBubble = true;
630     if (e.preventDefault) e.preventDefault();
631     else e.returnValue = false;
632   }
633 };
634
635 $jit.util = $jit.id = $;
636
637 var Class = function(properties) {
638   properties = properties || {};
639   var klass = function() {
640     for ( var key in this) {
641       if (typeof this[key] != 'function')
642         this[key] = $.unlink(this[key]);
643     }
644     this.constructor = klass;
645     if (Class.prototyping)
646       return this;
647     var instance = this.initialize ? this.initialize.apply(this, arguments)
648         : this;
649     //typize
650     this.$$family = 'class';
651     return instance;
652   };
653
654   for ( var mutator in Class.Mutators) {
655     if (!properties[mutator])
656       continue;
657     properties = Class.Mutators[mutator](properties, properties[mutator]);
658     delete properties[mutator];
659   }
660
661   $.extend(klass, this);
662   klass.constructor = Class;
663   klass.prototype = properties;
664   return klass;
665 };
666
667 Class.Mutators = {
668
669   Implements: function(self, klasses) {
670     $.each($.splat(klasses), function(klass) {
671       Class.prototyping = klass;
672       var instance = (typeof klass == 'function') ? new klass : klass;
673       for ( var prop in instance) {
674         if (!(prop in self)) {
675           self[prop] = instance[prop];
676         }
677       }
678       delete Class.prototyping;
679     });
680     return self;
681   }
682
683 };
684
685 $.extend(Class, {
686
687   inherit: function(object, properties) {
688     for ( var key in properties) {
689       var override = properties[key];
690       var previous = object[key];
691       var type = $.type(override);
692       if (previous && type == 'function') {
693         if (override != previous) {
694           Class.override(object, key, override);
695         }
696       } else if (type == 'object') {
697         object[key] = $.merge(previous, override);
698       } else {
699         object[key] = override;
700       }
701     }
702     return object;
703   },
704
705   override: function(object, name, method) {
706     var parent = Class.prototyping;
707     if (parent && object[name] != parent[name])
708       parent = null;
709     var override = function() {
710       var previous = this.parent;
711       this.parent = parent ? parent[name] : object[name];
712       var value = method.apply(this, arguments);
713       this.parent = previous;
714       return value;
715     };
716     object[name] = override;
717   }
718
719 });
720
721 Class.prototype.implement = function() {
722   var proto = this.prototype;
723   $.each(Array.prototype.slice.call(arguments || []), function(properties) {
724     Class.inherit(proto, properties);
725   });
726   return this;
727 };
728
729 $jit.Class = Class;
730
731 /*
732   Object: $jit.json
733   
734   Provides JSON utility functions.
735   
736   Most of these functions are JSON-tree traversal and manipulation functions.
737 */
738 $jit.json = {
739   /*
740      Method: prune
741   
742      Clears all tree nodes having depth greater than maxLevel.
743   
744      Parameters:
745   
746         tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
747         maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
748
749   */
750   prune: function(tree, maxLevel) {
751     this.each(tree, function(elem, i) {
752       if (i == maxLevel && elem.children) {
753         delete elem.children;
754         elem.children = [];
755       }
756     });
757   },
758   /*
759      Method: getParent
760   
761      Returns the parent node of the node having _id_ as id.
762   
763      Parameters:
764   
765         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
766         id - (string) The _id_ of the child node whose parent will be returned.
767
768     Returns:
769
770         A tree JSON node if any, or false otherwise.
771   
772   */
773   getParent: function(tree, id) {
774     if (tree.id == id)
775       return false;
776     var ch = tree.children;
777     if (ch && ch.length > 0) {
778       for ( var i = 0; i < ch.length; i++) {
779         if (ch[i].id == id)
780           return tree;
781         else {
782           var ans = this.getParent(ch[i], id);
783           if (ans)
784             return ans;
785         }
786       }
787     }
788     return false;
789   },
790   /*
791      Method: getSubtree
792   
793      Returns the subtree that matches the given id.
794   
795      Parameters:
796   
797         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
798         id - (string) A node *unique* identifier.
799   
800      Returns:
801   
802         A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
803
804   */
805   getSubtree: function(tree, id) {
806     if (tree.id == id)
807       return tree;
808     for ( var i = 0, ch = tree.children; i < ch.length; i++) {
809       var t = this.getSubtree(ch[i], id);
810       if (t != null)
811         return t;
812     }
813     return null;
814   },
815   /*
816      Method: eachLevel
817   
818       Iterates on tree nodes with relative depth less or equal than a specified level.
819   
820      Parameters:
821   
822         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
823         initLevel - (number) An integer specifying the initial relative level. Usually zero.
824         toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
825         action - (function) A function that receives a node and an integer specifying the actual level of the node.
826           
827     Example:
828    (start code js)
829      $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
830         alert(node.name + ' ' + depth);
831      });
832    (end code)
833   */
834   eachLevel: function(tree, initLevel, toLevel, action) {
835     if (initLevel <= toLevel) {
836       action(tree, initLevel);
837       if(!tree.children) return;
838       for ( var i = 0, ch = tree.children; i < ch.length; i++) {
839         this.eachLevel(ch[i], initLevel + 1, toLevel, action);
840       }
841     }
842   },
843   /*
844      Method: each
845   
846       A JSON tree iterator.
847   
848      Parameters:
849   
850         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
851         action - (function) A function that receives a node.
852
853     Example:
854     (start code js)
855       $jit.json.each(tree, function(node) {
856         alert(node.name);
857       });
858     (end code)
859           
860   */
861   each: function(tree, action) {
862     this.eachLevel(tree, 0, Number.MAX_VALUE, action);
863   }
864 };
865
866
867 /*
868      An object containing multiple type of transformations. 
869 */
870
871 $jit.Trans = {
872   $extend: true,
873   
874   linear: function(p){
875     return p;
876   }
877 };
878
879 var Trans = $jit.Trans;
880
881 (function(){
882
883   var makeTrans = function(transition, params){
884     params = $.splat(params);
885     return $.extend(transition, {
886       easeIn: function(pos){
887         return transition(pos, params);
888       },
889       easeOut: function(pos){
890         return 1 - transition(1 - pos, params);
891       },
892       easeInOut: function(pos){
893         return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
894             2 * (1 - pos), params)) / 2;
895       }
896     });
897   };
898
899   var transitions = {
900
901     Pow: function(p, x){
902       return Math.pow(p, x[0] || 6);
903     },
904
905     Expo: function(p){
906       return Math.pow(2, 8 * (p - 1));
907     },
908
909     Circ: function(p){
910       return 1 - Math.sin(Math.acos(p));
911     },
912
913     Sine: function(p){
914       return 1 - Math.sin((1 - p) * Math.PI / 2);
915     },
916
917     Back: function(p, x){
918       x = x[0] || 1.618;
919       return Math.pow(p, 2) * ((x + 1) * p - x);
920     },
921
922     Bounce: function(p){
923       var value;
924       for ( var a = 0, b = 1; 1; a += b, b /= 2) {
925         if (p >= (7 - 4 * a) / 11) {
926           value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
927           break;
928         }
929       }
930       return value;
931     },
932
933     Elastic: function(p, x){
934       return Math.pow(2, 10 * --p)
935           * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
936     }
937
938   };
939
940   $.each(transitions, function(val, key){
941     Trans[key] = makeTrans(val);
942   });
943
944   $.each( [
945       'Quad', 'Cubic', 'Quart', 'Quint'
946   ], function(elem, i){
947     Trans[elem] = makeTrans(function(p){
948       return Math.pow(p, [
949         i + 2
950       ]);
951     });
952   });
953
954 })();
955
956 /*
957    A Class that can perform animations for generic objects.
958
959    If you are looking for animation transitions please take a look at the <Trans> object.
960
961    Used by:
962
963    <Graph.Plot>
964    
965    Based on:
966    
967    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>.
968
969 */
970
971 var Animation = new Class( {
972
973   initialize: function(options){
974     this.setOptions(options);
975   },
976
977   setOptions: function(options){
978     var opt = {
979       duration: 2500,
980       fps: 40,
981       transition: Trans.Quart.easeInOut,
982       compute: $.empty,
983       complete: $.empty,
984       link: 'ignore'
985     };
986     this.opt = $.merge(opt, options || {});
987     return this;
988   },
989
990   step: function(){
991     var time = $.time(), opt = this.opt;
992     if (time < this.time + opt.duration) {
993       var delta = opt.transition((time - this.time) / opt.duration);
994       opt.compute(delta);
995     } else {
996       this.timer = clearInterval(this.timer);
997       opt.compute(1);
998       opt.complete();
999     }
1000   },
1001
1002   start: function(){
1003     if (!this.check())
1004       return this;
1005     this.time = 0;
1006     this.startTimer();
1007     return this;
1008   },
1009
1010   startTimer: function(){
1011     var that = this, fps = this.opt.fps;
1012     if (this.timer)
1013       return false;
1014     this.time = $.time() - this.time;
1015     this.timer = setInterval((function(){
1016       that.step();
1017     }), Math.round(1000 / fps));
1018     return true;
1019   },
1020
1021   pause: function(){
1022     this.stopTimer();
1023     return this;
1024   },
1025
1026   resume: function(){
1027     this.startTimer();
1028     return this;
1029   },
1030
1031   stopTimer: function(){
1032     if (!this.timer)
1033       return false;
1034     this.time = $.time() - this.time;
1035     this.timer = clearInterval(this.timer);
1036     return true;
1037   },
1038
1039   check: function(){
1040     if (!this.timer)
1041       return true;
1042     if (this.opt.link == 'cancel') {
1043       this.stopTimer();
1044       return true;
1045     }
1046     return false;
1047   }
1048 });
1049
1050
1051 var Options = function() {
1052   var args = arguments;
1053   for(var i=0, l=args.length, ans={}; i<l; i++) {
1054     var opt = Options[args[i]];
1055     if(opt.$extend) {
1056       $.extend(ans, opt);
1057     } else {
1058       ans[args[i]] = opt;  
1059     }
1060   }
1061   return ans;
1062 };
1063
1064 /*
1065  * File: Options.AreaChart.js
1066  *
1067 */
1068
1069 /*
1070   Object: Options.AreaChart
1071   
1072   <AreaChart> options. 
1073   Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1074   
1075   Syntax:
1076   
1077   (start code js)
1078
1079   Options.AreaChart = {
1080     animate: true,
1081     labelOffset: 3,
1082     type: 'stacked',
1083     selectOnHover: true,
1084     showAggregates: true,
1085     showLabels: true,
1086     filterOnClick: false,
1087     restoreOnRightClick: false
1088   };
1089   
1090   (end code)
1091   
1092   Example:
1093   
1094   (start code js)
1095
1096   var areaChart = new $jit.AreaChart({
1097     animate: true,
1098     type: 'stacked:gradient',
1099     selectOnHover: true,
1100     filterOnClick: true,
1101     restoreOnRightClick: true
1102   });
1103   
1104   (end code)
1105
1106   Parameters:
1107   
1108   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1109   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1110   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1111   selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1112   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1113   showLabels - (boolean) Default's *true*. Display the name of the slots.
1114   filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1115   restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1116   
1117 */
1118   
1119 Options.AreaChart = {
1120   $extend: true,
1121
1122   animate: true,
1123   labelOffset: 3, // label offset
1124   type: 'stacked', // gradient
1125   Tips: {
1126     enable: false,
1127     onShow: $.empty,
1128     onHide: $.empty
1129   },
1130   Events: {
1131     enable: false,
1132     onClick: $.empty
1133   },
1134   selectOnHover: true,
1135   showAggregates: true,
1136   showLabels: true,
1137   filterOnClick: false,
1138   restoreOnRightClick: false
1139 };
1140
1141 /*
1142  * File: Options.Margin.js
1143  *
1144 */
1145
1146 /*
1147   Object: Options.Margin
1148   
1149   Canvas drawing margins. 
1150   
1151   Syntax:
1152   
1153   (start code js)
1154
1155   Options.Margin = {
1156     top: 0,
1157     left: 0,
1158     right: 0,
1159     bottom: 0
1160   };
1161   
1162   (end code)
1163   
1164   Example:
1165   
1166   (start code js)
1167
1168   var viz = new $jit.Viz({
1169     Margin: {
1170       right: 10,
1171       bottom: 20
1172     }
1173   });
1174   
1175   (end code)
1176
1177   Parameters:
1178   
1179   top - (number) Default's *0*. Top margin.
1180   left - (number) Default's *0*. Left margin.
1181   right - (number) Default's *0*. Right margin.
1182   bottom - (number) Default's *0*. Bottom margin.
1183   
1184 */
1185
1186 Options.Margin = {
1187   $extend: false,
1188   
1189   top: 0,
1190   left: 0,
1191   right: 0,
1192   bottom: 0
1193 };
1194
1195 /*
1196  * File: Options.Canvas.js
1197  *
1198 */
1199
1200 /*
1201   Object: Options.Canvas
1202   
1203   These are Canvas general options, like where to append it in the DOM, its dimensions, background, 
1204   and other more advanced options.
1205   
1206   Syntax:
1207   
1208   (start code js)
1209
1210   Options.Canvas = {
1211     injectInto: 'id',
1212     width: false,
1213     height: false,
1214     useCanvas: false,
1215     withLabels: true,
1216     background: false
1217   };  
1218   (end code)
1219   
1220   Example:
1221   
1222   (start code js)
1223   var viz = new $jit.Viz({
1224     injectInto: 'someContainerId',
1225     width: 500,
1226     height: 700
1227   });
1228   (end code)
1229   
1230   Parameters:
1231   
1232   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.
1233   width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1234   height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1235   useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1236   withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1237   background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1238 */
1239
1240 Options.Canvas = {
1241     $extend: true,
1242     
1243     injectInto: 'id',
1244     width: false,
1245     height: false,
1246     useCanvas: false,
1247     withLabels: true,
1248     background: false,
1249     colorStop1: 'rgba(255,255,255,1)',
1250     colorStop2: 'rgba(255,255,255,0)'
1251 };
1252
1253 /*
1254  * File: Options.Tree.js
1255  *
1256 */
1257
1258 /*
1259   Object: Options.Tree
1260   
1261   Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1262   
1263   Syntax:
1264   
1265   (start code js)
1266   Options.Tree = {
1267     orientation: "left",
1268     subtreeOffset: 8,
1269     siblingOffset: 5,
1270     indent:10,
1271     multitree: false,
1272     align:"center"
1273   };
1274   (end code)
1275   
1276   Example:
1277   
1278   (start code js)
1279   var st = new $jit.ST({
1280     orientation: 'left',
1281     subtreeOffset: 1,
1282     siblingOFfset: 5,
1283     multitree: true
1284   });
1285   (end code)
1286
1287   Parameters:
1288     
1289   subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1290   siblingOffset - (number) Default's 5. Separation offset between siblings.
1291   orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1292   align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1293   indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1294   multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1295      
1296 */
1297 Options.Tree = {
1298     $extend: true,
1299     
1300     orientation: "left",
1301     subtreeOffset: 8,
1302     siblingOffset: 5,
1303     indent:10,
1304     multitree: false,
1305     align:"center"
1306 };
1307
1308
1309 /*
1310  * File: Options.Node.js
1311  *
1312 */
1313
1314 /*
1315   Object: Options.Node
1316
1317   Provides Node rendering options for Tree and Graph based visualizations.
1318
1319   Syntax:
1320     
1321   (start code js)
1322   Options.Node = {
1323     overridable: false,
1324     type: 'circle',
1325     color: '#ccb',
1326     alpha: 1,
1327     dim: 3,
1328     height: 20,
1329     width: 90,
1330     autoHeight: false,
1331     autoWidth: false,
1332     lineWidth: 1,
1333     transform: true,
1334     align: "center",
1335     angularWidth:1,
1336     span:1,
1337     CanvasStyles: {}
1338   };
1339   (end code)
1340   
1341   Example:
1342   
1343   (start code js)
1344   var viz = new $jit.Viz({
1345     Node: {
1346       overridable: true,
1347       width: 30,
1348       autoHeight: true,
1349       type: 'rectangle'
1350     }
1351   });
1352   (end code)
1353   
1354   Parameters:
1355
1356   overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1357   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.
1358   color - (string) Default's *#ccb*. Node color.
1359   alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1360   dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1361   height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1362   width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1363   autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1364   autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1365   lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1366   transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1367   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.
1368   angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1369   span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1370   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.
1371
1372 */
1373 Options.Node = {
1374   $extend: false,
1375   
1376   overridable: false,
1377   type: 'circle',
1378   color: '#ccb',
1379   alpha: 1,
1380   dim: 3,
1381   height: 20,
1382   width: 90,
1383   autoHeight: false,
1384   autoWidth: false,
1385   lineWidth: 1,
1386   transform: true,
1387   align: "center",
1388   angularWidth:1,
1389   span:1,
1390   //Raw canvas styles to be
1391   //applied to the context instance
1392   //before plotting a node
1393   CanvasStyles: {}
1394 };
1395
1396
1397 /*
1398  * File: Options.Edge.js
1399  *
1400 */
1401
1402 /*
1403   Object: Options.Edge
1404
1405   Provides Edge rendering options for Tree and Graph based visualizations.
1406
1407   Syntax:
1408     
1409   (start code js)
1410   Options.Edge = {
1411     overridable: false,
1412     type: 'line',
1413     color: '#ccb',
1414     lineWidth: 1,
1415     dim:15,
1416     alpha: 1,
1417     CanvasStyles: {}
1418   };
1419   (end code)
1420   
1421   Example:
1422   
1423   (start code js)
1424   var viz = new $jit.Viz({
1425     Edge: {
1426       overridable: true,
1427       type: 'line',
1428       color: '#fff',
1429       CanvasStyles: {
1430         shadowColor: '#ccc',
1431         shadowBlur: 10
1432       }
1433     }
1434   });
1435   (end code)
1436   
1437   Parameters:
1438     
1439    overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1440    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.
1441    color - (string) Default's '#ccb'. Edge color.
1442    lineWidth - (number) Default's *1*. Line/Edge width.
1443    alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1444    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.
1445    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*.
1446    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.
1447
1448   See also:
1449    
1450    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.
1451 */
1452 Options.Edge = {
1453   $extend: false,
1454   
1455   overridable: false,
1456   type: 'line',
1457   color: '#ccb',
1458   lineWidth: 1,
1459   dim:15,
1460   alpha: 1,
1461   epsilon: 7,
1462
1463   //Raw canvas styles to be
1464   //applied to the context instance
1465   //before plotting an edge
1466   CanvasStyles: {}
1467 };
1468
1469
1470 /*
1471  * File: Options.Fx.js
1472  *
1473 */
1474
1475 /*
1476   Object: Options.Fx
1477
1478   Provides animation options like duration of the animations, frames per second and animation transitions.  
1479
1480   Syntax:
1481   
1482   (start code js)
1483     Options.Fx = {
1484       fps:40,
1485       duration: 2500,
1486       transition: $jit.Trans.Quart.easeInOut,
1487       clearCanvas: true
1488     };
1489   (end code)
1490   
1491   Example:
1492   
1493   (start code js)
1494   var viz = new $jit.Viz({
1495     duration: 1000,
1496     fps: 35,
1497     transition: $jit.Trans.linear
1498   });
1499   (end code)
1500   
1501   Parameters:
1502   
1503   clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1504   duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1505   fps - (number) Default's *40*. Frames per second.
1506   transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1507   
1508   Object: $jit.Trans
1509   
1510   This object is used for specifying different animation transitions in all visualizations.
1511
1512   There are many different type of animation transitions.
1513
1514   linear:
1515
1516   Displays a linear transition
1517
1518   >Trans.linear
1519   
1520   (see Linear.png)
1521
1522   Quad:
1523
1524   Displays a Quadratic transition.
1525
1526   >Trans.Quad.easeIn
1527   >Trans.Quad.easeOut
1528   >Trans.Quad.easeInOut
1529   
1530  (see Quad.png)
1531
1532  Cubic:
1533
1534  Displays a Cubic transition.
1535
1536  >Trans.Cubic.easeIn
1537  >Trans.Cubic.easeOut
1538  >Trans.Cubic.easeInOut
1539
1540  (see Cubic.png)
1541
1542  Quart:
1543
1544  Displays a Quartetic transition.
1545
1546  >Trans.Quart.easeIn
1547  >Trans.Quart.easeOut
1548  >Trans.Quart.easeInOut
1549
1550  (see Quart.png)
1551
1552  Quint:
1553
1554  Displays a Quintic transition.
1555
1556  >Trans.Quint.easeIn
1557  >Trans.Quint.easeOut
1558  >Trans.Quint.easeInOut
1559
1560  (see Quint.png)
1561
1562  Expo:
1563
1564  Displays an Exponential transition.
1565
1566  >Trans.Expo.easeIn
1567  >Trans.Expo.easeOut
1568  >Trans.Expo.easeInOut
1569
1570  (see Expo.png)
1571
1572  Circ:
1573
1574  Displays a Circular transition.
1575
1576  >Trans.Circ.easeIn
1577  >Trans.Circ.easeOut
1578  >Trans.Circ.easeInOut
1579
1580  (see Circ.png)
1581
1582  Sine:
1583
1584  Displays a Sineousidal transition.
1585
1586  >Trans.Sine.easeIn
1587  >Trans.Sine.easeOut
1588  >Trans.Sine.easeInOut
1589
1590  (see Sine.png)
1591
1592  Back:
1593
1594  >Trans.Back.easeIn
1595  >Trans.Back.easeOut
1596  >Trans.Back.easeInOut
1597
1598  (see Back.png)
1599
1600  Bounce:
1601
1602  Bouncy transition.
1603
1604  >Trans.Bounce.easeIn
1605  >Trans.Bounce.easeOut
1606  >Trans.Bounce.easeInOut
1607
1608  (see Bounce.png)
1609
1610  Elastic:
1611
1612  Elastic curve.
1613
1614  >Trans.Elastic.easeIn
1615  >Trans.Elastic.easeOut
1616  >Trans.Elastic.easeInOut
1617
1618  (see Elastic.png)
1619  
1620  Based on:
1621      
1622  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>.
1623
1624
1625 */
1626 Options.Fx = {
1627   $extend: true,
1628   
1629   fps:40,
1630   duration: 2500,
1631   transition: $jit.Trans.Quart.easeInOut,
1632   clearCanvas: true
1633 };
1634
1635 /*
1636  * File: Options.Label.js
1637  *
1638 */
1639 /*
1640   Object: Options.Label
1641
1642   Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.  
1643
1644   Syntax:
1645   
1646   (start code js)
1647     Options.Label = {
1648       overridable: false,
1649       type: 'HTML', //'SVG', 'Native'
1650       style: ' ',
1651       size: 10,
1652       family: 'sans-serif',
1653       textAlign: 'center',
1654       textBaseline: 'alphabetic',
1655       color: '#fff'
1656     };
1657   (end code)
1658   
1659   Example:
1660   
1661   (start code js)
1662   var viz = new $jit.Viz({
1663     Label: {
1664       type: 'Native',
1665       size: 11,
1666       color: '#ccc'
1667     }
1668   });
1669   (end code)
1670   
1671   Parameters:
1672     
1673   overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1674   type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1675   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.
1676   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.
1677   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.
1678   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.
1679 */
1680 Options.Label = {
1681   $extend: false,
1682   
1683   overridable: false,
1684   type: 'HTML', //'SVG', 'Native'
1685   style: ' ',
1686   size: 10,
1687   family: 'sans-serif',
1688   textAlign: 'center',
1689   textBaseline: 'alphabetic',
1690   color: '#fff'
1691 };
1692
1693
1694 /*
1695  * File: Options.Tips.js
1696  *
1697  */
1698
1699 /*
1700   Object: Options.Tips
1701   
1702   Tips options
1703   
1704   Syntax:
1705     
1706   (start code js)
1707   Options.Tips = {
1708     enable: false,
1709     type: 'auto',
1710     offsetX: 20,
1711     offsetY: 20,
1712     onShow: $.empty,
1713     onHide: $.empty
1714   };
1715   (end code)
1716   
1717   Example:
1718   
1719   (start code js)
1720   var viz = new $jit.Viz({
1721     Tips: {
1722       enable: true,
1723       type: 'Native',
1724       offsetX: 10,
1725       offsetY: 10,
1726       onShow: function(tip, node) {
1727         tip.innerHTML = node.name;
1728       }
1729     }
1730   });
1731   (end code)
1732
1733   Parameters:
1734
1735   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. 
1736   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.
1737   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.
1738   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.
1739   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.
1740   onHide() - This callack is used when hiding a tooltip.
1741
1742 */
1743 Options.Tips = {
1744   $extend: false,
1745   
1746   enable: false,
1747   type: 'auto',
1748   offsetX: 20,
1749   offsetY: 20,
1750   force: false,
1751   onShow: $.empty,
1752   onHide: $.empty
1753 };
1754
1755
1756 /*
1757  * File: Options.NodeStyles.js
1758  *
1759  */
1760
1761 /*
1762   Object: Options.NodeStyles
1763   
1764   Apply different styles when a node is hovered or selected.
1765   
1766   Syntax:
1767     
1768   (start code js)
1769   Options.NodeStyles = {
1770     enable: false,
1771     type: 'auto',
1772     stylesHover: false,
1773     stylesClick: false
1774   };
1775   (end code)
1776   
1777   Example:
1778   
1779   (start code js)
1780   var viz = new $jit.Viz({
1781     NodeStyles: {
1782       enable: true,
1783       type: 'Native',
1784       stylesHover: {
1785         dim: 30,
1786         color: '#fcc'
1787       },
1788       duration: 600
1789     }
1790   });
1791   (end code)
1792
1793   Parameters:
1794   
1795   enable - (boolean) Default's *false*. Whether to enable this option.
1796   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>.
1797   stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1798   stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799 */
1800
1801 Options.NodeStyles = {
1802   $extend: false,
1803   
1804   enable: false,
1805   type: 'auto',
1806   stylesHover: false,
1807   stylesClick: false
1808 };
1809
1810
1811 /*
1812  * File: Options.Events.js
1813  *
1814 */
1815
1816 /*
1817   Object: Options.Events
1818   
1819   Configuration for adding mouse/touch event handlers to Nodes.
1820   
1821   Syntax:
1822   
1823   (start code js)
1824   Options.Events = {
1825     enable: false,
1826     enableForEdges: false,
1827     type: 'auto',
1828     onClick: $.empty,
1829     onRightClick: $.empty,
1830     onMouseMove: $.empty,
1831     onMouseEnter: $.empty,
1832     onMouseLeave: $.empty,
1833     onDragStart: $.empty,
1834     onDragMove: $.empty,
1835     onDragCancel: $.empty,
1836     onDragEnd: $.empty,
1837     onTouchStart: $.empty,
1838     onTouchMove: $.empty,
1839     onTouchEnd: $.empty,
1840     onTouchCancel: $.empty,
1841     onMouseWheel: $.empty
1842   };
1843   (end code)
1844   
1845   Example:
1846   
1847   (start code js)
1848   var viz = new $jit.Viz({
1849     Events: {
1850       enable: true,
1851       onClick: function(node, eventInfo, e) {
1852         viz.doSomething();
1853       },
1854       onMouseEnter: function(node, eventInfo, e) {
1855         viz.canvas.getElement().style.cursor = 'pointer';
1856       },
1857       onMouseLeave: function(node, eventInfo, e) {
1858         viz.canvas.getElement().style.cursor = '';
1859       }
1860     }
1861   });
1862   (end code)
1863   
1864   Parameters:
1865   
1866   enable - (boolean) Default's *false*. Whether to enable the Event system.
1867   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*.
1868   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.
1869   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. 
1870   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. 
1871   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.
1872   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. 
1873   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. 
1874   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. 
1875   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. 
1876   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. 
1877   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. 
1878   onTouchStart(node, eventInfo, e) - Behaves just like onDragStart. 
1879   onTouchMove(node, eventInfo, e) - Behaves just like onDragMove. 
1880   onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd. 
1881   onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1882   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.
1883 */
1884
1885 Options.Events = {
1886   $extend: false,
1887   
1888   enable: false,
1889   enableForEdges: false,
1890   type: 'auto',
1891   onClick: $.empty,
1892   onRightClick: $.empty,
1893   onMouseMove: $.empty,
1894   onMouseEnter: $.empty,
1895   onMouseLeave: $.empty,
1896   onDragStart: $.empty,
1897   onDragMove: $.empty,
1898   onDragCancel: $.empty,
1899   onDragEnd: $.empty,
1900   onTouchStart: $.empty,
1901   onTouchMove: $.empty,
1902   onTouchEnd: $.empty,
1903   onMouseWheel: $.empty
1904 };
1905
1906 /*
1907  * File: Options.Navigation.js
1908  *
1909 */
1910
1911 /*
1912   Object: Options.Navigation
1913   
1914   Panning and zooming options for Graph/Tree based visualizations. These options are implemented 
1915   by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1916   
1917   Syntax:
1918   
1919   (start code js)
1920
1921   Options.Navigation = {
1922     enable: false,
1923     type: 'auto',
1924     panning: false, //true, 'avoid nodes'
1925     zooming: false
1926   };
1927   
1928   (end code)
1929   
1930   Example:
1931     
1932   (start code js)
1933   var viz = new $jit.Viz({
1934     Navigation: {
1935       enable: true,
1936       panning: 'avoid nodes',
1937       zooming: 20
1938     }
1939   });
1940   (end code)
1941   
1942   Parameters:
1943   
1944   enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1945   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>.
1946   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.
1947   
1948 */
1949
1950 Options.Navigation = {
1951   $extend: false,
1952   
1953   enable: false,
1954   type: 'auto',
1955   panning: false, //true | 'avoid nodes'
1956   zooming: false
1957 };
1958
1959 /*
1960  * File: Options.Controller.js
1961  *
1962 */
1963
1964 /*
1965   Object: Options.Controller
1966   
1967   Provides controller methods. Controller methods are callback functions that get called at different stages 
1968   of the animation, computing or plotting of the visualization.
1969   
1970   Implemented by:
1971     
1972   All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1973   
1974   Syntax:
1975   
1976   (start code js)
1977
1978   Options.Controller = {
1979     onBeforeCompute: $.empty,
1980     onAfterCompute:  $.empty,
1981     onCreateLabel:   $.empty,
1982     onPlaceLabel:    $.empty,
1983     onComplete:      $.empty,
1984     onBeforePlotLine:$.empty,
1985     onAfterPlotLine: $.empty,
1986     onBeforePlotNode:$.empty,
1987     onAfterPlotNode: $.empty,
1988     request:         false
1989   };
1990   
1991   (end code)
1992   
1993   Example:
1994     
1995   (start code js)
1996   var viz = new $jit.Viz({
1997     onBeforePlotNode: function(node) {
1998       if(node.selected) {
1999         node.setData('color', '#ffc');
2000       } else {
2001         node.removeData('color');
2002       }
2003     },
2004     onBeforePlotLine: function(adj) {
2005       if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2006         adj.setData('color', '#ffc');
2007       } else {
2008         adj.removeData('color');
2009       }
2010     },
2011     onAfterCompute: function() {
2012       alert("computed!");
2013     }
2014   });
2015   (end code)
2016   
2017   Parameters:
2018
2019    onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2020    onAfterCompute() - This method is triggered after all animations or computations ended.
2021    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.
2022    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.
2023    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.
2024    onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2025    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.
2026    onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2027
2028     *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2029     
2030     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.  
2031  
2032  */
2033 Options.Controller = {
2034   $extend: true,
2035   
2036   onBeforeCompute: $.empty,
2037   onAfterCompute:  $.empty,
2038   onCreateLabel:   $.empty,
2039   onPlaceLabel:    $.empty,
2040   onComplete:      $.empty,
2041   onBeforePlotLine:$.empty,
2042   onAfterPlotLine: $.empty,
2043   onBeforePlotNode:$.empty,
2044   onAfterPlotNode: $.empty,
2045   request:         false
2046 };
2047
2048
2049 /*
2050  * File: Extras.js
2051  * 
2052  * Provides Extras such as Tips and Style Effects.
2053  * 
2054  * Description:
2055  * 
2056  * Provides the <Tips> and <NodeStyles> classes and functions.
2057  *
2058  */
2059
2060 /*
2061  * Manager for mouse events (clicking and mouse moving).
2062  * 
2063  * This class is used for registering objects implementing onClick
2064  * and onMousemove methods. These methods are called when clicking or
2065  * moving the mouse around  the Canvas.
2066  * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2067  * 
2068  */
2069 var ExtrasInitializer = {
2070   initialize: function(className, viz) {
2071     this.viz = viz;
2072     this.canvas = viz.canvas;
2073     this.config = viz.config[className];
2074     this.nodeTypes = viz.fx.nodeTypes;
2075     var type = this.config.type;
2076     this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2077     this.labelContainer = this.dom && viz.labels.getLabelContainer();
2078     this.isEnabled() && this.initializePost();
2079   },
2080   initializePost: $.empty,
2081   setAsProperty: $.lambda(false),
2082   isEnabled: function() {
2083     return this.config.enable;
2084   },
2085   isLabel: function(e, win) {
2086     e = $.event.get(e, win);
2087     var labelContainer = this.labelContainer,
2088         target = e.target || e.srcElement;
2089     if(target && target.parentNode == labelContainer)
2090       return target;
2091     return false;
2092   }
2093 };
2094
2095 var EventsInterface = {
2096   onMouseUp: $.empty,
2097   onMouseDown: $.empty,
2098   onMouseMove: $.empty,
2099   onMouseOver: $.empty,
2100   onMouseOut: $.empty,
2101   onMouseWheel: $.empty,
2102   onTouchStart: $.empty,
2103   onTouchMove: $.empty,
2104   onTouchEnd: $.empty,
2105   onTouchCancel: $.empty
2106 };
2107
2108 var MouseEventsManager = new Class({
2109   initialize: function(viz) {
2110     this.viz = viz;
2111     this.canvas = viz.canvas;
2112     this.node = false;
2113     this.edge = false;
2114     this.registeredObjects = [];
2115     this.attachEvents();
2116   },
2117   
2118   attachEvents: function() {
2119     var htmlCanvas = this.canvas.getElement(), 
2120         that = this;
2121     htmlCanvas.oncontextmenu = $.lambda(false);
2122     $.addEvents(htmlCanvas, {
2123       'mouseup': function(e, win) {
2124         var event = $.event.get(e, win);
2125         that.handleEvent('MouseUp', e, win, 
2126             that.makeEventObject(e, win), 
2127             $.event.isRightClick(event));
2128       },
2129       'mousedown': function(e, win) {
2130         var event = $.event.get(e, win);
2131         that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win), 
2132             $.event.isRightClick(event));
2133       },
2134       'mousemove': function(e, win) {
2135         that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2136       },
2137       'mouseover': function(e, win) {
2138         that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2139       },
2140       'mouseout': function(e, win) {
2141         that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2142       },
2143       'touchstart': function(e, win) {
2144         that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2145       },
2146       'touchmove': function(e, win) {
2147         that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2148       },
2149       'touchend': function(e, win) {
2150         that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2151       }
2152     });
2153     //attach mousewheel event
2154     var handleMouseWheel = function(e, win) {
2155       var event = $.event.get(e, win);
2156       var wheel = $.event.getWheel(event);
2157       that.handleEvent('MouseWheel', e, win, wheel);
2158     };
2159     //TODO(nico): this is a horrible check for non-gecko browsers!
2160     if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2161       $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2162     } else {
2163       htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2164     }
2165   },
2166   
2167   register: function(obj) {
2168     this.registeredObjects.push(obj);
2169   },
2170   
2171   handleEvent: function() {
2172     var args = Array.prototype.slice.call(arguments),
2173         type = args.shift();
2174     for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2175       regs[i]['on' + type].apply(regs[i], args);
2176     }
2177   },
2178   
2179   makeEventObject: function(e, win) {
2180     var that = this,
2181         graph = this.viz.graph,
2182         fx = this.viz.fx,
2183         ntypes = fx.nodeTypes,
2184         etypes = fx.edgeTypes;
2185     return {
2186       pos: false,
2187       node: false,
2188       edge: false,
2189       contains: false,
2190       getNodeCalled: false,
2191       getEdgeCalled: false,
2192       getPos: function() {
2193         //TODO(nico): check why this can't be cache anymore when using edge detection
2194         //if(this.pos) return this.pos;
2195         var canvas = that.viz.canvas,
2196             s = canvas.getSize(),
2197             p = canvas.getPos(),
2198             ox = canvas.translateOffsetX,
2199             oy = canvas.translateOffsetY,
2200             sx = canvas.scaleOffsetX,
2201             sy = canvas.scaleOffsetY,
2202             pos = $.event.getPos(e, win);
2203         this.pos = {
2204           x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2205           y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2206         };
2207         return this.pos;
2208       },
2209       getNode: function() {
2210         if(this.getNodeCalled) return this.node;
2211         this.getNodeCalled = true;
2212         for(var id in graph.nodes) {
2213           var n = graph.nodes[id],
2214               geom = n && ntypes[n.getData('type')],
2215               contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2216           if(contains) {
2217             this.contains = contains;
2218             return that.node = this.node = n;
2219           }
2220         }
2221         return that.node = this.node = false;
2222       },
2223       getEdge: function() {
2224         if(this.getEdgeCalled) return this.edge;
2225         this.getEdgeCalled = true;
2226         var hashset = {};
2227         for(var id in graph.edges) {
2228           var edgeFrom = graph.edges[id];
2229           hashset[id] = true;
2230           for(var edgeId in edgeFrom) {
2231             if(edgeId in hashset) continue;
2232             var e = edgeFrom[edgeId],
2233                 geom = e && etypes[e.getData('type')],
2234                 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2235             if(contains) {
2236               this.contains = contains;
2237               return that.edge = this.edge = e;
2238             }
2239           }
2240         }
2241         return that.edge = this.edge = false;
2242       },
2243       getContains: function() {
2244         if(this.getNodeCalled) return this.contains;
2245         this.getNode();
2246         return this.contains;
2247       }
2248     };
2249   }
2250 });
2251
2252 /* 
2253  * Provides the initialization function for <NodeStyles> and <Tips> implemented 
2254  * by all main visualizations.
2255  *
2256  */
2257 var Extras = {
2258   initializeExtras: function() {
2259     var mem = new MouseEventsManager(this), that = this;
2260     $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2261       var obj = new Extras.Classes[k](k, that);
2262       if(obj.isEnabled()) {
2263         mem.register(obj);
2264       }
2265       if(obj.setAsProperty()) {
2266         that[k.toLowerCase()] = obj;
2267       }
2268     });
2269   }   
2270 };
2271
2272 Extras.Classes = {};
2273 /*
2274   Class: Events
2275    
2276   This class defines an Event API to be accessed by the user.
2277   The methods implemented are the ones defined in the <Options.Events> object.
2278 */
2279
2280 Extras.Classes.Events = new Class({
2281   Implements: [ExtrasInitializer, EventsInterface],
2282   
2283   initializePost: function() {
2284     this.fx = this.viz.fx;
2285     this.ntypes = this.viz.fx.nodeTypes;
2286     this.etypes = this.viz.fx.edgeTypes;
2287     
2288     this.hovered = false;
2289     this.pressed = false;
2290     this.touched = false;
2291
2292     this.touchMoved = false;
2293     this.moved = false;
2294     
2295   },
2296   
2297   setAsProperty: $.lambda(true),
2298   
2299   onMouseUp: function(e, win, event, isRightClick) {
2300     var evt = $.event.get(e, win);
2301     if(!this.moved) {
2302       if(isRightClick) {
2303         this.config.onRightClick(this.hovered, event, evt);
2304       } else {
2305         this.config.onClick(this.pressed, event, evt);
2306       }
2307     }
2308     if(this.pressed) {
2309       if(this.moved) {
2310         this.config.onDragEnd(this.pressed, event, evt);
2311       } else {
2312         this.config.onDragCancel(this.pressed, event, evt);
2313       }
2314       this.pressed = this.moved = false;
2315     }
2316   },
2317
2318   onMouseOut: function(e, win, event) {
2319    //mouseout a label
2320    var evt = $.event.get(e, win), label;
2321    if(this.dom && (label = this.isLabel(e, win))) {
2322      this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2323                               event, evt);
2324      this.hovered = false;
2325      return;
2326    }
2327    //mouseout canvas
2328    var rt = evt.relatedTarget,
2329        canvasWidget = this.canvas.getElement();
2330    while(rt && rt.parentNode) {
2331      if(canvasWidget == rt.parentNode) return;
2332      rt = rt.parentNode;
2333    }
2334    if(this.hovered) {
2335      this.config.onMouseLeave(this.hovered,
2336          event, evt);
2337      this.hovered = false;
2338    }
2339   },
2340   
2341   onMouseOver: function(e, win, event) {
2342     //mouseover a label
2343     var evt = $.event.get(e, win), label;
2344     if(this.dom && (label = this.isLabel(e, win))) {
2345       this.hovered = this.viz.graph.getNode(label.id);
2346       this.config.onMouseEnter(this.hovered,
2347                                event, evt);
2348     }
2349   },
2350   
2351   onMouseMove: function(e, win, event) {
2352    var label, evt = $.event.get(e, win);
2353    if(this.pressed) {
2354      this.moved = true;
2355      this.config.onDragMove(this.pressed, event, evt);
2356      return;
2357    }
2358    if(this.dom) {
2359      this.config.onMouseMove(this.hovered,
2360          event, evt);
2361    } else {
2362      if(this.hovered) {
2363        var hn = this.hovered;
2364        var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2365        var contains = geom && geom.contains 
2366          && geom.contains.call(this.fx, hn, event.getPos());
2367        if(contains) {
2368          this.config.onMouseMove(hn, event, evt);
2369          return;
2370        } else {
2371          this.config.onMouseLeave(hn, event, evt);
2372          this.hovered = false;
2373        }
2374      }
2375      if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2376        this.config.onMouseEnter(this.hovered, event, evt);
2377      } else {
2378        this.config.onMouseMove(false, event, evt);
2379      }
2380    }
2381   },
2382   
2383   onMouseWheel: function(e, win, delta) {
2384     this.config.onMouseWheel(delta, $.event.get(e, win));
2385   },
2386   
2387   onMouseDown: function(e, win, event) {
2388     var evt = $.event.get(e, win);
2389     this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2390     this.config.onDragStart(this.pressed, event, evt);
2391   },
2392   
2393   onTouchStart: function(e, win, event) {
2394     var evt = $.event.get(e, win);
2395     this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2396     this.config.onTouchStart(this.touched, event, evt);
2397   },
2398   
2399   onTouchMove: function(e, win, event) {
2400     var evt = $.event.get(e, win);
2401     if(this.touched) {
2402       this.touchMoved = true;
2403       this.config.onTouchMove(this.touched, event, evt);
2404     }
2405   },
2406   
2407   onTouchEnd: function(e, win, event) {
2408     var evt = $.event.get(e, win);
2409     if(this.touched) {
2410       if(this.touchMoved) {
2411         this.config.onTouchEnd(this.touched, event, evt);
2412       } else {
2413         this.config.onTouchCancel(this.touched, event, evt);
2414       }
2415       this.touched = this.touchMoved = false;
2416     }
2417   }
2418 });
2419
2420 /*
2421    Class: Tips
2422     
2423    A class containing tip related functions. This class is used internally.
2424    
2425    Used by:
2426    
2427    <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2428    
2429    See also:
2430    
2431    <Options.Tips>
2432 */
2433
2434 Extras.Classes.Tips = new Class({
2435   Implements: [ExtrasInitializer, EventsInterface],
2436   
2437   initializePost: function() {
2438     //add DOM tooltip
2439     if(document.body) {
2440       var tip = $('_tooltip') || document.createElement('div');
2441       tip.id = '_tooltip';
2442       tip.className = 'tip';
2443       $.extend(tip.style, {
2444         position: 'absolute',
2445         display: 'none',
2446         zIndex: 13000
2447       });
2448       document.body.appendChild(tip);
2449       this.tip = tip;
2450       this.node = false;
2451     }
2452   },
2453   
2454   setAsProperty: $.lambda(true),
2455   
2456   onMouseOut: function(e, win) {
2457     //mouseout a label
2458     if(this.dom && this.isLabel(e, win)) {
2459       this.hide(true);
2460       return;
2461     }
2462     //mouseout canvas
2463     var rt = e.relatedTarget,
2464         canvasWidget = this.canvas.getElement();
2465     while(rt && rt.parentNode) {
2466       if(canvasWidget == rt.parentNode) return;
2467       rt = rt.parentNode;
2468     }
2469     this.hide(false);
2470   },
2471   
2472   onMouseOver: function(e, win) {
2473     //mouseover a label
2474     var label;
2475     if(this.dom && (label = this.isLabel(e, win))) {
2476       this.node = this.viz.graph.getNode(label.id);
2477       this.config.onShow(this.tip, this.node, label);
2478     }
2479   },
2480   
2481   onMouseMove: function(e, win, opt) {
2482     if(this.dom && this.isLabel(e, win)) {
2483       this.setTooltipPosition($.event.getPos(e, win));
2484     }
2485     if(!this.dom) {
2486       var node = opt.getNode();
2487       if(!node) {
2488         this.hide(true);
2489         return;
2490       }
2491       if(this.config.force || !this.node || this.node.id != node.id) {
2492         this.node = node;
2493         this.config.onShow(this.tip, node, opt.getContains());
2494       }
2495       this.setTooltipPosition($.event.getPos(e, win));
2496     }
2497   },
2498   
2499   setTooltipPosition: function(pos) {
2500     var tip = this.tip, 
2501         style = tip.style, 
2502         cont = this.config;
2503     style.display = '';
2504     //get window dimensions
2505     var win = {
2506       'height': document.body.clientHeight,
2507       'width': document.body.clientWidth
2508     };
2509     //get tooltip dimensions
2510     var obj = {
2511       'width': tip.offsetWidth,
2512       'height': tip.offsetHeight  
2513     };
2514     //set tooltip position
2515     var x = cont.offsetX, y = cont.offsetY;
2516     style.top = ((pos.y + y + obj.height > win.height)?  
2517         (pos.y - obj.height - y) : pos.y + y) + 'px';
2518     style.left = ((pos.x + obj.width + x > win.width)? 
2519         (pos.x - obj.width - x) : pos.x + x) + 'px';
2520   },
2521   
2522   hide: function(triggerCallback) {
2523     if(!SUGAR.util.isTouchScreen()) {
2524         this.tip.style.display = 'none';
2525     }
2526     triggerCallback && this.config.onHide();
2527   }
2528 });
2529
2530 /*
2531   Class: NodeStyles
2532    
2533   Change node styles when clicking or hovering a node. This class is used internally.
2534   
2535   Used by:
2536   
2537   <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2538   
2539   See also:
2540   
2541   <Options.NodeStyles>
2542 */
2543 Extras.Classes.NodeStyles = new Class({
2544   Implements: [ExtrasInitializer, EventsInterface],
2545   
2546   initializePost: function() {
2547     this.fx = this.viz.fx;
2548     this.types = this.viz.fx.nodeTypes;
2549     this.nStyles = this.config;
2550     this.nodeStylesOnHover = this.nStyles.stylesHover;
2551     this.nodeStylesOnClick = this.nStyles.stylesClick;
2552     this.hoveredNode = false;
2553     this.fx.nodeFxAnimation = new Animation();
2554     
2555     this.down = false;
2556     this.move = false;
2557   },
2558   
2559   onMouseOut: function(e, win) {
2560     this.down = this.move = false;
2561     if(!this.hoveredNode) return;
2562     //mouseout a label
2563     if(this.dom && this.isLabel(e, win)) {
2564       this.toggleStylesOnHover(this.hoveredNode, false);
2565     }
2566     //mouseout canvas
2567     var rt = e.relatedTarget,
2568         canvasWidget = this.canvas.getElement();
2569     while(rt && rt.parentNode) {
2570       if(canvasWidget == rt.parentNode) return;
2571       rt = rt.parentNode;
2572     }
2573     this.toggleStylesOnHover(this.hoveredNode, false);
2574     this.hoveredNode = false;
2575   },
2576   
2577   onMouseOver: function(e, win) {
2578     //mouseover a label
2579     var label;
2580     if(this.dom && (label = this.isLabel(e, win))) {
2581       var node = this.viz.graph.getNode(label.id);
2582       if(node.selected) return;
2583       this.hoveredNode = node;
2584       this.toggleStylesOnHover(this.hoveredNode, true);
2585     }
2586   },
2587   
2588   onMouseDown: function(e, win, event, isRightClick) {
2589     if(isRightClick) return;
2590     var label;
2591     if(this.dom && (label = this.isLabel(e, win))) {
2592       this.down = this.viz.graph.getNode(label.id);
2593     } else if(!this.dom) {
2594       this.down = event.getNode();
2595     }
2596     this.move = false;
2597   },
2598   
2599   onMouseUp: function(e, win, event, isRightClick) {
2600     if(isRightClick) return;
2601     if(!this.move) {
2602       this.onClick(event.getNode());
2603     }
2604     this.down = this.move = false;
2605   },
2606   
2607   getRestoredStyles: function(node, type) {
2608     var restoredStyles = {}, 
2609         nStyles = this['nodeStylesOn' + type];
2610     for(var prop in nStyles) {
2611       restoredStyles[prop] = node.styles['$' + prop];
2612     }
2613     return restoredStyles;
2614   },
2615   
2616   toggleStylesOnHover: function(node, set) {
2617     if(this.nodeStylesOnHover) {
2618       this.toggleStylesOn('Hover', node, set);
2619     }
2620   },
2621
2622   toggleStylesOnClick: function(node, set) {
2623     if(this.nodeStylesOnClick) {
2624       this.toggleStylesOn('Click', node, set);
2625     }
2626   },
2627   
2628   toggleStylesOn: function(type, node, set) {
2629     var viz = this.viz;
2630     var nStyles = this.nStyles;
2631     if(set) {
2632       var that = this;
2633       if(!node.styles) {
2634         node.styles = $.merge(node.data, {});
2635       }
2636       for(var s in this['nodeStylesOn' + type]) {
2637         var $s = '$' + s;
2638         if(!($s in node.styles)) {
2639             node.styles[$s] = node.getData(s); 
2640         }
2641       }
2642       viz.fx.nodeFx($.extend({
2643         'elements': {
2644           'id': node.id,
2645           'properties': that['nodeStylesOn' + type]
2646          },
2647          transition: Trans.Quart.easeOut,
2648          duration:300,
2649          fps:40
2650       }, this.config));
2651     } else {
2652       var restoredStyles = this.getRestoredStyles(node, type);
2653       viz.fx.nodeFx($.extend({
2654         'elements': {
2655           'id': node.id,
2656           'properties': restoredStyles
2657          },
2658          transition: Trans.Quart.easeOut,
2659          duration:300,
2660          fps:40
2661       }, this.config));
2662     }
2663   },
2664
2665   onClick: function(node) {
2666     if(!node) return;
2667     var nStyles = this.nodeStylesOnClick;
2668     if(!nStyles) return;
2669     //if the node is selected then unselect it
2670     if(node.selected) {
2671       this.toggleStylesOnClick(node, false);
2672       delete node.selected;
2673     } else {
2674       //unselect all selected nodes...
2675       this.viz.graph.eachNode(function(n) {
2676         if(n.selected) {
2677           for(var s in nStyles) {
2678             n.setData(s, n.styles['$' + s], 'end');
2679           }
2680           delete n.selected;
2681         }
2682       });
2683       //select clicked node
2684       this.toggleStylesOnClick(node, true);
2685       node.selected = true;
2686       delete node.hovered;
2687       this.hoveredNode = false;
2688     }
2689   },
2690   
2691   onMouseMove: function(e, win, event) {
2692     //if mouse button is down and moving set move=true
2693     if(this.down) this.move = true;
2694     //already handled by mouseover/out
2695     if(this.dom && this.isLabel(e, win)) return;
2696     var nStyles = this.nodeStylesOnHover;
2697     if(!nStyles) return;
2698     
2699     if(!this.dom) {
2700       if(this.hoveredNode) {
2701         var geom = this.types[this.hoveredNode.getData('type')];
2702         var contains = geom && geom.contains && geom.contains.call(this.fx, 
2703             this.hoveredNode, event.getPos());
2704         if(contains) return;
2705       }
2706       var node = event.getNode();
2707       //if no node is being hovered then just exit
2708       if(!this.hoveredNode && !node) return;
2709       //if the node is hovered then exit
2710       if(node.hovered) return;
2711       //select hovered node
2712       if(node && !node.selected) {
2713         //check if an animation is running and exit it
2714         this.fx.nodeFxAnimation.stopTimer();
2715         //unselect all hovered nodes...
2716         this.viz.graph.eachNode(function(n) {
2717           if(n.hovered && !n.selected) {
2718             for(var s in nStyles) {
2719               n.setData(s, n.styles['$' + s], 'end');
2720             }
2721             delete n.hovered;
2722           }
2723         });
2724         //select hovered node
2725         node.hovered = true;
2726         this.hoveredNode = node;
2727         this.toggleStylesOnHover(node, true);
2728       } else if(this.hoveredNode && !this.hoveredNode.selected) {
2729         //check if an animation is running and exit it
2730         this.fx.nodeFxAnimation.stopTimer();
2731         //unselect hovered node
2732         this.toggleStylesOnHover(this.hoveredNode, false);
2733         delete this.hoveredNode.hovered;
2734         this.hoveredNode = false;
2735       }
2736     }
2737   }
2738 });
2739
2740 Extras.Classes.Navigation = new Class({
2741   Implements: [ExtrasInitializer, EventsInterface],
2742   
2743   initializePost: function() {
2744     this.pos = false;
2745     this.pressed = false;
2746   },
2747   
2748   onMouseWheel: function(e, win, scroll) {
2749     if(!this.config.zooming) return;
2750     $.event.stop($.event.get(e, win));
2751     var val = this.config.zooming / 1000,
2752         ans = 1 + scroll * val;
2753     this.canvas.scale(ans, ans);
2754   },
2755   
2756   onMouseDown: function(e, win, eventInfo) {
2757     if(!this.config.panning) return;
2758     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2759     this.pressed = true;
2760     this.pos = eventInfo.getPos();
2761     var canvas = this.canvas,
2762         ox = canvas.translateOffsetX,
2763         oy = canvas.translateOffsetY,
2764         sx = canvas.scaleOffsetX,
2765         sy = canvas.scaleOffsetY;
2766     this.pos.x *= sx;
2767     this.pos.x += ox;
2768     this.pos.y *= sy;
2769     this.pos.y += oy;
2770   },
2771   
2772   onMouseMove: function(e, win, eventInfo) {
2773     if(!this.config.panning) return;
2774     if(!this.pressed) return;
2775     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2776     var thispos = this.pos, 
2777         currentPos = eventInfo.getPos(),
2778         canvas = this.canvas,
2779         ox = canvas.translateOffsetX,
2780         oy = canvas.translateOffsetY,
2781         sx = canvas.scaleOffsetX,
2782         sy = canvas.scaleOffsetY;
2783     currentPos.x *= sx;
2784     currentPos.y *= sy;
2785     currentPos.x += ox;
2786     currentPos.y += oy;
2787     var x = currentPos.x - thispos.x,
2788         y = currentPos.y - thispos.y;
2789     this.pos = currentPos;
2790     this.canvas.translate(x * 1/sx, y * 1/sy);
2791   },
2792   
2793   onMouseUp: function(e, win, eventInfo, isRightClick) {
2794     if(!this.config.panning) return;
2795     this.pressed = false;
2796   }
2797 });
2798
2799
2800 /*
2801  * File: Canvas.js
2802  *
2803  */
2804
2805 /*
2806  Class: Canvas
2807  
2808         A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to 
2809         know more about <Canvas> options take a look at <Options.Canvas>.
2810  
2811  A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior 
2812  across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2813  
2814  Example:
2815  
2816  Suppose we have this HTML
2817  
2818  (start code xml)
2819         <div id="infovis"></div>
2820  (end code)
2821  
2822  Now we create a new Visualization
2823  
2824  (start code js)
2825         var viz = new $jit.Viz({
2826                 //Where to inject the canvas. Any div container will do.
2827                 'injectInto':'infovis',
2828                  //width and height for canvas. 
2829                  //Default's to the container offsetWidth and Height.
2830                  'width': 900,
2831                  'height':500
2832          });
2833  (end code)
2834
2835  The generated HTML will look like this
2836  
2837  (start code xml)
2838  <div id="infovis">
2839         <div id="infovis-canvaswidget" style="position:relative;">
2840         <canvas id="infovis-canvas" width=900 height=500
2841         style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2842         <div id="infovis-label"
2843         style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2844         </div>
2845         </div>
2846  </div>
2847  (end code)
2848  
2849  As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2850  of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2851  */
2852
2853 var Canvas;
2854 (function() {
2855   //check for native canvas support
2856   var canvasType = typeof HTMLCanvasElement,
2857       supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2858   //create element function
2859   function $E(tag, props) {
2860     var elem = document.createElement(tag);
2861     for(var p in props) {
2862       if(typeof props[p] == "object") {
2863         $.extend(elem[p], props[p]);
2864       } else {
2865         elem[p] = props[p];
2866       }
2867     }
2868     if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2869       elem = G_vmlCanvasManager.initElement(elem);
2870     }
2871     return elem;
2872   }
2873   //canvas widget which we will call just Canvas
2874   $jit.Canvas = Canvas = new Class({
2875     canvases: [],
2876     pos: false,
2877     element: false,
2878     labelContainer: false,
2879     translateOffsetX: 0,
2880     translateOffsetY: 0,
2881     scaleOffsetX: 1,
2882     scaleOffsetY: 1,
2883     
2884     initialize: function(viz, opt) {
2885       this.viz = viz;
2886       this.opt = opt;
2887       var id = $.type(opt.injectInto) == 'string'? 
2888           opt.injectInto:opt.injectInto.id,
2889           idLabel = id + "-label", 
2890           wrapper = $(id),
2891           width = opt.width || wrapper.offsetWidth,
2892           height = opt.height || wrapper.offsetHeight;
2893       this.id = id;
2894       //canvas options
2895       var canvasOptions = {
2896         injectInto: id,
2897         width: width,
2898         height: height
2899       };
2900       //create main wrapper
2901       this.element = $E('div', {
2902         'id': id + '-canvaswidget',
2903         'style': {
2904           'position': 'relative',
2905           'width': width + 'px',
2906           'height': height + 'px'
2907         }
2908       });
2909       //create label container
2910       this.labelContainer = this.createLabelContainer(opt.Label.type, 
2911           idLabel, canvasOptions);
2912       //create primary canvas
2913       this.canvases.push(new Canvas.Base({
2914         config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2915         plot: function(base) {
2916           viz.fx.plot();
2917         },
2918         resize: function() {
2919           viz.refresh();
2920         }
2921       }));
2922       //create secondary canvas
2923       var back = opt.background;
2924       if(back) {
2925         var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2926         this.canvases.push(new Canvas.Base(backCanvas));
2927       }
2928       //insert canvases
2929       var len = this.canvases.length;
2930       while(len--) {
2931         this.element.appendChild(this.canvases[len].canvas);
2932         if(len > 0) {
2933           this.canvases[len].plot();
2934         }
2935       }
2936       this.element.appendChild(this.labelContainer);
2937       wrapper.appendChild(this.element);
2938       //Update canvas position when the page is scrolled.
2939       var timer = null, that = this;
2940       $.addEvent(window, 'scroll', function() {
2941         clearTimeout(timer);
2942         timer = setTimeout(function() {
2943           that.getPos(true); //update canvas position
2944         }, 500);
2945       });
2946       $.addEvent(window, 'click', function() {
2947         clearTimeout(timer);
2948         timer = setTimeout(function() {
2949           that.getPos(true); //update canvas position
2950         }, 500);
2951       });
2952       sb = document.getElementById('sb'+id);
2953       $.addEvent(sb, 'scroll', function() {
2954         clearTimeout(timer);
2955         timer = setTimeout(function() {
2956           that.getPos(true); //update canvas position
2957         }, 500);
2958       });
2959     },
2960     /*
2961       Method: getCtx
2962       
2963       Returns the main canvas context object
2964       
2965       Example:
2966       
2967       (start code js)
2968        var ctx = canvas.getCtx();
2969        //Now I can use the native canvas context
2970        //and for example change some canvas styles
2971        ctx.globalAlpha = 1;
2972       (end code)
2973     */
2974     getCtx: function(i) {
2975       return this.canvases[i || 0].getCtx();
2976     },
2977     /*
2978       Method: getConfig
2979       
2980       Returns the current Configuration for this Canvas Widget.
2981       
2982       Example:
2983       
2984       (start code js)
2985        var config = canvas.getConfig();
2986       (end code)
2987     */
2988     getConfig: function() {
2989       return this.opt;
2990     },
2991     /*
2992       Method: getElement
2993
2994       Returns the main Canvas DOM wrapper
2995       
2996       Example:
2997       
2998       (start code js)
2999        var wrapper = canvas.getElement();
3000        //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3001       (end code)
3002     */
3003     getElement: function() {
3004       return this.element;
3005     },
3006     /*
3007       Method: getSize
3008       
3009       Returns canvas dimensions.
3010       
3011       Returns:
3012       
3013       An object with *width* and *height* properties.
3014       
3015       Example:
3016       (start code js)
3017       canvas.getSize(); //returns { width: 900, height: 500 }
3018       (end code)
3019     */
3020     getSize: function(i) {
3021       return this.canvases[i || 0].getSize();
3022     },
3023     /*
3024       Method: resize
3025       
3026       Resizes the canvas.
3027       
3028       Parameters:
3029       
3030       width - New canvas width.
3031       height - New canvas height.
3032       
3033       Example:
3034       
3035       (start code js)
3036        canvas.resize(width, height);
3037       (end code)
3038     
3039     */
3040     resize: function(width, height) {
3041       this.getPos(true);
3042       this.translateOffsetX = this.translateOffsetY = 0;
3043       this.scaleOffsetX = this.scaleOffsetY = 1;
3044       for(var i=0, l=this.canvases.length; i<l; i++) {
3045         this.canvases[i].resize(width, height);
3046       }
3047       var style = this.element.style;
3048       style.width = width + 'px';
3049       style.height = height + 'px';
3050       if(this.labelContainer)
3051         this.labelContainer.style.width = width + 'px';
3052     },
3053     /*
3054       Method: translate
3055       
3056       Applies a translation to the canvas.
3057       
3058       Parameters:
3059       
3060       x - (number) x offset.
3061       y - (number) y offset.
3062       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3063       
3064       Example:
3065       
3066       (start code js)
3067        canvas.translate(30, 30);
3068       (end code)
3069     
3070     */
3071     translate: function(x, y, disablePlot) {
3072       this.translateOffsetX += x*this.scaleOffsetX;
3073       this.translateOffsetY += y*this.scaleOffsetY;
3074       for(var i=0, l=this.canvases.length; i<l; i++) {
3075         this.canvases[i].translate(x, y, disablePlot);
3076       }
3077     },
3078     /*
3079       Method: scale
3080       
3081       Scales the canvas.
3082       
3083       Parameters:
3084       
3085       x - (number) scale value.
3086       y - (number) scale value.
3087       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3088       
3089       Example:
3090       
3091       (start code js)
3092        canvas.scale(0.5, 0.5);
3093       (end code)
3094     
3095     */
3096     scale: function(x, y, disablePlot) {
3097       var px = this.scaleOffsetX * x,
3098           py = this.scaleOffsetY * y;
3099       var dx = this.translateOffsetX * (x -1) / px,
3100           dy = this.translateOffsetY * (y -1) / py;
3101       this.scaleOffsetX = px;
3102       this.scaleOffsetY = py;
3103       for(var i=0, l=this.canvases.length; i<l; i++) {
3104         this.canvases[i].scale(x, y, true);
3105       }
3106       this.translate(dx, dy, false);
3107     },
3108     /*
3109       Method: getPos
3110       
3111       Returns the canvas position as an *x, y* object.
3112       
3113       Parameters:
3114       
3115       force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3116       
3117       Returns:
3118       
3119       An object with *x* and *y* properties.
3120       
3121       Example:
3122       (start code js)
3123       canvas.getPos(true); //returns { x: 900, y: 500 }
3124       (end code)
3125     */
3126     getPos: function(force){
3127       if(force || !this.pos) {
3128         return this.pos = $.getPos(this.getElement());
3129       }
3130       return this.pos;
3131     },
3132     /*
3133        Method: clear
3134        
3135        Clears the canvas.
3136     */
3137     clear: function(i){
3138       this.canvases[i||0].clear();
3139     },
3140     
3141     path: function(type, action){
3142       var ctx = this.canvases[0].getCtx();
3143       ctx.beginPath();
3144       action(ctx);
3145       ctx[type]();
3146       ctx.closePath();
3147     },
3148     
3149     createLabelContainer: function(type, idLabel, dim) {
3150       var NS = 'http://www.w3.org/2000/svg';
3151       if(type == 'HTML' || type == 'Native') {
3152         return $E('div', {
3153           'id': idLabel,
3154           'style': {
3155             'overflow': 'visible',
3156             'position': 'absolute',
3157             'top': 0,
3158             'left': 0,
3159             'width': dim.width + 'px',
3160             'height': 0
3161           }
3162         });
3163       } else if(type == 'SVG') {
3164         var svgContainer = document.createElementNS(NS, 'svg:svg');
3165         svgContainer.setAttribute("width", dim.width);
3166         svgContainer.setAttribute('height', dim.height);
3167         var style = svgContainer.style;
3168         style.position = 'absolute';
3169         style.left = style.top = '0px';
3170         var labelContainer = document.createElementNS(NS, 'svg:g');
3171         labelContainer.setAttribute('width', dim.width);
3172         labelContainer.setAttribute('height', dim.height);
3173         labelContainer.setAttribute('x', 0);
3174         labelContainer.setAttribute('y', 0);
3175         labelContainer.setAttribute('id', idLabel);
3176         svgContainer.appendChild(labelContainer);
3177         return svgContainer;
3178       }
3179     }
3180   });
3181   //base canvas wrapper
3182   Canvas.Base = new Class({
3183     translateOffsetX: 0,
3184     translateOffsetY: 0,
3185     scaleOffsetX: 1,
3186     scaleOffsetY: 1,
3187
3188     initialize: function(viz) {
3189       this.viz = viz;
3190       this.opt = viz.config;
3191       this.size = false;
3192       this.createCanvas();
3193       this.translateToCenter();
3194     },
3195     createCanvas: function() {
3196       var opt = this.opt,
3197           width = opt.width,
3198           height = opt.height;
3199       this.canvas = $E('canvas', {
3200         'id': opt.injectInto + opt.idSuffix,
3201         'width': width,
3202         'height': height,
3203         'style': {
3204           'position': 'absolute',
3205           'top': 0,
3206           'left': 0,
3207           'width': width + 'px',
3208           'height': height + 'px'
3209         }
3210       });
3211     },
3212     getCtx: function() {
3213       if(!this.ctx) 
3214         return this.ctx = this.canvas.getContext('2d');
3215       return this.ctx;
3216     },
3217     getSize: function() {
3218       if(this.size) return this.size;
3219       var canvas = this.canvas;
3220       return this.size = {
3221         width: canvas.width,
3222         height: canvas.height
3223       };
3224     },
3225     translateToCenter: function(ps) {
3226       var size = this.getSize(),
3227           width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3228           height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3229       var ctx = this.getCtx();
3230       ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3231       ctx.translate(width/2, height/2);
3232     },
3233     resize: function(width, height) {
3234       var size = this.getSize(),
3235           canvas = this.canvas,
3236           styles = canvas.style;
3237       this.size = false;
3238       canvas.width = width;
3239       canvas.height = height;
3240       styles.width = width + "px";
3241       styles.height = height + "px";
3242       //small ExCanvas fix
3243       if(!supportsCanvas) {
3244         this.translateToCenter(size);
3245       } else {
3246         this.translateToCenter();
3247       }
3248       this.translateOffsetX =
3249         this.translateOffsetY = 0;
3250       this.scaleOffsetX = 
3251         this.scaleOffsetY = 1;
3252       this.clear();
3253       this.viz.resize(width, height, this);
3254     },
3255     translate: function(x, y, disablePlot) {
3256       var sx = this.scaleOffsetX,
3257           sy = this.scaleOffsetY;
3258       this.translateOffsetX += x*sx;
3259       this.translateOffsetY += y*sy;
3260       this.getCtx().translate(x, y);
3261       !disablePlot && this.plot();
3262     },
3263     scale: function(x, y, disablePlot) {
3264       this.scaleOffsetX *= x;
3265       this.scaleOffsetY *= y;
3266       this.getCtx().scale(x, y);
3267       !disablePlot && this.plot();
3268     },
3269     clear: function(){
3270       var size = this.getSize(),
3271           ox = this.translateOffsetX,
3272           oy = this.translateOffsetY,
3273           sx = this.scaleOffsetX,
3274           sy = this.scaleOffsetY;
3275       this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx, 
3276                               (-size.height / 2 - oy) * 1/sy, 
3277                               size.width * 1/sx, size.height * 1/sy);
3278     },
3279     plot: function() {
3280       this.clear();
3281       this.viz.plot(this);
3282     }
3283   });
3284   //background canvases
3285   //TODO(nico): document this!
3286   Canvas.Background = {};
3287   Canvas.Background.Circles = new Class({
3288     initialize: function(viz, options) {
3289       this.viz = viz;
3290       this.config = $.merge({
3291         idSuffix: '-bkcanvas',
3292         levelDistance: 100,
3293         numberOfCircles: 6,
3294         CanvasStyles: {},
3295         offset: 0
3296       }, options);
3297     },
3298     resize: function(width, height, base) {
3299       this.plot(base);
3300     },
3301     plot: function(base) {
3302       var canvas = base.canvas,
3303           ctx = base.getCtx(),
3304           conf = this.config,
3305           styles = conf.CanvasStyles;
3306       //set canvas styles
3307       for(var s in styles) ctx[s] = styles[s];
3308       var n = conf.numberOfCircles,
3309           rho = conf.levelDistance;
3310       for(var i=1; i<=n; i++) {
3311         ctx.beginPath();
3312         ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3313         ctx.stroke();
3314         ctx.closePath();
3315       }
3316       //TODO(nico): print labels too!
3317     }
3318   });
3319   Canvas.Background.Fade = new Class({
3320     initialize: function(viz, options) {
3321       this.viz = viz;
3322       this.config = $.merge({
3323         idSuffix: '-bkcanvas',
3324         CanvasStyles: {},
3325         offset: 0
3326       }, options);
3327     },
3328     resize: function(width, height, base) {
3329       this.plot(base);
3330     },
3331     plot: function(base) {
3332       var canvas = base.canvas,
3333           ctx = base.getCtx(),
3334           conf = this.config,
3335           styles = conf.CanvasStyles,
3336           size = base.getSize();
3337                   ctx.fillStyle = 'rgb(255,255,255)';
3338                   ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3339       //TODO(nico): print labels too!
3340     }
3341   });
3342 })();
3343
3344
3345 /*
3346  * File: Polar.js
3347  * 
3348  * Defines the <Polar> class.
3349  *
3350  * Description:
3351  *
3352  * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3353  *
3354  * See also:
3355  *
3356  * <http://en.wikipedia.org/wiki/Polar_coordinates>
3357  *
3358 */
3359
3360 /*
3361    Class: Polar
3362
3363    A multi purpose polar representation.
3364
3365    Description:
3366  
3367    The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3368  
3369    See also:
3370  
3371    <http://en.wikipedia.org/wiki/Polar_coordinates>
3372  
3373    Parameters:
3374
3375       theta - An angle.
3376       rho - The norm.
3377 */
3378
3379 var Polar = function(theta, rho) {
3380   this.theta = theta;
3381   this.rho = rho;
3382 };
3383
3384 $jit.Polar = Polar;
3385
3386 Polar.prototype = {
3387     /*
3388        Method: getc
3389     
3390        Returns a complex number.
3391     
3392        Parameters:
3393
3394        simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3395
3396       Returns:
3397     
3398           A complex number.
3399     */
3400     getc: function(simple) {
3401         return this.toComplex(simple);
3402     },
3403
3404     /*
3405        Method: getp
3406     
3407        Returns a <Polar> representation.
3408     
3409        Returns:
3410     
3411           A variable in polar coordinates.
3412     */
3413     getp: function() {
3414         return this;
3415     },
3416
3417
3418     /*
3419        Method: set
3420     
3421        Sets a number.
3422
3423        Parameters:
3424
3425        v - A <Complex> or <Polar> instance.
3426     
3427     */
3428     set: function(v) {
3429         v = v.getp();
3430         this.theta = v.theta; this.rho = v.rho;
3431     },
3432
3433     /*
3434        Method: setc
3435     
3436        Sets a <Complex> number.
3437
3438        Parameters:
3439
3440        x - A <Complex> number real part.
3441        y - A <Complex> number imaginary part.
3442     
3443     */
3444     setc: function(x, y) {
3445         this.rho = Math.sqrt(x * x + y * y);
3446         this.theta = Math.atan2(y, x);
3447         if(this.theta < 0) this.theta += Math.PI * 2;
3448     },
3449
3450     /*
3451        Method: setp
3452     
3453        Sets a polar number.
3454
3455        Parameters:
3456
3457        theta - A <Polar> number angle property.
3458        rho - A <Polar> number rho property.
3459     
3460     */
3461     setp: function(theta, rho) {
3462         this.theta = theta; 
3463         this.rho = rho;
3464     },
3465
3466     /*
3467        Method: clone
3468     
3469        Returns a copy of the current object.
3470     
3471        Returns:
3472     
3473           A copy of the real object.
3474     */
3475     clone: function() {
3476         return new Polar(this.theta, this.rho);
3477     },
3478
3479     /*
3480        Method: toComplex
3481     
3482         Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3483     
3484         Parameters:
3485
3486         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*.
3487  
3488         Returns:
3489     
3490           A new <Complex> instance.
3491     */
3492     toComplex: function(simple) {
3493         var x = Math.cos(this.theta) * this.rho;
3494         var y = Math.sin(this.theta) * this.rho;
3495         if(simple) return { 'x': x, 'y': y};
3496         return new Complex(x, y);
3497     },
3498
3499     /*
3500        Method: add
3501     
3502         Adds two <Polar> instances.
3503     
3504        Parameters:
3505
3506        polar - A <Polar> number.
3507
3508        Returns:
3509     
3510           A new Polar instance.
3511     */
3512     add: function(polar) {
3513         return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3514     },
3515     
3516     /*
3517        Method: scale
3518     
3519         Scales a polar norm.
3520     
3521         Parameters:
3522
3523         number - A scale factor.
3524         
3525         Returns:
3526     
3527           A new Polar instance.
3528     */
3529     scale: function(number) {
3530         return new Polar(this.theta, this.rho * number);
3531     },
3532     
3533     /*
3534        Method: equals
3535     
3536        Comparison method.
3537
3538        Returns *true* if the theta and rho properties are equal.
3539
3540        Parameters:
3541
3542        c - A <Polar> number.
3543
3544        Returns:
3545
3546        *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3547     */
3548     equals: function(c) {
3549         return this.theta == c.theta && this.rho == c.rho;
3550     },
3551     
3552     /*
3553        Method: $add
3554     
3555         Adds two <Polar> instances affecting the current object.
3556     
3557        Paramters:
3558
3559        polar - A <Polar> instance.
3560
3561        Returns:
3562     
3563           The changed object.
3564     */
3565     $add: function(polar) {
3566         this.theta = this.theta + polar.theta; this.rho += polar.rho;
3567         return this;
3568     },
3569
3570     /*
3571        Method: $madd
3572     
3573         Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3574     
3575        Parameters:
3576
3577        polar - A <Polar> instance.
3578
3579        Returns:
3580     
3581           The changed object.
3582     */
3583     $madd: function(polar) {
3584         this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3585         return this;
3586     },
3587
3588     
3589     /*
3590        Method: $scale
3591     
3592         Scales a polar instance affecting the object.
3593     
3594       Parameters:
3595
3596       number - A scaling factor.
3597
3598       Returns:
3599     
3600           The changed object.
3601     */
3602     $scale: function(number) {
3603         this.rho *= number;
3604         return this;
3605     },
3606     
3607     /*
3608        Method: interpolate
3609     
3610         Calculates a polar interpolation between two points at a given delta moment.
3611
3612         Parameters:
3613       
3614         elem - A <Polar> instance.
3615         delta - A delta factor ranging [0, 1].
3616     
3617        Returns:
3618     
3619           A new <Polar> instance representing an interpolation between _this_ and _elem_
3620     */
3621     interpolate: function(elem, delta) {
3622         var pi = Math.PI, pi2 = pi * 2;
3623         var ch = function(t) {
3624             var a =  (t < 0)? (t % pi2) + pi2 : t % pi2;
3625             return a;
3626         };
3627         var tt = this.theta, et = elem.theta;
3628         var sum, diff = Math.abs(tt - et);
3629         if(diff == pi) {
3630           if(tt > et) {
3631             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3632           } else {
3633             sum = ch((et - pi2 + (tt - (et)) * delta));
3634           }
3635         } else if(diff >= pi) {
3636           if(tt > et) {
3637             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3638           } else {
3639             sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3640           }
3641         } else {  
3642           sum = ch((et + (tt - et) * delta)) ;
3643         }
3644         var r = (this.rho - elem.rho) * delta + elem.rho;
3645         return {
3646           'theta': sum,
3647           'rho': r
3648         };
3649     }
3650 };
3651
3652
3653 var $P = function(a, b) { return new Polar(a, b); };
3654
3655 Polar.KER = $P(0, 0);
3656
3657
3658
3659 /*
3660  * File: Complex.js
3661  * 
3662  * Defines the <Complex> class.
3663  *
3664  * Description:
3665  *
3666  * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3667  *
3668  * See also:
3669  *
3670  * <http://en.wikipedia.org/wiki/Complex_number>
3671  *
3672 */
3673
3674 /*
3675    Class: Complex
3676     
3677    A multi-purpose Complex Class with common methods.
3678  
3679    Description:
3680  
3681    The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3682  
3683    See also:
3684  
3685    <http://en.wikipedia.org/wiki/Complex_number>
3686
3687    Parameters:
3688
3689    x - _optional_ A Complex number real part.
3690    y - _optional_ A Complex number imaginary part.
3691  
3692 */
3693
3694 var Complex = function(x, y) {
3695   this.x = x;
3696   this.y = y;
3697 };
3698
3699 $jit.Complex = Complex;
3700
3701 Complex.prototype = {
3702     /*
3703        Method: getc
3704     
3705        Returns a complex number.
3706     
3707        Returns:
3708     
3709           A complex number.
3710     */
3711     getc: function() {
3712         return this;
3713     },
3714
3715     /*
3716        Method: getp
3717     
3718        Returns a <Polar> representation of this number.
3719     
3720        Parameters:
3721
3722        simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3723
3724        Returns:
3725     
3726           A variable in <Polar> coordinates.
3727     */
3728     getp: function(simple) {
3729         return this.toPolar(simple);
3730     },
3731
3732
3733     /*
3734        Method: set
3735     
3736        Sets a number.
3737
3738        Parameters:
3739
3740        c - A <Complex> or <Polar> instance.
3741     
3742     */
3743     set: function(c) {
3744       c = c.getc(true);
3745       this.x = c.x; 
3746       this.y = c.y;
3747     },
3748
3749     /*
3750        Method: setc
3751     
3752        Sets a complex number.
3753
3754        Parameters:
3755
3756        x - A <Complex> number Real part.
3757        y - A <Complex> number Imaginary part.
3758     
3759     */
3760     setc: function(x, y) {
3761         this.x = x; 
3762         this.y = y;
3763     },
3764
3765     /*
3766        Method: setp
3767     
3768        Sets a polar number.
3769
3770        Parameters:
3771
3772        theta - A <Polar> number theta property.
3773        rho - A <Polar> number rho property.
3774     
3775     */
3776     setp: function(theta, rho) {
3777         this.x = Math.cos(theta) * rho;
3778         this.y = Math.sin(theta) * rho;
3779     },
3780
3781     /*
3782        Method: clone
3783     
3784        Returns a copy of the current object.
3785     
3786        Returns:
3787     
3788           A copy of the real object.
3789     */
3790     clone: function() {
3791         return new Complex(this.x, this.y);
3792     },
3793
3794     /*
3795        Method: toPolar
3796     
3797        Transforms cartesian to polar coordinates.
3798     
3799        Parameters:
3800
3801        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*.
3802        
3803        Returns:
3804     
3805           A new <Polar> instance.
3806     */
3807     
3808     toPolar: function(simple) {
3809         var rho = this.norm();
3810         var atan = Math.atan2(this.y, this.x);
3811         if(atan < 0) atan += Math.PI * 2;
3812         if(simple) return { 'theta': atan, 'rho': rho };
3813         return new Polar(atan, rho);
3814     },
3815     /*
3816        Method: norm
3817     
3818        Calculates a <Complex> number norm.
3819     
3820        Returns:
3821     
3822           A real number representing the complex norm.
3823     */
3824     norm: function () {
3825         return Math.sqrt(this.squaredNorm());
3826     },
3827     
3828     /*
3829        Method: squaredNorm
3830     
3831        Calculates a <Complex> number squared norm.
3832     
3833        Returns:
3834     
3835           A real number representing the complex squared norm.
3836     */
3837     squaredNorm: function () {
3838         return this.x*this.x + this.y*this.y;
3839     },
3840
3841     /*
3842        Method: add
3843     
3844        Returns the result of adding two complex numbers.
3845        
3846        Does not alter the original object.
3847
3848        Parameters:
3849     
3850           pos - A <Complex> instance.
3851     
3852        Returns:
3853     
3854          The result of adding two complex numbers.
3855     */
3856     add: function(pos) {
3857         return new Complex(this.x + pos.x, this.y + pos.y);
3858     },
3859
3860     /*
3861        Method: prod
3862     
3863        Returns the result of multiplying two <Complex> numbers.
3864        
3865        Does not alter the original object.
3866
3867        Parameters:
3868     
3869           pos - A <Complex> instance.
3870     
3871        Returns:
3872     
3873          The result of multiplying two complex numbers.
3874     */
3875     prod: function(pos) {
3876         return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3877     },
3878
3879     /*
3880        Method: conjugate
3881     
3882        Returns the conjugate of this <Complex> number.
3883
3884        Does not alter the original object.
3885
3886        Returns:
3887     
3888          The conjugate of this <Complex> number.
3889     */
3890     conjugate: function() {
3891         return new Complex(this.x, -this.y);
3892     },
3893
3894
3895     /*
3896        Method: scale
3897     
3898        Returns the result of scaling a <Complex> instance.
3899        
3900        Does not alter the original object.
3901
3902        Parameters:
3903     
3904           factor - A scale factor.
3905     
3906        Returns:
3907     
3908          The result of scaling this complex to a factor.
3909     */
3910     scale: function(factor) {
3911         return new Complex(this.x * factor, this.y * factor);
3912     },
3913
3914     /*
3915        Method: equals
3916     
3917        Comparison method.
3918
3919        Returns *true* if both real and imaginary parts are equal.
3920
3921        Parameters:
3922
3923        c - A <Complex> instance.
3924
3925        Returns:
3926
3927        A boolean instance indicating if both <Complex> numbers are equal.
3928     */
3929     equals: function(c) {
3930         return this.x == c.x && this.y == c.y;
3931     },
3932
3933     /*
3934        Method: $add
3935     
3936        Returns the result of adding two <Complex> numbers.
3937        
3938        Alters the original object.
3939
3940        Parameters:
3941     
3942           pos - A <Complex> instance.
3943     
3944        Returns:
3945     
3946          The result of adding two complex numbers.
3947     */
3948     $add: function(pos) {
3949         this.x += pos.x; this.y += pos.y;
3950         return this;    
3951     },
3952     
3953     /*
3954        Method: $prod
3955     
3956        Returns the result of multiplying two <Complex> numbers.
3957        
3958        Alters the original object.
3959
3960        Parameters:
3961     
3962           pos - A <Complex> instance.
3963     
3964        Returns:
3965     
3966          The result of multiplying two complex numbers.
3967     */
3968     $prod:function(pos) {
3969         var x = this.x, y = this.y;
3970         this.x = x*pos.x - y*pos.y;
3971         this.y = y*pos.x + x*pos.y;
3972         return this;
3973     },
3974     
3975     /*
3976        Method: $conjugate
3977     
3978        Returns the conjugate for this <Complex>.
3979        
3980        Alters the original object.
3981
3982        Returns:
3983     
3984          The conjugate for this complex.
3985     */
3986     $conjugate: function() {
3987         this.y = -this.y;
3988         return this;
3989     },
3990     
3991     /*
3992        Method: $scale
3993     
3994        Returns the result of scaling a <Complex> instance.
3995        
3996        Alters the original object.
3997
3998        Parameters:
3999     
4000           factor - A scale factor.
4001     
4002        Returns:
4003     
4004          The result of scaling this complex to a factor.
4005     */
4006     $scale: function(factor) {
4007         this.x *= factor; this.y *= factor;
4008         return this;
4009     },
4010     
4011     /*
4012        Method: $div
4013     
4014        Returns the division of two <Complex> numbers.
4015        
4016        Alters the original object.
4017
4018        Parameters:
4019     
4020           pos - A <Complex> number.
4021     
4022        Returns:
4023     
4024          The result of scaling this complex to a factor.
4025     */
4026     $div: function(pos) {
4027         var x = this.x, y = this.y;
4028         var sq = pos.squaredNorm();
4029         this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4030         return this.$scale(1 / sq);
4031     }
4032 };
4033
4034 var $C = function(a, b) { return new Complex(a, b); };
4035
4036 Complex.KER = $C(0, 0);
4037
4038
4039
4040 /*
4041  * File: Graph.js
4042  *
4043 */
4044
4045 /*
4046  Class: Graph
4047
4048  A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4049
4050  An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4051  
4052  Example:
4053
4054  (start code js)
4055    //create new visualization
4056    var viz = new $jit.Viz(options);
4057    //load JSON data
4058    viz.loadJSON(json);
4059    //access model
4060    viz.graph; //<Graph> instance
4061  (end code)
4062  
4063  Implements:
4064  
4065  The following <Graph.Util> methods are implemented in <Graph>
4066  
4067   - <Graph.Util.getNode>
4068   - <Graph.Util.eachNode>
4069   - <Graph.Util.computeLevels>
4070   - <Graph.Util.eachBFS>
4071   - <Graph.Util.clean>
4072   - <Graph.Util.getClosestNodeToPos>
4073   - <Graph.Util.getClosestNodeToOrigin>
4074  
4075 */  
4076
4077 $jit.Graph = new Class({
4078
4079   initialize: function(opt, Node, Edge, Label) {
4080     var innerOptions = {
4081     'complex': false,
4082     'Node': {}
4083     };
4084     this.Node = Node;
4085     this.Edge = Edge;
4086     this.Label = Label;
4087     this.opt = $.merge(innerOptions, opt || {});
4088     this.nodes = {};
4089     this.edges = {};
4090     
4091     //add nodeList methods
4092     var that = this;
4093     this.nodeList = {};
4094     for(var p in Accessors) {
4095       that.nodeList[p] = (function(p) {
4096         return function() {
4097           var args = Array.prototype.slice.call(arguments);
4098           that.eachNode(function(n) {
4099             n[p].apply(n, args);
4100           });
4101         };
4102       })(p);
4103     }
4104
4105  },
4106
4107 /*
4108      Method: getNode
4109     
4110      Returns a <Graph.Node> by *id*.
4111
4112      Parameters:
4113
4114      id - (string) A <Graph.Node> id.
4115
4116      Example:
4117
4118      (start code js)
4119        var node = graph.getNode('nodeId');
4120      (end code)
4121 */  
4122  getNode: function(id) {
4123     if(this.hasNode(id)) return this.nodes[id];
4124     return false;
4125  },
4126
4127  /*
4128    Method: getByName
4129   
4130    Returns a <Graph.Node> by *name*.
4131   
4132    Parameters:
4133   
4134    name - (string) A <Graph.Node> name.
4135   
4136    Example:
4137   
4138    (start code js)
4139      var node = graph.getByName('someName');
4140    (end code)
4141   */  
4142   getByName: function(name) {
4143     for(var id in this.nodes) {
4144       var n = this.nodes[id];
4145       if(n.name == name) return n;
4146     }
4147     return false;
4148   },
4149
4150 /*
4151    Method: getAdjacence
4152   
4153    Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4154
4155    Parameters:
4156
4157    id - (string) A <Graph.Node> id.
4158    id2 - (string) A <Graph.Node> id.
4159 */  
4160   getAdjacence: function (id, id2) {
4161     if(id in this.edges) {
4162       return this.edges[id][id2];
4163     }
4164     return false;
4165  },
4166
4167     /*
4168      Method: addNode
4169     
4170      Adds a node.
4171      
4172      Parameters:
4173     
4174       obj - An object with the properties described below
4175
4176       id - (string) A node id
4177       name - (string) A node's name
4178       data - (object) A node's data hash
4179
4180     See also:
4181     <Graph.Node>
4182
4183   */  
4184   addNode: function(obj) { 
4185    if(!this.nodes[obj.id]) {  
4186      var edges = this.edges[obj.id] = {};
4187      this.nodes[obj.id] = new Graph.Node($.extend({
4188         'id': obj.id,
4189         'name': obj.name,
4190         'data': $.merge(obj.data || {}, {}),
4191         'adjacencies': edges 
4192       }, this.opt.Node), 
4193       this.opt.complex, 
4194       this.Node, 
4195       this.Edge,
4196       this.Label);
4197     }
4198     return this.nodes[obj.id];
4199   },
4200   
4201     /*
4202      Method: addAdjacence
4203     
4204      Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4205      
4206      Parameters:
4207     
4208       obj - (object) A <Graph.Node> object.
4209       obj2 - (object) Another <Graph.Node> object.
4210       data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4211
4212     See also:
4213
4214     <Graph.Node>, <Graph.Adjacence>
4215     */  
4216   addAdjacence: function (obj, obj2, data) {
4217     if(!this.hasNode(obj.id)) { this.addNode(obj); }
4218     if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4219     obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4220     if(!obj.adjacentTo(obj2)) {
4221       var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4222       var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4223       adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4224       return adjsObj[obj2.id];
4225     }
4226     return this.edges[obj.id][obj2.id];
4227  },
4228
4229     /*
4230      Method: removeNode
4231     
4232      Removes a <Graph.Node> matching the specified *id*.
4233
4234      Parameters:
4235
4236      id - (string) A node's id.
4237
4238     */  
4239   removeNode: function(id) {
4240     if(this.hasNode(id)) {
4241       delete this.nodes[id];
4242       var adjs = this.edges[id];
4243       for(var to in adjs) {
4244         delete this.edges[to][id];
4245       }
4246       delete this.edges[id];
4247     }
4248   },
4249   
4250 /*
4251      Method: removeAdjacence
4252     
4253      Removes a <Graph.Adjacence> matching *id1* and *id2*.
4254
4255      Parameters:
4256
4257      id1 - (string) A <Graph.Node> id.
4258      id2 - (string) A <Graph.Node> id.
4259 */  
4260   removeAdjacence: function(id1, id2) {
4261     delete this.edges[id1][id2];
4262     delete this.edges[id2][id1];
4263   },
4264
4265    /*
4266      Method: hasNode
4267     
4268      Returns a boolean indicating if the node belongs to the <Graph> or not.
4269      
4270      Parameters:
4271     
4272         id - (string) Node id.
4273    */  
4274   hasNode: function(id) {
4275     return id in this.nodes;
4276   },
4277   
4278   /*
4279     Method: empty
4280
4281     Empties the Graph
4282
4283   */
4284   empty: function() { this.nodes = {}; this.edges = {};}
4285
4286 });
4287
4288 var Graph = $jit.Graph;
4289
4290 /*
4291  Object: Accessors
4292  
4293  Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4294  
4295  */
4296 var Accessors;
4297
4298 (function () {
4299   var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4300     var data;
4301     type = type || 'current';
4302     prefix = "$" + (prefix ? prefix + "-" : "");
4303
4304     if(type == 'current') {
4305       data = this.data;
4306     } else if(type == 'start') {
4307       data = this.startData;
4308     } else if(type == 'end') {
4309       data = this.endData;
4310     }
4311
4312     var dollar = prefix + prop;
4313
4314     if(force) {
4315       return data[dollar];
4316     }
4317
4318     if(!this.Config.overridable)
4319       return prefixConfig[prop] || 0;
4320
4321     return (dollar in data) ?
4322       data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4323   }
4324
4325   var setDataInternal = function(prefix, prop, value, type) {
4326     type = type || 'current';
4327     prefix = '$' + (prefix ? prefix + '-' : '');
4328
4329     var data;
4330
4331     if(type == 'current') {
4332       data = this.data;
4333     } else if(type == 'start') {
4334       data = this.startData;
4335     } else if(type == 'end') {
4336       data = this.endData;
4337     }
4338
4339     data[prefix + prop] = value;
4340   }
4341
4342   var removeDataInternal = function(prefix, properties) {
4343     prefix = '$' + (prefix ? prefix + '-' : '');
4344     var that = this;
4345     $.each(properties, function(prop) {
4346       var pref = prefix + prop;
4347       delete that.data[pref];
4348       delete that.endData[pref];
4349       delete that.startData[pref];
4350     });
4351   }
4352
4353   Accessors = {
4354     /*
4355     Method: getData
4356
4357     Returns the specified data value property.
4358     This is useful for querying special/reserved <Graph.Node> data properties
4359     (i.e dollar prefixed properties).
4360
4361     Parameters:
4362
4363       prop  - (string) The name of the property. The dollar sign is not needed. For
4364               example *getData(width)* will return *data.$width*.
4365       type  - (string) The type of the data property queried. Default's "current". You can access *start* and *end* 
4366               data properties also. These properties are used when making animations.
4367       force - (boolean) Whether to obtain the true value of the property (equivalent to
4368               *data.$prop*) or to check for *node.overridable = true* first.
4369
4370     Returns:
4371
4372       The value of the dollar prefixed property or the global Node/Edge property
4373       value if *overridable=false*
4374
4375     Example:
4376     (start code js)
4377      node.getData('width'); //will return node.data.$width if Node.overridable=true;
4378     (end code)
4379     */
4380     getData: function(prop, type, force) {
4381       return getDataInternal.call(this, "", prop, type, force, this.Config);
4382     },
4383
4384
4385     /*
4386     Method: setData
4387
4388     Sets the current data property with some specific value.
4389     This method is only useful for reserved (dollar prefixed) properties.
4390
4391     Parameters:
4392
4393       prop  - (string) The name of the property. The dollar sign is not necessary. For
4394               example *setData(width)* will set *data.$width*.
4395       value - (mixed) The value to store.
4396       type  - (string) The type of the data property to store. Default's "current" but
4397               can also be "start" or "end".
4398
4399     Example:
4400     
4401     (start code js)
4402      node.setData('width', 30);
4403     (end code)
4404     
4405     If we were to make an animation of a node/edge width then we could do
4406     
4407     (start code js)
4408       var node = viz.getNode('nodeId');
4409       //set start and end values
4410       node.setData('width', 10, 'start');
4411       node.setData('width', 30, 'end');
4412       //will animate nodes width property
4413       viz.fx.animate({
4414         modes: ['node-property:width'],
4415         duration: 1000
4416       });
4417     (end code)
4418     */
4419     setData: function(prop, value, type) {
4420       setDataInternal.call(this, "", prop, value, type);
4421     },
4422
4423     /*
4424     Method: setDataset
4425
4426     Convenience method to set multiple data values at once.
4427     
4428     Parameters:
4429     
4430     types - (array|string) A set of 'current', 'end' or 'start' values.
4431     obj - (object) A hash containing the names and values of the properties to be altered.
4432
4433     Example:
4434     (start code js)
4435       node.setDataset(['current', 'end'], {
4436         'width': [100, 5],
4437         'color': ['#fff', '#ccc']
4438       });
4439       //...or also
4440       node.setDataset('end', {
4441         'width': 5,
4442         'color': '#ccc'
4443       });
4444     (end code)
4445     
4446     See also: 
4447     
4448     <Accessors.setData>
4449     
4450     */
4451     setDataset: function(types, obj) {
4452       types = $.splat(types);
4453       for(var attr in obj) {
4454         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4455           this.setData(attr, val[i], types[i]);
4456         }
4457       }
4458     },
4459     
4460     /*
4461     Method: removeData
4462
4463     Remove data properties.
4464
4465     Parameters:
4466
4467     One or more property names as arguments. The dollar sign is not needed.
4468
4469     Example:
4470     (start code js)
4471     node.removeData('width'); //now the default width value is returned
4472     (end code)
4473     */
4474     removeData: function() {
4475       removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4476     },
4477
4478     /*
4479     Method: getCanvasStyle
4480
4481     Returns the specified canvas style data value property. This is useful for
4482     querying special/reserved <Graph.Node> canvas style data properties (i.e.
4483     dollar prefixed properties that match with $canvas-<name of canvas style>).
4484
4485     Parameters:
4486
4487       prop  - (string) The name of the property. The dollar sign is not needed. For
4488               example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4489       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4490               data properties also.
4491               
4492     Example:
4493     (start code js)
4494       node.getCanvasStyle('shadowBlur');
4495     (end code)
4496     
4497     See also:
4498     
4499     <Accessors.getData>
4500     */
4501     getCanvasStyle: function(prop, type, force) {
4502       return getDataInternal.call(
4503           this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4504     },
4505
4506     /*
4507     Method: setCanvasStyle
4508
4509     Sets the canvas style data property with some specific value.
4510     This method is only useful for reserved (dollar prefixed) properties.
4511     
4512     Parameters:
4513     
4514     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4515     value - (mixed) The value to set to the property.
4516     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4517     
4518     Example:
4519     
4520     (start code js)
4521      node.setCanvasStyle('shadowBlur', 30);
4522     (end code)
4523     
4524     If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4525     
4526     (start code js)
4527       var node = viz.getNode('nodeId');
4528       //set start and end values
4529       node.setCanvasStyle('shadowBlur', 10, 'start');
4530       node.setCanvasStyle('shadowBlur', 30, 'end');
4531       //will animate nodes canvas style property for nodes
4532       viz.fx.animate({
4533         modes: ['node-style:shadowBlur'],
4534         duration: 1000
4535       });
4536     (end code)
4537     
4538     See also:
4539     
4540     <Accessors.setData>.
4541     */
4542     setCanvasStyle: function(prop, value, type) {
4543       setDataInternal.call(this, 'canvas', prop, value, type);
4544     },
4545
4546     /*
4547     Method: setCanvasStyles
4548
4549     Convenience method to set multiple styles at once.
4550
4551     Parameters:
4552     
4553     types - (array|string) A set of 'current', 'end' or 'start' values.
4554     obj - (object) A hash containing the names and values of the properties to be altered.
4555
4556     See also:
4557     
4558     <Accessors.setDataset>.
4559     */
4560     setCanvasStyles: function(types, obj) {
4561       types = $.splat(types);
4562       for(var attr in obj) {
4563         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4564           this.setCanvasStyle(attr, val[i], types[i]);
4565         }
4566       }
4567     },
4568
4569     /*
4570     Method: removeCanvasStyle
4571
4572     Remove canvas style properties from data.
4573
4574     Parameters:
4575     
4576     A variable number of canvas style strings.
4577
4578     See also:
4579     
4580     <Accessors.removeData>.
4581     */
4582     removeCanvasStyle: function() {
4583       removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4584     },
4585
4586     /*
4587     Method: getLabelData
4588
4589     Returns the specified label data value property. This is useful for
4590     querying special/reserved <Graph.Node> label options (i.e.
4591     dollar prefixed properties that match with $label-<name of label style>).
4592
4593     Parameters:
4594
4595       prop  - (string) The name of the property. The dollar sign prefix is not needed. For
4596               example *getLabelData(size)* will return *data[$label-size]*.
4597       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4598               data properties also.
4599               
4600     See also:
4601     
4602     <Accessors.getData>.
4603     */
4604     getLabelData: function(prop, type, force) {
4605       return getDataInternal.call(
4606           this, 'label', prop, type, force, this.Label);
4607     },
4608
4609     /*
4610     Method: setLabelData
4611
4612     Sets the current label data with some specific value.
4613     This method is only useful for reserved (dollar prefixed) properties.
4614
4615     Parameters:
4616     
4617     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4618     value - (mixed) The value to set to the property.
4619     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4620     
4621     Example:
4622     
4623     (start code js)
4624      node.setLabelData('size', 30);
4625     (end code)
4626     
4627     If we were to make an animation of a node label size then we could do
4628     
4629     (start code js)
4630       var node = viz.getNode('nodeId');
4631       //set start and end values
4632       node.setLabelData('size', 10, 'start');
4633       node.setLabelData('size', 30, 'end');
4634       //will animate nodes label size
4635       viz.fx.animate({
4636         modes: ['label-property:size'],
4637         duration: 1000
4638       });
4639     (end code)
4640     
4641     See also:
4642     
4643     <Accessors.setData>.
4644     */
4645     setLabelData: function(prop, value, type) {
4646       setDataInternal.call(this, 'label', prop, value, type);
4647     },
4648
4649     /*
4650     Method: setLabelDataset
4651
4652     Convenience function to set multiple label data at once.
4653
4654     Parameters:
4655     
4656     types - (array|string) A set of 'current', 'end' or 'start' values.
4657     obj - (object) A hash containing the names and values of the properties to be altered.
4658
4659     See also:
4660     
4661     <Accessors.setDataset>.
4662     */
4663     setLabelDataset: function(types, obj) {
4664       types = $.splat(types);
4665       for(var attr in obj) {
4666         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4667           this.setLabelData(attr, val[i], types[i]);
4668         }
4669       }
4670     },
4671
4672     /*
4673     Method: removeLabelData
4674
4675     Remove label properties from data.
4676     
4677     Parameters:
4678     
4679     A variable number of label property strings.
4680
4681     See also:
4682     
4683     <Accessors.removeData>.
4684     */
4685     removeLabelData: function() {
4686       removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4687     }
4688   };
4689 })();
4690
4691 /*
4692      Class: Graph.Node
4693
4694      A <Graph> node.
4695      
4696      Implements:
4697      
4698      <Accessors> methods.
4699      
4700      The following <Graph.Util> methods are implemented by <Graph.Node>
4701      
4702     - <Graph.Util.eachAdjacency>
4703     - <Graph.Util.eachLevel>
4704     - <Graph.Util.eachSubgraph>
4705     - <Graph.Util.eachSubnode>
4706     - <Graph.Util.anySubnode>
4707     - <Graph.Util.getSubnodes>
4708     - <Graph.Util.getParents>
4709     - <Graph.Util.isDescendantOf>     
4710 */
4711 Graph.Node = new Class({
4712     
4713   initialize: function(opt, complex, Node, Edge, Label) {
4714     var innerOptions = {
4715       'id': '',
4716       'name': '',
4717       'data': {},
4718       'startData': {},
4719       'endData': {},
4720       'adjacencies': {},
4721
4722       'selected': false,
4723       'drawn': false,
4724       'exist': false,
4725
4726       'angleSpan': {
4727         'begin': 0,
4728         'end' : 0
4729       },
4730
4731       'pos': (complex && $C(0, 0)) || $P(0, 0),
4732       'startPos': (complex && $C(0, 0)) || $P(0, 0),
4733       'endPos': (complex && $C(0, 0)) || $P(0, 0)
4734     };
4735     
4736     $.extend(this, $.extend(innerOptions, opt));
4737     this.Config = this.Node = Node;
4738     this.Edge = Edge;
4739     this.Label = Label;
4740   },
4741
4742     /*
4743        Method: adjacentTo
4744     
4745        Indicates if the node is adjacent to the node specified by id
4746
4747        Parameters:
4748     
4749           id - (string) A node id.
4750     
4751        Example:
4752        (start code js)
4753         node.adjacentTo('nodeId') == true;
4754        (end code)
4755     */
4756     adjacentTo: function(node) {
4757         return node.id in this.adjacencies;
4758     },
4759
4760     /*
4761        Method: getAdjacency
4762     
4763        Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4764
4765        Parameters:
4766     
4767           id - (string) A node id.
4768     */  
4769     getAdjacency: function(id) {
4770         return this.adjacencies[id];
4771     },
4772
4773     /*
4774       Method: getPos
4775    
4776       Returns the position of the node.
4777   
4778       Parameters:
4779    
4780          type - (string) Default's *current*. Possible values are "start", "end" or "current".
4781    
4782       Returns:
4783    
4784         A <Complex> or <Polar> instance.
4785   
4786       Example:
4787       (start code js)
4788        var pos = node.getPos('end');
4789       (end code)
4790    */
4791    getPos: function(type) {
4792        type = type || "current";
4793        if(type == "current") {
4794          return this.pos;
4795        } else if(type == "end") {
4796          return this.endPos;
4797        } else if(type == "start") {
4798          return this.startPos;
4799        }
4800    },
4801    /*
4802      Method: setPos
4803   
4804      Sets the node's position.
4805   
4806      Parameters:
4807   
4808         value - (object) A <Complex> or <Polar> instance.
4809         type - (string) Default's *current*. Possible values are "start", "end" or "current".
4810   
4811      Example:
4812      (start code js)
4813       node.setPos(new $jit.Complex(0, 0), 'end');
4814      (end code)
4815   */
4816   setPos: function(value, type) {
4817       type = type || "current";
4818       var pos;
4819       if(type == "current") {
4820         pos = this.pos;
4821       } else if(type == "end") {
4822         pos = this.endPos;
4823       } else if(type == "start") {
4824         pos = this.startPos;
4825       }
4826       pos.set(value);
4827   }
4828 });
4829
4830 Graph.Node.implement(Accessors);
4831
4832 /*
4833      Class: Graph.Adjacence
4834
4835      A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4836      
4837      Implements:
4838      
4839      <Accessors> methods.
4840
4841      See also:
4842
4843      <Graph>, <Graph.Node>
4844
4845      Properties:
4846      
4847       nodeFrom - A <Graph.Node> connected by this edge.
4848       nodeTo - Another  <Graph.Node> connected by this edge.
4849       data - Node data property containing a hash (i.e {}) with custom options.
4850 */
4851 Graph.Adjacence = new Class({
4852   
4853   initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4854     this.nodeFrom = nodeFrom;
4855     this.nodeTo = nodeTo;
4856     this.data = data || {};
4857     this.startData = {};
4858     this.endData = {};
4859     this.Config = this.Edge = Edge;
4860     this.Label = Label;
4861   }
4862 });
4863
4864 Graph.Adjacence.implement(Accessors);
4865
4866 /*
4867    Object: Graph.Util
4868
4869    <Graph> traversal and processing utility object.
4870    
4871    Note:
4872    
4873    For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4874 */
4875 Graph.Util = {
4876     /*
4877        filter
4878     
4879        For internal use only. Provides a filtering function based on flags.
4880     */
4881     filter: function(param) {
4882         if(!param || !($.type(param) == 'string')) return function() { return true; };
4883         var props = param.split(" ");
4884         return function(elem) {
4885             for(var i=0; i<props.length; i++) { 
4886               if(elem[props[i]]) { 
4887                 return false; 
4888               }
4889             }
4890             return true;
4891         };
4892     },
4893     /*
4894        Method: getNode
4895     
4896        Returns a <Graph.Node> by *id*.
4897        
4898        Also implemented by:
4899        
4900        <Graph>
4901
4902        Parameters:
4903
4904        graph - (object) A <Graph> instance.
4905        id - (string) A <Graph.Node> id.
4906
4907        Example:
4908
4909        (start code js)
4910          $jit.Graph.Util.getNode(graph, 'nodeid');
4911          //or...
4912          graph.getNode('nodeid');
4913        (end code)
4914     */
4915     getNode: function(graph, id) {
4916         return graph.nodes[id];
4917     },
4918     
4919     /*
4920        Method: eachNode
4921     
4922        Iterates over <Graph> nodes performing an *action*.
4923        
4924        Also implemented by:
4925        
4926        <Graph>.
4927
4928        Parameters:
4929
4930        graph - (object) A <Graph> instance.
4931        action - (function) A callback function having a <Graph.Node> as first formal parameter.
4932
4933        Example:
4934        (start code js)
4935          $jit.Graph.Util.eachNode(graph, function(node) {
4936           alert(node.name);
4937          });
4938          //or...
4939          graph.eachNode(function(node) {
4940            alert(node.name);
4941          });
4942        (end code)
4943     */
4944     eachNode: function(graph, action, flags) {
4945         var filter = this.filter(flags);
4946         for(var i in graph.nodes) {
4947           if(filter(graph.nodes[i])) action(graph.nodes[i]);
4948         } 
4949     },
4950     
4951     /*
4952        Method: eachAdjacency
4953     
4954        Iterates over <Graph.Node> adjacencies applying the *action* function.
4955        
4956        Also implemented by:
4957        
4958        <Graph.Node>.
4959
4960        Parameters:
4961
4962        node - (object) A <Graph.Node>.
4963        action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4964
4965        Example:
4966        (start code js)
4967          $jit.Graph.Util.eachAdjacency(node, function(adj) {
4968           alert(adj.nodeTo.name);
4969          });
4970          //or...
4971          node.eachAdjacency(function(adj) {
4972            alert(adj.nodeTo.name);
4973          });
4974        (end code)
4975     */
4976     eachAdjacency: function(node, action, flags) {
4977         var adj = node.adjacencies, filter = this.filter(flags);
4978         for(var id in adj) {
4979           var a = adj[id];
4980           if(filter(a)) {
4981             if(a.nodeFrom != node) {
4982               var tmp = a.nodeFrom;
4983               a.nodeFrom = a.nodeTo;
4984               a.nodeTo = tmp;
4985             }
4986             action(a, id);
4987           }
4988         }
4989     },
4990
4991      /*
4992        Method: computeLevels
4993     
4994        Performs a BFS traversal setting the correct depth for each node.
4995         
4996        Also implemented by:
4997        
4998        <Graph>.
4999        
5000        Note:
5001        
5002        The depth of each node can then be accessed by 
5003        >node._depth
5004
5005        Parameters:
5006
5007        graph - (object) A <Graph>.
5008        id - (string) A starting node id for the BFS traversal.
5009        startDepth - (optional|number) A minimum depth value. Default's 0.
5010
5011     */
5012     computeLevels: function(graph, id, startDepth, flags) {
5013         startDepth = startDepth || 0;
5014         var filter = this.filter(flags);
5015         this.eachNode(graph, function(elem) {
5016             elem._flag = false;
5017             elem._depth = -1;
5018         }, flags);
5019         var root = graph.getNode(id);
5020         root._depth = startDepth;
5021         var queue = [root];
5022         while(queue.length != 0) {
5023             var node = queue.pop();
5024             node._flag = true;
5025             this.eachAdjacency(node, function(adj) {
5026                 var n = adj.nodeTo;
5027                 if(n._flag == false && filter(n)) {
5028                     if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5029                     queue.unshift(n);
5030                 }
5031             }, flags);
5032         }
5033     },
5034
5035     /*
5036        Method: eachBFS
5037     
5038        Performs a BFS traversal applying *action* to each <Graph.Node>.
5039        
5040        Also implemented by:
5041        
5042        <Graph>.
5043
5044        Parameters:
5045
5046        graph - (object) A <Graph>.
5047        id - (string) A starting node id for the BFS traversal.
5048        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5049
5050        Example:
5051        (start code js)
5052          $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5053           alert(node.name);
5054          });
5055          //or...
5056          graph.eachBFS('mynodeid', function(node) {
5057            alert(node.name);
5058          });
5059        (end code)
5060     */
5061     eachBFS: function(graph, id, action, flags) {
5062         var filter = this.filter(flags);
5063         this.clean(graph);
5064         var queue = [graph.getNode(id)];
5065         while(queue.length != 0) {
5066             var node = queue.pop();
5067             node._flag = true;
5068             action(node, node._depth);
5069             this.eachAdjacency(node, function(adj) {
5070                 var n = adj.nodeTo;
5071                 if(n._flag == false && filter(n)) {
5072                     n._flag = true;
5073                     queue.unshift(n);
5074                 }
5075             }, flags);
5076         }
5077     },
5078     
5079     /*
5080        Method: eachLevel
5081     
5082        Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5083        
5084        Also implemented by:
5085        
5086        <Graph.Node>.
5087
5088        Parameters:
5089        
5090        node - (object) A <Graph.Node>.
5091        levelBegin - (number) A relative level value.
5092        levelEnd - (number) A relative level value.
5093        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5094
5095     */
5096     eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5097         var d = node._depth, filter = this.filter(flags), that = this;
5098         levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5099         (function loopLevel(node, levelBegin, levelEnd) {
5100             var d = node._depth;
5101             if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5102             if(d < levelEnd) {
5103                 that.eachAdjacency(node, function(adj) {
5104                     var n = adj.nodeTo;
5105                     if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5106                 });
5107             }
5108         })(node, levelBegin + d, levelEnd + d);      
5109     },
5110
5111     /*
5112        Method: eachSubgraph
5113     
5114        Iterates over a node's children recursively.
5115        
5116        Also implemented by:
5117        
5118        <Graph.Node>.
5119
5120        Parameters:
5121        node - (object) A <Graph.Node>.
5122        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5123
5124        Example:
5125        (start code js)
5126          $jit.Graph.Util.eachSubgraph(node, function(node) {
5127            alert(node.name);
5128          });
5129          //or...
5130          node.eachSubgraph(function(node) {
5131            alert(node.name);
5132          });
5133        (end code)
5134     */
5135     eachSubgraph: function(node, action, flags) {
5136       this.eachLevel(node, 0, false, action, flags);
5137     },
5138
5139     /*
5140        Method: eachSubnode
5141     
5142        Iterates over a node's children (without deeper recursion).
5143        
5144        Also implemented by:
5145        
5146        <Graph.Node>.
5147        
5148        Parameters:
5149        node - (object) A <Graph.Node>.
5150        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5151
5152        Example:
5153        (start code js)
5154          $jit.Graph.Util.eachSubnode(node, function(node) {
5155           alert(node.name);
5156          });
5157          //or...
5158          node.eachSubnode(function(node) {
5159            alert(node.name);
5160          });
5161        (end code)
5162     */
5163     eachSubnode: function(node, action, flags) {
5164         this.eachLevel(node, 1, 1, action, flags);
5165     },
5166
5167     /*
5168        Method: anySubnode
5169     
5170        Returns *true* if any subnode matches the given condition.
5171        
5172        Also implemented by:
5173        
5174        <Graph.Node>.
5175
5176        Parameters:
5177        node - (object) A <Graph.Node>.
5178        cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5179
5180        Example:
5181        (start code js)
5182          $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5183          //or...
5184          node.anySubnode(function(node) { return node.name == 'mynodename'; });
5185        (end code)
5186     */
5187     anySubnode: function(node, cond, flags) {
5188       var flag = false;
5189       cond = cond || $.lambda(true);
5190       var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5191       this.eachSubnode(node, function(elem) {
5192         if(c(elem)) flag = true;
5193       }, flags);
5194       return flag;
5195     },
5196   
5197     /*
5198        Method: getSubnodes
5199     
5200        Collects all subnodes for a specified node. 
5201        The *level* parameter filters nodes having relative depth of *level* from the root node. 
5202        
5203        Also implemented by:
5204        
5205        <Graph.Node>.
5206
5207        Parameters:
5208        node - (object) A <Graph.Node>.
5209        level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5210
5211        Returns:
5212        An array of nodes.
5213
5214     */
5215     getSubnodes: function(node, level, flags) {
5216         var ans = [], that = this;
5217         level = level || 0;
5218         var levelStart, levelEnd;
5219         if($.type(level) == 'array') {
5220             levelStart = level[0];
5221             levelEnd = level[1];
5222         } else {
5223             levelStart = level;
5224             levelEnd = Number.MAX_VALUE - node._depth;
5225         }
5226         this.eachLevel(node, levelStart, levelEnd, function(n) {
5227             ans.push(n);
5228         }, flags);
5229         return ans;
5230     },
5231   
5232   
5233     /*
5234        Method: getParents
5235     
5236        Returns an Array of <Graph.Nodes> which are parents of the given node.
5237        
5238        Also implemented by:
5239        
5240        <Graph.Node>.
5241
5242        Parameters:
5243        node - (object) A <Graph.Node>.
5244
5245        Returns:
5246        An Array of <Graph.Nodes>.
5247
5248        Example:
5249        (start code js)
5250          var pars = $jit.Graph.Util.getParents(node);
5251          //or...
5252          var pars = node.getParents();
5253          
5254          if(pars.length > 0) {
5255            //do stuff with parents
5256          }
5257        (end code)
5258     */
5259     getParents: function(node) {
5260         var ans = [];
5261         this.eachAdjacency(node, function(adj) {
5262             var n = adj.nodeTo;
5263             if(n._depth < node._depth) ans.push(n);
5264         });
5265         return ans;
5266     },
5267     
5268     /*
5269     Method: isDescendantOf
5270  
5271     Returns a boolean indicating if some node is descendant of the node with the given id. 
5272
5273     Also implemented by:
5274     
5275     <Graph.Node>.
5276     
5277     
5278     Parameters:
5279     node - (object) A <Graph.Node>.
5280     id - (string) A <Graph.Node> id.
5281
5282     Example:
5283     (start code js)
5284       $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5285       //or...
5286       node.isDescendantOf('nodeid');//true|false
5287     (end code)
5288  */
5289  isDescendantOf: function(node, id) {
5290     if(node.id == id) return true;
5291     var pars = this.getParents(node), ans = false;
5292     for ( var i = 0; !ans && i < pars.length; i++) {
5293     ans = ans || this.isDescendantOf(pars[i], id);
5294   }
5295     return ans;
5296  },
5297
5298  /*
5299      Method: clean
5300   
5301      Cleans flags from nodes.
5302
5303      Also implemented by:
5304      
5305      <Graph>.
5306      
5307      Parameters:
5308      graph - A <Graph> instance.
5309   */
5310   clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5311   
5312   /* 
5313     Method: getClosestNodeToOrigin 
5314   
5315     Returns the closest node to the center of canvas.
5316   
5317     Also implemented by:
5318     
5319     <Graph>.
5320     
5321     Parameters:
5322    
5323      graph - (object) A <Graph> instance.
5324      prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5325   
5326   */
5327   getClosestNodeToOrigin: function(graph, prop, flags) {
5328    return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5329   },
5330   
5331   /* 
5332     Method: getClosestNodeToPos
5333   
5334     Returns the closest node to the given position.
5335   
5336     Also implemented by:
5337     
5338     <Graph>.
5339     
5340     Parameters:
5341    
5342      graph - (object) A <Graph> instance.
5343      pos - (object) A <Complex> or <Polar> instance.
5344      prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5345   
5346   */
5347   getClosestNodeToPos: function(graph, pos, prop, flags) {
5348    var node = null;
5349    prop = prop || 'current';
5350    pos = pos && pos.getc(true) || Complex.KER;
5351    var distance = function(a, b) {
5352      var d1 = a.x - b.x, d2 = a.y - b.y;
5353      return d1 * d1 + d2 * d2;
5354    };
5355    this.eachNode(graph, function(elem) {
5356      node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5357          node.getPos(prop).getc(true), pos)) ? elem : node;
5358    }, flags);
5359    return node;
5360   } 
5361 };
5362
5363 //Append graph methods to <Graph>
5364 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5365   Graph.prototype[m] = function() {
5366     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5367   };
5368 });
5369
5370 //Append node methods to <Graph.Node>
5371 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5372   Graph.Node.prototype[m] = function() {
5373     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5374   };
5375 });
5376
5377 /*
5378  * File: Graph.Op.js
5379  *
5380 */
5381
5382 /*
5383    Object: Graph.Op
5384
5385    Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>, 
5386    morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5387
5388 */
5389 Graph.Op = {
5390
5391     options: {
5392       type: 'nothing',
5393       duration: 2000,
5394       hideLabels: true,
5395       fps:30
5396     },
5397     
5398     initialize: function(viz) {
5399       this.viz = viz;
5400     },
5401
5402     /*
5403        Method: removeNode
5404     
5405        Removes one or more <Graph.Nodes> from the visualization. 
5406        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5407
5408        Parameters:
5409     
5410         node - (string|array) The node's id. Can also be an array having many ids.
5411         opt - (object) Animation options. It's an object with optional properties described below
5412         type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5413         duration - Described in <Options.Fx>.
5414         fps - Described in <Options.Fx>.
5415         transition - Described in <Options.Fx>.
5416         hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5417    
5418       Example:
5419       (start code js)
5420         var viz = new $jit.Viz(options);
5421         viz.op.removeNode('nodeId', {
5422           type: 'fade:seq',
5423           duration: 1000,
5424           hideLabels: false,
5425           transition: $jit.Trans.Quart.easeOut
5426         });
5427         //or also
5428         viz.op.removeNode(['someId', 'otherId'], {
5429           type: 'fade:con',
5430           duration: 1500
5431         });
5432       (end code)
5433     */
5434   
5435     removeNode: function(node, opt) {
5436         var viz = this.viz;
5437         var options = $.merge(this.options, viz.controller, opt);
5438         var n = $.splat(node);
5439         var i, that, nodeObj;
5440         switch(options.type) {
5441             case 'nothing':
5442                 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5443                 break;
5444             
5445             case 'replot':
5446                 this.removeNode(n, { type: 'nothing' });
5447                 viz.labels.clearLabels();
5448                 viz.refresh(true);
5449                 break;
5450             
5451             case 'fade:seq': case 'fade':
5452                 that = this;
5453                 //set alpha to 0 for nodes to remove.
5454                 for(i=0; i<n.length; i++) {
5455                     nodeObj = viz.graph.getNode(n[i]);
5456                     nodeObj.setData('alpha', 0, 'end');
5457                 }
5458                 viz.fx.animate($.merge(options, {
5459                     modes: ['node-property:alpha'],
5460                     onComplete: function() {
5461                         that.removeNode(n, { type: 'nothing' });
5462                         viz.labels.clearLabels();
5463                         viz.reposition();
5464                         viz.fx.animate($.merge(options, {
5465                             modes: ['linear']
5466                         }));
5467                     }
5468                 }));
5469                 break;
5470             
5471             case 'fade:con':
5472                 that = this;
5473                 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5474                 for(i=0; i<n.length; i++) {
5475                     nodeObj = viz.graph.getNode(n[i]);
5476                     nodeObj.setData('alpha', 0, 'end');
5477                     nodeObj.ignore = true;
5478                 }
5479                 viz.reposition();
5480                 viz.fx.animate($.merge(options, {
5481                     modes: ['node-property:alpha', 'linear'],
5482                     onComplete: function() {
5483                         that.removeNode(n, { type: 'nothing' });
5484                     }
5485                 }));
5486                 break;
5487             
5488             case 'iter':
5489                 that = this;
5490                 viz.fx.sequence({
5491                     condition: function() { return n.length != 0; },
5492                     step: function() { that.removeNode(n.shift(), { type: 'nothing' });  viz.labels.clearLabels(); },
5493                     onComplete: function() { options.onComplete(); },
5494                     duration: Math.ceil(options.duration / n.length)
5495                 });
5496                 break;
5497                 
5498             default: this.doError();
5499         }
5500     },
5501     
5502     /*
5503        Method: removeEdge
5504     
5505        Removes one or more <Graph.Adjacences> from the visualization. 
5506        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5507
5508        Parameters:
5509     
5510        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'], ...]).
5511        opt - (object) Animation options. It's an object with optional properties described below
5512        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5513        duration - Described in <Options.Fx>.
5514        fps - Described in <Options.Fx>.
5515        transition - Described in <Options.Fx>.
5516        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5517    
5518       Example:
5519       (start code js)
5520         var viz = new $jit.Viz(options);
5521         viz.op.removeEdge(['nodeId', 'otherId'], {
5522           type: 'fade:seq',
5523           duration: 1000,
5524           hideLabels: false,
5525           transition: $jit.Trans.Quart.easeOut
5526         });
5527         //or also
5528         viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5529           type: 'fade:con',
5530           duration: 1500
5531         });
5532       (end code)
5533     
5534     */
5535     removeEdge: function(vertex, opt) {
5536         var viz = this.viz;
5537         var options = $.merge(this.options, viz.controller, opt);
5538         var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5539         var i, that, adj;
5540         switch(options.type) {
5541             case 'nothing':
5542                 for(i=0; i<v.length; i++)   viz.graph.removeAdjacence(v[i][0], v[i][1]);
5543                 break;
5544             
5545             case 'replot':
5546                 this.removeEdge(v, { type: 'nothing' });
5547                 viz.refresh(true);
5548                 break;
5549             
5550             case 'fade:seq': case 'fade':
5551                 that = this;
5552                 //set alpha to 0 for edges to remove.
5553                 for(i=0; i<v.length; i++) {
5554                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5555                     if(adj) {
5556                         adj.setData('alpha', 0,'end');
5557                     }
5558                 }
5559                 viz.fx.animate($.merge(options, {
5560                     modes: ['edge-property:alpha'],
5561                     onComplete: function() {
5562                         that.removeEdge(v, { type: 'nothing' });
5563                         viz.reposition();
5564                         viz.fx.animate($.merge(options, {
5565                             modes: ['linear']
5566                         }));
5567                     }
5568                 }));
5569                 break;
5570             
5571             case 'fade:con':
5572                 that = this;
5573                 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5574                 for(i=0; i<v.length; i++) {
5575                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5576                     if(adj) {
5577                         adj.setData('alpha',0 ,'end');
5578                         adj.ignore = true;
5579                     }
5580                 }
5581                 viz.reposition();
5582                 viz.fx.animate($.merge(options, {
5583                     modes: ['edge-property:alpha', 'linear'],
5584                     onComplete: function() {
5585                         that.removeEdge(v, { type: 'nothing' });
5586                     }
5587                 }));
5588                 break;
5589             
5590             case 'iter':
5591                 that = this;
5592                 viz.fx.sequence({
5593                     condition: function() { return v.length != 0; },
5594                     step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5595                     onComplete: function() { options.onComplete(); },
5596                     duration: Math.ceil(options.duration / v.length)
5597                 });
5598                 break;
5599                 
5600             default: this.doError();
5601         }
5602     },
5603     
5604     /*
5605        Method: sum
5606     
5607        Adds a new graph to the visualization. 
5608        The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization. 
5609        The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5610
5611        Parameters:
5612     
5613        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5614        opt - (object) Animation options. It's an object with optional properties described below
5615        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con".
5616        duration - Described in <Options.Fx>.
5617        fps - Described in <Options.Fx>.
5618        transition - Described in <Options.Fx>.
5619        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5620    
5621       Example:
5622       (start code js)
5623         //...json contains a tree or graph structure...
5624
5625         var viz = new $jit.Viz(options);
5626         viz.op.sum(json, {
5627           type: 'fade:seq',
5628           duration: 1000,
5629           hideLabels: false,
5630           transition: $jit.Trans.Quart.easeOut
5631         });
5632         //or also
5633         viz.op.sum(json, {
5634           type: 'fade:con',
5635           duration: 1500
5636         });
5637       (end code)
5638     
5639     */
5640     sum: function(json, opt) {
5641         var viz = this.viz;
5642         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5643         var graph;
5644         viz.root = opt.id || viz.root;
5645         switch(options.type) {
5646             case 'nothing':
5647                 graph = viz.construct(json);
5648                 graph.eachNode(function(elem) {
5649                     elem.eachAdjacency(function(adj) {
5650                         viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5651                     });
5652                 });
5653                 break;
5654             
5655             case 'replot':
5656                 viz.refresh(true);
5657                 this.sum(json, { type: 'nothing' });
5658                 viz.refresh(true);
5659                 break;
5660             
5661             case 'fade:seq': case 'fade': case 'fade:con':
5662                 that = this;
5663                 graph = viz.construct(json);
5664
5665                 //set alpha to 0 for nodes to add.
5666                 var fadeEdges = this.preprocessSum(graph);
5667                 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5668                 viz.reposition();
5669                 if(options.type != 'fade:con') {
5670                     viz.fx.animate($.merge(options, {
5671                         modes: ['linear'],
5672                         onComplete: function() {
5673                             viz.fx.animate($.merge(options, {
5674                                 modes: modes,
5675                                 onComplete: function() {
5676                                     options.onComplete();
5677                                 }
5678                             }));
5679                         }
5680                     }));
5681                 } else {
5682                     viz.graph.eachNode(function(elem) {
5683                         if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5684                           elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5685                         }
5686                     });
5687                     viz.fx.animate($.merge(options, {
5688                         modes: ['linear'].concat(modes)
5689                     }));
5690                 }
5691                 break;
5692
5693             default: this.doError();
5694         }
5695     },
5696     
5697     /*
5698        Method: morph
5699     
5700        This method will transform the current visualized graph into the new JSON representation passed in the method. 
5701        The JSON object must at least have the root node in common with the current visualized graph.
5702
5703        Parameters:
5704     
5705        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5706        opt - (object) Animation options. It's an object with optional properties described below
5707        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5708        duration - Described in <Options.Fx>.
5709        fps - Described in <Options.Fx>.
5710        transition - Described in <Options.Fx>.
5711        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5712        id - (string) The shared <Graph.Node> id between both graphs.
5713        
5714        extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to 
5715                     *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation. 
5716                     For animating these extra-parameters you have to specify an object that has animation groups as keys and animation 
5717                     properties as values, just like specified in <Graph.Plot.animate>.
5718    
5719       Example:
5720       (start code js)
5721         //...json contains a tree or graph structure...
5722
5723         var viz = new $jit.Viz(options);
5724         viz.op.morph(json, {
5725           type: 'fade',
5726           duration: 1000,
5727           hideLabels: false,
5728           transition: $jit.Trans.Quart.easeOut
5729         });
5730         //or also
5731         viz.op.morph(json, {
5732           type: 'fade',
5733           duration: 1500
5734         });
5735         //if the json data contains dollar prefixed params
5736         //like $width or $height these too can be animated
5737         viz.op.morph(json, {
5738           type: 'fade',
5739           duration: 1500
5740         }, {
5741           'node-property': ['width', 'height']
5742         });
5743       (end code)
5744     
5745     */
5746     morph: function(json, opt, extraModes) {
5747         var viz = this.viz;
5748         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5749         var graph;
5750         //TODO(nico) this hack makes morphing work with the Hypertree. 
5751         //Need to check if it has been solved and this can be removed.
5752         viz.root = opt.id || viz.root;
5753         switch(options.type) {
5754             case 'nothing':
5755                 graph = viz.construct(json);
5756                 graph.eachNode(function(elem) {
5757                   var nodeExists = viz.graph.hasNode(elem.id);  
5758                   elem.eachAdjacency(function(adj) {
5759                     var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5760                     viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5761                     //Update data properties if the node existed
5762                     if(adjExists) {
5763                       var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5764                       for(var prop in (adj.data || {})) {
5765                         addedAdj.data[prop] = adj.data[prop];
5766                       }
5767                     }
5768                   });
5769                   //Update data properties if the node existed
5770                   if(nodeExists) {
5771                     var addedNode = viz.graph.getNode(elem.id);
5772                     for(var prop in (elem.data || {})) {
5773                       addedNode.data[prop] = elem.data[prop];
5774                     }
5775                   }
5776                 });
5777                 viz.graph.eachNode(function(elem) {
5778                     elem.eachAdjacency(function(adj) {
5779                         if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5780                             viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5781                         }
5782                     });
5783                     if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5784                 });
5785                 
5786                 break;
5787             
5788             case 'replot':
5789                 viz.labels.clearLabels(true);
5790                 this.morph(json, { type: 'nothing' });
5791                 viz.refresh(true);
5792                 viz.refresh(true);
5793                 break;
5794                 
5795             case 'fade:seq': case 'fade': case 'fade:con':
5796                 that = this;
5797                 graph = viz.construct(json);
5798                 //preprocessing for nodes to delete.
5799                 //get node property modes to interpolate
5800                 var nodeModes = extraModes && ('node-property' in extraModes) 
5801                   && $.map($.splat(extraModes['node-property']), 
5802                       function(n) { return '$' + n; });
5803                 viz.graph.eachNode(function(elem) {
5804                   var graphNode = graph.getNode(elem.id);   
5805                   if(!graphNode) {
5806                       elem.setData('alpha', 1);
5807                       elem.setData('alpha', 1, 'start');
5808                       elem.setData('alpha', 0, 'end');
5809                       elem.ignore = true;
5810                     } else {
5811                       //Update node data information
5812                       var graphNodeData = graphNode.data;
5813                       for(var prop in graphNodeData) {
5814                         if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5815                           elem.endData[prop] = graphNodeData[prop];
5816                         } else {
5817                           elem.data[prop] = graphNodeData[prop];
5818                         }
5819                       }
5820                     }
5821                 }); 
5822                 viz.graph.eachNode(function(elem) {
5823                     if(elem.ignore) return;
5824                     elem.eachAdjacency(function(adj) {
5825                         if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5826                         var nodeFrom = graph.getNode(adj.nodeFrom.id);
5827                         var nodeTo = graph.getNode(adj.nodeTo.id);
5828                         if(!nodeFrom.adjacentTo(nodeTo)) {
5829                             var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5830                             fadeEdges = true;
5831                             adj.setData('alpha', 1);
5832                             adj.setData('alpha', 1, 'start');
5833                             adj.setData('alpha', 0, 'end');
5834                         }
5835                     });
5836                 }); 
5837                 //preprocessing for adding nodes.
5838                 var fadeEdges = this.preprocessSum(graph);
5839
5840                 var modes = !fadeEdges? ['node-property:alpha'] : 
5841                                         ['node-property:alpha', 
5842                                          'edge-property:alpha'];
5843                 //Append extra node-property animations (if any)
5844                 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))? 
5845                     (':' + $.splat(extraModes['node-property']).join(':')) : '');
5846                 //Append extra edge-property animations (if any)
5847                 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))? 
5848                     (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5849                 //Add label-property animations (if any)
5850                 if(extraModes && ('label-property' in extraModes)) {
5851                   modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5852                 }
5853                 viz.reposition();
5854                 viz.graph.eachNode(function(elem) {
5855                     if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5856                       elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5857                     }
5858                 });
5859                 viz.fx.animate($.merge(options, {
5860                     modes: ['polar'].concat(modes),
5861                     onComplete: function() {
5862                         viz.graph.eachNode(function(elem) {
5863                             if(elem.ignore) viz.graph.removeNode(elem.id);
5864                         });
5865                         viz.graph.eachNode(function(elem) {
5866                             elem.eachAdjacency(function(adj) {
5867                                 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5868                             });
5869                         });
5870                         options.onComplete();
5871                     }
5872                 }));
5873                 break;
5874
5875             default:;
5876         }
5877     },
5878
5879     
5880   /*
5881     Method: contract
5882  
5883     Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5884     
5885     Parameters:
5886  
5887     node - (object) A <Graph.Node>.
5888     opt - (object) An object containing options described below
5889     type - (string) Whether to 'replot' or 'animate' the contraction.
5890    
5891     There are also a number of Animation options. For more information see <Options.Fx>.
5892
5893     Example:
5894     (start code js)
5895      var viz = new $jit.Viz(options);
5896      viz.op.contract(node, {
5897        type: 'animate',
5898        duration: 1000,
5899        hideLabels: true,
5900        transition: $jit.Trans.Quart.easeOut
5901      });
5902    (end code)
5903  
5904    */
5905     contract: function(node, opt) {
5906       var viz = this.viz;
5907       if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5908       opt = $.merge(this.options, viz.config, opt || {}, {
5909         'modes': ['node-property:alpha:span', 'linear']
5910       });
5911       node.collapsed = true;
5912       (function subn(n) {
5913         n.eachSubnode(function(ch) {
5914           ch.ignore = true;
5915           ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5916           subn(ch);
5917         });
5918       })(node);
5919       if(opt.type == 'animate') {
5920         viz.compute('end');
5921         if(viz.rotated) {
5922           viz.rotate(viz.rotated, 'none', {
5923             'property':'end'
5924           });
5925         }
5926         (function subn(n) {
5927           n.eachSubnode(function(ch) {
5928             ch.setPos(node.getPos('end'), 'end');
5929             subn(ch);
5930           });
5931         })(node);
5932         viz.fx.animate(opt);
5933       } else if(opt.type == 'replot'){
5934         viz.refresh();
5935       }
5936     },
5937     
5938     /*
5939     Method: expand
5940  
5941     Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5942     
5943     Parameters:
5944  
5945     node - (object) A <Graph.Node>.
5946     opt - (object) An object containing options described below
5947     type - (string) Whether to 'replot' or 'animate'.
5948      
5949     There are also a number of Animation options. For more information see <Options.Fx>.
5950
5951     Example:
5952     (start code js)
5953       var viz = new $jit.Viz(options);
5954       viz.op.expand(node, {
5955         type: 'animate',
5956         duration: 1000,
5957         hideLabels: true,
5958         transition: $jit.Trans.Quart.easeOut
5959       });
5960     (end code)
5961  
5962    */
5963     expand: function(node, opt) {
5964       if(!('collapsed' in node)) return;
5965       var viz = this.viz;
5966       opt = $.merge(this.options, viz.config, opt || {}, {
5967         'modes': ['node-property:alpha:span', 'linear']
5968       });
5969       delete node.collapsed;
5970       (function subn(n) {
5971         n.eachSubnode(function(ch) {
5972           delete ch.ignore;
5973           ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5974           subn(ch);
5975         });
5976       })(node);
5977       if(opt.type == 'animate') {
5978         viz.compute('end');
5979         if(viz.rotated) {
5980           viz.rotate(viz.rotated, 'none', {
5981             'property':'end'
5982           });
5983         }
5984         viz.fx.animate(opt);
5985       } else if(opt.type == 'replot'){
5986         viz.refresh();
5987       }
5988     },
5989
5990     preprocessSum: function(graph) {
5991         var viz = this.viz;
5992         graph.eachNode(function(elem) {
5993             if(!viz.graph.hasNode(elem.id)) {
5994                 viz.graph.addNode(elem);
5995                 var n = viz.graph.getNode(elem.id);
5996                 n.setData('alpha', 0);
5997                 n.setData('alpha', 0, 'start');
5998                 n.setData('alpha', 1, 'end');
5999             }
6000         }); 
6001         var fadeEdges = false;
6002         graph.eachNode(function(elem) {
6003             elem.eachAdjacency(function(adj) {
6004                 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6005                 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6006                 if(!nodeFrom.adjacentTo(nodeTo)) {
6007                     var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6008                     if(nodeFrom.startAlpha == nodeFrom.endAlpha 
6009                     && nodeTo.startAlpha == nodeTo.endAlpha) {
6010                         fadeEdges = true;
6011                         adj.setData('alpha', 0);
6012                         adj.setData('alpha', 0, 'start');
6013                         adj.setData('alpha', 1, 'end');
6014                     } 
6015                 }
6016             });
6017         }); 
6018         return fadeEdges;
6019     }
6020 };
6021
6022
6023
6024 /*
6025    File: Helpers.js
6026  
6027    Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6028    Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6029    position is over the rendered shape.
6030    
6031    Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and 
6032    *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6033    
6034    Example:
6035    (start code js)
6036    //implement a new node type
6037    $jit.Viz.Plot.NodeTypes.implement({
6038      'customNodeType': {
6039        'render': function(node, canvas) {
6040          this.nodeHelper.circle.render ...
6041        },
6042        'contains': function(node, pos) {
6043          this.nodeHelper.circle.contains ...
6044        }
6045      }
6046    });
6047    //implement an edge type
6048    $jit.Viz.Plot.EdgeTypes.implement({
6049      'customNodeType': {
6050        'render': function(node, canvas) {
6051          this.edgeHelper.circle.render ...
6052        },
6053        //optional
6054        'contains': function(node, pos) {
6055          this.edgeHelper.circle.contains ...
6056        }
6057      }
6058    });
6059    (end code)
6060
6061 */
6062
6063 /*
6064    Object: NodeHelper
6065    
6066    Contains rendering and other type of primitives for simple shapes.
6067  */
6068 var NodeHelper = {
6069   'none': {
6070     'render': $.empty,
6071     'contains': $.lambda(false)
6072   },
6073   /*
6074    Object: NodeHelper.circle
6075    */
6076   'circle': {
6077     /*
6078      Method: render
6079      
6080      Renders a circle into the canvas.
6081      
6082      Parameters:
6083      
6084      type - (string) Possible options are 'fill' or 'stroke'.
6085      pos - (object) An *x*, *y* object with the position of the center of the circle.
6086      radius - (number) The radius of the circle to be rendered.
6087      canvas - (object) A <Canvas> instance.
6088      
6089      Example:
6090      (start code js)
6091      NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6092      (end code)
6093      */
6094     'render': function(type, pos, radius, canvas){
6095       var ctx = canvas.getCtx();
6096       ctx.beginPath();
6097       ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6098       ctx.closePath();
6099       ctx[type]();
6100     },
6101     /*
6102     Method: contains
6103     
6104     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6105     
6106     Parameters:
6107     
6108     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6109     pos - (object) An *x*, *y* object with the position to check.
6110     radius - (number) The radius of the rendered circle.
6111     
6112     Example:
6113     (start code js)
6114     NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6115     (end code)
6116     */
6117     'contains': function(npos, pos, radius){
6118       var diffx = npos.x - pos.x, 
6119           diffy = npos.y - pos.y, 
6120           diff = diffx * diffx + diffy * diffy;
6121       return diff <= radius * radius;
6122     }
6123   },
6124   /*
6125   Object: NodeHelper.ellipse
6126   */
6127   'ellipse': {
6128     /*
6129     Method: render
6130     
6131     Renders an ellipse into the canvas.
6132     
6133     Parameters:
6134     
6135     type - (string) Possible options are 'fill' or 'stroke'.
6136     pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6137     width - (number) The width of the ellipse.
6138     height - (number) The height of the ellipse.
6139     canvas - (object) A <Canvas> instance.
6140     
6141     Example:
6142     (start code js)
6143     NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6144     (end code)
6145     */
6146     'render': function(type, pos, width, height, canvas){
6147       var ctx = canvas.getCtx();
6148       height /= 2;
6149       width /= 2;
6150       ctx.save();
6151       ctx.scale(width / height, height / width);
6152       ctx.beginPath();
6153       ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6154           Math.PI * 2, true);
6155       ctx.closePath();
6156       ctx[type]();
6157       ctx.restore();
6158     },
6159     /*
6160     Method: contains
6161     
6162     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6163     
6164     Parameters:
6165     
6166     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6167     pos - (object) An *x*, *y* object with the position to check.
6168     width - (number) The width of the rendered ellipse.
6169     height - (number) The height of the rendered ellipse.
6170     
6171     Example:
6172     (start code js)
6173     NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6174     (end code)
6175     */
6176     'contains': function(npos, pos, width, height){
6177       // TODO(nico): be more precise...
6178       width /= 2; 
6179       height /= 2;
6180       var dist = (width + height) / 2, 
6181           diffx = npos.x - pos.x, 
6182           diffy = npos.y - pos.y, 
6183           diff = diffx * diffx + diffy * diffy;
6184       return diff <= dist * dist;
6185     }
6186   },
6187   /*
6188   Object: NodeHelper.square
6189   */
6190   'square': {
6191     /*
6192     Method: render
6193     
6194     Renders a square into the canvas.
6195     
6196     Parameters:
6197     
6198     type - (string) Possible options are 'fill' or 'stroke'.
6199     pos - (object) An *x*, *y* object with the position of the center of the square.
6200     dim - (number) The radius (or half-diameter) of the square.
6201     canvas - (object) A <Canvas> instance.
6202     
6203     Example:
6204     (start code js)
6205     NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6206     (end code)
6207     */
6208     'render': function(type, pos, dim, canvas){
6209       canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6210     },
6211     /*
6212     Method: contains
6213     
6214     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6215     
6216     Parameters:
6217     
6218     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6219     pos - (object) An *x*, *y* object with the position to check.
6220     dim - (number) The radius (or half-diameter) of the square.
6221     
6222     Example:
6223     (start code js)
6224     NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6225     (end code)
6226     */
6227     'contains': function(npos, pos, dim){
6228       return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6229     }
6230   },
6231   /*
6232   Object: NodeHelper.rectangle
6233   */
6234   'rectangle': {
6235     /*
6236     Method: render
6237     
6238     Renders a rectangle into the canvas.
6239     
6240     Parameters:
6241     
6242     type - (string) Possible options are 'fill' or 'stroke'.
6243     pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6244     width - (number) The width of the rectangle.
6245     height - (number) The height of the rectangle.
6246     canvas - (object) A <Canvas> instance.
6247     
6248     Example:
6249     (start code js)
6250     NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6251     (end code)
6252     */
6253     'render': function(type, pos, width, height, canvas){
6254       canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2, 
6255                                       width, height);
6256     },
6257     /*
6258     Method: contains
6259     
6260     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6261     
6262     Parameters:
6263     
6264     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6265     pos - (object) An *x*, *y* object with the position to check.
6266     width - (number) The width of the rendered rectangle.
6267     height - (number) The height of the rendered rectangle.
6268     
6269     Example:
6270     (start code js)
6271     NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6272     (end code)
6273     */
6274     'contains': function(npos, pos, width, height){
6275       return Math.abs(pos.x - npos.x) <= width / 2
6276           && Math.abs(pos.y - npos.y) <= height / 2;
6277     }
6278   },
6279   /*
6280   Object: NodeHelper.triangle
6281   */
6282   'triangle': {
6283     /*
6284     Method: render
6285     
6286     Renders a triangle into the canvas.
6287     
6288     Parameters:
6289     
6290     type - (string) Possible options are 'fill' or 'stroke'.
6291     pos - (object) An *x*, *y* object with the position of the center of the triangle.
6292     dim - (number) The dimension of the triangle.
6293     canvas - (object) A <Canvas> instance.
6294     
6295     Example:
6296     (start code js)
6297     NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6298     (end code)
6299     */
6300     'render': function(type, pos, dim, canvas){
6301       var ctx = canvas.getCtx(), 
6302           c1x = pos.x, 
6303           c1y = pos.y - dim, 
6304           c2x = c1x - dim, 
6305           c2y = pos.y + dim, 
6306           c3x = c1x + dim, 
6307           c3y = c2y;
6308       ctx.beginPath();
6309       ctx.moveTo(c1x, c1y);
6310       ctx.lineTo(c2x, c2y);
6311       ctx.lineTo(c3x, c3y);
6312       ctx.closePath();
6313       ctx[type]();
6314     },
6315     /*
6316     Method: contains
6317     
6318     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6319     
6320     Parameters:
6321     
6322     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6323     pos - (object) An *x*, *y* object with the position to check.
6324     dim - (number) The dimension of the shape.
6325     
6326     Example:
6327     (start code js)
6328     NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6329     (end code)
6330     */
6331     'contains': function(npos, pos, dim) {
6332       return NodeHelper.circle.contains(npos, pos, dim);
6333     }
6334   },
6335   /*
6336   Object: NodeHelper.star
6337   */
6338   'star': {
6339     /*
6340     Method: render
6341     
6342     Renders a star into the canvas.
6343     
6344     Parameters:
6345     
6346     type - (string) Possible options are 'fill' or 'stroke'.
6347     pos - (object) An *x*, *y* object with the position of the center of the star.
6348     dim - (number) The dimension of the star.
6349     canvas - (object) A <Canvas> instance.
6350     
6351     Example:
6352     (start code js)
6353     NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6354     (end code)
6355     */
6356     'render': function(type, pos, dim, canvas){
6357       var ctx = canvas.getCtx(), 
6358           pi5 = Math.PI / 5;
6359       ctx.save();
6360       ctx.translate(pos.x, pos.y);
6361       ctx.beginPath();
6362       ctx.moveTo(dim, 0);
6363       for (var i = 0; i < 9; i++) {
6364         ctx.rotate(pi5);
6365         if (i % 2 == 0) {
6366           ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6367         } else {
6368           ctx.lineTo(dim, 0);
6369         }
6370       }
6371       ctx.closePath();
6372       ctx[type]();
6373       ctx.restore();
6374     },
6375     /*
6376     Method: contains
6377     
6378     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6379     
6380     Parameters:
6381     
6382     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6383     pos - (object) An *x*, *y* object with the position to check.
6384     dim - (number) The dimension of the shape.
6385     
6386     Example:
6387     (start code js)
6388     NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6389     (end code)
6390     */
6391     'contains': function(npos, pos, dim) {
6392       return NodeHelper.circle.contains(npos, pos, dim);
6393     }
6394   }
6395 };
6396
6397 /*
6398   Object: EdgeHelper
6399   
6400   Contains rendering primitives for simple edge shapes.
6401 */
6402 var EdgeHelper = {
6403   /*
6404     Object: EdgeHelper.line
6405   */
6406   'line': {
6407       /*
6408       Method: render
6409       
6410       Renders a line into the canvas.
6411       
6412       Parameters:
6413       
6414       from - (object) An *x*, *y* object with the starting position of the line.
6415       to - (object) An *x*, *y* object with the ending position of the line.
6416       canvas - (object) A <Canvas> instance.
6417       
6418       Example:
6419       (start code js)
6420       EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6421       (end code)
6422       */
6423       'render': function(from, to, canvas){
6424         var ctx = canvas.getCtx();
6425         ctx.beginPath();
6426         ctx.moveTo(from.x, from.y);
6427         ctx.lineTo(to.x, to.y);
6428         ctx.stroke();
6429       },
6430       /*
6431       Method: contains
6432       
6433       Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6434       
6435       Parameters:
6436       
6437       posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6438       posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6439       pos - (object) An *x*, *y* object with the position to check.
6440       epsilon - (number) The dimension of the shape.
6441       
6442       Example:
6443       (start code js)
6444       EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6445       (end code)
6446       */
6447       'contains': function(posFrom, posTo, pos, epsilon) {
6448         var min = Math.min, 
6449             max = Math.max,
6450             minPosX = min(posFrom.x, posTo.x),
6451             maxPosX = max(posFrom.x, posTo.x),
6452             minPosY = min(posFrom.y, posTo.y),
6453             maxPosY = max(posFrom.y, posTo.y);
6454         
6455         if(pos.x >= minPosX && pos.x <= maxPosX 
6456             && pos.y >= minPosY && pos.y <= maxPosY) {
6457           if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6458             return true;
6459           }
6460           var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6461           return Math.abs(dist - pos.y) <= epsilon;
6462         }
6463         return false;
6464       }
6465     },
6466   /*
6467     Object: EdgeHelper.arrow
6468   */
6469   'arrow': {
6470       /*
6471       Method: render
6472       
6473       Renders an arrow into the canvas.
6474       
6475       Parameters:
6476       
6477       from - (object) An *x*, *y* object with the starting position of the arrow.
6478       to - (object) An *x*, *y* object with the ending position of the arrow.
6479       dim - (number) The dimension of the arrow.
6480       swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6481       canvas - (object) A <Canvas> instance.
6482       
6483       Example:
6484       (start code js)
6485       EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6486       (end code)
6487       */
6488     'render': function(from, to, dim, swap, canvas){
6489         var ctx = canvas.getCtx();
6490         // invert edge direction
6491         if (swap) {
6492           var tmp = from;
6493           from = to;
6494           to = tmp;
6495         }
6496         var vect = new Complex(to.x - from.x, to.y - from.y);
6497         vect.$scale(dim / vect.norm());
6498         var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6499             normal = new Complex(-vect.y / 2, vect.x / 2),
6500             v1 = intermediatePoint.add(normal), 
6501             v2 = intermediatePoint.$add(normal.$scale(-1));
6502         
6503         ctx.beginPath();
6504         ctx.moveTo(from.x, from.y);
6505         ctx.lineTo(to.x, to.y);
6506         ctx.stroke();
6507         ctx.beginPath();
6508         ctx.moveTo(v1.x, v1.y);
6509         ctx.lineTo(v2.x, v2.y);
6510         ctx.lineTo(to.x, to.y);
6511         ctx.closePath();
6512         ctx.fill();
6513     },
6514     /*
6515     Method: contains
6516     
6517     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6518     
6519     Parameters:
6520     
6521     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6522     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6523     pos - (object) An *x*, *y* object with the position to check.
6524     epsilon - (number) The dimension of the shape.
6525     
6526     Example:
6527     (start code js)
6528     EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6529     (end code)
6530     */
6531     'contains': function(posFrom, posTo, pos, epsilon) {
6532       return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6533     }
6534   },
6535   /*
6536     Object: EdgeHelper.hyperline
6537   */
6538   'hyperline': {
6539     /*
6540     Method: render
6541     
6542     Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6543     
6544     Parameters:
6545     
6546     from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6547     to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6548     r - (number) The scaling factor.
6549     canvas - (object) A <Canvas> instance.
6550     
6551     Example:
6552     (start code js)
6553     EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6554     (end code)
6555     */
6556     'render': function(from, to, r, canvas){
6557       var ctx = canvas.getCtx();  
6558       var centerOfCircle = computeArcThroughTwoPoints(from, to);
6559       if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6560           || centerOfCircle.ratio < 0) {
6561         ctx.beginPath();
6562         ctx.moveTo(from.x * r, from.y * r);
6563         ctx.lineTo(to.x * r, to.y * r);
6564         ctx.stroke();
6565       } else {
6566         var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6567             - centerOfCircle.x);
6568         var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6569             - centerOfCircle.x);
6570         var sense = sense(angleBegin, angleEnd);
6571         ctx.beginPath();
6572         ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6573             * r, angleBegin, angleEnd, sense);
6574         ctx.stroke();
6575       }
6576       /*      
6577         Calculates the arc parameters through two points.
6578         
6579         More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane> 
6580       
6581         Parameters:
6582       
6583         p1 - A <Complex> instance.
6584         p2 - A <Complex> instance.
6585         scale - The Disk's diameter.
6586       
6587         Returns:
6588       
6589         An object containing some arc properties.
6590       */
6591       function computeArcThroughTwoPoints(p1, p2){
6592         var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6593         var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6594         // Fall back to a straight line
6595         if (aDen == 0)
6596           return {
6597             x: 0,
6598             y: 0,
6599             ratio: -1
6600           };
6601     
6602         var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6603         var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6604         var x = -a / 2;
6605         var y = -b / 2;
6606         var squaredRatio = (a * a + b * b) / 4 - 1;
6607         // Fall back to a straight line
6608         if (squaredRatio < 0)
6609           return {
6610             x: 0,
6611             y: 0,
6612             ratio: -1
6613           };
6614         var ratio = Math.sqrt(squaredRatio);
6615         var out = {
6616           x: x,
6617           y: y,
6618           ratio: ratio > 1000? -1 : ratio,
6619           a: a,
6620           b: b
6621         };
6622     
6623         return out;
6624       }
6625       /*      
6626         Sets angle direction to clockwise (true) or counterclockwise (false). 
6627          
6628         Parameters: 
6629       
6630            angleBegin - Starting angle for drawing the arc. 
6631            angleEnd - The HyperLine will be drawn from angleBegin to angleEnd. 
6632       
6633         Returns: 
6634       
6635            A Boolean instance describing the sense for drawing the HyperLine. 
6636       */
6637       function sense(angleBegin, angleEnd){
6638         return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6639             : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6640       }
6641     },
6642     /*
6643     Method: contains
6644     
6645     Not Implemented
6646     
6647     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6648     
6649     Parameters:
6650     
6651     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6652     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6653     pos - (object) An *x*, *y* object with the position to check.
6654     epsilon - (number) The dimension of the shape.
6655     
6656     Example:
6657     (start code js)
6658     EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6659     (end code)
6660     */
6661     'contains': $.lambda(false)
6662   }
6663 };
6664
6665
6666 /*
6667  * File: Graph.Plot.js
6668  */
6669
6670 /*
6671    Object: Graph.Plot
6672
6673    <Graph> rendering and animation methods.
6674    
6675    Properties:
6676    
6677    nodeHelper - <NodeHelper> object.
6678    edgeHelper - <EdgeHelper> object.
6679 */
6680 Graph.Plot = {
6681     //Default intializer
6682     initialize: function(viz, klass){
6683       this.viz = viz;
6684       this.config = viz.config;
6685       this.node = viz.config.Node;
6686       this.edge = viz.config.Edge;
6687       this.animation = new Animation;
6688       this.nodeTypes = new klass.Plot.NodeTypes;
6689       this.edgeTypes = new klass.Plot.EdgeTypes;
6690       this.labels = viz.labels;
6691    },
6692
6693     //Add helpers
6694     nodeHelper: NodeHelper,
6695     edgeHelper: EdgeHelper,
6696     
6697     Interpolator: {
6698         //node/edge property parsers
6699         'map': {
6700           'border': 'color',
6701           'color': 'color',
6702           'width': 'number',
6703           'height': 'number',
6704           'dim': 'number',
6705           'alpha': 'number',
6706           'lineWidth': 'number',
6707           'angularWidth':'number',
6708           'span':'number',
6709           'valueArray':'array-number',
6710           'dimArray':'array-number'
6711           //'colorArray':'array-color'
6712         },
6713         
6714         //canvas specific parsers
6715         'canvas': {
6716           'globalAlpha': 'number',
6717           'fillStyle': 'color',
6718           'strokeStyle': 'color',
6719           'lineWidth': 'number',
6720           'shadowBlur': 'number',
6721           'shadowColor': 'color',
6722           'shadowOffsetX': 'number',
6723           'shadowOffsetY': 'number',
6724           'miterLimit': 'number'
6725         },
6726   
6727         //label parsers
6728         'label': {
6729           'size': 'number',
6730           'color': 'color'
6731         },
6732   
6733         //Number interpolator
6734         'compute': function(from, to, delta) {
6735           return from + (to - from) * delta;
6736         },
6737         
6738         //Position interpolators
6739         'moebius': function(elem, props, delta, vector) {
6740           var v = vector.scale(-delta);  
6741           if(v.norm() < 1) {
6742               var x = v.x, y = v.y;
6743               var ans = elem.startPos
6744                 .getc().moebiusTransformation(v);
6745               elem.pos.setc(ans.x, ans.y);
6746               v.x = x; v.y = y;
6747             }           
6748         },
6749
6750         'linear': function(elem, props, delta) {
6751             var from = elem.startPos.getc(true);
6752             var to = elem.endPos.getc(true);
6753             elem.pos.setc(this.compute(from.x, to.x, delta), 
6754                           this.compute(from.y, to.y, delta));
6755         },
6756
6757         'polar': function(elem, props, delta) {
6758           var from = elem.startPos.getp(true);
6759           var to = elem.endPos.getp();
6760           var ans = to.interpolate(from, delta);
6761           elem.pos.setp(ans.theta, ans.rho);
6762         },
6763         
6764         //Graph's Node/Edge interpolators
6765         'number': function(elem, prop, delta, getter, setter) {
6766           var from = elem[getter](prop, 'start');
6767           var to = elem[getter](prop, 'end');
6768           elem[setter](prop, this.compute(from, to, delta));
6769         },
6770
6771         'color': function(elem, prop, delta, getter, setter) {
6772           var from = $.hexToRgb(elem[getter](prop, 'start'));
6773           var to = $.hexToRgb(elem[getter](prop, 'end'));
6774           var comp = this.compute;
6775           var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6776                                 parseInt(comp(from[1], to[1], delta)),
6777                                 parseInt(comp(from[2], to[2], delta))]);
6778           
6779           elem[setter](prop, val);
6780         },
6781         
6782         'array-number': function(elem, prop, delta, getter, setter) {
6783           var from = elem[getter](prop, 'start'),
6784               to = elem[getter](prop, 'end'),
6785               cur = [];
6786           for(var i=0, l=from.length; i<l; i++) {
6787             var fromi = from[i], toi = to[i];
6788             if(fromi.length) {
6789               for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6790                 curi.push(this.compute(fromi[j], toi[j], delta));
6791               }
6792               cur.push(curi);
6793             } else {
6794               cur.push(this.compute(fromi, toi, delta));
6795             }
6796           }
6797           elem[setter](prop, cur);
6798         },
6799         
6800         'node': function(elem, props, delta, map, getter, setter) {
6801           map = this[map];
6802           if(props) {
6803             var len = props.length;
6804             for(var i=0; i<len; i++) {
6805               var pi = props[i];
6806               this[map[pi]](elem, pi, delta, getter, setter);
6807             }
6808           } else {
6809             for(var pi in map) {
6810               this[map[pi]](elem, pi, delta, getter, setter);
6811             }
6812           }
6813         },
6814         
6815         'edge': function(elem, props, delta, mapKey, getter, setter) {
6816             var adjs = elem.adjacencies;
6817             for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6818         },
6819         
6820         'node-property': function(elem, props, delta) {
6821           this['node'](elem, props, delta, 'map', 'getData', 'setData');
6822         },
6823         
6824         'edge-property': function(elem, props, delta) {
6825           this['edge'](elem, props, delta, 'map', 'getData', 'setData');  
6826         },
6827
6828         'label-property': function(elem, props, delta) {
6829           this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6830         },
6831         
6832         'node-style': function(elem, props, delta) {
6833           this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6834         },
6835         
6836         'edge-style': function(elem, props, delta) {
6837           this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');  
6838         }
6839     },
6840     
6841   
6842     /*
6843        sequence
6844     
6845        Iteratively performs an action while refreshing the state of the visualization.
6846
6847        Parameters:
6848
6849        options - (object) An object containing some sequence options described below
6850        condition - (function) A function returning a boolean instance in order to stop iterations.
6851        step - (function) A function to execute on each step of the iteration.
6852        onComplete - (function) A function to execute when the sequence finishes.
6853        duration - (number) Duration (in milliseconds) of each step.
6854
6855       Example:
6856        (start code js)
6857         var rg = new $jit.RGraph(options);
6858         var i = 0;
6859         rg.fx.sequence({
6860           condition: function() {
6861            return i == 10;
6862           },
6863           step: function() {
6864             alert(i++);
6865           },
6866           onComplete: function() {
6867            alert('done!');
6868           }
6869         });
6870        (end code)
6871
6872     */
6873     sequence: function(options) {
6874         var that = this;
6875         options = $.merge({
6876           condition: $.lambda(false),
6877           step: $.empty,
6878           onComplete: $.empty,
6879           duration: 200
6880         }, options || {});
6881
6882         var interval = setInterval(function() {
6883           if(options.condition()) {
6884             options.step();
6885           } else {
6886             clearInterval(interval);
6887             options.onComplete();
6888           }
6889           that.viz.refresh(true);
6890         }, options.duration);
6891     },
6892     
6893     /*
6894       prepare
6895  
6896       Prepare graph position and other attribute values before performing an Animation. 
6897       This method is used internally by the Toolkit.
6898       
6899       See also:
6900        
6901        <Animation>, <Graph.Plot.animate>
6902
6903     */
6904     prepare: function(modes) {
6905       var graph = this.viz.graph,
6906           accessors = {
6907             'node-property': {
6908               'getter': 'getData',
6909               'setter': 'setData'
6910             },
6911             'edge-property': {
6912               'getter': 'getData',
6913               'setter': 'setData'
6914             },
6915             'node-style': {
6916               'getter': 'getCanvasStyle',
6917               'setter': 'setCanvasStyle'
6918             },
6919             'edge-style': {
6920               'getter': 'getCanvasStyle',
6921               'setter': 'setCanvasStyle'
6922             }
6923           };
6924
6925       //parse modes
6926       var m = {};
6927       if($.type(modes) == 'array') {
6928         for(var i=0, len=modes.length; i < len; i++) {
6929           var elems = modes[i].split(':');
6930           m[elems.shift()] = elems;
6931         }
6932       } else {
6933         for(var p in modes) {
6934           if(p == 'position') {
6935             m[modes.position] = [];
6936           } else {
6937             m[p] = $.splat(modes[p]);
6938           }
6939         }
6940       }
6941       
6942       graph.eachNode(function(node) { 
6943         node.startPos.set(node.pos);
6944         $.each(['node-property', 'node-style'], function(p) {
6945           if(p in m) {
6946             var prop = m[p];
6947             for(var i=0, l=prop.length; i < l; i++) {
6948               node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6949             }
6950           }
6951         });
6952         $.each(['edge-property', 'edge-style'], function(p) {
6953           if(p in m) {
6954             var prop = m[p];
6955             node.eachAdjacency(function(adj) {
6956               for(var i=0, l=prop.length; i < l; i++) {
6957                 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6958               }
6959             });
6960           }
6961         });
6962       });
6963       return m;
6964     },
6965     
6966     /*
6967        Method: animate
6968     
6969        Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6970
6971        Parameters:
6972
6973        opt - (object) Animation options. The object properties are described below
6974        duration - (optional) Described in <Options.Fx>.
6975        fps - (optional) Described in <Options.Fx>.
6976        hideLabels - (optional|boolean) Whether to hide labels during the animation.
6977        modes - (required|object) An object with animation modes (described below).
6978
6979        Animation modes:
6980        
6981        Animation modes are strings representing different node/edge and graph properties that you'd like to animate. 
6982        They are represented by an object that has as keys main categories of properties to animate and as values a list 
6983        of these specific properties. The properties are described below
6984        
6985        position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6986        node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6987        edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6988        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.
6989        node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6990        edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6991
6992        Example:
6993        (start code js)
6994        var viz = new $jit.Viz(options);
6995        //...tweak some Data, CanvasStyles or LabelData properties...
6996        viz.fx.animate({
6997          modes: {
6998            'position': 'linear',
6999            'node-property': ['width', 'height'],
7000            'node-style': 'shadowColor',
7001            'label-property': 'size'
7002          },
7003          hideLabels: false
7004        });
7005        //...can also be written like this...
7006        viz.fx.animate({
7007          modes: ['linear',
7008                  'node-property:width:height',
7009                  'node-style:shadowColor',
7010                  'label-property:size'],
7011          hideLabels: false
7012        });
7013        (end code)
7014     */
7015     animate: function(opt, versor) {
7016       opt = $.merge(this.viz.config, opt || {});
7017       var that = this,
7018           viz = this.viz,
7019           graph  = viz.graph,
7020           interp = this.Interpolator,
7021           animation =  opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7022       //prepare graph values
7023       var m = this.prepare(opt.modes);
7024       
7025       //animate
7026       if(opt.hideLabels) this.labels.hideLabels(true);
7027       animation.setOptions($.merge(opt, {
7028         $animating: false,
7029         compute: function(delta) {
7030           graph.eachNode(function(node) { 
7031             for(var p in m) {
7032               interp[p](node, m[p], delta, versor);
7033             }
7034           });
7035           that.plot(opt, this.$animating, delta);
7036           this.$animating = true;
7037         },
7038         complete: function() {
7039           if(opt.hideLabels) that.labels.hideLabels(false);
7040           that.plot(opt);
7041           opt.onComplete();
7042           opt.onAfterCompute();
7043         }       
7044       })).start();
7045     },
7046     
7047     /*
7048       nodeFx
7049    
7050       Apply animation to node properties like color, width, height, dim, etc.
7051   
7052       Parameters:
7053   
7054       options - Animation options. This object properties is described below
7055       elements - The Elements to be transformed. This is an object that has a properties
7056       
7057       (start code js)
7058       'elements': {
7059         //can also be an array of ids
7060         'id': 'id-of-node-to-transform',
7061         //properties to be modified. All properties are optional.
7062         'properties': {
7063           'color': '#ccc', //some color
7064           'width': 10, //some width
7065           'height': 10, //some height
7066           'dim': 20, //some dim
7067           'lineWidth': 10 //some line width
7068         } 
7069       }
7070       (end code)
7071       
7072       - _reposition_ Whether to recalculate positions and add a motion animation. 
7073       This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7074       
7075       - _onComplete_ A method that is called when the animation completes.
7076       
7077       ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7078   
7079       Example:
7080       (start code js)
7081        var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7082        rg.fx.nodeFx({
7083          'elements': {
7084            'id':'mynodeid',
7085            'properties': {
7086              'color':'#ccf'
7087            },
7088            'transition': Trans.Quart.easeOut
7089          }
7090        });
7091       (end code)    
7092    */
7093    nodeFx: function(opt) {
7094      var viz = this.viz,
7095          graph  = viz.graph,
7096          animation = this.nodeFxAnimation,
7097          options = $.merge(this.viz.config, {
7098            'elements': {
7099              'id': false,
7100              'properties': {}
7101            },
7102            'reposition': false
7103          });
7104      opt = $.merge(options, opt || {}, {
7105        onBeforeCompute: $.empty,
7106        onAfterCompute: $.empty
7107      });
7108      //check if an animation is running
7109      animation.stopTimer();
7110      var props = opt.elements.properties;
7111      //set end values for nodes
7112      if(!opt.elements.id) {
7113        graph.eachNode(function(n) {
7114          for(var prop in props) {
7115            n.setData(prop, props[prop], 'end');
7116          }
7117        });
7118      } else {
7119        var ids = $.splat(opt.elements.id);
7120        $.each(ids, function(id) {
7121          var n = graph.getNode(id);
7122          if(n) {
7123            for(var prop in props) {
7124              n.setData(prop, props[prop], 'end');
7125            }
7126          }
7127        });
7128      }
7129      //get keys
7130      var propnames = [];
7131      for(var prop in props) propnames.push(prop);
7132      //add node properties modes
7133      var modes = ['node-property:' + propnames.join(':')];
7134      //set new node positions
7135      if(opt.reposition) {
7136        modes.push('linear');
7137        viz.compute('end');
7138      }
7139      //animate
7140      this.animate($.merge(opt, {
7141        modes: modes,
7142        type: 'nodefx'
7143      }));
7144    },
7145
7146     
7147     /*
7148        Method: plot
7149     
7150        Plots a <Graph>.
7151
7152        Parameters:
7153
7154        opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7155
7156        Example:
7157
7158        (start code js)
7159        var viz = new $jit.Viz(options);
7160        viz.fx.plot(); 
7161        (end code)
7162
7163     */
7164     plot: function(opt, animating) {
7165       var viz = this.viz, 
7166       aGraph = viz.graph, 
7167       canvas = viz.canvas, 
7168       id = viz.root, 
7169       that = this, 
7170       ctx = canvas.getCtx(), 
7171       min = Math.min,
7172       opt = opt || this.viz.controller;
7173       opt.clearCanvas && canvas.clear();
7174         
7175       var root = aGraph.getNode(id);
7176       if(!root) return;
7177       
7178       var T = !!root.visited;
7179       aGraph.eachNode(function(node) {
7180         var nodeAlpha = node.getData('alpha');
7181         node.eachAdjacency(function(adj) {
7182           var nodeTo = adj.nodeTo;
7183           if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7184             !animating && opt.onBeforePlotLine(adj);
7185             ctx.save();
7186             ctx.globalAlpha = min(nodeAlpha, 
7187                 nodeTo.getData('alpha'), 
7188                 adj.getData('alpha'));
7189             that.plotLine(adj, canvas, animating);
7190             ctx.restore();
7191             !animating && opt.onAfterPlotLine(adj);
7192           }
7193         });
7194         ctx.save();
7195         if(node.drawn) {
7196           !animating && opt.onBeforePlotNode(node);
7197           that.plotNode(node, canvas, animating);
7198           !animating && opt.onAfterPlotNode(node);
7199         }
7200         if(!that.labelsHidden && opt.withLabels) {
7201           if(node.drawn && nodeAlpha >= 0.95) {
7202             that.labels.plotLabel(canvas, node, opt);
7203           } else {
7204             that.labels.hideLabel(node, false);
7205           }
7206         }
7207         ctx.restore();
7208         node.visited = !T;
7209       });
7210     },
7211
7212   /*
7213       Plots a Subtree.
7214    */
7215    plotTree: function(node, opt, animating) {
7216        var that = this, 
7217        viz = this.viz, 
7218        canvas = viz.canvas,
7219        config = this.config,
7220        ctx = canvas.getCtx();
7221        var nodeAlpha = node.getData('alpha');
7222        node.eachSubnode(function(elem) {
7223          if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7224              var adj = node.getAdjacency(elem.id);
7225              !animating && opt.onBeforePlotLine(adj);
7226              ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7227              that.plotLine(adj, canvas, animating);
7228              !animating && opt.onAfterPlotLine(adj);
7229              that.plotTree(elem, opt, animating);
7230          }
7231        });
7232        if(node.drawn) {
7233            !animating && opt.onBeforePlotNode(node);
7234            this.plotNode(node, canvas, animating);
7235            !animating && opt.onAfterPlotNode(node);
7236            if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95) 
7237                this.labels.plotLabel(canvas, node, opt);
7238            else 
7239                this.labels.hideLabel(node, false);
7240        } else {
7241            this.labels.hideLabel(node, true);
7242        }
7243    },
7244
7245   /*
7246        Method: plotNode
7247     
7248        Plots a <Graph.Node>.
7249
7250        Parameters:
7251        
7252        node - (object) A <Graph.Node>.
7253        canvas - (object) A <Canvas> element.
7254
7255     */
7256     plotNode: function(node, canvas, animating) {
7257         var f = node.getData('type'), 
7258             ctxObj = this.node.CanvasStyles;
7259         if(f != 'none') {
7260           var width = node.getData('lineWidth'),
7261               color = node.getData('color'),
7262               alpha = node.getData('alpha'),
7263               ctx = canvas.getCtx();
7264           
7265           ctx.lineWidth = width;
7266           ctx.fillStyle = ctx.strokeStyle = color;
7267           ctx.globalAlpha = alpha;
7268           
7269           for(var s in ctxObj) {
7270             ctx[s] = node.getCanvasStyle(s);
7271           }
7272
7273           this.nodeTypes[f].render.call(this, node, canvas, animating);
7274         }
7275     },
7276     
7277     /*
7278        Method: plotLine
7279     
7280        Plots a <Graph.Adjacence>.
7281
7282        Parameters:
7283
7284        adj - (object) A <Graph.Adjacence>.
7285        canvas - (object) A <Canvas> instance.
7286
7287     */
7288     plotLine: function(adj, canvas, animating) {
7289       var f = adj.getData('type'),
7290           ctxObj = this.edge.CanvasStyles;
7291       if(f != 'none') {
7292         var width = adj.getData('lineWidth'),
7293             color = adj.getData('color'),
7294             ctx = canvas.getCtx();
7295         
7296         ctx.lineWidth = width;
7297         ctx.fillStyle = ctx.strokeStyle = color;
7298         
7299         for(var s in ctxObj) {
7300           ctx[s] = adj.getCanvasStyle(s);
7301         }
7302
7303         this.edgeTypes[f].render.call(this, adj, canvas, animating);
7304       }
7305     }    
7306   
7307 };
7308
7309
7310
7311 /*
7312  * File: Graph.Label.js
7313  *
7314 */
7315
7316 /*
7317    Object: Graph.Label
7318
7319    An interface for plotting/hiding/showing labels.
7320
7321    Description:
7322
7323    This is a generic interface for plotting/hiding/showing labels.
7324    The <Graph.Label> interface is implemented in multiple ways to provide
7325    different label types.
7326
7327    For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7328    HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels. 
7329    The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7330    
7331    All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7332 */
7333
7334 Graph.Label = {};
7335
7336 /*
7337    Class: Graph.Label.Native
7338
7339    Implements labels natively, using the Canvas text API.
7340 */
7341 Graph.Label.Native = new Class({
7342     /*
7343        Method: plotLabel
7344
7345        Plots a label for a given node.
7346
7347        Parameters:
7348
7349        canvas - (object) A <Canvas> instance.
7350        node - (object) A <Graph.Node>.
7351        controller - (object) A configuration object.
7352        
7353        Example:
7354        
7355        (start code js)
7356        var viz = new $jit.Viz(options);
7357        var node = viz.graph.getNode('nodeId');
7358        viz.labels.plotLabel(viz.canvas, node, viz.config);
7359        (end code)
7360     */
7361     plotLabel: function(canvas, node, controller) {
7362       var ctx = canvas.getCtx();
7363       var pos = node.pos.getc(true);
7364
7365       ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7366       ctx.textAlign = node.getLabelData('textAlign');
7367       ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7368       ctx.textBaseline = node.getLabelData('textBaseline');
7369
7370       this.renderLabel(canvas, node, controller);
7371     },
7372
7373     /*
7374        renderLabel
7375
7376        Does the actual rendering of the label in the canvas. The default
7377        implementation renders the label close to the position of the node, this
7378        method should be overriden to position the labels differently.
7379
7380        Parameters:
7381
7382        canvas - A <Canvas> instance.
7383        node - A <Graph.Node>.
7384        controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7385     */
7386     renderLabel: function(canvas, node, controller) {
7387       var ctx = canvas.getCtx();
7388       var pos = node.pos.getc(true);
7389       ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7390     },
7391
7392     hideLabel: $.empty,
7393     hideLabels: $.empty
7394 });
7395
7396 /*
7397    Class: Graph.Label.DOM
7398
7399    Abstract Class implementing some DOM label methods.
7400
7401    Implemented by:
7402
7403    <Graph.Label.HTML> and <Graph.Label.SVG>.
7404
7405 */
7406 Graph.Label.DOM = new Class({
7407     //A flag value indicating if node labels are being displayed or not.
7408     labelsHidden: false,
7409     //Label container
7410     labelContainer: false,
7411     //Label elements hash.
7412     labels: {},
7413
7414     /*
7415        Method: getLabelContainer
7416
7417        Lazy fetcher for the label container.
7418
7419        Returns:
7420
7421        The label container DOM element.
7422
7423        Example:
7424
7425       (start code js)
7426         var viz = new $jit.Viz(options);
7427         var labelContainer = viz.labels.getLabelContainer();
7428         alert(labelContainer.innerHTML);
7429       (end code)
7430     */
7431     getLabelContainer: function() {
7432       return this.labelContainer ?
7433         this.labelContainer :
7434         this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7435     },
7436
7437     /*
7438        Method: getLabel
7439
7440        Lazy fetcher for the label element.
7441
7442        Parameters:
7443
7444        id - (string) The label id (which is also a <Graph.Node> id).
7445
7446        Returns:
7447
7448        The label element.
7449
7450        Example:
7451
7452       (start code js)
7453         var viz = new $jit.Viz(options);
7454         var label = viz.labels.getLabel('someid');
7455         alert(label.innerHTML);
7456       (end code)
7457
7458     */
7459     getLabel: function(id) {
7460       return (id in this.labels && this.labels[id] != null) ?
7461         this.labels[id] :
7462         this.labels[id] = document.getElementById(id);
7463     },
7464
7465     /*
7466        Method: hideLabels
7467
7468        Hides all labels (by hiding the label container).
7469
7470        Parameters:
7471
7472        hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7473
7474        Example:
7475        (start code js)
7476         var viz = new $jit.Viz(options);
7477         rg.labels.hideLabels(true);
7478        (end code)
7479
7480     */
7481     hideLabels: function (hide) {
7482       var container = this.getLabelContainer();
7483       if(hide)
7484         container.style.display = 'none';
7485       else
7486         container.style.display = '';
7487       this.labelsHidden = hide;
7488     },
7489
7490     /*
7491        Method: clearLabels
7492
7493        Clears the label container.
7494
7495        Useful when using a new visualization with the same canvas element/widget.
7496
7497        Parameters:
7498
7499        force - (boolean) Forces deletion of all labels.
7500
7501        Example:
7502        (start code js)
7503         var viz = new $jit.Viz(options);
7504         viz.labels.clearLabels();
7505         (end code)
7506     */
7507     clearLabels: function(force) {
7508       for(var id in this.labels) {
7509         if (force || !this.viz.graph.hasNode(id)) {
7510           this.disposeLabel(id);
7511           delete this.labels[id];
7512         }
7513       }
7514     },
7515
7516     /*
7517        Method: disposeLabel
7518
7519        Removes a label.
7520
7521        Parameters:
7522
7523        id - (string) A label id (which generally is also a <Graph.Node> id).
7524
7525        Example:
7526        (start code js)
7527         var viz = new $jit.Viz(options);
7528         viz.labels.disposeLabel('labelid');
7529        (end code)
7530     */
7531     disposeLabel: function(id) {
7532       var elem = this.getLabel(id);
7533       if(elem && elem.parentNode) {
7534         elem.parentNode.removeChild(elem);
7535       }
7536     },
7537
7538     /*
7539        Method: hideLabel
7540
7541        Hides the corresponding <Graph.Node> label.
7542
7543        Parameters:
7544
7545        node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7546        show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7547
7548        Example:
7549        (start code js)
7550         var rg = new $jit.Viz(options);
7551         viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7552        (end code)
7553     */
7554     hideLabel: function(node, show) {
7555       node = $.splat(node);
7556       var st = show ? "" : "none", lab, that = this;
7557       $.each(node, function(n) {
7558         var lab = that.getLabel(n.id);
7559         if (lab) {
7560           lab.style.display = st;
7561         }
7562       });
7563     },
7564
7565     /*
7566        fitsInCanvas
7567
7568        Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7569
7570        Parameters:
7571
7572        pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7573        canvas - A <Canvas> instance.
7574
7575        Returns:
7576
7577        A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7578
7579     */
7580     fitsInCanvas: function(pos, canvas) {
7581       var size = canvas.getSize();
7582       if(pos.x >= size.width || pos.x < 0
7583          || pos.y >= size.height || pos.y < 0) return false;
7584        return true;
7585     }
7586 });
7587
7588 /*
7589    Class: Graph.Label.HTML
7590
7591    Implements HTML labels.
7592
7593    Extends:
7594
7595    All <Graph.Label.DOM> methods.
7596
7597 */
7598 Graph.Label.HTML = new Class({
7599     Implements: Graph.Label.DOM,
7600
7601     /*
7602        Method: plotLabel
7603
7604        Plots a label for a given node.
7605
7606        Parameters:
7607
7608        canvas - (object) A <Canvas> instance.
7609        node - (object) A <Graph.Node>.
7610        controller - (object) A configuration object.
7611        
7612       Example:
7613        
7614        (start code js)
7615        var viz = new $jit.Viz(options);
7616        var node = viz.graph.getNode('nodeId');
7617        viz.labels.plotLabel(viz.canvas, node, viz.config);
7618        (end code)
7619
7620
7621     */
7622     plotLabel: function(canvas, node, controller) {
7623       var id = node.id, tag = this.getLabel(id);
7624
7625       if(!tag && !(tag = document.getElementById(id))) {
7626         tag = document.createElement('div');
7627         var container = this.getLabelContainer();
7628         tag.id = id;
7629         tag.className = 'node';
7630         tag.style.position = 'absolute';
7631         controller.onCreateLabel(tag, node);
7632         container.appendChild(tag);
7633         this.labels[node.id] = tag;
7634       }
7635
7636       this.placeLabel(tag, node, controller);
7637     }
7638 });
7639
7640 /*
7641    Class: Graph.Label.SVG
7642
7643    Implements SVG labels.
7644
7645    Extends:
7646
7647    All <Graph.Label.DOM> methods.
7648 */
7649 Graph.Label.SVG = new Class({
7650     Implements: Graph.Label.DOM,
7651
7652     /*
7653        Method: plotLabel
7654
7655        Plots a label for a given node.
7656
7657        Parameters:
7658
7659        canvas - (object) A <Canvas> instance.
7660        node - (object) A <Graph.Node>.
7661        controller - (object) A configuration object.
7662        
7663        Example:
7664        
7665        (start code js)
7666        var viz = new $jit.Viz(options);
7667        var node = viz.graph.getNode('nodeId');
7668        viz.labels.plotLabel(viz.canvas, node, viz.config);
7669        (end code)
7670
7671
7672     */
7673     plotLabel: function(canvas, node, controller) {
7674       var id = node.id, tag = this.getLabel(id);
7675       if(!tag && !(tag = document.getElementById(id))) {
7676         var ns = 'http://www.w3.org/2000/svg';
7677           tag = document.createElementNS(ns, 'svg:text');
7678         var tspan = document.createElementNS(ns, 'svg:tspan');
7679         tag.appendChild(tspan);
7680         var container = this.getLabelContainer();
7681         tag.setAttribute('id', id);
7682         tag.setAttribute('class', 'node');
7683         container.appendChild(tag);
7684         controller.onCreateLabel(tag, node);
7685         this.labels[node.id] = tag;
7686       }
7687       this.placeLabel(tag, node, controller);
7688     }
7689 });
7690
7691
7692
7693 Graph.Geom = new Class({
7694
7695   initialize: function(viz) {
7696     this.viz = viz;
7697     this.config = viz.config;
7698     this.node = viz.config.Node;
7699     this.edge = viz.config.Edge;
7700   },
7701   /*
7702     Applies a translation to the tree.
7703   
7704     Parameters:
7705   
7706     pos - A <Complex> number specifying translation vector.
7707     prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7708   
7709     Example:
7710   
7711     (start code js)
7712       st.geom.translate(new Complex(300, 100), 'end');
7713     (end code)
7714   */  
7715   translate: function(pos, prop) {
7716      prop = $.splat(prop);
7717      this.viz.graph.eachNode(function(elem) {
7718          $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7719      });
7720   },
7721   /*
7722     Hides levels of the tree until it properly fits in canvas.
7723   */  
7724   setRightLevelToShow: function(node, canvas, callback) {
7725      var level = this.getRightLevelToShow(node, canvas), 
7726          fx = this.viz.labels,
7727          opt = $.merge({
7728            execShow:true,
7729            execHide:true,
7730            onHide: $.empty,
7731            onShow: $.empty
7732          }, callback || {});
7733      node.eachLevel(0, this.config.levelsToShow, function(n) {
7734          var d = n._depth - node._depth;
7735          if(d > level) {
7736              opt.onHide(n);
7737              if(opt.execHide) {
7738                n.drawn = false; 
7739                n.exist = false;
7740                fx.hideLabel(n, false);
7741              }
7742          } else {
7743              opt.onShow(n);
7744              if(opt.execShow) {
7745                n.exist = true;
7746              }
7747          }
7748      });
7749      node.drawn= true;
7750   },
7751   /*
7752     Returns the right level to show for the current tree in order to fit in canvas.
7753   */  
7754   getRightLevelToShow: function(node, canvas) {
7755      var config = this.config;
7756      var level = config.levelsToShow;
7757      var constrained = config.constrained;
7758      if(!constrained) return level;
7759      while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7760      return level;
7761   }
7762 });
7763
7764 /*
7765  * File: Loader.js
7766  * 
7767  */
7768
7769 /*
7770    Object: Loader
7771
7772    Provides methods for loading and serving JSON data.
7773 */
7774 var Loader = {
7775      construct: function(json) {
7776         var isGraph = ($.type(json) == 'array');
7777         var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7778         if(!isGraph) 
7779             //make tree
7780             (function (ans, json) {
7781                 ans.addNode(json);
7782                 if(json.children) {
7783                   for(var i=0, ch = json.children; i<ch.length; i++) {
7784                     ans.addAdjacence(json, ch[i]);
7785                     arguments.callee(ans, ch[i]);
7786                   }
7787                 }
7788             })(ans, json);
7789         else
7790             //make graph
7791             (function (ans, json) {
7792                 var getNode = function(id) {
7793                   for(var i=0, l=json.length; i<l; i++) {
7794                     if(json[i].id == id) {
7795                       return json[i];
7796                     }
7797                   }
7798                   // The node was not defined in the JSON
7799                   // Let's create it
7800                   var newNode = {
7801                                 "id" : id,
7802                                 "name" : id
7803                         };
7804                   return ans.addNode(newNode);
7805                 };
7806
7807                 for(var i=0, l=json.length; i<l; i++) {
7808                   ans.addNode(json[i]);
7809                   var adj = json[i].adjacencies;
7810                   if (adj) {
7811                     for(var j=0, lj=adj.length; j<lj; j++) {
7812                       var node = adj[j], data = {};
7813                       if(typeof adj[j] != 'string') {
7814                         data = $.merge(node.data, {});
7815                         node = node.nodeTo;
7816                       }
7817                       ans.addAdjacence(json[i], getNode(node), data);
7818                     }
7819                   }
7820                 }
7821             })(ans, json);
7822
7823         return ans;
7824     },
7825
7826     /*
7827      Method: loadJSON
7828     
7829      Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7830      
7831       A JSON tree or graph structure consists of nodes, each having as properties
7832        
7833        id - (string) A unique identifier for the node
7834        name - (string) A node's name
7835        data - (object) The data optional property contains a hash (i.e {}) 
7836        where you can store all the information you want about this node.
7837         
7838       For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7839       
7840       Example:
7841
7842       (start code js)
7843         var json = {  
7844           "id": "aUniqueIdentifier",  
7845           "name": "usually a nodes name",  
7846           "data": {
7847             "some key": "some value",
7848             "some other key": "some other value"
7849            },  
7850           "children": [ *other nodes or empty* ]  
7851         };  
7852       (end code)
7853         
7854         JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected. 
7855         For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7856         
7857         There are two types of *Graph* structures, *simple* and *extended* graph structures.
7858         
7859         For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the 
7860         id of the node connected to the main node.
7861         
7862         Example:
7863         
7864         (start code js)
7865         var json = [  
7866           {  
7867             "id": "aUniqueIdentifier",  
7868             "name": "usually a nodes name",  
7869             "data": {
7870               "some key": "some value",
7871               "some other key": "some other value"
7872              },  
7873             "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']  
7874           },
7875
7876           'other nodes go here...' 
7877         ];          
7878         (end code)
7879         
7880         For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7881         
7882         nodeTo - (string) The other node connected by this adjacency.
7883         data - (object) A data property, where we can store custom key/value information.
7884         
7885         Example:
7886         
7887         (start code js)
7888         var json = [  
7889           {  
7890             "id": "aUniqueIdentifier",  
7891             "name": "usually a nodes name",  
7892             "data": {
7893               "some key": "some value",
7894               "some other key": "some other value"
7895              },  
7896             "adjacencies": [  
7897             {  
7898               nodeTo:"aNodeId",  
7899               data: {} //put whatever you want here  
7900             },
7901             'other adjacencies go here...'  
7902           },
7903
7904           'other nodes go here...' 
7905         ];          
7906         (end code)
7907        
7908        About the data property:
7909        
7910        As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*. 
7911        You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and 
7912        have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7913        
7914        For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in 
7915        <Options.Node> will override the general value for that option with that particular value. For this to work 
7916        however, you do have to set *overridable = true* in <Options.Node>.
7917        
7918        The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge> 
7919        if <Options.Edge> has *overridable = true*.
7920        
7921        When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key, 
7922        since this is the value which will be taken into account when creating the layout. 
7923        The same thing goes for the *$color* parameter.
7924        
7925        In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example, 
7926        *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set 
7927        canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer 
7928        to the *shadowBlur* property.
7929        
7930        These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences> 
7931        by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7932        
7933        Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more 
7934        information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7935        
7936        loadJSON Parameters:
7937     
7938         json - A JSON Tree or Graph structure.
7939         i - For Graph structures only. Sets the indexed node as root for the visualization.
7940
7941     */
7942     loadJSON: function(json, i) {
7943       this.json = json;
7944       //if they're canvas labels erase them.
7945       if(this.labels && this.labels.clearLabels) {
7946         this.labels.clearLabels(true);
7947       }
7948       this.graph = this.construct(json);
7949       if($.type(json) != 'array'){
7950         this.root = json.id;
7951       } else {
7952         this.root = json[i? i : 0].id;
7953       }
7954     },
7955     
7956     /*
7957       Method: toJSON
7958    
7959       Returns a JSON tree/graph structure from the visualization's <Graph>. 
7960       See <Loader.loadJSON> for the graph formats available.
7961       
7962       See also:
7963       
7964       <Loader.loadJSON>
7965       
7966       Parameters:
7967       
7968       type - (string) Default's "tree". The type of the JSON structure to be returned. 
7969       Possible options are "tree" or "graph".
7970     */    
7971     toJSON: function(type) {
7972       type = type || "tree";
7973       if(type == 'tree') {
7974         var ans = {};
7975         var rootNode = this.graph.getNode(this.root);
7976         var ans = (function recTree(node) {
7977           var ans = {};
7978           ans.id = node.id;
7979           ans.name = node.name;
7980           ans.data = node.data;
7981           var ch =[];
7982           node.eachSubnode(function(n) {
7983             ch.push(recTree(n));
7984           });
7985           ans.children = ch;
7986           return ans;
7987         })(rootNode);
7988         return ans;
7989       } else {
7990         var ans = [];
7991         var T = !!this.graph.getNode(this.root).visited;
7992         this.graph.eachNode(function(node) {
7993           var ansNode = {};
7994           ansNode.id = node.id;
7995           ansNode.name = node.name;
7996           ansNode.data = node.data;
7997           var adjs = [];
7998           node.eachAdjacency(function(adj) {
7999             var nodeTo = adj.nodeTo;
8000             if(!!nodeTo.visited === T) {
8001               var ansAdj = {};
8002               ansAdj.nodeTo = nodeTo.id;
8003               ansAdj.data = adj.data;
8004               adjs.push(ansAdj);
8005             }
8006           });
8007           ansNode.adjacencies = adjs;
8008           ans.push(ansNode);
8009           node.visited = !T;
8010         });
8011         return ans;
8012       }
8013     }
8014 };
8015
8016
8017
8018 /*
8019  * File: Layouts.js
8020  * 
8021  * Implements base Tree and Graph layouts.
8022  *
8023  * Description:
8024  *
8025  * Implements base Tree and Graph layouts like Radial, Tree, etc.
8026  * 
8027  */
8028
8029 /*
8030  * Object: Layouts
8031  * 
8032  * Parent object for common layouts.
8033  *
8034  */
8035 var Layouts = $jit.Layouts = {};
8036
8037
8038 //Some util shared layout functions are defined here.
8039 var NodeDim = {
8040   label: null,
8041   
8042   compute: function(graph, prop, opt) {
8043     this.initializeLabel(opt);
8044     var label = this.label, style = label.style;
8045     graph.eachNode(function(n) {
8046       var autoWidth  = n.getData('autoWidth'),
8047           autoHeight = n.getData('autoHeight');
8048       if(autoWidth || autoHeight) {
8049         //delete dimensions since these are
8050         //going to be overridden now.
8051         delete n.data.$width;
8052         delete n.data.$height;
8053         delete n.data.$dim;
8054         
8055         var width  = n.getData('width'),
8056             height = n.getData('height');
8057         //reset label dimensions
8058         style.width  = autoWidth? 'auto' : width + 'px';
8059         style.height = autoHeight? 'auto' : height + 'px';
8060         
8061         //TODO(nico) should let the user choose what to insert here.
8062         label.innerHTML = n.name;
8063         
8064         var offsetWidth  = label.offsetWidth,
8065             offsetHeight = label.offsetHeight;
8066         var type = n.getData('type');
8067         if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8068           n.setData('width', offsetWidth);
8069           n.setData('height', offsetHeight);
8070         } else {
8071           var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8072           n.setData('width', dim);
8073           n.setData('height', dim);
8074           n.setData('dim', dim); 
8075         }
8076       }
8077     });
8078   },
8079   
8080   initializeLabel: function(opt) {
8081     if(!this.label) {
8082       this.label = document.createElement('div');
8083       document.body.appendChild(this.label);
8084     }
8085     this.setLabelStyles(opt);
8086   },
8087   
8088   setLabelStyles: function(opt) {
8089     $.extend(this.label.style, {
8090       'visibility': 'hidden',
8091       'position': 'absolute',
8092       'width': 'auto',
8093       'height': 'auto'
8094     });
8095     this.label.className = 'jit-autoadjust-label';
8096   }
8097 };
8098
8099
8100 /*
8101  * Class: Layouts.Tree
8102  * 
8103  * Implements a Tree Layout.
8104  * 
8105  * Implemented By:
8106  * 
8107  * <ST>
8108  * 
8109  * Inspired by:
8110  * 
8111  * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8112  * 
8113  */
8114 Layouts.Tree = (function() {
8115   //Layout functions
8116   var slice = Array.prototype.slice;
8117
8118   /*
8119      Calculates the max width and height nodes for a tree level
8120   */  
8121   function getBoundaries(graph, config, level, orn, prop) {
8122     var dim = config.Node;
8123     var multitree = config.multitree;
8124     if (dim.overridable) {
8125       var w = -1, h = -1;
8126       graph.eachNode(function(n) {
8127         if (n._depth == level
8128             && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8129           var dw = n.getData('width', prop);
8130           var dh = n.getData('height', prop);
8131           w = (w < dw) ? dw : w;
8132           h = (h < dh) ? dh : h;
8133         }
8134       });
8135       return {
8136         'width' : w < 0 ? dim.width : w,
8137         'height' : h < 0 ? dim.height : h
8138       };
8139     } else {
8140       return dim;
8141     }
8142   }
8143
8144
8145   function movetree(node, prop, val, orn) {
8146     var p = (orn == "left" || orn == "right") ? "y" : "x";
8147     node.getPos(prop)[p] += val;
8148   }
8149
8150
8151   function moveextent(extent, val) {
8152     var ans = [];
8153     $.each(extent, function(elem) {
8154       elem = slice.call(elem);
8155       elem[0] += val;
8156       elem[1] += val;
8157       ans.push(elem);
8158     });
8159     return ans;
8160   }
8161
8162
8163   function merge(ps, qs) {
8164     if (ps.length == 0)
8165       return qs;
8166     if (qs.length == 0)
8167       return ps;
8168     var p = ps.shift(), q = qs.shift();
8169     return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8170   }
8171
8172
8173   function mergelist(ls, def) {
8174     def = def || [];
8175     if (ls.length == 0)
8176       return def;
8177     var ps = ls.pop();
8178     return mergelist(ls, merge(ps, def));
8179   }
8180
8181
8182   function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8183     if (ext1.length <= i || ext2.length <= i)
8184       return 0;
8185
8186     var p = ext1[i][1], q = ext2[i][0];
8187     return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8188         + subtreeOffset, p - q + siblingOffset);
8189   }
8190
8191
8192   function fitlistl(es, subtreeOffset, siblingOffset) {
8193     function $fitlistl(acc, es, i) {
8194       if (es.length <= i)
8195         return [];
8196       var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8197       return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8198     }
8199     ;
8200     return $fitlistl( [], es, 0);
8201   }
8202
8203
8204   function fitlistr(es, subtreeOffset, siblingOffset) {
8205     function $fitlistr(acc, es, i) {
8206       if (es.length <= i)
8207         return [];
8208       var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8209       return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8210     }
8211     ;
8212     es = slice.call(es);
8213     var ans = $fitlistr( [], es.reverse(), 0);
8214     return ans.reverse();
8215   }
8216
8217
8218   function fitlist(es, subtreeOffset, siblingOffset, align) {
8219     var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8220         subtreeOffset, siblingOffset);
8221
8222     if (align == "left")
8223       esr = esl;
8224     else if (align == "right")
8225       esl = esr;
8226
8227     for ( var i = 0, ans = []; i < esl.length; i++) {
8228       ans[i] = (esl[i] + esr[i]) / 2;
8229     }
8230     return ans;
8231   }
8232
8233
8234   function design(graph, node, prop, config, orn) {
8235     var multitree = config.multitree;
8236     var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8237     var ind = +(orn == "left" || orn == "right");
8238     var p = auxp[ind], notp = auxp[1 - ind];
8239
8240     var cnode = config.Node;
8241     var s = auxs[ind], nots = auxs[1 - ind];
8242
8243     var siblingOffset = config.siblingOffset;
8244     var subtreeOffset = config.subtreeOffset;
8245     var align = config.align;
8246
8247     function $design(node, maxsize, acum) {
8248       var sval = node.getData(s, prop);
8249       var notsval = maxsize
8250           || (node.getData(nots, prop));
8251
8252       var trees = [], extents = [], chmaxsize = false;
8253       var chacum = notsval + config.levelDistance;
8254       node.eachSubnode(function(n) {
8255             if (n.exist
8256                 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8257
8258               if (!chmaxsize)
8259                 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8260
8261               var s = $design(n, chmaxsize[nots], acum + chacum);
8262               trees.push(s.tree);
8263               extents.push(s.extent);
8264             }
8265           });
8266       var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8267       for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8268         movetree(trees[i], prop, positions[i], orn);
8269         pextents.push(moveextent(extents[i], positions[i]));
8270       }
8271       var resultextent = [ [ -sval / 2, sval / 2 ] ]
8272           .concat(mergelist(pextents));
8273       node.getPos(prop)[p] = 0;
8274
8275       if (orn == "top" || orn == "left") {
8276         node.getPos(prop)[notp] = acum;
8277       } else {
8278         node.getPos(prop)[notp] = -acum;
8279       }
8280
8281       return {
8282         tree : node,
8283         extent : resultextent
8284       };
8285     }
8286
8287     $design(node, false, 0);
8288   }
8289
8290
8291   return new Class({
8292     /*
8293     Method: compute
8294     
8295     Computes nodes' positions.
8296
8297      */
8298     compute : function(property, computeLevels) {
8299       var prop = property || 'start';
8300       var node = this.graph.getNode(this.root);
8301       $.extend(node, {
8302         'drawn' : true,
8303         'exist' : true,
8304         'selected' : true
8305       });
8306       NodeDim.compute(this.graph, prop, this.config);
8307       if (!!computeLevels || !("_depth" in node)) {
8308         this.graph.computeLevels(this.root, 0, "ignore");
8309       }
8310       
8311       this.computePositions(node, prop);
8312     },
8313
8314     computePositions : function(node, prop) {
8315       var config = this.config;
8316       var multitree = config.multitree;
8317       var align = config.align;
8318       var indent = align !== 'center' && config.indent;
8319       var orn = config.orientation;
8320       var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8321       var that = this;
8322       $.each(orns, function(orn) {
8323         //calculate layout
8324           design(that.graph, node, prop, that.config, orn, prop);
8325           var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8326           //absolutize
8327           (function red(node) {
8328             node.eachSubnode(function(n) {
8329               if (n.exist
8330                   && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8331
8332                 n.getPos(prop)[i] += node.getPos(prop)[i];
8333                 if (indent) {
8334                   n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8335                 }
8336                 red(n);
8337               }
8338             });
8339           })(node);
8340         });
8341     }
8342   });
8343   
8344 })();
8345
8346 /*
8347  * File: Spacetree.js
8348  */
8349
8350 /*
8351    Class: ST
8352    
8353   A Tree layout with advanced contraction and expansion animations.
8354      
8355   Inspired by:
8356  
8357   SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson) 
8358   <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8359   
8360   Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8361   
8362   Note:
8363  
8364   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.
8365  
8366   Implements:
8367   
8368   All <Loader> methods
8369   
8370   Constructor Options:
8371   
8372   Inherits options from
8373   
8374   - <Options.Canvas>
8375   - <Options.Controller>
8376   - <Options.Tree>
8377   - <Options.Node>
8378   - <Options.Edge>
8379   - <Options.Label>
8380   - <Options.Events>
8381   - <Options.Tips>
8382   - <Options.NodeStyles>
8383   - <Options.Navigation>
8384   
8385   Additionally, there are other parameters and some default values changed
8386   
8387   constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8388   levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8389   levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8390   Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8391   offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8392   offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8393   duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8394   
8395   Instance Properties:
8396   
8397   canvas - Access a <Canvas> instance.
8398   graph - Access a <Graph> instance.
8399   op - Access a <ST.Op> instance.
8400   fx - Access a <ST.Plot> instance.
8401   labels - Access a <ST.Label> interface implementation.
8402
8403  */
8404
8405 $jit.ST= (function() {
8406     // Define some private methods first...
8407     // Nodes in path
8408     var nodesInPath = [];
8409     // Nodes to contract
8410     function getNodesToHide(node) {
8411       node = node || this.clickedNode;
8412       if(!this.config.constrained) {
8413         return [];
8414       }
8415       var Geom = this.geom;
8416       var graph = this.graph;
8417       var canvas = this.canvas;
8418       var level = node._depth, nodeArray = [];
8419           graph.eachNode(function(n) {
8420           if(n.exist && !n.selected) {
8421               if(n.isDescendantOf(node.id)) {
8422                 if(n._depth <= level) nodeArray.push(n);
8423               } else {
8424                 nodeArray.push(n);
8425               }
8426           }
8427           });
8428           var leafLevel = Geom.getRightLevelToShow(node, canvas);
8429           node.eachLevel(leafLevel, leafLevel, function(n) {
8430           if(n.exist && !n.selected) nodeArray.push(n);
8431           });
8432             
8433           for (var i = 0; i < nodesInPath.length; i++) {
8434             var n = this.graph.getNode(nodesInPath[i]);
8435             if(!n.isDescendantOf(node.id)) {
8436               nodeArray.push(n);
8437             }
8438           } 
8439           return nodeArray;       
8440     };
8441     // Nodes to expand
8442      function getNodesToShow(node) {
8443         var nodeArray = [], config = this.config;
8444         node = node || this.clickedNode;
8445         this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8446             if(config.multitree && !('$orn' in n.data) 
8447                         && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8448                 nodeArray.push(n);
8449             } else if(n.drawn && !n.anySubnode("drawn")) {
8450               nodeArray.push(n);
8451             }
8452         });
8453         return nodeArray;
8454      };
8455     // Now define the actual class.
8456     return new Class({
8457     
8458         Implements: [Loader, Extras, Layouts.Tree],
8459         
8460         initialize: function(controller) {            
8461           var $ST = $jit.ST;
8462           
8463           var config= {
8464                 levelsToShow: 2,
8465                 levelDistance: 30,
8466                 constrained: true,                
8467                 Node: {
8468                   type: 'rectangle'
8469                 },
8470                 duration: 700,
8471                 offsetX: 0,
8472                 offsetY: 0
8473             };
8474             
8475             this.controller = this.config = $.merge(
8476                 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller", 
8477                     "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8478
8479             var canvasConfig = this.config;
8480             if(canvasConfig.useCanvas) {
8481               this.canvas = canvasConfig.useCanvas;
8482               this.config.labelContainer = this.canvas.id + '-label';
8483             } else {
8484               if(canvasConfig.background) {
8485                 canvasConfig.background = $.merge({
8486                   type: 'Fade',
8487                   colorStop1: this.config.colorStop1,
8488                   colorStop2: this.config.colorStop2
8489                 }, canvasConfig.background);
8490               }
8491               this.canvas = new Canvas(this, canvasConfig);
8492               this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8493             }
8494
8495             this.graphOptions = {
8496                 'complex': true
8497             };
8498             this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8499             this.labels = new $ST.Label[canvasConfig.Label.type](this);
8500             this.fx = new $ST.Plot(this, $ST);
8501             this.op = new $ST.Op(this);
8502             this.group = new $ST.Group(this);
8503             this.geom = new $ST.Geom(this);
8504             this.clickedNode=  null;
8505             // initialize extras
8506             this.initializeExtras();
8507         },
8508     
8509         /*
8510          Method: plot
8511         
8512          Plots the <ST>. This is a shortcut to *fx.plot*.
8513
8514         */  
8515         plot: function() { this.fx.plot(this.controller); },
8516     
8517       
8518         /*
8519          Method: switchPosition
8520         
8521          Switches the tree orientation.
8522
8523          Parameters:
8524
8525         pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8526         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.
8527         onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8528
8529          Example:
8530
8531          (start code js)
8532            st.switchPosition("right", "animate", {
8533             onComplete: function() {
8534               alert('completed!');
8535             } 
8536            });
8537          (end code)
8538         */  
8539         switchPosition: function(pos, method, onComplete) {
8540           var Geom = this.geom, Plot = this.fx, that = this;
8541           if(!Plot.busy) {
8542               Plot.busy = true;
8543               this.contract({
8544                   onComplete: function() {
8545                       Geom.switchOrientation(pos);
8546                       that.compute('end', false);
8547                       Plot.busy = false;
8548                       if(method == 'animate') {
8549                           that.onClick(that.clickedNode.id, onComplete);  
8550                       } else if(method == 'replot') {
8551                           that.select(that.clickedNode.id, onComplete);
8552                       }
8553                   }
8554               }, pos);
8555           }
8556         },
8557
8558         /*
8559         Method: switchAlignment
8560        
8561         Switches the tree alignment.
8562
8563         Parameters:
8564
8565        align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8566        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.
8567        onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8568
8569         Example:
8570
8571         (start code js)
8572           st.switchAlignment("right", "animate", {
8573            onComplete: function() {
8574              alert('completed!');
8575            } 
8576           });
8577         (end code)
8578        */  
8579        switchAlignment: function(align, method, onComplete) {
8580         this.config.align = align;
8581         if(method == 'animate') {
8582                 this.select(this.clickedNode.id, onComplete);
8583         } else if(method == 'replot') {
8584                 this.onClick(this.clickedNode.id, onComplete);  
8585         }
8586        },
8587
8588        /*
8589         Method: addNodeInPath
8590        
8591         Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8592         
8593
8594         Parameters:
8595
8596        id - (string) A <Graph.Node> id.
8597
8598         Example:
8599
8600         (start code js)
8601           st.addNodeInPath("nodeId");
8602         (end code)
8603        */  
8604        addNodeInPath: function(id) {
8605            nodesInPath.push(id);
8606            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8607        },       
8608
8609        /*
8610        Method: clearNodesInPath
8611       
8612        Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8613        
8614        See also:
8615        
8616        <ST.addNodeInPath>
8617      
8618        Example:
8619
8620        (start code js)
8621          st.clearNodesInPath();
8622        (end code)
8623       */  
8624        clearNodesInPath: function(id) {
8625            nodesInPath.length = 0;
8626            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8627        },
8628         
8629        /*
8630          Method: refresh
8631         
8632          Computes positions and plots the tree.
8633          
8634        */
8635        refresh: function() {
8636            this.reposition();
8637            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8638        },    
8639
8640        reposition: function() {
8641             this.graph.computeLevels(this.root, 0, "ignore");
8642              this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8643             this.graph.eachNode(function(n) {
8644                 if(n.exist) n.drawn = true;
8645             });
8646             this.compute('end');
8647         },
8648         
8649         requestNodes: function(node, onComplete) {
8650           var handler = $.merge(this.controller, onComplete), 
8651           lev = this.config.levelsToShow;
8652           if(handler.request) {
8653               var leaves = [], d = node._depth;
8654               node.eachLevel(0, lev, function(n) {
8655                   if(n.drawn && 
8656                    !n.anySubnode()) {
8657                    leaves.push(n);
8658                    n._level = lev - (n._depth - d);
8659                   }
8660               });
8661               this.group.requestNodes(leaves, handler);
8662           }
8663             else
8664               handler.onComplete();
8665         },
8666      
8667         contract: function(onComplete, switched) {
8668           var orn  = this.config.orientation;
8669           var Geom = this.geom, Group = this.group;
8670           if(switched) Geom.switchOrientation(switched);
8671           var nodes = getNodesToHide.call(this);
8672           if(switched) Geom.switchOrientation(orn);
8673           Group.contract(nodes, $.merge(this.controller, onComplete));
8674         },
8675       
8676          move: function(node, onComplete) {
8677             this.compute('end', false);
8678             var move = onComplete.Move, offset = {
8679                 'x': move.offsetX,
8680                 'y': move.offsetY 
8681             };
8682             if(move.enable) {
8683                 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8684             }
8685             this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8686          },
8687       
8688         expand: function (node, onComplete) {
8689             var nodeArray = getNodesToShow.call(this, node);
8690             this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8691         },
8692     
8693         selectPath: function(node) {
8694           var that = this;
8695           this.graph.eachNode(function(n) { n.selected = false; }); 
8696           function path(node) {
8697               if(node == null || node.selected) return;
8698               node.selected = true;
8699               $.each(that.group.getSiblings([node])[node.id], 
8700               function(n) { 
8701                    n.exist = true; 
8702                    n.drawn = true; 
8703               });    
8704               var parents = node.getParents();
8705               parents = (parents.length > 0)? parents[0] : null;
8706               path(parents);
8707           };
8708           for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8709               path(this.graph.getNode(ns[i]));
8710           }
8711         },
8712       
8713         /*
8714         Method: setRoot
8715      
8716          Switches the current root node. Changes the topology of the Tree.
8717      
8718         Parameters:
8719            id - (string) The id of the node to be set as root.
8720            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.
8721            onComplete - (optional|object) An action to perform after the animation (if any).
8722  
8723         Example:
8724
8725         (start code js)
8726           st.setRoot('nodeId', 'animate', {
8727              onComplete: function() {
8728                alert('complete!');
8729              }
8730           });
8731         (end code)
8732      */
8733      setRoot: function(id, method, onComplete) {
8734                 if(this.busy) return;
8735                 this.busy = true;
8736           var that = this, canvas = this.canvas;
8737                 var rootNode = this.graph.getNode(this.root);
8738                 var clickedNode = this.graph.getNode(id);
8739                 function $setRoot() {
8740                 if(this.config.multitree && clickedNode.data.$orn) {
8741                         var orn = clickedNode.data.$orn;
8742                         var opp = {
8743                                         'left': 'right',
8744                                         'right': 'left',
8745                                         'top': 'bottom',
8746                                         'bottom': 'top'
8747                         }[orn];
8748                         rootNode.data.$orn = opp;
8749                         (function tag(rootNode) {
8750                                 rootNode.eachSubnode(function(n) {
8751                                         if(n.id != id) {
8752                                                 n.data.$orn = opp;
8753                                                 tag(n);
8754                                         }
8755                                 });
8756                         })(rootNode);
8757                         delete clickedNode.data.$orn;
8758                 }
8759                 this.root = id;
8760                 this.clickedNode = clickedNode;
8761                 this.graph.computeLevels(this.root, 0, "ignore");
8762                 this.geom.setRightLevelToShow(clickedNode, canvas, {
8763                   execHide: false,
8764                   onShow: function(node) {
8765                     if(!node.drawn) {
8766                     node.drawn = true;
8767                     node.setData('alpha', 1, 'end');
8768                     node.setData('alpha', 0);
8769                     node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8770                     }
8771                   }
8772                 });
8773               this.compute('end');
8774               this.busy = true;
8775               this.fx.animate({
8776                 modes: ['linear', 'node-property:alpha'],
8777                 onComplete: function() {
8778                   that.busy = false;
8779                   that.onClick(id, {
8780                     onComplete: function() {
8781                       onComplete && onComplete.onComplete();
8782                     }
8783                   });
8784                 }
8785               });
8786                 }
8787
8788                 // delete previous orientations (if any)
8789                 delete rootNode.data.$orns;
8790
8791                 if(method == 'animate') {
8792                   $setRoot.call(this);
8793                   that.selectPath(clickedNode);
8794                 } else if(method == 'replot') {
8795                         $setRoot.call(this);
8796                         this.select(this.root);
8797                 }
8798      },
8799
8800      /*
8801            Method: addSubtree
8802         
8803             Adds a subtree.
8804         
8805            Parameters:
8806               subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8807               method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8808               onComplete - (optional|object) An action to perform after the animation (if any).
8809     
8810            Example:
8811
8812            (start code js)
8813              st.addSubtree(json, 'animate', {
8814                 onComplete: function() {
8815                   alert('complete!');
8816                 }
8817              });
8818            (end code)
8819         */
8820         addSubtree: function(subtree, method, onComplete) {
8821             if(method == 'replot') {
8822                 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8823             } else if (method == 'animate') {
8824                 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8825             }
8826         },
8827     
8828         /*
8829            Method: removeSubtree
8830         
8831             Removes a subtree.
8832         
8833            Parameters:
8834               id - (string) The _id_ of the subtree to be removed.
8835               removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8836               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.
8837               onComplete - (optional|object) An action to perform after the animation (if any).
8838
8839           Example:
8840
8841           (start code js)
8842             st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8843               onComplete: function() {
8844                 alert('complete!');
8845               }
8846             });
8847           (end code)
8848     
8849         */
8850         removeSubtree: function(id, removeRoot, method, onComplete) {
8851             var node = this.graph.getNode(id), subids = [];
8852             node.eachLevel(+!removeRoot, false, function(n) {
8853                 subids.push(n.id);
8854             });
8855             if(method == 'replot') {
8856                 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8857             } else if (method == 'animate') {
8858                 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8859             }
8860         },
8861     
8862         /*
8863            Method: select
8864         
8865             Selects a node in the <ST> without performing an animation. Useful when selecting 
8866             nodes which are currently hidden or deep inside the tree.
8867
8868           Parameters:
8869             id - (string) The id of the node to select.
8870             onComplete - (optional|object) an onComplete callback.
8871
8872           Example:
8873           (start code js)
8874             st.select('mynodeid', {
8875               onComplete: function() {
8876                 alert('complete!');
8877               }
8878             });
8879           (end code)
8880         */
8881         select: function(id, onComplete) {
8882             var group = this.group, geom = this.geom;
8883             var node=  this.graph.getNode(id), canvas = this.canvas;
8884             var root  = this.graph.getNode(this.root);
8885             var complete = $.merge(this.controller, onComplete);
8886             var that = this;
8887     
8888             complete.onBeforeCompute(node);
8889             this.selectPath(node);
8890             this.clickedNode= node;
8891             this.requestNodes(node, {
8892                 onComplete: function(){
8893                     group.hide(group.prepare(getNodesToHide.call(that)), complete);
8894                     geom.setRightLevelToShow(node, canvas);
8895                     that.compute("current");
8896                     that.graph.eachNode(function(n) { 
8897                         var pos = n.pos.getc(true);
8898                         n.startPos.setc(pos.x, pos.y);
8899                         n.endPos.setc(pos.x, pos.y);
8900                         n.visited = false; 
8901                     });
8902                     var offset = { x: complete.offsetX, y: complete.offsetY };
8903                     that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8904                     group.show(getNodesToShow.call(that));              
8905                     that.plot();
8906                     complete.onAfterCompute(that.clickedNode);
8907                     complete.onComplete();
8908                 }
8909             });     
8910         },
8911     
8912       /*
8913          Method: onClick
8914     
8915         Animates the <ST> to center the node specified by *id*.
8916             
8917         Parameters:
8918         
8919         id - (string) A node id.
8920         options - (optional|object) A group of options and callbacks described below.
8921         onComplete - (object) An object callback called when the animation finishes.
8922         Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8923
8924         Example:
8925
8926         (start code js)
8927           st.onClick('mynodeid', {
8928                   Move: {
8929                         enable: true,
8930                     offsetX: 30,
8931                     offsetY: 5
8932                   },
8933                   onComplete: function() {
8934                       alert('yay!');
8935                   }
8936           });
8937         (end code)
8938     
8939         */    
8940       onClick: function (id, options) {
8941         var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8942         var innerController = {
8943             Move: {
8944                     enable: true,
8945               offsetX: config.offsetX || 0,
8946               offsetY: config.offsetY || 0  
8947             },
8948             setRightLevelToShowConfig: false,
8949             onBeforeRequest: $.empty,
8950             onBeforeContract: $.empty,
8951             onBeforeMove: $.empty,
8952             onBeforeExpand: $.empty
8953         };
8954         var complete = $.merge(this.controller, innerController, options);
8955         
8956         if(!this.busy) {
8957             this.busy = true;
8958             var node = this.graph.getNode(id);
8959             this.selectPath(node, this.clickedNode);
8960                 this.clickedNode = node;
8961             complete.onBeforeCompute(node);
8962             complete.onBeforeRequest(node);
8963             this.requestNodes(node, {
8964                 onComplete: function() {
8965                     complete.onBeforeContract(node);
8966                     that.contract({
8967                         onComplete: function() {
8968                             Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8969                             complete.onBeforeMove(node);
8970                             that.move(node, {
8971                                 Move: complete.Move,
8972                                 onComplete: function() {
8973                                     complete.onBeforeExpand(node);
8974                                     that.expand(node, {
8975                                         onComplete: function() {
8976                                             that.busy = false;
8977                                             complete.onAfterCompute(id);
8978                                             complete.onComplete();
8979                                         }
8980                                     }); // expand
8981                                 }
8982                             }); // move
8983                         }
8984                     });// contract
8985                 }
8986             });// request
8987         }
8988       }
8989     });
8990
8991 })();
8992
8993 $jit.ST.$extend = true;
8994
8995 /*
8996    Class: ST.Op
8997     
8998    Custom extension of <Graph.Op>.
8999
9000    Extends:
9001
9002    All <Graph.Op> methods
9003    
9004    See also:
9005    
9006    <Graph.Op>
9007
9008 */
9009 $jit.ST.Op = new Class({
9010
9011   Implements: Graph.Op
9012     
9013 });
9014
9015 /*
9016     
9017      Performs operations on group of nodes.
9018
9019 */
9020 $jit.ST.Group = new Class({
9021     
9022     initialize: function(viz) {
9023         this.viz = viz;
9024         this.canvas = viz.canvas;
9025         this.config = viz.config;
9026         this.animation = new Animation;
9027         this.nodes = null;
9028     },
9029     
9030     /*
9031     
9032        Calls the request method on the controller to request a subtree for each node. 
9033     */
9034     requestNodes: function(nodes, controller) {
9035         var counter = 0, len = nodes.length, nodeSelected = {};
9036         var complete = function() { controller.onComplete(); };
9037         var viz = this.viz;
9038         if(len == 0) complete();
9039         for(var i=0; i<len; i++) {
9040             nodeSelected[nodes[i].id] = nodes[i];
9041             controller.request(nodes[i].id, nodes[i]._level, {
9042                 onComplete: function(nodeId, data) {
9043                     if(data && data.children) {
9044                         data.id = nodeId;
9045                         viz.op.sum(data, { type: 'nothing' });
9046                     }
9047                     if(++counter == len) {
9048                         viz.graph.computeLevels(viz.root, 0);
9049                         complete();
9050                     }
9051                 }
9052             });
9053         }
9054     },
9055     
9056     /*
9057     
9058        Collapses group of nodes. 
9059     */
9060     contract: function(nodes, controller) {
9061         var viz = this.viz;
9062         var that = this;
9063
9064         nodes = this.prepare(nodes);
9065         this.animation.setOptions($.merge(controller, {
9066             $animating: false,
9067             compute: function(delta) {
9068               if(delta == 1) delta = 0.99;
9069               that.plotStep(1 - delta, controller, this.$animating);
9070               this.$animating = 'contract';
9071             },
9072             
9073             complete: function() {
9074                 that.hide(nodes, controller);
9075             }       
9076         })).start();
9077     },
9078     
9079     hide: function(nodes, controller) {
9080         var viz = this.viz;
9081         for(var i=0; i<nodes.length; i++) {
9082             // TODO nodes are requested on demand, but not
9083             // deleted when hidden. Would that be a good feature?
9084             // Currently that feature is buggy, so I'll turn it off
9085             // Actually this feature is buggy because trimming should take
9086             // place onAfterCompute and not right after collapsing nodes.
9087             if (true || !controller || !controller.request) {
9088                 nodes[i].eachLevel(1, false, function(elem){
9089                     if (elem.exist) {
9090                         $.extend(elem, {
9091                             'drawn': false,
9092                             'exist': false
9093                         });
9094                     }
9095                 });
9096             } else {
9097                 var ids = [];
9098                 nodes[i].eachLevel(1, false, function(n) {
9099                     ids.push(n.id);
9100                 });
9101                 viz.op.removeNode(ids, { 'type': 'nothing' });
9102                 viz.labels.clearLabels();
9103             }
9104         }
9105         controller.onComplete();
9106     },    
9107     
9108
9109     /*
9110        Expands group of nodes. 
9111     */
9112     expand: function(nodes, controller) {
9113         var that = this;
9114         this.show(nodes);
9115         this.animation.setOptions($.merge(controller, {
9116             $animating: false,
9117             compute: function(delta) {
9118                 that.plotStep(delta, controller, this.$animating);
9119                 this.$animating = 'expand';
9120             },
9121             
9122             complete: function() {
9123                 that.plotStep(undefined, controller, false);
9124                 controller.onComplete();
9125             }       
9126         })).start();
9127         
9128     },
9129     
9130     show: function(nodes) {
9131         var config = this.config;
9132         this.prepare(nodes);
9133         $.each(nodes, function(n) {
9134                 // check for root nodes if multitree
9135                 if(config.multitree && !('$orn' in n.data)) {
9136                         delete n.data.$orns;
9137                         var orns = ' ';
9138                         n.eachSubnode(function(ch) {
9139                                 if(('$orn' in ch.data) 
9140                                                 && orns.indexOf(ch.data.$orn) < 0 
9141                                                 && ch.exist && !ch.drawn) {
9142                                         orns += ch.data.$orn + ' ';
9143                                 }
9144                         });
9145                         n.data.$orns = orns;
9146                 }
9147             n.eachLevel(0, config.levelsToShow, function(n) {
9148                 if(n.exist) n.drawn = true;
9149             });     
9150         });
9151     },
9152     
9153     prepare: function(nodes) {
9154         this.nodes = this.getNodesWithChildren(nodes);
9155         return this.nodes;
9156     },
9157     
9158     /*
9159        Filters an array of nodes leaving only nodes with children.
9160     */
9161     getNodesWithChildren: function(nodes) {
9162         var ans = [], config = this.config, root = this.viz.root;
9163         nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9164         for(var i=0; i<nodes.length; i++) {
9165             if(nodes[i].anySubnode("exist")) {
9166                 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9167                     if(!config.multitree || '$orn' in nodes[j].data) {
9168                                 desc = desc || nodes[i].isDescendantOf(nodes[j].id);                            
9169                     }
9170                 }
9171                 if(!desc) ans.push(nodes[i]);
9172             }
9173         }
9174         return ans;
9175     },
9176     
9177     plotStep: function(delta, controller, animating) {
9178         var viz = this.viz,
9179         config = this.config,
9180         canvas = viz.canvas, 
9181         ctx = canvas.getCtx(),
9182         nodes = this.nodes;
9183         var i, node;
9184         // hide nodes that are meant to be collapsed/expanded
9185         var nds = {};
9186         for(i=0; i<nodes.length; i++) {
9187           node = nodes[i];
9188           nds[node.id] = [];
9189           var root = config.multitree && !('$orn' in node.data);
9190           var orns = root && node.data.$orns;
9191           node.eachSubgraph(function(n) { 
9192             // TODO(nico): Cleanup
9193                   // special check for root node subnodes when
9194                   // multitree is checked.
9195                   if(root && orns && orns.indexOf(n.data.$orn) > 0 
9196                                   && n.drawn) {
9197                           n.drawn = false;
9198                   nds[node.id].push(n);
9199               } else if((!root || !orns) && n.drawn) {
9200                 n.drawn = false;
9201                 nds[node.id].push(n);
9202               }
9203             }); 
9204             node.drawn = true;
9205         }
9206         // plot the whole (non-scaled) tree
9207         if(nodes.length > 0) viz.fx.plot();
9208         // show nodes that were previously hidden
9209         for(i in nds) {
9210           $.each(nds[i], function(n) { n.drawn = true; });
9211         }
9212         // plot each scaled subtree
9213         for(i=0; i<nodes.length; i++) {
9214           node = nodes[i];
9215           ctx.save();
9216           viz.fx.plotSubtree(node, controller, delta, animating);                
9217           ctx.restore();
9218         }
9219       },
9220
9221       getSiblings: function(nodes) {
9222         var siblings = {};
9223         $.each(nodes, function(n) {
9224             var par = n.getParents();
9225             if (par.length == 0) {
9226                 siblings[n.id] = [n];
9227             } else {
9228                 var ans = [];
9229                 par[0].eachSubnode(function(sn) {
9230                     ans.push(sn);
9231                 });
9232                 siblings[n.id] = ans;
9233             }
9234         });
9235         return siblings;
9236     }
9237 });
9238
9239 /*
9240    ST.Geom
9241
9242    Performs low level geometrical computations.
9243
9244    Access:
9245
9246    This instance can be accessed with the _geom_ parameter of the st instance created.
9247
9248    Example:
9249
9250    (start code js)
9251     var st = new ST(canvas, config);
9252     st.geom.translate //or can also call any other <ST.Geom> method
9253    (end code)
9254
9255 */
9256
9257 $jit.ST.Geom = new Class({
9258     Implements: Graph.Geom,
9259     /*
9260        Changes the tree current orientation to the one specified.
9261
9262        You should usually use <ST.switchPosition> instead.
9263     */  
9264     switchOrientation: function(orn) {
9265         this.config.orientation = orn;
9266     },
9267
9268     /*
9269        Makes a value dispatch according to the current layout
9270        Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9271      */
9272     dispatch: function() {
9273           // TODO(nico) should store Array.prototype.slice.call somewhere.
9274         var args = Array.prototype.slice.call(arguments);
9275         var s = args.shift(), len = args.length;
9276         var val = function(a) { return typeof a == 'function'? a() : a; };
9277         if(len == 2) {
9278             return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9279         } else if(len == 4) {
9280             switch(s) {
9281                 case "top": return val(args[0]);
9282                 case "right": return val(args[1]);
9283                 case "bottom": return val(args[2]);
9284                 case "left": return val(args[3]);
9285             }
9286         }
9287         return undefined;
9288     },
9289
9290     /*
9291        Returns label height or with, depending on the tree current orientation.
9292     */  
9293     getSize: function(n, invert) {
9294         var data = n.data, config = this.config;
9295         var siblingOffset = config.siblingOffset;
9296         var s = (config.multitree 
9297                         && ('$orn' in data) 
9298                         && data.$orn) || config.orientation;
9299         var w = n.getData('width') + siblingOffset;
9300         var h = n.getData('height') + siblingOffset;
9301         if(!invert)
9302             return this.dispatch(s, h, w);
9303         else
9304             return this.dispatch(s, w, h);
9305     },
9306     
9307     /*
9308        Calculates a subtree base size. This is an utility function used by _getBaseSize_
9309     */  
9310     getTreeBaseSize: function(node, level, leaf) {
9311         var size = this.getSize(node, true), baseHeight = 0, that = this;
9312         if(leaf(level, node)) return size;
9313         if(level === 0) return 0;
9314         node.eachSubnode(function(elem) {
9315             baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9316         });
9317         return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9318     },
9319
9320
9321     /*
9322        getEdge
9323        
9324        Returns a Complex instance with the begin or end position of the edge to be plotted.
9325
9326        Parameters:
9327
9328        node - A <Graph.Node> that is connected to this edge.
9329        type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9330
9331        Returns:
9332
9333        A <Complex> number specifying the begin or end position.
9334     */  
9335     getEdge: function(node, type, s) {
9336         var $C = function(a, b) { 
9337           return function(){
9338             return node.pos.add(new Complex(a, b));
9339           }; 
9340         };
9341         var dim = this.node;
9342         var w = node.getData('width');
9343         var h = node.getData('height');
9344
9345         if(type == 'begin') {
9346             if(dim.align == "center") {
9347                 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9348                                      $C(0, -h/2),$C(w/2, 0));
9349             } else if(dim.align == "left") {
9350                 return this.dispatch(s, $C(0, h), $C(0, 0),
9351                                      $C(0, 0), $C(w, 0));
9352             } else if(dim.align == "right") {
9353                 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9354                                      $C(0, -h),$C(0, 0));
9355             } else throw "align: not implemented";
9356             
9357             
9358         } else if(type == 'end') {
9359             if(dim.align == "center") {
9360                 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9361                                      $C(0, h/2),  $C(-w/2, 0));
9362             } else if(dim.align == "left") {
9363                 return this.dispatch(s, $C(0, 0), $C(w, 0),
9364                                      $C(0, h), $C(0, 0));
9365             } else if(dim.align == "right") {
9366                 return this.dispatch(s, $C(0, -h),$C(0, 0),
9367                                      $C(0, 0), $C(-w, 0));
9368             } else throw "align: not implemented";
9369         }
9370     },
9371
9372     /*
9373        Adjusts the tree position due to canvas scaling or translation.
9374     */  
9375     getScaledTreePosition: function(node, scale) {
9376         var dim = this.node;
9377         var w = node.getData('width');
9378         var h = node.getData('height');
9379         var s = (this.config.multitree 
9380                         && ('$orn' in node.data) 
9381                         && node.data.$orn) || this.config.orientation;
9382
9383         var $C = function(a, b) { 
9384           return function(){
9385             return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9386           }; 
9387         };
9388         if(dim.align == "left") {
9389             return this.dispatch(s, $C(0, h), $C(0, 0),
9390                                  $C(0, 0), $C(w, 0));
9391         } else if(dim.align == "center") {
9392             return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9393                                  $C(0, -h / 2),$C(w / 2, 0));
9394         } else if(dim.align == "right") {
9395             return this.dispatch(s, $C(0, 0), $C(-w, 0),
9396                                  $C(0, -h),$C(0, 0));
9397         } else throw "align: not implemented";
9398     },
9399
9400     /*
9401        treeFitsInCanvas
9402        
9403        Returns a Boolean if the current subtree fits in canvas.
9404
9405        Parameters:
9406
9407        node - A <Graph.Node> which is the current root of the subtree.
9408        canvas - The <Canvas> object.
9409        level - The depth of the subtree to be considered.
9410     */  
9411     treeFitsInCanvas: function(node, canvas, level) {
9412         var csize = canvas.getSize();
9413         var s = (this.config.multitree 
9414                         && ('$orn' in node.data) 
9415                         && node.data.$orn) || this.config.orientation;
9416
9417         var size = this.dispatch(s, csize.width, csize.height);
9418         var baseSize = this.getTreeBaseSize(node, level, function(level, node) { 
9419           return level === 0 || !node.anySubnode();
9420         });
9421         return (baseSize < size);
9422     }
9423 });
9424
9425 /*
9426   Class: ST.Plot
9427   
9428   Custom extension of <Graph.Plot>.
9429
9430   Extends:
9431
9432   All <Graph.Plot> methods
9433   
9434   See also:
9435   
9436   <Graph.Plot>
9437
9438 */
9439 $jit.ST.Plot = new Class({
9440     
9441     Implements: Graph.Plot,
9442     
9443     /*
9444        Plots a subtree from the spacetree.
9445     */
9446     plotSubtree: function(node, opt, scale, animating) {
9447         var viz = this.viz, canvas = viz.canvas, config = viz.config;
9448         scale = Math.min(Math.max(0.001, scale), 1);
9449         if(scale >= 0) {
9450             node.drawn = false;     
9451             var ctx = canvas.getCtx();
9452             var diff = viz.geom.getScaledTreePosition(node, scale);
9453             ctx.translate(diff.x, diff.y);
9454             ctx.scale(scale, scale);
9455         }
9456         this.plotTree(node, $.merge(opt, {
9457           'withLabels': true,
9458           'hideLabels': !!scale,
9459           'plotSubtree': function(n, ch) {
9460             var root = config.multitree && !('$orn' in node.data);
9461             var orns = root && node.getData('orns');
9462             return !root || orns.indexOf(elem.getData('orn')) > -1;
9463           }
9464         }), animating);
9465         if(scale >= 0) node.drawn = true;
9466     },   
9467    
9468     /*
9469         Method: getAlignedPos
9470         
9471         Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9472         
9473         Parameters:
9474         
9475         pos - (object) A <Graph.Node> position.
9476         width - (number) The width of the node.
9477         height - (number) The height of the node.
9478         
9479      */
9480     getAlignedPos: function(pos, width, height) {
9481         var nconfig = this.node;
9482         var square, orn;
9483         if(nconfig.align == "center") {
9484             square = {
9485                 x: pos.x - width / 2,
9486                 y: pos.y - height / 2
9487             };
9488         } else if (nconfig.align == "left") {
9489             orn = this.config.orientation;
9490             if(orn == "bottom" || orn == "top") {
9491                 square = {
9492                     x: pos.x - width / 2,
9493                     y: pos.y
9494                 };
9495             } else {
9496                 square = {
9497                     x: pos.x,
9498                     y: pos.y - height / 2
9499                 };
9500             }
9501         } else if(nconfig.align == "right") {
9502             orn = this.config.orientation;
9503             if(orn == "bottom" || orn == "top") {
9504                 square = {
9505                     x: pos.x - width / 2,
9506                     y: pos.y - height
9507                 };
9508             } else {
9509                 square = {
9510                     x: pos.x - width,
9511                     y: pos.y - height / 2
9512                 };
9513             }
9514         } else throw "align: not implemented";
9515         
9516         return square;
9517     },
9518     
9519     getOrientation: function(adj) {
9520         var config = this.config;
9521         var orn = config.orientation;
9522
9523         if(config.multitree) {
9524                 var nodeFrom = adj.nodeFrom;
9525                 var nodeTo = adj.nodeTo;
9526                 orn = (('$orn' in nodeFrom.data) 
9527                         && nodeFrom.data.$orn) 
9528                         || (('$orn' in nodeTo.data) 
9529                         && nodeTo.data.$orn);
9530         }
9531
9532         return orn; 
9533     }
9534 });
9535
9536 /*
9537   Class: ST.Label
9538
9539   Custom extension of <Graph.Label>. 
9540   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9541
9542   Extends:
9543
9544   All <Graph.Label> methods and subclasses.
9545
9546   See also:
9547
9548   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9549  */ 
9550 $jit.ST.Label = {};
9551
9552 /*
9553    ST.Label.Native
9554
9555    Custom extension of <Graph.Label.Native>.
9556
9557    Extends:
9558
9559    All <Graph.Label.Native> methods
9560
9561    See also:
9562
9563    <Graph.Label.Native>
9564 */
9565 $jit.ST.Label.Native = new Class({
9566   Implements: Graph.Label.Native,
9567
9568   renderLabel: function(canvas, node, controller) {
9569     var ctx = canvas.getCtx();
9570     var coord = node.pos.getc(true);
9571     ctx.fillText(node.name, coord.x, coord.y);
9572   }
9573 });
9574
9575 $jit.ST.Label.DOM = new Class({
9576   Implements: Graph.Label.DOM,
9577
9578   /* 
9579       placeLabel
9580
9581       Overrides abstract method placeLabel in <Graph.Plot>.
9582
9583       Parameters:
9584
9585       tag - A DOM label element.
9586       node - A <Graph.Node>.
9587       controller - A configuration/controller object passed to the visualization.
9588      
9589     */
9590     placeLabel: function(tag, node, controller) {
9591         var pos = node.pos.getc(true), 
9592             config = this.viz.config, 
9593             dim = config.Node, 
9594             canvas = this.viz.canvas,
9595             w = node.getData('width'),
9596             h = node.getData('height'),
9597             radius = canvas.getSize(),
9598             labelPos, orn;
9599         
9600         var ox = canvas.translateOffsetX,
9601             oy = canvas.translateOffsetY,
9602             sx = canvas.scaleOffsetX,
9603             sy = canvas.scaleOffsetY,
9604             posx = pos.x * sx + ox,
9605             posy = pos.y * sy + oy;
9606
9607         if(dim.align == "center") {
9608             labelPos= {
9609                 x: Math.round(posx - w / 2 + radius.width/2),
9610                 y: Math.round(posy - h / 2 + radius.height/2)
9611             };
9612         } else if (dim.align == "left") {
9613             orn = config.orientation;
9614             if(orn == "bottom" || orn == "top") {
9615                 labelPos= {
9616                     x: Math.round(posx - w / 2 + radius.width/2),
9617                     y: Math.round(posy + radius.height/2)
9618                 };
9619             } else {
9620                 labelPos= {
9621                     x: Math.round(posx + radius.width/2),
9622                     y: Math.round(posy - h / 2 + radius.height/2)
9623                 };
9624             }
9625         } else if(dim.align == "right") {
9626             orn = config.orientation;
9627             if(orn == "bottom" || orn == "top") {
9628                 labelPos= {
9629                     x: Math.round(posx - w / 2 + radius.width/2),
9630                     y: Math.round(posy - h + radius.height/2)
9631                 };
9632             } else {
9633                 labelPos= {
9634                     x: Math.round(posx - w + radius.width/2),
9635                     y: Math.round(posy - h / 2 + radius.height/2)
9636                 };
9637             }
9638         } else throw "align: not implemented";
9639
9640         var style = tag.style;
9641         style.left = labelPos.x + 'px';
9642         style.top  = labelPos.y + 'px';
9643         style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9644         controller.onPlaceLabel(tag, node);
9645     }
9646 });
9647
9648 /*
9649   ST.Label.SVG
9650
9651   Custom extension of <Graph.Label.SVG>.
9652
9653   Extends:
9654
9655   All <Graph.Label.SVG> methods
9656
9657   See also:
9658
9659   <Graph.Label.SVG>
9660 */
9661 $jit.ST.Label.SVG = new Class({
9662   Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9663
9664   initialize: function(viz) {
9665     this.viz = viz;
9666   }
9667 });
9668
9669 /*
9670    ST.Label.HTML
9671
9672    Custom extension of <Graph.Label.HTML>.
9673
9674    Extends:
9675
9676    All <Graph.Label.HTML> methods.
9677
9678    See also:
9679
9680    <Graph.Label.HTML>
9681
9682 */
9683 $jit.ST.Label.HTML = new Class({
9684   Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9685
9686   initialize: function(viz) {
9687     this.viz = viz;
9688   }
9689 });
9690
9691
9692 /*
9693   Class: ST.Plot.NodeTypes
9694
9695   This class contains a list of <Graph.Node> built-in types. 
9696   Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9697
9698   You can add your custom node types, customizing your visualization to the extreme.
9699
9700   Example:
9701
9702   (start code js)
9703     ST.Plot.NodeTypes.implement({
9704       'mySpecialType': {
9705         'render': function(node, canvas) {
9706           //print your custom node to canvas
9707         },
9708         //optional
9709         'contains': function(node, pos) {
9710           //return true if pos is inside the node or false otherwise
9711         }
9712       }
9713     });
9714   (end code)
9715
9716 */
9717 $jit.ST.Plot.NodeTypes = new Class({
9718   'none': {
9719     'render': $.empty,
9720     'contains': $.lambda(false)
9721   },
9722   'circle': {
9723     'render': function(node, canvas) {
9724       var dim  = node.getData('dim'),
9725           pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9726           dim2 = dim/2;
9727       this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9728     },
9729     'contains': function(node, pos) {
9730       var dim  = node.getData('dim'),
9731           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9732           dim2 = dim/2;
9733       this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9734     }
9735   },
9736   'square': {
9737     'render': function(node, canvas) {
9738       var dim  = node.getData('dim'),
9739           dim2 = dim/2,
9740           pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9741       this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9742     },
9743     'contains': function(node, pos) {
9744       var dim  = node.getData('dim'),
9745           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9746           dim2 = dim/2;
9747       this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9748     }
9749   },
9750   'ellipse': {
9751     'render': function(node, canvas) {
9752       var width = node.getData('width'),
9753           height = node.getData('height'),
9754           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9755       this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9756     },
9757     'contains': function(node, pos) {
9758       var width = node.getData('width'),
9759           height = node.getData('height'),
9760           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9761       this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9762     }
9763   },
9764   'rectangle': {
9765     'render': function(node, canvas) {
9766       var width = node.getData('width'),
9767           height = node.getData('height'),
9768           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9769       this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9770     },
9771     'contains': function(node, pos) {
9772       var width = node.getData('width'),
9773           height = node.getData('height'),
9774           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9775       this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9776     }
9777   }
9778 });
9779
9780 /*
9781   Class: ST.Plot.EdgeTypes
9782
9783   This class contains a list of <Graph.Adjacence> built-in types. 
9784   Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9785
9786   You can add your custom edge types, customizing your visualization to the extreme.
9787
9788   Example:
9789
9790   (start code js)
9791     ST.Plot.EdgeTypes.implement({
9792       'mySpecialType': {
9793         'render': function(adj, canvas) {
9794           //print your custom edge to canvas
9795         },
9796         //optional
9797         'contains': function(adj, pos) {
9798           //return true if pos is inside the arc or false otherwise
9799         }
9800       }
9801     });
9802   (end code)
9803
9804 */
9805 $jit.ST.Plot.EdgeTypes = new Class({
9806     'none': $.empty,
9807     'line': {
9808       'render': function(adj, canvas) {
9809         var orn = this.getOrientation(adj),
9810             nodeFrom = adj.nodeFrom, 
9811             nodeTo = adj.nodeTo,
9812             rel = nodeFrom._depth < nodeTo._depth,
9813             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9814             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9815         this.edgeHelper.line.render(from, to, canvas);
9816       },
9817       'contains': function(adj, pos) {
9818         var orn = this.getOrientation(adj),
9819             nodeFrom = adj.nodeFrom, 
9820             nodeTo = adj.nodeTo,
9821             rel = nodeFrom._depth < nodeTo._depth,
9822             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9823             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9824         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9825       }
9826     },
9827      'arrow': {
9828        'render': function(adj, canvas) {
9829          var orn = this.getOrientation(adj),
9830              node = adj.nodeFrom, 
9831              child = adj.nodeTo,
9832              dim = adj.getData('dim'),
9833              from = this.viz.geom.getEdge(node, 'begin', orn),
9834              to = this.viz.geom.getEdge(child, 'end', orn),
9835              direction = adj.data.$direction,
9836              inv = (direction && direction.length>1 && direction[0] != node.id);
9837          this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9838        },
9839        'contains': function(adj, pos) {
9840          var orn = this.getOrientation(adj),
9841              nodeFrom = adj.nodeFrom, 
9842              nodeTo = adj.nodeTo,
9843              rel = nodeFrom._depth < nodeTo._depth,
9844              from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9845              to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9846          return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9847        }
9848      },
9849     'quadratic:begin': {
9850        'render': function(adj, canvas) {
9851           var orn = this.getOrientation(adj);
9852           var nodeFrom = adj.nodeFrom, 
9853               nodeTo = adj.nodeTo,
9854               rel = nodeFrom._depth < nodeTo._depth,
9855               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9856               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9857               dim = adj.getData('dim'),
9858               ctx = canvas.getCtx();
9859           ctx.beginPath();
9860           ctx.moveTo(begin.x, begin.y);
9861           switch(orn) {
9862             case "left":
9863               ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9864               break;
9865             case "right":
9866               ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9867               break;
9868             case "top":
9869               ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9870               break;
9871             case "bottom":
9872               ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9873               break;
9874           }
9875           ctx.stroke();
9876         }
9877      },
9878     'quadratic:end': {
9879        'render': function(adj, canvas) {
9880           var orn = this.getOrientation(adj);
9881           var nodeFrom = adj.nodeFrom, 
9882               nodeTo = adj.nodeTo,
9883               rel = nodeFrom._depth < nodeTo._depth,
9884               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9885               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9886               dim = adj.getData('dim'),
9887               ctx = canvas.getCtx();
9888           ctx.beginPath();
9889           ctx.moveTo(begin.x, begin.y);
9890           switch(orn) {
9891             case "left":
9892               ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9893               break;
9894             case "right":
9895               ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9896               break;
9897             case "top":
9898               ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9899               break;
9900             case "bottom":
9901               ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9902               break;
9903           }
9904           ctx.stroke();
9905        }
9906      },
9907     'bezier': {
9908        'render': function(adj, canvas) {
9909          var orn = this.getOrientation(adj),
9910              nodeFrom = adj.nodeFrom, 
9911              nodeTo = adj.nodeTo,
9912              rel = nodeFrom._depth < nodeTo._depth,
9913              begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9914              end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9915              dim = adj.getData('dim'),
9916              ctx = canvas.getCtx();
9917          ctx.beginPath();
9918          ctx.moveTo(begin.x, begin.y);
9919          switch(orn) {
9920            case "left":
9921              ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9922              break;
9923            case "right":
9924              ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9925              break;
9926            case "top":
9927              ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9928              break;
9929            case "bottom":
9930              ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9931              break;
9932          }
9933          ctx.stroke();
9934        }
9935     }
9936 });
9937
9938
9939 Options.LineChart = {
9940   $extend: true,
9941
9942   animate: false,
9943   labelOffset: 3, // label offset
9944   type: 'basic', // gradient
9945   dataPointSize: 10,
9946   Tips: {
9947     enable: false,
9948     onShow: $.empty,
9949     onHide: $.empty
9950   },
9951   Ticks: {
9952         enable: false,
9953         segments: 4,
9954         color: '#000000'
9955   },
9956   Events: {
9957     enable: false,
9958     onClick: $.empty
9959   },
9960   selectOnHover: true,
9961   showAggregates: true,
9962   showLabels: true,
9963   filterOnClick: false,
9964   restoreOnRightClick: false
9965 };
9966
9967
9968 /*
9969  * File: LineChart.js
9970  *
9971 */
9972
9973 $jit.ST.Plot.NodeTypes.implement({
9974   'linechart-basic' : {
9975     'render' : function(node, canvas) {
9976       var pos = node.pos.getc(true), 
9977           width = node.getData('width'),
9978           height = node.getData('height'),
9979           algnPos = this.getAlignedPos(pos, width, height),
9980           x = algnPos.x + width/2 , y = algnPos.y,
9981           stringArray = node.getData('stringArray'),
9982           lastNode = node.getData('lastNode'),
9983           dimArray = node.getData('dimArray'),
9984           valArray = node.getData('valueArray'),
9985           colorArray = node.getData('colorArray'),
9986           colorLength = colorArray.length,
9987           config = node.getData('config'),
9988           gradient = node.getData('gradient'),
9989           showLabels = config.showLabels,
9990           aggregates = config.showAggregates,
9991           label = config.Label,
9992           prev = node.getData('prev'),
9993           dataPointSize = config.dataPointSize;
9994
9995       var ctx = canvas.getCtx(), border = node.getData('border');
9996       if (colorArray && dimArray && stringArray) {
9997         
9998                for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
9999                 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10000                         ctx.lineWidth = 4;
10001                         ctx.lineCap = "round";
10002                   if(!lastNode) {
10003
10004                           ctx.save();
10005                                   //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
10006                           ctx.beginPath();
10007                           ctx.moveTo(x, y  - dimArray[i][0]); 
10008                           ctx.lineTo(x + width, y - dimArray[i][1]);
10009                           ctx.stroke();
10010                           ctx.restore();
10011                   }
10012                   //render data point
10013                   ctx.fillRect(x - (dataPointSize/2), y  - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10014                 }
10015         
10016
10017           if(label.type == 'Native' && showLabels) {
10018           //bottom labels
10019           ctx.fillStyle = ctx.strokeStyle = label.color;
10020           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10021           ctx.textAlign = 'center';
10022           ctx.textBaseline = 'middle';
10023           ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10024           }
10025           
10026
10027       }
10028     },
10029     'contains': function(node, mpos) {
10030       var pos = node.pos.getc(true), 
10031           width = node.getData('width'),
10032           height = node.getData('height'),
10033           config = node.getData('config'),
10034           dataPointSize = config.dataPointSize,
10035           dataPointMidPoint = dataPointSize/2,
10036           algnPos = this.getAlignedPos(pos, width, height),
10037           x = algnPos.x + width/2, y = algnPos.y,
10038           dimArray = node.getData('dimArray');
10039       //bounding box check
10040       if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10041         return false;
10042       }
10043       //deep check
10044       for(var i=0, l=dimArray.length; i<l; i++) {
10045         var dimi = dimArray[i];
10046                 var url = Url.decode(node.getData('linkArray')[i]);
10047           if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10048                 var valArrayCur = node.getData('valArrayCur');
10049           var results = array_match(valArrayCur[i],valArrayCur);
10050           var matches = results[0];
10051           var indexValues = results[1];
10052           if(matches > 1) {
10053                         var names = new Array(),
10054                                 values = new Array(),
10055                                 percentages = new Array(),
10056                                 linksArr = new Array();
10057                                 for(var j=0, il=indexValues.length; j<il; j++) {
10058                                         names[j] = node.getData('stringArray')[indexValues[j]];
10059                                         values[j] = valArrayCur[indexValues[j]];
10060                                         percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10061                                         linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10062                                         
10063                                 }       
10064                         return {
10065                             'name': names,
10066                             'color': node.getData('colorArray')[i],
10067                             'value': values,
10068                             'percentage': percentages,
10069                             'link': false,
10070                             'collision': true
10071                         };
10072                 }
10073           else {
10074                   return {
10075                     'name': node.getData('stringArray')[i],
10076                     'color': node.getData('colorArray')[i],
10077                     'value': node.getData('valueArray')[i][0],
10078         //            'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10079                     'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10080                     'link': url,
10081                     'collision': false
10082                   };
10083           }
10084         }
10085       }
10086       return false;
10087     }
10088   }
10089 });
10090
10091 /*
10092   Class: Line
10093   
10094   A visualization that displays line charts.
10095   
10096   Constructor Options:
10097   
10098   See <Options.Line>.
10099
10100 */
10101 $jit.LineChart = new Class({
10102   st: null,
10103   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10104   selected: {},
10105   busy: false,
10106   
10107   initialize: function(opt) {
10108     this.controller = this.config = 
10109       $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10110         Label: { type: 'Native' }
10111       }, opt);
10112     //set functions for showLabels and showAggregates
10113     var showLabels = this.config.showLabels,
10114         typeLabels = $.type(showLabels),
10115         showAggregates = this.config.showAggregates,
10116         typeAggregates = $.type(showAggregates);
10117     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10118     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10119     Options.Fx.clearCanvas = false;
10120     this.initializeViz();
10121   },
10122   
10123   initializeViz: function() {
10124     var config = this.config,
10125         that = this,
10126         nodeType = config.type.split(":")[0],
10127         nodeLabels = {};
10128
10129     var st = new $jit.ST({
10130       injectInto: config.injectInto,
10131       orientation: "bottom",
10132       backgroundColor: config.backgroundColor,
10133       renderBackground: config.renderBackground,
10134       levelDistance: 0,
10135       siblingOffset: 0,
10136       subtreeOffset: 0,
10137       withLabels: config.Label.type != 'Native',
10138       useCanvas: config.useCanvas,
10139       Label: {
10140         type: config.Label.type
10141       },
10142       Node: {
10143         overridable: true,
10144         type: 'linechart-' + nodeType,
10145         align: 'left',
10146         width: 1,
10147         height: 1
10148       },
10149       Edge: {
10150         type: 'none'
10151       },
10152       Tips: {
10153         enable: config.Tips.enable,
10154         type: 'Native',
10155         force: true,
10156         onShow: function(tip, node, contains) {
10157           var elem = contains;
10158           config.Tips.onShow(tip, elem, node);
10159         }
10160       },
10161       Events: {
10162         enable: true,
10163         type: 'Native',
10164         onClick: function(node, eventInfo, evt) {
10165           if(!config.filterOnClick && !config.Events.enable) return;
10166           var elem = eventInfo.getContains();
10167           if(elem) config.filterOnClick && that.filter(elem.name);
10168           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10169         },
10170         onRightClick: function(node, eventInfo, evt) {
10171           if(!config.restoreOnRightClick) return;
10172           that.restore();
10173         },
10174         onMouseMove: function(node, eventInfo, evt) {
10175           if(!config.selectOnHover) return;
10176           if(node) {
10177             var elem = eventInfo.getContains();
10178             that.select(node.id, elem.name, elem.index);
10179           } else {
10180             that.select(false, false, false);
10181           }
10182         }
10183       },
10184       onCreateLabel: function(domElement, node) {
10185         var labelConf = config.Label,
10186             valueArray = node.getData('valueArray'),
10187             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10188             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10189         if(node.getData('prev')) {
10190           var nlbs = {
10191             wrapper: document.createElement('div'),
10192             aggregate: document.createElement('div'),
10193             label: document.createElement('div')
10194           };
10195           var wrapper = nlbs.wrapper,
10196               label = nlbs.label,
10197               aggregate = nlbs.aggregate,
10198               wrapperStyle = wrapper.style,
10199               labelStyle = label.style,
10200               aggregateStyle = aggregate.style;
10201           //store node labels
10202           nodeLabels[node.id] = nlbs;
10203           //append labels
10204           wrapper.appendChild(label);
10205           wrapper.appendChild(aggregate);
10206           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10207             label.style.display = 'none';
10208           }
10209           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10210             aggregate.style.display = 'none';
10211           }
10212           wrapperStyle.position = 'relative';
10213           wrapperStyle.overflow = 'visible';
10214           wrapperStyle.fontSize = labelConf.size + 'px';
10215           wrapperStyle.fontFamily = labelConf.family;
10216           wrapperStyle.color = labelConf.color;
10217           wrapperStyle.textAlign = 'center';
10218           aggregateStyle.position = labelStyle.position = 'absolute';
10219           
10220           domElement.style.width = node.getData('width') + 'px';
10221           domElement.style.height = node.getData('height') + 'px';
10222           label.innerHTML = node.name;
10223           
10224           domElement.appendChild(wrapper);
10225         }
10226       },
10227       onPlaceLabel: function(domElement, node) {
10228         if(!node.getData('prev')) return;
10229         var labels = nodeLabels[node.id],
10230             wrapperStyle = labels.wrapper.style,
10231             labelStyle = labels.label.style,
10232             aggregateStyle = labels.aggregate.style,
10233             width = node.getData('width'),
10234             height = node.getData('height'),
10235             dimArray = node.getData('dimArray'),
10236             valArray = node.getData('valueArray'),
10237             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10238             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10239             font = parseInt(wrapperStyle.fontSize, 10),
10240             domStyle = domElement.style;
10241         
10242         if(dimArray && valArray) {
10243           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10244             labelStyle.display = '';
10245           } else {
10246             labelStyle.display = 'none';
10247           }
10248           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10249             aggregateStyle.display = '';
10250           } else {
10251             aggregateStyle.display = 'none';
10252           }
10253           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10254           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10255           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10256             if(dimArray[i][0] > 0) {
10257               acum+= valArray[i][0];
10258               leftAcum+= dimArray[i][0];
10259             }
10260           }
10261           aggregateStyle.top = (-font - config.labelOffset) + 'px';
10262           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10263           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10264           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10265           labels.aggregate.innerHTML = acum;
10266         }
10267       }
10268     });
10269     
10270     var size = st.canvas.getSize(),
10271         margin = config.Margin;
10272     st.config.offsetY = -size.height/2 + margin.bottom 
10273       + (config.showLabels && (config.labelOffset + config.Label.size));
10274     st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10275     this.st = st;
10276     this.canvas = this.st.canvas;
10277   },
10278   
10279     renderTitle: function() {
10280         var canvas = this.canvas,
10281         size = canvas.getSize(),
10282         config = this.config,
10283         margin = config.Margin,
10284         label = config.Label,
10285         title = config.Title;
10286         ctx = canvas.getCtx();
10287         ctx.fillStyle = title.color;
10288         ctx.textAlign = 'left';
10289         ctx.textBaseline = 'top';
10290         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10291         if(label.type == 'Native') {
10292                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10293         }
10294   },  
10295   
10296     renderTicks: function() {
10297
10298         var canvas = this.canvas,
10299         size = canvas.getSize(),
10300         config = this.config,
10301         margin = config.Margin,
10302         ticks = config.Ticks,
10303         title = config.Title,
10304         subtitle = config.Subtitle,
10305         label = config.Label,
10306         maxValue = this.maxValue,
10307         maxTickValue = Math.ceil(maxValue*.1)*10;
10308         if(maxTickValue == maxValue) {
10309                 var length = maxTickValue.toString().length;
10310                 maxTickValue = maxTickValue + parseInt(pad(1,length));
10311         }
10312
10313
10314         labelValue = 0,
10315         labelIncrement = maxTickValue/ticks.segments,
10316         ctx = canvas.getCtx();
10317         ctx.strokeStyle = ticks.color;
10318     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10319         ctx.textAlign = 'center';
10320         ctx.textBaseline = 'middle';
10321         
10322         idLabel = canvas.id + "-label";
10323         labelDim = 100;
10324         container = document.getElementById(idLabel);
10325                   
10326                   
10327                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10328                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10329                 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)),
10330                 segmentLength = grid/ticks.segments;
10331                 ctx.fillStyle = ticks.color;
10332                 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));
10333
10334                 while(axis>=grid) {
10335                         ctx.save();
10336                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10337                         ctx.rotate(Math.PI / 2);
10338                         ctx.fillStyle = label.color;
10339                         if(config.showLabels) {
10340                                 if(label.type == 'Native') { 
10341                                         ctx.fillText(labelValue, 0, 0);
10342                                 } else {
10343                                         //html labels on y axis
10344                                         labelDiv = document.createElement('div');
10345                                         labelDiv.innerHTML = labelValue;
10346                                         labelDiv.className = "rotatedLabel";
10347 //                                      labelDiv.class = "rotatedLabel";
10348                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10349                                         labelDiv.style.left = margin.left + "px";
10350                                         labelDiv.style.width = labelDim + "px";
10351                                         labelDiv.style.height = labelDim + "px";
10352                                         labelDiv.style.textAlign = "center";
10353                                         labelDiv.style.verticalAlign = "middle";
10354                                         labelDiv.style.position = "absolute";
10355                                         container.appendChild(labelDiv);
10356                                 }
10357                         }
10358                         ctx.restore();
10359                         ctx.fillStyle = ticks.color;
10360                         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 );
10361                         htmlOrigin += segmentLength;
10362                         axis += segmentLength;
10363                         labelValue += labelIncrement;
10364                 }
10365         
10366
10367         
10368         
10369         
10370
10371   },
10372   
10373   renderBackground: function() {
10374                 var canvas = this.canvas,
10375                 config = this.config,
10376                 backgroundColor = config.backgroundColor,
10377                 size = canvas.getSize(),
10378                 ctx = canvas.getCtx();
10379                 //ctx.globalCompositeOperation = "destination-over";
10380             ctx.fillStyle = backgroundColor;
10381             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
10382   },
10383   
10384   
10385  /*
10386   Method: loadJSON
10387  
10388   Loads JSON data into the visualization. 
10389   
10390   Parameters:
10391   
10392   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>.
10393   
10394   Example:
10395   (start code js)
10396   var areaChart = new $jit.AreaChart(options);
10397   areaChart.loadJSON(json);
10398   (end code)
10399  */  
10400   loadJSON: function(json) {
10401     var prefix = $.time(), 
10402         ch = [], 
10403         st = this.st,
10404         name = $.splat(json.label), 
10405         color = $.splat(json.color || this.colors),
10406         config = this.config,
10407         ticks = config.Ticks,
10408         renderBackground = config.renderBackground,
10409         gradient = !!config.type.split(":")[1],
10410         animate = config.animate,
10411         title = config.Title,
10412         groupTotalValue = 0;
10413     
10414     var valArrayAll = new Array();
10415
10416     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10417         var val = values[i];
10418         var valArray = $.splat(val.values);
10419         for (var j=0, len=valArray.length; j<len; j++) {
10420                 valArrayAll.push(parseInt(valArray[j]));
10421         }
10422         groupTotalValue += parseInt(valArray.sum());
10423     }
10424     
10425     this.maxValue =  Math.max.apply(null, valArrayAll);
10426     
10427     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10428       var val = values[i], prev = values[i-1];
10429
10430       var next = (i+1 < l) ? values[i+1] : 0;
10431       var valLeft = $.splat(values[i].values);
10432       var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10433       var valArray = $.zip(valLeft, valRight);
10434       var valArrayCur = $.splat(values[i].values);
10435       var linkArray = $.splat(values[i].links);
10436       var acumLeft = 0, acumRight = 0;
10437       var lastNode = (l-1 == i) ? true : false; 
10438       ch.push({
10439         'id': prefix + val.label,
10440         'name': val.label,
10441         'data': {
10442           'value': valArray,
10443           '$valueArray': valArray,
10444           '$valArrayCur': valArrayCur,
10445           '$colorArray': color,
10446           '$linkArray': linkArray,
10447           '$stringArray': name,
10448           '$next': next? next.label:false,
10449           '$prev': prev? prev.label:false,
10450           '$config': config,
10451           '$lastNode': lastNode,
10452           '$groupTotalValue': groupTotalValue,
10453           '$gradient': gradient
10454         },
10455         'children': []
10456       });
10457     }
10458     var root = {
10459       'id': prefix + '$root',
10460       'name': '',
10461       'data': {
10462         '$type': 'none',
10463         '$width': 1,
10464         '$height': 1
10465       },
10466       'children': ch
10467     };
10468     st.loadJSON(root);
10469     
10470     this.normalizeDims();
10471     
10472     if(renderBackground) {
10473         this.renderBackground();        
10474     }
10475     
10476     if(!animate && ticks.enable) {
10477                 this.renderTicks();
10478         }
10479         
10480         
10481         if(title.text) {
10482                 this.renderTitle();     
10483         }
10484         
10485     st.compute();
10486     st.select(st.root);
10487     if(animate) {
10488       st.fx.animate({
10489         modes: ['node-property:height:dimArray'],
10490         duration:1500
10491       });
10492     }
10493   },
10494   
10495  /*
10496   Method: updateJSON
10497  
10498   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.
10499   
10500   Parameters:
10501   
10502   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10503   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10504   
10505   Example:
10506   
10507   (start code js)
10508   areaChart.updateJSON(json, {
10509     onComplete: function() {
10510       alert('update complete!');
10511     }
10512   });
10513   (end code)
10514  */  
10515   updateJSON: function(json, onComplete) {
10516     if(this.busy) return;
10517     this.busy = true;
10518     
10519     var st = this.st,
10520         graph = st.graph,
10521         labels = json.label && $.splat(json.label),
10522         values = json.values,
10523         animate = this.config.animate,
10524         that = this;
10525     $.each(values, function(v) {
10526       var n = graph.getByName(v.label);
10527       if(n) {
10528         v.values = $.splat(v.values);
10529         var stringArray = n.getData('stringArray'),
10530             valArray = n.getData('valueArray');
10531         $.each(valArray, function(a, i) {
10532           a[0] = v.values[i];
10533           if(labels) stringArray[i] = labels[i];
10534         });
10535         n.setData('valueArray', valArray);
10536         var prev = n.getData('prev'),
10537             next = n.getData('next'),
10538             nextNode = graph.getByName(next);
10539         if(prev) {
10540           var p = graph.getByName(prev);
10541           if(p) {
10542             var valArray = p.getData('valueArray');
10543             $.each(valArray, function(a, i) {
10544               a[1] = v.values[i];
10545             });
10546           }
10547         }
10548         if(!nextNode) {
10549           var valArray = n.getData('valueArray');
10550           $.each(valArray, function(a, i) {
10551             a[1] = v.values[i];
10552           });
10553         }
10554       }
10555     });
10556     this.normalizeDims();
10557     st.compute();
10558     
10559     st.select(st.root);
10560     if(animate) {
10561       st.fx.animate({
10562         modes: ['node-property:height:dimArray'],
10563         duration:1500,
10564         onComplete: function() {
10565           that.busy = false;
10566           onComplete && onComplete.onComplete();
10567         }
10568       });
10569     }
10570   },
10571   
10572 /*
10573   Method: filter
10574  
10575   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10576   
10577   Parameters:
10578   
10579   Variable strings arguments with the name of the stacks.
10580   
10581   Example:
10582   
10583   (start code js)
10584   areaChart.filter('label A', 'label C');
10585   (end code)
10586   
10587   See also:
10588   
10589   <AreaChart.restore>.
10590  */  
10591   filter: function() {
10592     if(this.busy) return;
10593     this.busy = true;
10594     if(this.config.Tips.enable) this.st.tips.hide();
10595     this.select(false, false, false);
10596     var args = Array.prototype.slice.call(arguments);
10597     var rt = this.st.graph.getNode(this.st.root);
10598     var that = this;
10599     rt.eachAdjacency(function(adj) {
10600       var n = adj.nodeTo, 
10601           dimArray = n.getData('dimArray'),
10602           stringArray = n.getData('stringArray');
10603       n.setData('dimArray', $.map(dimArray, function(d, i) {
10604         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10605       }), 'end');
10606     });
10607     this.st.fx.animate({
10608       modes: ['node-property:dimArray'],
10609       duration:1500,
10610       onComplete: function() {
10611         that.busy = false;
10612       }
10613     });
10614   },
10615   
10616   /*
10617   Method: restore
10618  
10619   Sets all stacks that could have been filtered visible.
10620   
10621   Example:
10622   
10623   (start code js)
10624   areaChart.restore();
10625   (end code)
10626   
10627   See also:
10628   
10629   <AreaChart.filter>.
10630  */  
10631   restore: function() {
10632     if(this.busy) return;
10633     this.busy = true;
10634     if(this.config.Tips.enable) this.st.tips.hide();
10635     this.select(false, false, false);
10636     this.normalizeDims();
10637     var that = this;
10638     this.st.fx.animate({
10639       modes: ['node-property:height:dimArray'],
10640       duration:1500,
10641       onComplete: function() {
10642         that.busy = false;
10643       }
10644     });
10645   },
10646   //adds the little brown bar when hovering the node
10647   select: function(id, name, index) {
10648     if(!this.config.selectOnHover) return;
10649     var s = this.selected;
10650     if(s.id != id || s.name != name 
10651         || s.index != index) {
10652       s.id = id;
10653       s.name = name;
10654       s.index = index;
10655       this.st.graph.eachNode(function(n) {
10656         n.setData('border', false);
10657       });
10658       if(id) {
10659         var n = this.st.graph.getNode(id);
10660         n.setData('border', s);
10661         var link = index === 0? 'prev':'next';
10662         link = n.getData(link);
10663         if(link) {
10664           n = this.st.graph.getByName(link);
10665           if(n) {
10666             n.setData('border', {
10667               name: name,
10668               index: 1-index
10669             });
10670           }
10671         }
10672       }
10673       this.st.plot();
10674     }
10675   },
10676   
10677   /*
10678     Method: getLegend
10679    
10680     Returns an object containing as keys the legend names and as values hex strings with color values.
10681     
10682     Example:
10683     
10684     (start code js)
10685     var legend = areaChart.getLegend();
10686     (end code)
10687  */  
10688   getLegend: function() {
10689     var legend = new Array();
10690     var name = new Array();
10691     var color = new Array();
10692     var n;
10693     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10694       n = adj.nodeTo;
10695     });
10696     var colors = n.getData('colorArray'),
10697         len = colors.length;
10698     $.each(n.getData('stringArray'), function(s, i) {
10699       color[i] = colors[i % len];
10700       name[i] = s;
10701     });
10702         legend['name'] = name;
10703         legend['color'] = color;
10704     return legend;
10705   },
10706   
10707   /*
10708     Method: getMaxValue
10709    
10710     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10711     
10712     Example:
10713     
10714     (start code js)
10715     var ans = areaChart.getMaxValue();
10716     (end code)
10717     
10718     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10719     
10720     Example:
10721     
10722     (start code js)
10723     //will return 100 for all AreaChart instances,
10724     //displaying all of them with the same scale
10725     $jit.AreaChart.implement({
10726       'getMaxValue': function() {
10727         return 100;
10728       }
10729     });
10730     (end code)
10731     
10732 */  
10733
10734   normalizeDims: function() {
10735     //number of elements
10736     var root = this.st.graph.getNode(this.st.root), l=0;
10737     root.eachAdjacency(function() {
10738       l++;
10739     });
10740     
10741
10742     var maxValue = this.maxValue || 1,
10743         size = this.st.canvas.getSize(),
10744         config = this.config,
10745         margin = config.Margin,
10746         labelOffset = config.labelOffset + config.Label.size,
10747         fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10748         animate = config.animate,
10749         ticks = config.Ticks,
10750         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
10751           - (config.showLabels && labelOffset);
10752           
10753           
10754         var maxTickValue = Math.ceil(maxValue*.1)*10;
10755                 if(maxTickValue == maxValue) {
10756                         var length = maxTickValue.toString().length;
10757                         maxTickValue = maxTickValue + parseInt(pad(1,length));
10758                 }
10759                 
10760                 
10761                 
10762     this.st.graph.eachNode(function(n) {
10763       var acumLeft = 0, acumRight = 0, animateValue = [];
10764       $.each(n.getData('valueArray'), function(v) {
10765         acumLeft += +v[0];
10766         acumRight += +v[1];
10767         animateValue.push([0, 0]);
10768       });
10769       var acum = acumRight>acumLeft? acumRight:acumLeft;
10770       
10771       n.setData('width', fixedDim);
10772       if(animate) {
10773         n.setData('height', acum * height / maxValue, 'end');
10774         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10775           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10776         }), 'end');
10777         var dimArray = n.getData('dimArray');
10778         if(!dimArray) {
10779           n.setData('dimArray', animateValue);
10780         }
10781       } else {
10782         
10783         if(ticks.enable) {
10784                 n.setData('height', acum * height / maxValue);
10785                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10786                   return [n[0] * height / maxTickValue, n[1] * height / maxTickValue]; 
10787                 }));
10788         } else {
10789                 n.setData('height', acum * height / maxValue);
10790                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10791                   return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10792                 }));
10793         }
10794         
10795         
10796       }
10797     });
10798   }
10799 });
10800
10801
10802
10803
10804
10805 /*
10806  * File: AreaChart.js
10807  *
10808 */
10809
10810 $jit.ST.Plot.NodeTypes.implement({
10811   'areachart-stacked' : {
10812     'render' : function(node, canvas) {
10813       var pos = node.pos.getc(true), 
10814           width = node.getData('width'),
10815           height = node.getData('height'),
10816           algnPos = this.getAlignedPos(pos, width, height),
10817           x = algnPos.x, y = algnPos.y,
10818           stringArray = node.getData('stringArray'),
10819           dimArray = node.getData('dimArray'),
10820           valArray = node.getData('valueArray'),
10821           valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10822           valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10823           colorArray = node.getData('colorArray'),
10824           colorLength = colorArray.length,
10825           config = node.getData('config'),
10826           gradient = node.getData('gradient'),
10827           showLabels = config.showLabels,
10828           aggregates = config.showAggregates,
10829           label = config.Label,
10830           prev = node.getData('prev');
10831
10832       var ctx = canvas.getCtx(), border = node.getData('border');
10833       if (colorArray && dimArray && stringArray) {
10834         for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10835           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10836           ctx.save();
10837           if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10838             var h1 = acumLeft + dimArray[i][0],
10839                 h2 = acumRight + dimArray[i][1],
10840                 alpha = Math.atan((h2 - h1) / width),
10841                 delta = 55;
10842             var linear = ctx.createLinearGradient(x + width/2, 
10843                 y - (h1 + h2)/2,
10844                 x + width/2 + delta * Math.sin(alpha),
10845                 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10846             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10847                 function(v) { return (v * 0.85) >> 0; }));
10848             linear.addColorStop(0, colorArray[i % colorLength]);
10849             linear.addColorStop(1, color);
10850             ctx.fillStyle = linear;
10851           }
10852           ctx.beginPath();
10853           ctx.moveTo(x, y - acumLeft);
10854           ctx.lineTo(x + width, y - acumRight);
10855           ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10856           ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10857           ctx.lineTo(x, y - acumLeft);
10858           ctx.fill();
10859           ctx.restore();
10860           if(border) {
10861             var strong = border.name == stringArray[i];
10862             var perc = strong? 0.7 : 0.8;
10863             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10864                 function(v) { return (v * perc) >> 0; }));
10865             ctx.strokeStyle = color;
10866             ctx.lineWidth = strong? 4 : 1;
10867             ctx.save();
10868             ctx.beginPath();
10869             if(border.index === 0) {
10870               ctx.moveTo(x, y - acumLeft);
10871               ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10872             } else {
10873               ctx.moveTo(x + width, y - acumRight);
10874               ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10875             }
10876             ctx.stroke();
10877             ctx.restore();
10878           }
10879           acumLeft += (dimArray[i][0] || 0);
10880           acumRight += (dimArray[i][1] || 0);
10881           
10882           if(dimArray[i][0] > 0)
10883             valAcum += (valArray[i][0] || 0);
10884         }
10885         if(prev && label.type == 'Native') {
10886           ctx.save();
10887           ctx.beginPath();
10888           ctx.fillStyle = ctx.strokeStyle = label.color;
10889           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10890           ctx.textAlign = 'center';
10891           ctx.textBaseline = 'middle';
10892           if(aggregates(node.name, valLeft, valRight, node)) {
10893             ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10894           }
10895           if(showLabels(node.name, valLeft, valRight, node)) {
10896             ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10897           }
10898           ctx.restore();
10899         }
10900       }
10901     },
10902     'contains': function(node, mpos) {
10903       var pos = node.pos.getc(true), 
10904           width = node.getData('width'),
10905           height = node.getData('height'),
10906           algnPos = this.getAlignedPos(pos, width, height),
10907           x = algnPos.x, y = algnPos.y,
10908           dimArray = node.getData('dimArray'),
10909           rx = mpos.x - x;
10910       //bounding box check
10911       if(mpos.x < x || mpos.x > x + width
10912         || mpos.y > y || mpos.y < y - height) {
10913         return false;
10914       }
10915       //deep check
10916       for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10917         var dimi = dimArray[i];
10918         lAcum -= dimi[0];
10919         rAcum -= dimi[1];
10920         var intersec = lAcum + (rAcum - lAcum) * rx / width;
10921         if(mpos.y >= intersec) {
10922           var index = +(rx > width/2);
10923           return {
10924             'name': node.getData('stringArray')[i],
10925             'color': node.getData('colorArray')[i],
10926             'value': node.getData('valueArray')[i][index],
10927             'index': index
10928           };
10929         }
10930       }
10931       return false;
10932     }
10933   }
10934 });
10935
10936 /*
10937   Class: AreaChart
10938   
10939   A visualization that displays stacked area charts.
10940   
10941   Constructor Options:
10942   
10943   See <Options.AreaChart>.
10944
10945 */
10946 $jit.AreaChart = new Class({
10947   st: null,
10948   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10949   selected: {},
10950   busy: false,
10951   
10952   initialize: function(opt) {
10953     this.controller = this.config = 
10954       $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10955         Label: { type: 'Native' }
10956       }, opt);
10957     //set functions for showLabels and showAggregates
10958     var showLabels = this.config.showLabels,
10959         typeLabels = $.type(showLabels),
10960         showAggregates = this.config.showAggregates,
10961         typeAggregates = $.type(showAggregates);
10962     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10963     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10964     
10965     this.initializeViz();
10966   },
10967   
10968   initializeViz: function() {
10969     var config = this.config,
10970         that = this,
10971         nodeType = config.type.split(":")[0],
10972         nodeLabels = {};
10973
10974     var st = new $jit.ST({
10975       injectInto: config.injectInto,
10976       orientation: "bottom",
10977       levelDistance: 0,
10978       siblingOffset: 0,
10979       subtreeOffset: 0,
10980       withLabels: config.Label.type != 'Native',
10981       useCanvas: config.useCanvas,
10982       Label: {
10983         type: config.Label.type
10984       },
10985       Node: {
10986         overridable: true,
10987         type: 'areachart-' + nodeType,
10988         align: 'left',
10989         width: 1,
10990         height: 1
10991       },
10992       Edge: {
10993         type: 'none'
10994       },
10995       Tips: {
10996         enable: config.Tips.enable,
10997         type: 'Native',
10998         force: true,
10999         onShow: function(tip, node, contains) {
11000           var elem = contains;
11001           config.Tips.onShow(tip, elem, node);
11002         }
11003       },
11004       Events: {
11005         enable: true,
11006         type: 'Native',
11007         onClick: function(node, eventInfo, evt) {
11008           if(!config.filterOnClick && !config.Events.enable) return;
11009           var elem = eventInfo.getContains();
11010           if(elem) config.filterOnClick && that.filter(elem.name);
11011           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11012         },
11013         onRightClick: function(node, eventInfo, evt) {
11014           if(!config.restoreOnRightClick) return;
11015           that.restore();
11016         },
11017         onMouseMove: function(node, eventInfo, evt) {
11018           if(!config.selectOnHover) return;
11019           if(node) {
11020             var elem = eventInfo.getContains();
11021             that.select(node.id, elem.name, elem.index);
11022           } else {
11023             that.select(false, false, false);
11024           }
11025         }
11026       },
11027       onCreateLabel: function(domElement, node) {
11028         var labelConf = config.Label,
11029             valueArray = node.getData('valueArray'),
11030             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11031             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11032         if(node.getData('prev')) {
11033           var nlbs = {
11034             wrapper: document.createElement('div'),
11035             aggregate: document.createElement('div'),
11036             label: document.createElement('div')
11037           };
11038           var wrapper = nlbs.wrapper,
11039               label = nlbs.label,
11040               aggregate = nlbs.aggregate,
11041               wrapperStyle = wrapper.style,
11042               labelStyle = label.style,
11043               aggregateStyle = aggregate.style;
11044           //store node labels
11045           nodeLabels[node.id] = nlbs;
11046           //append labels
11047           wrapper.appendChild(label);
11048           wrapper.appendChild(aggregate);
11049           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11050             label.style.display = 'none';
11051           }
11052           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11053             aggregate.style.display = 'none';
11054           }
11055           wrapperStyle.position = 'relative';
11056           wrapperStyle.overflow = 'visible';
11057           wrapperStyle.fontSize = labelConf.size + 'px';
11058           wrapperStyle.fontFamily = labelConf.family;
11059           wrapperStyle.color = labelConf.color;
11060           wrapperStyle.textAlign = 'center';
11061           aggregateStyle.position = labelStyle.position = 'absolute';
11062           
11063           domElement.style.width = node.getData('width') + 'px';
11064           domElement.style.height = node.getData('height') + 'px';
11065           label.innerHTML = node.name;
11066           
11067           domElement.appendChild(wrapper);
11068         }
11069       },
11070       onPlaceLabel: function(domElement, node) {
11071         if(!node.getData('prev')) return;
11072         var labels = nodeLabels[node.id],
11073             wrapperStyle = labels.wrapper.style,
11074             labelStyle = labels.label.style,
11075             aggregateStyle = labels.aggregate.style,
11076             width = node.getData('width'),
11077             height = node.getData('height'),
11078             dimArray = node.getData('dimArray'),
11079             valArray = node.getData('valueArray'),
11080             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11081             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11082             font = parseInt(wrapperStyle.fontSize, 10),
11083             domStyle = domElement.style;
11084         
11085         if(dimArray && valArray) {
11086           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11087             labelStyle.display = '';
11088           } else {
11089             labelStyle.display = 'none';
11090           }
11091           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11092             aggregateStyle.display = '';
11093           } else {
11094             aggregateStyle.display = 'none';
11095           }
11096           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11097           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11098           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11099             if(dimArray[i][0] > 0) {
11100               acum+= valArray[i][0];
11101               leftAcum+= dimArray[i][0];
11102             }
11103           }
11104           aggregateStyle.top = (-font - config.labelOffset) + 'px';
11105           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11106           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11107           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11108           labels.aggregate.innerHTML = acum;
11109         }
11110       }
11111     });
11112     
11113     var size = st.canvas.getSize(),
11114         margin = config.Margin;
11115     st.config.offsetY = -size.height/2 + margin.bottom 
11116       + (config.showLabels && (config.labelOffset + config.Label.size));
11117     st.config.offsetX = (margin.right - margin.left)/2;
11118     this.st = st;
11119     this.canvas = this.st.canvas;
11120   },
11121   
11122  /*
11123   Method: loadJSON
11124  
11125   Loads JSON data into the visualization. 
11126   
11127   Parameters:
11128   
11129   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>.
11130   
11131   Example:
11132   (start code js)
11133   var areaChart = new $jit.AreaChart(options);
11134   areaChart.loadJSON(json);
11135   (end code)
11136  */  
11137   loadJSON: function(json) {
11138     var prefix = $.time(), 
11139         ch = [], 
11140         st = this.st,
11141         name = $.splat(json.label), 
11142         color = $.splat(json.color || this.colors),
11143         config = this.config,
11144         gradient = !!config.type.split(":")[1],
11145         animate = config.animate;
11146     
11147     for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11148       var val = values[i], prev = values[i-1], next = values[i+1];
11149       var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11150       var valArray = $.zip(valLeft, valRight);
11151       var acumLeft = 0, acumRight = 0;
11152       ch.push({
11153         'id': prefix + val.label,
11154         'name': val.label,
11155         'data': {
11156           'value': valArray,
11157           '$valueArray': valArray,
11158           '$colorArray': color,
11159           '$stringArray': name,
11160           '$next': next.label,
11161           '$prev': prev? prev.label:false,
11162           '$config': config,
11163           '$gradient': gradient
11164         },
11165         'children': []
11166       });
11167     }
11168     var root = {
11169       'id': prefix + '$root',
11170       'name': '',
11171       'data': {
11172         '$type': 'none',
11173         '$width': 1,
11174         '$height': 1
11175       },
11176       'children': ch
11177     };
11178     st.loadJSON(root);
11179     
11180     this.normalizeDims();
11181     st.compute();
11182     st.select(st.root);
11183     if(animate) {
11184       st.fx.animate({
11185         modes: ['node-property:height:dimArray'],
11186         duration:1500
11187       });
11188     }
11189   },
11190   
11191  /*
11192   Method: updateJSON
11193  
11194   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.
11195   
11196   Parameters:
11197   
11198   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11199   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11200   
11201   Example:
11202   
11203   (start code js)
11204   areaChart.updateJSON(json, {
11205     onComplete: function() {
11206       alert('update complete!');
11207     }
11208   });
11209   (end code)
11210  */  
11211   updateJSON: function(json, onComplete) {
11212     if(this.busy) return;
11213     this.busy = true;
11214     
11215     var st = this.st,
11216         graph = st.graph,
11217         labels = json.label && $.splat(json.label),
11218         values = json.values,
11219         animate = this.config.animate,
11220         that = this;
11221     $.each(values, function(v) {
11222       var n = graph.getByName(v.label);
11223       if(n) {
11224         v.values = $.splat(v.values);
11225         var stringArray = n.getData('stringArray'),
11226             valArray = n.getData('valueArray');
11227         $.each(valArray, function(a, i) {
11228           a[0] = v.values[i];
11229           if(labels) stringArray[i] = labels[i];
11230         });
11231         n.setData('valueArray', valArray);
11232         var prev = n.getData('prev'),
11233             next = n.getData('next'),
11234             nextNode = graph.getByName(next);
11235         if(prev) {
11236           var p = graph.getByName(prev);
11237           if(p) {
11238             var valArray = p.getData('valueArray');
11239             $.each(valArray, function(a, i) {
11240               a[1] = v.values[i];
11241             });
11242           }
11243         }
11244         if(!nextNode) {
11245           var valArray = n.getData('valueArray');
11246           $.each(valArray, function(a, i) {
11247             a[1] = v.values[i];
11248           });
11249         }
11250       }
11251     });
11252     this.normalizeDims();
11253     st.compute();
11254     st.select(st.root);
11255     if(animate) {
11256       st.fx.animate({
11257         modes: ['node-property:height:dimArray'],
11258         duration:1500,
11259         onComplete: function() {
11260           that.busy = false;
11261           onComplete && onComplete.onComplete();
11262         }
11263       });
11264     }
11265   },
11266   
11267 /*
11268   Method: filter
11269  
11270   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11271   
11272   Parameters:
11273   
11274   Variable strings arguments with the name of the stacks.
11275   
11276   Example:
11277   
11278   (start code js)
11279   areaChart.filter('label A', 'label C');
11280   (end code)
11281   
11282   See also:
11283   
11284   <AreaChart.restore>.
11285  */  
11286   filter: function() {
11287     if(this.busy) return;
11288     this.busy = true;
11289     if(this.config.Tips.enable) this.st.tips.hide();
11290     this.select(false, false, false);
11291     var args = Array.prototype.slice.call(arguments);
11292     var rt = this.st.graph.getNode(this.st.root);
11293     var that = this;
11294     rt.eachAdjacency(function(adj) {
11295       var n = adj.nodeTo, 
11296           dimArray = n.getData('dimArray'),
11297           stringArray = n.getData('stringArray');
11298       n.setData('dimArray', $.map(dimArray, function(d, i) {
11299         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11300       }), 'end');
11301     });
11302     this.st.fx.animate({
11303       modes: ['node-property:dimArray'],
11304       duration:1500,
11305       onComplete: function() {
11306         that.busy = false;
11307       }
11308     });
11309   },
11310   
11311   /*
11312   Method: restore
11313  
11314   Sets all stacks that could have been filtered visible.
11315   
11316   Example:
11317   
11318   (start code js)
11319   areaChart.restore();
11320   (end code)
11321   
11322   See also:
11323   
11324   <AreaChart.filter>.
11325  */  
11326   restore: function() {
11327     if(this.busy) return;
11328     this.busy = true;
11329     if(this.config.Tips.enable) this.st.tips.hide();
11330     this.select(false, false, false);
11331     this.normalizeDims();
11332     var that = this;
11333     this.st.fx.animate({
11334       modes: ['node-property:height:dimArray'],
11335       duration:1500,
11336       onComplete: function() {
11337         that.busy = false;
11338       }
11339     });
11340   },
11341   //adds the little brown bar when hovering the node
11342   select: function(id, name, index) {
11343     if(!this.config.selectOnHover) return;
11344     var s = this.selected;
11345     if(s.id != id || s.name != name 
11346         || s.index != index) {
11347       s.id = id;
11348       s.name = name;
11349       s.index = index;
11350       this.st.graph.eachNode(function(n) {
11351         n.setData('border', false);
11352       });
11353       if(id) {
11354         var n = this.st.graph.getNode(id);
11355         n.setData('border', s);
11356         var link = index === 0? 'prev':'next';
11357         link = n.getData(link);
11358         if(link) {
11359           n = this.st.graph.getByName(link);
11360           if(n) {
11361             n.setData('border', {
11362               name: name,
11363               index: 1-index
11364             });
11365           }
11366         }
11367       }
11368       this.st.plot();
11369     }
11370   },
11371   
11372   /*
11373     Method: getLegend
11374    
11375     Returns an object containing as keys the legend names and as values hex strings with color values.
11376     
11377     Example:
11378     
11379     (start code js)
11380     var legend = areaChart.getLegend();
11381     (end code)
11382  */  
11383   getLegend: function() {
11384     var legend = {};
11385     var n;
11386     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11387       n = adj.nodeTo;
11388     });
11389     var colors = n.getData('colorArray'),
11390         len = colors.length;
11391     $.each(n.getData('stringArray'), function(s, i) {
11392       legend[s] = colors[i % len];
11393     });
11394     return legend;
11395   },
11396   
11397   /*
11398     Method: getMaxValue
11399    
11400     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11401     
11402     Example:
11403     
11404     (start code js)
11405     var ans = areaChart.getMaxValue();
11406     (end code)
11407     
11408     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11409     
11410     Example:
11411     
11412     (start code js)
11413     //will return 100 for all AreaChart instances,
11414     //displaying all of them with the same scale
11415     $jit.AreaChart.implement({
11416       'getMaxValue': function() {
11417         return 100;
11418       }
11419     });
11420     (end code)
11421     
11422 */  
11423   getMaxValue: function() {
11424     var maxValue = 0;
11425     this.st.graph.eachNode(function(n) {
11426       var valArray = n.getData('valueArray'),
11427           acumLeft = 0, acumRight = 0;
11428       $.each(valArray, function(v) { 
11429         acumLeft += +v[0];
11430         acumRight += +v[1];
11431       });
11432       var acum = acumRight>acumLeft? acumRight:acumLeft;
11433       maxValue = maxValue>acum? maxValue:acum;
11434     });
11435     return maxValue;
11436   },
11437   
11438   normalizeDims: function() {
11439     //number of elements
11440     var root = this.st.graph.getNode(this.st.root), l=0;
11441     root.eachAdjacency(function() {
11442       l++;
11443     });
11444     var maxValue = this.getMaxValue() || 1,
11445         size = this.st.canvas.getSize(),
11446         config = this.config,
11447         margin = config.Margin,
11448         labelOffset = config.labelOffset + config.Label.size,
11449         fixedDim = (size.width - (margin.left + margin.right)) / l,
11450         animate = config.animate,
11451         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
11452           - (config.showLabels && labelOffset);
11453     this.st.graph.eachNode(function(n) {
11454       var acumLeft = 0, acumRight = 0, animateValue = [];
11455       $.each(n.getData('valueArray'), function(v) {
11456         acumLeft += +v[0];
11457         acumRight += +v[1];
11458         animateValue.push([0, 0]);
11459       });
11460       var acum = acumRight>acumLeft? acumRight:acumLeft;
11461       n.setData('width', fixedDim);
11462       if(animate) {
11463         n.setData('height', acum * height / maxValue, 'end');
11464         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11465           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11466         }), 'end');
11467         var dimArray = n.getData('dimArray');
11468         if(!dimArray) {
11469           n.setData('dimArray', animateValue);
11470         }
11471       } else {
11472         n.setData('height', acum * height / maxValue);
11473         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11474           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11475         }));
11476       }
11477     });
11478   }
11479 });
11480
11481 /*
11482  * File: Options.BarChart.js
11483  *
11484 */
11485
11486 /*
11487   Object: Options.BarChart
11488   
11489   <BarChart> options. 
11490   Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11491   
11492   Syntax:
11493   
11494   (start code js)
11495
11496   Options.BarChart = {
11497     animate: true,
11498     labelOffset: 3,
11499     barsOffset: 0,
11500     type: 'stacked',
11501     hoveredColor: '#9fd4ff',
11502     orientation: 'horizontal',
11503     showAggregates: true,
11504     showLabels: true
11505   };
11506   
11507   (end code)
11508   
11509   Example:
11510   
11511   (start code js)
11512
11513   var barChart = new $jit.BarChart({
11514     animate: true,
11515     barsOffset: 10,
11516     type: 'stacked:gradient'
11517   });
11518   
11519   (end code)
11520
11521   Parameters:
11522   
11523   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11524   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11525   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11526   barsOffset - (number) Default's *0*. Separation between bars.
11527   type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11528   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11529   orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11530   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11531   showLabels - (boolean) Default's *true*. Display the name of the slots.
11532   
11533 */
11534
11535 Options.BarChart = {
11536   $extend: true,
11537   
11538   animate: true,
11539   type: 'stacked', //stacked, grouped, : gradient
11540   labelOffset: 3, //label offset
11541   barsOffset: 0, //distance between bars
11542   nodeCount: 0, //number of bars
11543   hoveredColor: '#9fd4ff',
11544   background: false,
11545   renderBackground: false,
11546   orientation: 'horizontal',
11547   showAggregates: true,
11548   showLabels: true,
11549   Ticks: {
11550         enable: false,
11551         segments: 4,
11552         color: '#000000'
11553   },
11554   Tips: {
11555     enable: false,
11556     onShow: $.empty,
11557     onHide: $.empty
11558   },
11559   Events: {
11560     enable: false,
11561     onClick: $.empty
11562   }
11563 };
11564
11565 /*
11566  * File: BarChart.js
11567  *
11568 */
11569
11570 $jit.ST.Plot.NodeTypes.implement({
11571   'barchart-stacked' : {
11572     'render' : function(node, canvas) {
11573       var pos = node.pos.getc(true), 
11574           width = node.getData('width'),
11575           height = node.getData('height'),
11576           algnPos = this.getAlignedPos(pos, width, height),
11577           x = algnPos.x, y = algnPos.y,
11578           dimArray = node.getData('dimArray'),
11579           valueArray = node.getData('valueArray'),
11580           stringArray = node.getData('stringArray'),
11581           linkArray = node.getData('linkArray'),
11582           gvl = node.getData('gvl'),
11583           colorArray = node.getData('colorArray'),
11584           colorLength = colorArray.length,
11585           nodeCount = node.getData('nodeCount');
11586       var ctx = canvas.getCtx(),
11587           canvasSize = canvas.getSize(),
11588           opt = {},
11589           border = node.getData('border'),
11590           gradient = node.getData('gradient'),
11591           config = node.getData('config'),
11592           horz = config.orientation == 'horizontal',
11593           aggregates = config.showAggregates,
11594           showLabels = config.showLabels,
11595           label = config.Label,
11596           margin = config.Margin;
11597           
11598           
11599       if (colorArray && dimArray && stringArray) {
11600         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11601                 acum += (dimArray[i] || 0);
11602         }
11603       }
11604       
11605        //drop shadow 
11606        if(config.shadow.enable) {
11607        shadowThickness = config.shadow.size;
11608        ctx.fillStyle = "rgba(0,0,0,.2)";
11609           if(horz) {
11610             ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11611           } else {
11612             ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11613           }
11614        }
11615        
11616       if (colorArray && dimArray && stringArray) {
11617         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11618           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11619           if(gradient) {
11620             var linear;
11621             
11622
11623           
11624             if(horz) {
11625               linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y, 
11626                   x + acum + dimArray[i]/2, y + height);
11627             } else {
11628               linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2, 
11629                   x + width, y - acum- dimArray[i]/2);
11630             }
11631             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11632                 function(v) { return (v * 0.8) >> 0; }));
11633             linear.addColorStop(0, color);
11634             linear.addColorStop(0.3, colorArray[i % colorLength]);
11635             linear.addColorStop(0.7, colorArray[i % colorLength]);
11636             linear.addColorStop(1, color);
11637             ctx.fillStyle = linear;
11638           }
11639           if(horz) {
11640             ctx.fillRect(x + acum, y, dimArray[i], height);
11641           } else {
11642             ctx.fillRect(x, y - acum - dimArray[i], width, dimArray[i]);
11643           }
11644           if(border && border.name == stringArray[i]) {
11645             opt.acum = acum;
11646             opt.dimValue = dimArray[i];
11647           }
11648           acum += (dimArray[i] || 0);
11649           valAcum += (valueArray[i] || 0);
11650         }
11651         if(border) {
11652           ctx.save();
11653           ctx.lineWidth = 2;
11654           ctx.strokeStyle = border.color;
11655           if(horz) {
11656             ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11657           } else {
11658             ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11659           }
11660           ctx.restore();
11661         }
11662         if(label.type == 'Native') {
11663           ctx.save();
11664           ctx.fillStyle = ctx.strokeStyle = label.color;
11665           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11666           ctx.textBaseline = 'middle';
11667                         if(gvl) {
11668                                 acumValueLabel = gvl;
11669                         } else {
11670                                 acumValueLabel = valAcum;
11671                         }
11672           if(aggregates(node.name, valAcum)) {
11673             if(!horz) {
11674                           ctx.textAlign = 'center';
11675                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11676                           //background box
11677                           ctx.save();
11678                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11679                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11680                                  (label ? label.size + config.labelOffset : 0));
11681                           mtxt = ctx.measureText(acumValueLabel);
11682                           boxWidth = mtxt.width+10;
11683                           inset = 10;
11684                           boxHeight = label.size+6;
11685                           
11686                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11687                                 bottomPadding = acum - config.labelOffset - boxHeight;
11688                           } else {
11689                                 bottomPadding = acum + config.labelOffset + inset;
11690                           }
11691                         
11692                         
11693                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11694                           cornerRadius = 4;     
11695                           boxX = -inset/2;
11696                           boxY = -boxHeight/2;
11697                           
11698                           ctx.rotate(0 * Math.PI / 180);
11699                           ctx.fillStyle = "rgba(255,255,255,.8)";
11700                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11701                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11702                           }
11703                           //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11704                           ctx.fillStyle = ctx.strokeStyle = label.color;
11705                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11706                           ctx.restore();
11707
11708             }
11709           }
11710           if(showLabels(node.name, valAcum, node)) {
11711             if(horz) {
11712
11713
11714                 //background box
11715                 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11716                                 inset = 10;
11717                                 
11718                                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11719                 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11720                 boxWidth = mtxt.width+10;
11721                 inset = 10;
11722                 
11723                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11724                         leftPadding = acum - config.labelOffset - boxWidth - inset;
11725                 } else {
11726                         leftPadding = acum + config.labelOffset;
11727                 }
11728                 
11729                 
11730                                 ctx.textAlign = 'left';
11731                                 ctx.translate(x + inset + leftPadding, y + height/2);
11732                                 boxHeight = label.size+6;
11733                                 boxX = -inset/2;
11734                                 boxY = -boxHeight/2;
11735                                 ctx.fillStyle = "rgba(255,255,255,.8)";
11736                                 cornerRadius = 4;
11737                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {  
11738                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11739                                 }
11740                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11741                                 
11742                           ctx.fillStyle = label.color;
11743               ctx.rotate(0 * Math.PI / 180);
11744               ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11745
11746
11747             } else {
11748               ctx.textAlign = 'center';
11749               ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11750             }
11751           }
11752           ctx.restore();
11753         }
11754       }
11755     },
11756     'contains': function(node, mpos) {
11757       var pos = node.pos.getc(true), 
11758           width = node.getData('width'),
11759           height = node.getData('height'),
11760           algnPos = this.getAlignedPos(pos, width, height),
11761           x = algnPos.x, y = algnPos.y,
11762           dimArray = node.getData('dimArray'),
11763           config = node.getData('config'),
11764           rx = mpos.x - x,
11765           horz = config.orientation == 'horizontal';
11766       //bounding box check
11767       if(horz) {
11768         if(mpos.x < x || mpos.x > x + width
11769             || mpos.y > y + height || mpos.y < y) {
11770             return false;
11771           }
11772       } else {
11773         if(mpos.x < x || mpos.x > x + width
11774             || mpos.y > y || mpos.y < y - height) {
11775             return false;
11776           }
11777       }
11778       //deep check
11779       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11780         var dimi = dimArray[i];
11781                 var url = Url.decode(node.getData('linkArray')[i]);
11782         if(horz) {
11783           acum += dimi;
11784           var intersec = acum;
11785           if(mpos.x <= intersec) {
11786             return {
11787               'name': node.getData('stringArray')[i],
11788               'color': node.getData('colorArray')[i],
11789               'value': node.getData('valueArray')[i],
11790               'valuelabel': node.getData('valuelabelArray')[i],
11791                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11792                           'link': url,
11793               'label': node.name
11794             };
11795           }
11796         } else {
11797           acum -= dimi;
11798           var intersec = acum;
11799           if(mpos.y >= intersec) {
11800             return {
11801               'name': node.getData('stringArray')[i],
11802               'color': node.getData('colorArray')[i],
11803               'value': node.getData('valueArray')[i],
11804                           'valuelabel': node.getData('valuelabelArray')[i],
11805                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11806               'link': url,
11807               'label': node.name
11808             };
11809           }
11810         }
11811       }
11812       return false;
11813     }
11814   },
11815   'barchart-grouped' : {
11816     'render' : function(node, canvas) {
11817       var pos = node.pos.getc(true), 
11818           width = node.getData('width'),
11819           height = node.getData('height'),
11820           algnPos = this.getAlignedPos(pos, width, height),
11821           x = algnPos.x, y = algnPos.y,
11822           dimArray = node.getData('dimArray'),
11823           valueArray = node.getData('valueArray'),
11824           valuelabelArray = node.getData('valuelabelArray'),
11825           linkArray = node.getData('linkArray'),
11826           valueLength = valueArray.length,
11827           colorArray = node.getData('colorArray'),
11828           colorLength = colorArray.length,
11829           stringArray = node.getData('stringArray'); 
11830
11831       var ctx = canvas.getCtx(),
11832           canvasSize = canvas.getSize(),
11833           opt = {},
11834           border = node.getData('border'),
11835           gradient = node.getData('gradient'),
11836           config = node.getData('config'),
11837           horz = config.orientation == 'horizontal',
11838           aggregates = config.showAggregates,
11839           showLabels = config.showLabels,
11840           label = config.Label,
11841           shadow = config.shadow,
11842           margin = config.Margin,
11843           fixedDim = (horz? height : width) / valueLength;
11844       
11845       //drop shadow
11846       
11847        maxValue = Math.max.apply(null, dimArray);
11848        
11849        
11850           
11851            ctx.fillStyle = "rgba(0,0,0,.2)";
11852       if (colorArray && dimArray && stringArray && shadow.enable) {
11853                  shadowThickness = shadow.size;
11854
11855         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11856                 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11857                 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11858                 if(horz) {
11859                                     
11860                         ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11861                                         
11862                 } else {
11863                         
11864                         if(i == 0) {
11865                                 if(nextBar && nextBar > dimArray[i]) {
11866                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11867                                 } else if (nextBar && nextBar < dimArray[i]){
11868                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11869                                 } else {
11870                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11871                                 }
11872                         } else if (i> 0 && i<l-1) {
11873                                 if(nextBar && nextBar > dimArray[i]) {
11874                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11875                                 } else if (nextBar && nextBar < dimArray[i]){
11876                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11877                                 } else {
11878                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11879                                 }
11880                         } else if (i == l-1) {
11881                                 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11882                         }
11883                         
11884                         
11885                 }
11886         }
11887
11888       } 
11889                         
11890       
11891       if (colorArray && dimArray && stringArray) {
11892         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11893           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11894           if(gradient) {
11895             var linear;
11896             if(horz) {
11897               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
11898                   x + dimArray[i]/2, y + fixedDim * (i + 1));
11899             } else {
11900               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
11901                   x + fixedDim * (i + 1), y - dimArray[i]/2);
11902             }
11903             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11904                 function(v) { return (v * 0.8) >> 0; }));
11905             linear.addColorStop(0, color);
11906             linear.addColorStop(0.3, colorArray[i % colorLength]);
11907             linear.addColorStop(0.7, colorArray[i % colorLength]);
11908             linear.addColorStop(1, color);
11909             ctx.fillStyle = linear;
11910           }
11911           if(horz) {
11912             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11913           } else {
11914             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11915           }
11916           if(border && border.name == stringArray[i]) {
11917             opt.acum = fixedDim * i;
11918             opt.dimValue = dimArray[i];
11919           }
11920           acum += (dimArray[i] || 0);
11921           valAcum += (valueArray[i] || 0);
11922                   ctx.fillStyle = ctx.strokeStyle = label.color;
11923           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11924           
11925           inset = 10;
11926                   if(aggregates(node.name, valAcum) && label.type == 'Native') {
11927                                 if(valuelabelArray[i]) {
11928                                         acumValueLabel = valuelabelArray[i];
11929                                 } else {
11930                                         acumValueLabel = valueArray[i];
11931                                 }
11932                            if(horz) {
11933                                   ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11934                                   ctx.textAlign = 'left';
11935                                   ctx.textBaseline = 'top';
11936                                   ctx.fillStyle = "rgba(255,255,255,.8)";
11937                                   //background box
11938                                   gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
11939                                   mtxt = ctx.measureText(acumValueLabel);
11940                                   boxWidth = mtxt.width+10;
11941                                   
11942                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11943                                         leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
11944                                   } else {
11945                                         leftPadding = dimArray[i] + config.labelOffset + inset;
11946                                   }
11947                               boxHeight = label.size+6;
11948                                   boxX = x + leftPadding;
11949                                   boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
11950                                   cornerRadius = 4;     
11951                                   
11952                                   
11953                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
11954                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11955                                   }
11956                                 //  $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11957                                   
11958                                   ctx.fillStyle = ctx.strokeStyle = label.color;
11959                                   ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
11960                                   
11961
11962                                         
11963                                         
11964                                 } else {
11965                                   
11966                                         ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11967                                         ctx.save();
11968                                         ctx.textAlign = 'center';
11969                                         
11970                                         //background box
11971                                         gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11972                                          (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11973                                          (label ? label.size + config.labelOffset : 0));
11974                                         
11975                                         mtxt = ctx.measureText(acumValueLabel);
11976                                         boxWidth = mtxt.width+10;
11977                                         boxHeight = label.size+6;
11978                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
11979                                                 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
11980                                         } else {
11981                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
11982                                         }
11983                                                                                                 
11984                                         
11985                                         ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
11986                                         
11987                                         boxX = -boxWidth/2;
11988                                         boxY = -boxHeight/2;
11989                                         ctx.fillStyle = "rgba(255,255,255,.8)";
11990                                         
11991                                         cornerRadius = 4;       
11992
11993                                         //ctx.rotate(270* Math.PI / 180);
11994                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
11995                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11996                                         }
11997                                         //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11998                                         
11999                                         ctx.fillStyle = ctx.strokeStyle = label.color;
12000                                         ctx.fillText(acumValueLabel, 0,0);
12001                                         ctx.restore();
12002
12003                                 }
12004                         }
12005         }
12006         if(border) {
12007           ctx.save();
12008           ctx.lineWidth = 2;
12009           ctx.strokeStyle = border.color;
12010           if(horz) {
12011             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12012           } else {
12013             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12014           }
12015           ctx.restore();
12016         }
12017         if(label.type == 'Native') {
12018           ctx.save();
12019           ctx.fillStyle = ctx.strokeStyle = label.color;
12020           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12021           ctx.textBaseline = 'middle';
12022
12023           if(showLabels(node.name, valAcum, node)) {
12024             if(horz) {
12025               ctx.textAlign = 'center';
12026               ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12027               ctx.rotate(Math.PI / 2);
12028               ctx.fillText(node.name, 0, 0);
12029             } else {
12030               ctx.textAlign = 'center';
12031               ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12032             }
12033           }
12034           ctx.restore();
12035         }
12036       }
12037     },
12038     'contains': function(node, mpos) {
12039       var pos = node.pos.getc(true), 
12040           width = node.getData('width'),
12041           height = node.getData('height'),
12042           algnPos = this.getAlignedPos(pos, width, height),
12043           x = algnPos.x, y = algnPos.y,
12044           dimArray = node.getData('dimArray'),
12045           len = dimArray.length,
12046           config = node.getData('config'),
12047           rx = mpos.x - x,
12048           horz = config.orientation == 'horizontal',
12049           fixedDim = (horz? height : width) / len;
12050       //bounding box check
12051       if(horz) {
12052         if(mpos.x < x || mpos.x > x + width
12053             || mpos.y > y + height || mpos.y < y) {
12054             return false;
12055           }
12056       } else {
12057         if(mpos.x < x || mpos.x > x + width
12058             || mpos.y > y || mpos.y < y - height) {
12059             return false;
12060           }
12061       }
12062       //deep check
12063       for(var i=0, l=dimArray.length; i<l; i++) {
12064         var dimi = dimArray[i];
12065                 var url = Url.decode(node.getData('linkArray')[i]);
12066         if(horz) {
12067           var limit = y + fixedDim * i;
12068           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12069             return {
12070               'name': node.getData('stringArray')[i],
12071               'color': node.getData('colorArray')[i],
12072               'value': node.getData('valueArray')[i],
12073                           'valuelabel': node.getData('valuelabelArray')[i],
12074               'title': node.getData('titleArray')[i],
12075                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12076               'link': url,
12077               'label': node.name
12078             };
12079           }
12080         } else {
12081           var limit = x + fixedDim * i;
12082           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12083             return {
12084               'name': node.getData('stringArray')[i],
12085               'color': node.getData('colorArray')[i],
12086               'value': node.getData('valueArray')[i],
12087                           'valuelabel': node.getData('valuelabelArray')[i],
12088               'title': node.getData('titleArray')[i],
12089                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12090               'link': url,
12091               'label': node.name
12092             };
12093           }
12094         }
12095       }
12096       return false;
12097     }
12098   },
12099   'barchart-basic' : {
12100     'render' : function(node, canvas) {
12101       var pos = node.pos.getc(true), 
12102           width = node.getData('width'),
12103           height = node.getData('height'),
12104           algnPos = this.getAlignedPos(pos, width, height),
12105           x = algnPos.x, y = algnPos.y,
12106           dimArray = node.getData('dimArray'),
12107           valueArray = node.getData('valueArray'),
12108                   valuelabelArray = node.getData('valuelabelArray'),
12109           linkArray = node.getData('linkArray'),
12110           valueLength = valueArray.length,
12111           colorArray = node.getData('colorMono'),
12112           colorLength = colorArray.length,
12113           stringArray = node.getData('stringArray'); 
12114
12115       var ctx = canvas.getCtx(),
12116           canvasSize = canvas.getSize(),
12117           opt = {},
12118           border = node.getData('border'),
12119           gradient = node.getData('gradient'),
12120           config = node.getData('config'),
12121           horz = config.orientation == 'horizontal',
12122           aggregates = config.showAggregates,
12123           showLabels = config.showLabels,
12124           label = config.Label,
12125           fixedDim = (horz? height : width) / valueLength,
12126           margin = config.Margin;
12127       
12128       if (colorArray && dimArray && stringArray) {
12129         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12130           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12131
12132           if(gradient) {
12133             var linear;
12134             if(horz) {
12135               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
12136                   x + dimArray[i]/2, y + fixedDim * (i + 1));
12137             } else {
12138               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
12139                   x + fixedDim * (i + 1), y - dimArray[i]/2);
12140             }
12141             //drop shadow 
12142            if(config.shadow.size) {
12143                   shadowThickness = config.shadow.size;
12144                   ctx.fillStyle = "rgba(0,0,0,.2)";
12145                   if(horz) {
12146                     ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12147                   } else {
12148                     ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12149                   }
12150           }
12151           
12152             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
12153                 function(v) { return (v * 0.8) >> 0; }));
12154             linear.addColorStop(0, color);
12155             linear.addColorStop(0.3, colorArray[i % colorLength]);
12156             linear.addColorStop(0.7, colorArray[i % colorLength]);
12157             linear.addColorStop(1, color);
12158             ctx.fillStyle = linear;
12159           }
12160           if(horz) {
12161             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12162           } else {
12163             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12164           }
12165           if(border && border.name == stringArray[i]) {
12166             opt.acum = fixedDim * i;
12167             opt.dimValue = dimArray[i];
12168           }
12169           acum += (dimArray[i] || 0);
12170           valAcum += (valueArray[i] || 0);
12171                   
12172               if(label.type == 'Native') {
12173                                  ctx.fillStyle = ctx.strokeStyle = label.color;
12174                                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12175                                  if(aggregates(node.name, valAcum)) {
12176                                         if(valuelabelArray[i]) {
12177                                                 acumValueLabel = valuelabelArray[i];
12178                                           } else {
12179                                                 acumValueLabel = valueArray[i];
12180                                           }
12181                                          if(!horz) {
12182                                           ctx.textAlign = 'center';
12183                                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12184                                           //background box
12185                                           ctx.save();
12186                                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12187                                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12188                                                  (label ? label.size + config.labelOffset : 0));
12189                           mtxt = ctx.measureText(acumValueLabel);
12190                                           boxWidth = mtxt.width+10;
12191                                           inset = 10;
12192                                           boxHeight = label.size+6;
12193                                           
12194                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12195                                                 bottomPadding = dimArray[i] - config.labelOffset  - inset;
12196                                           } else {
12197                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12198                                           }
12199                                         
12200                                         
12201                                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12202                                           cornerRadius = 4;     
12203                                           boxX = -inset/2;
12204                                           boxY = -boxHeight/2;
12205                                           
12206                                           //ctx.rotate(270* Math.PI / 180);
12207                                           ctx.fillStyle = "rgba(255,255,255,.6)";
12208                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12209                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12210                                           }
12211                                          // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12212                                           ctx.fillStyle = ctx.strokeStyle = label.color;
12213                                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12214                                           ctx.restore();
12215                                         }
12216                                 }
12217                 }
12218         }
12219         if(border) {
12220           ctx.save();
12221           ctx.lineWidth = 2;
12222           ctx.strokeStyle = border.color;
12223           if(horz) {
12224             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12225           } else {
12226             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12227           }
12228           ctx.restore();
12229         }
12230         if(label.type == 'Native') {
12231           ctx.save();
12232           ctx.fillStyle = ctx.strokeStyle = label.color;
12233           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12234           ctx.textBaseline = 'middle';
12235           if(showLabels(node.name, valAcum, node)) {
12236             if(horz) {
12237                 
12238                 //background box
12239                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12240                 mtxt = ctx.measureText(node.name + ": " + valAcum);
12241                 boxWidth = mtxt.width+10;
12242                 inset = 10;
12243                 
12244                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12245                         leftPadding = acum - config.labelOffset - boxWidth - inset;
12246                 } else {
12247                         leftPadding = acum + config.labelOffset;
12248                 }
12249                 
12250                                 
12251                                 ctx.textAlign = 'left';
12252                                 ctx.translate(x + inset + leftPadding, y + height/2);
12253                                 boxHeight = label.size+6;
12254                                 boxX = -inset/2;
12255                                 boxY = -boxHeight/2;
12256                                 ctx.fillStyle = "rgba(255,255,255,.8)";
12257                                 
12258                                 cornerRadius = 4;       
12259                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12260                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12261                                 }
12262                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12263                 
12264                                 
12265                                 ctx.fillStyle = label.color;
12266                                 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12267
12268             } else {
12269               
12270                           if(stringArray.length > 8) {
12271                                 ctx.textAlign = 'left';
12272                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12273                                 ctx.rotate(45* Math.PI / 180);
12274                                 ctx.fillText(node.name, 0, 0);
12275                           } else {
12276                                 ctx.textAlign = 'center';
12277                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12278                           }
12279               
12280             }
12281           }
12282           ctx.restore();
12283         }
12284       }
12285     },
12286     'contains': function(node, mpos) {
12287       var pos = node.pos.getc(true), 
12288           width = node.getData('width'),
12289           height = node.getData('height'),
12290           config = node.getData('config'),
12291           algnPos = this.getAlignedPos(pos, width, height),
12292           x = algnPos.x, y = algnPos.y ,
12293           dimArray = node.getData('dimArray'),
12294           len = dimArray.length,
12295           rx = mpos.x - x,
12296           horz = config.orientation == 'horizontal',
12297           fixedDim = (horz? height : width) / len;
12298
12299       //bounding box check
12300       if(horz) {
12301         if(mpos.x < x || mpos.x > x + width
12302             || mpos.y > y + height || mpos.y < y) {
12303             return false;
12304           }
12305       } else {
12306         if(mpos.x < x || mpos.x > x + width
12307             || mpos.y > y || mpos.y < y - height) {
12308             return false;
12309           }
12310       }
12311       //deep check
12312       for(var i=0, l=dimArray.length; i<l; i++) {
12313         var dimi = dimArray[i];
12314                 var url = Url.decode(node.getData('linkArray')[i]);
12315         if(horz) {
12316           var limit = y + fixedDim * i;
12317           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12318             return {
12319               'name': node.getData('stringArray')[i],
12320               'color': node.getData('colorArray')[i],
12321               'value': node.getData('valueArray')[i],
12322                           'valuelabel': node.getData('valuelabelArray')[i],
12323                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12324               'link': url,
12325               'label': node.name
12326             };
12327           }
12328         } else {
12329           var limit = x + fixedDim * i;
12330           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12331             return {
12332               'name': node.getData('stringArray')[i],
12333               'color': node.getData('colorArray')[i],
12334               'value': node.getData('valueArray')[i],
12335                           'valuelabel': node.getData('valuelabelArray')[i],
12336                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12337               'link': url,
12338               'label': node.name
12339             };
12340           }
12341         }
12342       }
12343       return false;
12344     }
12345   }
12346 });
12347
12348 /*
12349   Class: BarChart
12350   
12351   A visualization that displays stacked bar charts.
12352   
12353   Constructor Options:
12354   
12355   See <Options.BarChart>.
12356
12357 */
12358 $jit.BarChart = new Class({
12359   st: null,
12360   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12361   selected: {},
12362   busy: false,
12363   
12364   initialize: function(opt) {
12365     this.controller = this.config = 
12366       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12367         Label: { type: 'Native' }
12368       }, opt);
12369     //set functions for showLabels and showAggregates
12370     var showLabels = this.config.showLabels,
12371         typeLabels = $.type(showLabels),
12372         showAggregates = this.config.showAggregates,
12373         typeAggregates = $.type(showAggregates);
12374     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12375     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12376     Options.Fx.clearCanvas = false;
12377     this.initializeViz();
12378   },
12379   
12380   initializeViz: function() {
12381     var config = this.config, that = this;
12382     var nodeType = config.type.split(":")[0],
12383         horz = config.orientation == 'horizontal',
12384         nodeLabels = {};
12385     var st = new $jit.ST({
12386       injectInto: config.injectInto,
12387       orientation: horz? 'left' : 'bottom',
12388       background: config.background,
12389       renderBackground: config.renderBackground,
12390       backgroundColor: config.backgroundColor,
12391       colorStop1: config.colorStop1,
12392       colorStop2: config.colorStop2,
12393       levelDistance: 0,
12394       nodeCount: config.nodeCount,
12395       siblingOffset: config.barsOffset,
12396       subtreeOffset: 0,
12397       withLabels: config.Label.type != 'Native',      
12398       useCanvas: config.useCanvas,
12399       Label: {
12400         type: config.Label.type
12401       },
12402       Node: {
12403         overridable: true,
12404         type: 'barchart-' + nodeType,
12405         align: 'left',
12406         width: 1,
12407         height: 1
12408       },
12409       Edge: {
12410         type: 'none'
12411       },
12412       Tips: {
12413         enable: config.Tips.enable,
12414         type: 'Native',
12415         force: true,
12416         onShow: function(tip, node, contains) {
12417           var elem = contains;
12418           config.Tips.onShow(tip, elem, node);
12419                           if(elem.link != 'undefined' && elem.link != '') {
12420                                 document.body.style.cursor = 'pointer';
12421                           }
12422         },
12423                 onHide: function(call) {
12424                         document.body.style.cursor = 'default';
12425
12426         }
12427       },
12428       Events: {
12429         enable: true,
12430         type: 'Native',
12431         onClick: function(node, eventInfo, evt) {
12432           if(!config.Events.enable) return;
12433           var elem = eventInfo.getContains();
12434           config.Events.onClick(elem, eventInfo, evt);
12435         },
12436         onMouseMove: function(node, eventInfo, evt) {
12437           if(!config.hoveredColor) return;
12438           if(node) {
12439             var elem = eventInfo.getContains();
12440             that.select(node.id, elem.name, elem.index);
12441           } else {
12442             that.select(false, false, false);
12443           }
12444         }
12445       },
12446       onCreateLabel: function(domElement, node) {
12447         var labelConf = config.Label,
12448             valueArray = node.getData('valueArray'),
12449             acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12450             grouped = config.type.split(':')[0] == 'grouped',
12451             horz = config.orientation == 'horizontal';
12452         var nlbs = {
12453           wrapper: document.createElement('div'),
12454           aggregate: document.createElement('div'),
12455           label: document.createElement('div')
12456         };
12457         
12458         var wrapper = nlbs.wrapper,
12459             label = nlbs.label,
12460             aggregate = nlbs.aggregate,
12461             wrapperStyle = wrapper.style,
12462             labelStyle = label.style,
12463             aggregateStyle = aggregate.style;
12464         //store node labels
12465         nodeLabels[node.id] = nlbs;
12466         //append labels
12467         wrapper.appendChild(label);
12468         wrapper.appendChild(aggregate);
12469         if(!config.showLabels(node.name, acum, node)) {
12470           labelStyle.display = 'none';
12471         }
12472         if(!config.showAggregates(node.name, acum, node)) {
12473           aggregateStyle.display = 'none';
12474         }
12475         wrapperStyle.position = 'relative';
12476         wrapperStyle.overflow = 'visible';
12477         wrapperStyle.fontSize = labelConf.size + 'px';
12478         wrapperStyle.fontFamily = labelConf.family;
12479         wrapperStyle.color = labelConf.color;
12480         wrapperStyle.textAlign = 'center';
12481         aggregateStyle.position = labelStyle.position = 'absolute';
12482         
12483         domElement.style.width = node.getData('width') + 'px';
12484         domElement.style.height = node.getData('height') + 'px';
12485         aggregateStyle.left = "0px";
12486         labelStyle.left =  config.labelOffset + 'px';
12487         labelStyle.whiteSpace =  "nowrap";
12488                 label.innerHTML = node.name;       
12489         
12490         domElement.appendChild(wrapper);
12491       },
12492       onPlaceLabel: function(domElement, node) {
12493         if(!nodeLabels[node.id]) return;
12494         var labels = nodeLabels[node.id],
12495             wrapperStyle = labels.wrapper.style,
12496             labelStyle = labels.label.style,
12497             aggregateStyle = labels.aggregate.style,
12498             grouped = config.type.split(':')[0] == 'grouped',
12499             horz = config.orientation == 'horizontal',
12500             dimArray = node.getData('dimArray'),
12501             valArray = node.getData('valueArray'),
12502             nodeCount = node.getData('nodeCount'),
12503             valueLength = valArray.length;
12504             valuelabelArray = node.getData('valuelabelArray'),
12505             stringArray = node.getData('stringArray'),
12506             width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12507             height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12508             font = parseInt(wrapperStyle.fontSize, 10),
12509             domStyle = domElement.style,
12510             fixedDim = (horz? height : width) / valueLength;
12511             
12512         
12513         if(dimArray && valArray) {
12514           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12515           
12516           aggregateStyle.width = width  - config.labelOffset + "px";
12517           for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12518             if(dimArray[i] > 0) {
12519               acum+= valArray[i];
12520             }
12521           }
12522           if(config.showLabels(node.name, acum, node)) {
12523             labelStyle.display = '';
12524           } else {
12525             labelStyle.display = 'none';
12526           }
12527           if(config.showAggregates(node.name, acum, node)) {
12528             aggregateStyle.display = '';
12529           } else {
12530             aggregateStyle.display = 'none';
12531           }
12532           if(config.orientation == 'horizontal') {
12533             aggregateStyle.textAlign = 'right';
12534             labelStyle.textAlign = 'left';
12535             labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12536             aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12537             domElement.style.height = wrapperStyle.height = height + 'px';
12538           } else {
12539             aggregateStyle.top = (-font - config.labelOffset) + 'px';
12540             labelStyle.top = (config.labelOffset + height) + 'px';
12541             domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12542             domElement.style.height = wrapperStyle.height = height + 'px';
12543             if(stringArray.length > 8) {
12544                 labels.label.className = "rotatedLabelReverse";
12545                 labelStyle.textAlign = "left";
12546                 labelStyle.top = config.labelOffset + height + width/2 + "px";
12547             }
12548           }
12549           
12550           if(horz) {
12551
12552                         labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12553                         labels.aggregate.innerHTML = "";
12554
12555           } else {
12556                 
12557                         if(grouped) {
12558                                 maxValue = Math.max.apply(null,dimArray);
12559                                 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12560                                         valueLabelDim = 50;
12561                                         valueLabel = document.createElement('div');
12562                                         valueLabel.innerHTML =  valuelabelArray[i];
12563 //                                      valueLabel.class = "rotatedLabel";
12564                                         valueLabel.className = "rotatedLabel";
12565                                         valueLabel.style.position = "absolute";
12566                                                 valueLabel.style.textAlign = "left";
12567                                                 valueLabel.style.verticalAlign = "middle";
12568                                         valueLabel.style.height = valueLabelDim + "px";
12569                                         valueLabel.style.width = valueLabelDim + "px";
12570                                         valueLabel.style.top =  (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12571                                         valueLabel.style.left = (fixedDim * i) + "px";
12572                                         labels.wrapper.appendChild(valueLabel);
12573                                 }
12574                         } else {
12575                                 labels.aggregate.innerHTML = acum;
12576                         }
12577           }
12578         }
12579       }
12580     });
12581
12582     var size = st.canvas.getSize(),
12583         l = config.nodeCount,
12584         margin = config.Margin;
12585         title = config.Title;
12586         subtitle = config.Subtitle,
12587         grouped = config.type.split(':')[0] == 'grouped',
12588         margin = config.Margin,
12589         ticks = config.Ticks,
12590         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12591         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12592         horz = config.orientation == 'horizontal',
12593         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12594         fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12595         whiteSpace = size.width - (marginWidth + (fixedDim * l));
12596         
12597         //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12598         if(!grouped && !horz) {
12599                 st.config.siblingOffset = whiteSpace/(l+1);
12600         }
12601         
12602         
12603         
12604         //Bars offset
12605     if(horz) {
12606       st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);    
12607           if(config.Ticks.enable)       {
12608                 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;
12609           } else {
12610                 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12611           }
12612     } else {
12613       st.config.offsetY = -size.height/2 + margin.bottom 
12614         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12615           if(config.Ticks.enable)       {
12616                 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12617           } else {
12618                 st.config.offsetX = (margin.right - margin.left)/2;
12619           }
12620     }
12621     this.st = st;
12622     this.canvas = this.st.canvas;
12623   },
12624   
12625   renderTitle: function() {
12626         var canvas = this.canvas,
12627         size = canvas.getSize(),
12628         config = this.config,
12629         margin = config.Margin,
12630         label = config.Label,
12631         title = config.Title;
12632         ctx = canvas.getCtx();
12633         ctx.fillStyle = title.color;
12634         ctx.textAlign = 'left';
12635         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12636         if(label.type == 'Native') {
12637                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12638         }
12639   },  
12640   
12641   renderSubtitle: function() {
12642         var canvas = this.canvas,
12643         size = canvas.getSize(),
12644         config = this.config,
12645         margin = config.Margin,
12646         label = config.Label,
12647         subtitle = config.Subtitle;
12648         ctx = canvas.getCtx();
12649         ctx.fillStyle = title.color;
12650         ctx.textAlign = 'left';
12651         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12652         if(label.type == 'Native') {
12653                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
12654         }
12655   },
12656   
12657   renderScrollNote: function() {
12658         var canvas = this.canvas,
12659         size = canvas.getSize(),
12660         config = this.config,
12661         margin = config.Margin,
12662         label = config.Label,
12663         note = config.ScrollNote;
12664         ctx = canvas.getCtx();
12665         ctx.fillStyle = title.color;
12666         title = config.Title;
12667         ctx.textAlign = 'center';
12668         ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12669         if(label.type == 'Native') {
12670                 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12671         }
12672   },  
12673   
12674   renderTicks: function() {
12675
12676         var canvas = this.canvas,
12677         size = canvas.getSize(),
12678         config = this.config,
12679         margin = config.Margin,
12680         ticks = config.Ticks,
12681         title = config.Title,
12682         subtitle = config.Subtitle,
12683         label = config.Label,
12684         shadow = config.shadow;
12685         horz = config.orientation == 'horizontal',
12686         maxValue = this.getMaxValue(),
12687         maxTickValue = Math.ceil(maxValue*.1)*10;
12688         if(maxTickValue == maxValue) {
12689                 var length = maxTickValue.toString().length;
12690                 maxTickValue = maxTickValue + parseInt(pad(1,length));
12691         }
12692         grouped = config.type.split(':')[0] == 'grouped',
12693         labelValue = 0,
12694         labelIncrement = maxTickValue/ticks.segments,
12695         ctx = canvas.getCtx();
12696         ctx.strokeStyle = ticks.color;
12697     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12698
12699         ctx.textAlign = 'center';
12700         ctx.textBaseline = 'middle';
12701         
12702         idLabel = canvas.id + "-label";
12703         labelDim = 100;
12704         container = document.getElementById(idLabel);
12705                   
12706                   
12707         if(horz) {
12708                 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12709                 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12710                 segmentLength = grid/ticks.segments;
12711                 ctx.fillStyle = ticks.color;
12712                 ctx.fillRect(axis,
12713                  (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12714                  size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12715                  1);
12716                 while(axis<=grid) {
12717                         ctx.fillStyle = ticks.color;
12718                         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);
12719                         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));
12720                         ctx.fillStyle = label.color;
12721                         
12722                         if(label.type == 'Native' && config.showLabels) {            
12723                  ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12724                         }
12725                         axis += segmentLength;
12726                         labelValue += labelIncrement;
12727                 }
12728         
12729         } else {
12730         
12731                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12732                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12733                 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)),
12734                 segmentLength = grid/ticks.segments;
12735                 ctx.fillStyle = ticks.color;
12736                 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));
12737
12738                 while(axis>=grid) {
12739                         ctx.save();
12740                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12741                         ctx.rotate(0 * Math.PI / 180 );
12742                         ctx.fillStyle = label.color;
12743                         if(config.showLabels) {
12744                                 if(label.type == 'Native') { 
12745                                         ctx.fillText(labelValue, 0, 0);
12746                                 } else {
12747                                         //html labels on y axis
12748                                         labelDiv = document.createElement('div');
12749                                         labelDiv.innerHTML = labelValue;
12750                                         labelDiv.className = "rotatedLabel";
12751 //                                      labelDiv.class = "rotatedLabel";
12752                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12753                                         labelDiv.style.left = margin.left + "px";
12754                                         labelDiv.style.width = labelDim + "px";
12755                                         labelDiv.style.height = labelDim + "px";
12756                                         labelDiv.style.textAlign = "center";
12757                                         labelDiv.style.verticalAlign = "middle";
12758                                         labelDiv.style.position = "absolute";
12759                                         container.appendChild(labelDiv);
12760                                 }
12761                         }
12762                         ctx.restore();
12763                         ctx.fillStyle = ticks.color;
12764                         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 );
12765                         htmlOrigin += segmentLength;
12766                         axis += segmentLength;
12767                         labelValue += labelIncrement;
12768                 }
12769         }
12770         
12771         
12772         
12773
12774   },
12775   
12776   renderBackground: function() {
12777                 var canvas = this.canvas,
12778                 config = this.config,
12779                 backgroundColor = config.backgroundColor,
12780                 size = canvas.getSize(),
12781                 ctx = canvas.getCtx();
12782             ctx.fillStyle = backgroundColor;
12783             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
12784   },
12785   /*
12786     Method: loadJSON
12787    
12788     Loads JSON data into the visualization. 
12789     
12790     Parameters:
12791     
12792     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>.
12793     
12794     Example:
12795     (start code js)
12796     var barChart = new $jit.BarChart(options);
12797     barChart.loadJSON(json);
12798     (end code)
12799  */  
12800   loadJSON: function(json) {
12801     if(this.busy) return;
12802     this.busy = true;
12803     
12804     var prefix = $.time(), 
12805         ch = [], 
12806         st = this.st,
12807         name = $.splat(json.label), 
12808         color = $.splat(json.color || this.colors),
12809         config = this.config,
12810         gradient = !!config.type.split(":")[1],
12811         renderBackground = config.renderBackground,
12812         animate = config.animate,
12813         ticks = config.Ticks,
12814         title = config.Title,
12815         note = config.ScrollNote,
12816         subtitle = config.Subtitle,
12817         horz = config.orientation == 'horizontal',
12818         that = this,
12819                 colorLength = color.length,
12820                 nameLength = name.length;
12821         groupTotalValue = 0;
12822     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12823         var val = values[i];
12824         var valArray = $.splat(val.values);
12825         groupTotalValue += parseInt(valArray.sum());
12826     }
12827
12828     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12829       var val = values[i];
12830       var valArray = $.splat(values[i].values);
12831       var valuelabelArray = $.splat(values[i].valuelabels);
12832       var linkArray = $.splat(values[i].links);
12833       var titleArray = $.splat(values[i].titles);
12834       var barTotalValue = valArray.sum();
12835       var acum = 0;
12836       ch.push({
12837         'id': prefix + val.label,
12838         'name': val.label,
12839         
12840         'data': {
12841           'value': valArray,
12842           '$linkArray': linkArray,
12843                   '$gvl': val.gvaluelabel,
12844           '$titleArray': titleArray,
12845           '$valueArray': valArray,
12846           '$valuelabelArray': valuelabelArray,
12847           '$colorArray': color,
12848           '$colorMono': $.splat(color[i % colorLength]),
12849           '$stringArray': name,
12850           '$barTotalValue': barTotalValue,
12851           '$groupTotalValue': groupTotalValue,
12852           '$nodeCount': values.length,
12853           '$gradient': gradient,
12854           '$config': config
12855         },
12856         'children': []
12857       });
12858     }
12859     var root = {
12860       'id': prefix + '$root',
12861       'name': '',
12862       'data': {
12863         '$type': 'none',
12864         '$width': 1,
12865         '$height': 1
12866       },
12867       'children': ch
12868     };
12869     st.loadJSON(root);
12870     
12871     this.normalizeDims();
12872     
12873     if(renderBackground) {
12874                 this.renderBackground();
12875     }
12876         
12877         if(!animate && ticks.enable) {
12878                 this.renderTicks();
12879         }
12880         if(!animate && note.text) {
12881                 this.renderScrollNote();
12882         }
12883         if(!animate && title.text) {
12884                 this.renderTitle();
12885         }
12886         if(!animate && subtitle.text) {
12887                 this.renderSubtitle();
12888         }
12889     st.compute();
12890     st.select(st.root);
12891     if(animate) {
12892       if(horz) {
12893         st.fx.animate({
12894           modes: ['node-property:width:dimArray'],
12895           duration:1500,
12896           onComplete: function() {
12897             that.busy = false;
12898           }
12899         });
12900       } else {
12901         st.fx.animate({
12902           modes: ['node-property:height:dimArray'],
12903           duration:1500,
12904           onComplete: function() {
12905             that.busy = false;
12906           }
12907         });
12908       }
12909     } else {
12910       this.busy = false;
12911     }
12912   },
12913   
12914   /*
12915     Method: updateJSON
12916    
12917     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.
12918     
12919     Parameters:
12920     
12921     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
12922     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
12923     
12924     Example:
12925     
12926     (start code js)
12927     barChart.updateJSON(json, {
12928       onComplete: function() {
12929         alert('update complete!');
12930       }
12931     });
12932     (end code)
12933  */  
12934   updateJSON: function(json, onComplete) {
12935     if(this.busy) return;
12936     this.busy = true;
12937     
12938     var st = this.st;
12939     var graph = st.graph;
12940     var values = json.values;
12941     var animate = this.config.animate;
12942     var that = this;
12943     var horz = this.config.orientation == 'horizontal';
12944     $.each(values, function(v) {
12945       var n = graph.getByName(v.label);
12946       if(n) {
12947         n.setData('valueArray', $.splat(v.values));
12948         if(json.label) {
12949           n.setData('stringArray', $.splat(json.label));
12950         }
12951       }
12952     });
12953     this.normalizeDims();
12954     st.compute();
12955     st.select(st.root);
12956     if(animate) {
12957       if(horz) {
12958         st.fx.animate({
12959           modes: ['node-property:width:dimArray'],
12960           duration:1500,
12961           onComplete: function() {
12962             that.busy = false;
12963             onComplete && onComplete.onComplete();
12964           }
12965         });
12966       } else {
12967         st.fx.animate({
12968           modes: ['node-property:height:dimArray'],
12969           duration:1500,
12970           onComplete: function() {
12971             that.busy = false;
12972             onComplete && onComplete.onComplete();
12973           }
12974         });
12975       }
12976     }
12977   },
12978   
12979   //adds the little brown bar when hovering the node
12980   select: function(id, name) {
12981
12982     if(!this.config.hoveredColor) return;
12983     var s = this.selected;
12984     if(s.id != id || s.name != name) {
12985       s.id = id;
12986       s.name = name;
12987       s.color = this.config.hoveredColor;
12988       this.st.graph.eachNode(function(n) {
12989         if(id == n.id) {
12990           n.setData('border', s);
12991         } else {
12992           n.setData('border', false);
12993         }
12994       });
12995       this.st.plot();
12996     }
12997   },
12998   
12999   /*
13000     Method: getLegend
13001    
13002     Returns an object containing as keys the legend names and as values hex strings with color values.
13003     
13004     Example:
13005     
13006     (start code js)
13007     var legend = barChart.getLegend();
13008     (end code)
13009   */  
13010   getLegend: function() {
13011     var legend = new Array();
13012     var name = new Array();
13013     var color = new Array();
13014     var n;
13015     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13016       n = adj.nodeTo;
13017     });
13018     var colors = n.getData('colorArray'),
13019         len = colors.length;
13020     $.each(n.getData('stringArray'), function(s, i) {
13021       color[i] = colors[i % len];
13022       name[i] = s;
13023     });
13024         legend['name'] = name;
13025         legend['color'] = color;
13026     return legend;
13027   },
13028   
13029   /*
13030     Method: getMaxValue
13031    
13032     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13033     
13034     Example:
13035     
13036     (start code js)
13037     var ans = barChart.getMaxValue();
13038     (end code)
13039     
13040     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13041     
13042     Example:
13043     
13044     (start code js)
13045     //will return 100 for all BarChart instances,
13046     //displaying all of them with the same scale
13047     $jit.BarChart.implement({
13048       'getMaxValue': function() {
13049         return 100;
13050       }
13051     });
13052     (end code)
13053     
13054   */  
13055   getMaxValue: function() {
13056     var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13057     this.st.graph.eachNode(function(n) {
13058       var valArray = n.getData('valueArray'),
13059           acum = 0;
13060       if(!valArray) return;
13061       if(stacked) {
13062         $.each(valArray, function(v) { 
13063           acum += +v;
13064         });
13065       } else {
13066         acum = Math.max.apply(null, valArray);
13067       }
13068       maxValue = maxValue>acum? maxValue:acum;
13069     });
13070     return maxValue;
13071   },
13072   
13073   setBarType: function(type) {
13074     this.config.type = type;
13075     this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13076   },
13077   
13078   normalizeDims: function() {
13079     //number of elements
13080     var root = this.st.graph.getNode(this.st.root), l=0;
13081     root.eachAdjacency(function() {
13082       l++;
13083     });
13084     var maxValue = this.getMaxValue() || 1,
13085         size = this.st.canvas.getSize(),
13086         config = this.config,
13087         margin = config.Margin,
13088         ticks = config.Ticks,
13089         title = config.Title,
13090         subtitle = config.Subtitle,
13091         grouped = config.type.split(':')[0] == 'grouped',
13092         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13093         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13094         horz = config.orientation == 'horizontal',
13095         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13096         animate = config.animate,
13097         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13098
13099           - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13100         dim1 = horz? 'height':'width',
13101         dim2 = horz? 'width':'height',
13102         basic = config.type.split(':')[0] == 'basic';
13103         
13104         
13105                 var maxTickValue = Math.ceil(maxValue*.1)*10;
13106                 if(maxTickValue == maxValue) {
13107                         var length = maxTickValue.toString().length;
13108                         maxTickValue = maxTickValue + parseInt(pad(1,length));
13109                 }
13110
13111                 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13112
13113                 
13114     this.st.graph.eachNode(function(n) {
13115       var acum = 0, animateValue = [];
13116       $.each(n.getData('valueArray'), function(v) {
13117         acum += +v;
13118         animateValue.push(0);
13119       });
13120       
13121       if(grouped) {
13122         fixedDim = animateValue.length * 40;
13123       }
13124       n.setData(dim1, fixedDim);
13125       
13126       
13127       if(animate) {
13128         n.setData(dim2, acum * height / maxValue, 'end');
13129         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13130           return n * height / maxValue; 
13131         }), 'end');
13132         var dimArray = n.getData('dimArray');
13133         if(!dimArray) {
13134           n.setData('dimArray', animateValue);
13135         }
13136       } else {
13137         
13138
13139                 if(ticks.enable) {
13140                         n.setData(dim2, acum * height / maxTickValue);
13141                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13142                           return n * height / maxTickValue; 
13143                         }));
13144                 } else {
13145                         n.setData(dim2, acum * height / maxValue);
13146                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13147                           return n * height / maxValue; 
13148                         }));
13149                 }
13150       }
13151     });
13152   }
13153 });
13154
13155 //funnel chart options
13156
13157
13158 Options.FunnelChart = {
13159   $extend: true,
13160   
13161   animate: true,
13162   type: 'stacked', //stacked, grouped, : gradient
13163   labelOffset: 3, //label offset
13164   barsOffset: 0, //distance between bars
13165   hoveredColor: '#9fd4ff',
13166   orientation: 'vertical',
13167   showAggregates: true,
13168   showLabels: true,
13169   Tips: {
13170     enable: false,
13171     onShow: $.empty,
13172     onHide: $.empty
13173   },
13174   Events: {
13175     enable: false,
13176     onClick: $.empty
13177   }
13178 };
13179
13180 $jit.ST.Plot.NodeTypes.implement({
13181   'funnelchart-basic' : {
13182     'render' : function(node, canvas) {
13183       var pos = node.pos.getc(true), 
13184           width  = node.getData('width'),
13185           height = node.getData('height'),
13186           algnPos = this.getAlignedPos(pos, width, height),
13187           x = algnPos.x, y = algnPos.y,
13188           dimArray = node.getData('dimArray'),
13189           valueArray = node.getData('valueArray'),
13190           valuelabelArray = node.getData('valuelabelArray'),
13191           linkArray = node.getData('linkArray'),
13192           colorArray = node.getData('colorArray'),
13193           colorLength = colorArray.length,
13194           stringArray = node.getData('stringArray');
13195       var ctx = canvas.getCtx(),
13196           opt = {},
13197           border = node.getData('border'),
13198           gradient = node.getData('gradient'),
13199           config = node.getData('config'),
13200           horz = config.orientation == 'horizontal',
13201           aggregates = config.showAggregates,
13202           showLabels = config.showLabels,
13203           label = config.Label,
13204           size = canvas.getSize(),
13205           labelOffset = config.labelOffset + 10;
13206           minWidth =  width * .25;
13207           ratio = .65;
13208
13209       if (colorArray && dimArray && stringArray) {
13210         
13211         
13212         // horizontal lines
13213         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13214         ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13215
13216         if(label.type == 'Native') {      
13217        if(showLabels(node.name, valAcum, node)) {
13218                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13219                  var stringValue = stringArray[i];
13220                  var valueLabel = String(valuelabelArray[i]);
13221              var mV = ctx.measureText(stringValue);
13222              var mVL = ctx.measureText(valueLabel);
13223                  var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13224                          var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13225                          var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13226                  var bottomWidth = minWidth + ((acum) * ratio);  
13227                  var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);  
13228                          var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13229                          var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13230 //             ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13231
13232                         //right lines
13233                         ctx.beginPath();
13234                         ctx.moveTo(bottomWidth/2,y - acum); //
13235                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13236                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight);  // bottom right
13237                         ctx.stroke();
13238                         //left lines
13239                         ctx.beginPath();
13240                         ctx.moveTo(-bottomWidth/2,y - acum); //
13241                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13242                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight);  // bottom right
13243                         ctx.stroke();
13244        }
13245         }
13246
13247                 acum += (dimArray[i] || 0);
13248           valAcum += (valueArray[i] || 0);
13249           
13250           
13251                 }
13252                 
13253  
13254   
13255         //funnel segments and labels
13256         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13257           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13258                           var colori = colorArray[i % colorLength];
13259                           if(label.type == 'Native') { 
13260                                   var stringValue = stringArray[i];
13261                           var valueLabel = String(valuelabelArray[i]);
13262                               var mV = ctx.measureText(stringValue);
13263                       var mVL = ctx.measureText(valueLabel);
13264                           } else {
13265                                   var mV = 10;
13266                       var mVL = 10;     
13267                           }
13268                       var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13269                       var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13270                       var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13271                       var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13272                       
13273           var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13274           var bottomWidth = minWidth + ((acum) * ratio);
13275           var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13276           
13277
13278           if(gradient) {
13279             var linear;
13280               linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13281                         var colorRgb = $.hexToRgb(colori);
13282             var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
13283                 function(v) { return (v * .5) >> 0; });
13284             linear.addColorStop(0, 'rgba('+color+',1)');
13285             linear.addColorStop(0.5,  'rgba('+colorRgb+',1)');
13286             linear.addColorStop(1, 'rgba('+color+',1)');
13287             ctx.fillStyle = linear;
13288           }
13289           
13290                         ctx.beginPath();
13291                         ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13292                         ctx.lineTo(topWidth/2,y - acum - dimArray[i]);  // top right
13293                         ctx.lineTo(bottomWidth/2,y - acum);  // bottom right
13294                         ctx.lineTo(-bottomWidth/2,y - acum);  // bottom left
13295                         ctx.closePath(); 
13296                         ctx.fill();
13297                 
13298           
13299           if(border && border.name == stringArray[i]) {
13300             opt.acum = acum;
13301             opt.dimValue = dimArray[i];
13302           }
13303           
13304           
13305         if(border) {
13306           ctx.save();
13307           ctx.lineWidth = 2;
13308           ctx.strokeStyle = border.color;
13309
13310             //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13311          
13312           ctx.restore();
13313         }
13314         if(label.type == 'Native') {
13315           ctx.save();
13316           ctx.fillStyle = ctx.strokeStyle = label.color;
13317           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13318           ctx.textBaseline = 'middle';
13319
13320                                 acumValueLabel = valAcum;
13321
13322           if(showLabels(node.name, valAcum, node)) {
13323
13324                       
13325               ctx.textAlign = 'left';
13326               ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13327               ctx.textAlign = 'right';
13328               ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13329               }
13330           ctx.restore();
13331         }
13332
13333           acum += (dimArray[i] || 0);
13334           valAcum += (valueArray[i] || 0);
13335           
13336         }
13337
13338       }
13339     },
13340     'contains': function(node, mpos) {
13341       var pos = node.pos.getc(true), 
13342           width = node.getData('width'),
13343           height = node.getData('height'),
13344           algnPos = this.getAlignedPos(pos, width, height),
13345           x = algnPos.x, y = algnPos.y,
13346           dimArray = node.getData('dimArray'),
13347           config = node.getData('config'),
13348           st = node.getData('st'),
13349           rx = mpos.x - x,
13350           horz = config.orientation == 'horizontal',
13351            minWidth =  width * .25;
13352           ratio = .65,
13353           canvas = node.getData('canvas'),
13354           size = canvas.getSize(),
13355           offsetY = st.config.offsetY;
13356       //bounding box check
13357
13358         if(mpos.y > y || mpos.y < y - height) {
13359             return false;
13360           }
13361           
13362          var newY = Math.abs(mpos.y + offsetY);
13363         var bound = minWidth + (newY * ratio);
13364         var boundLeft = -bound/2;
13365         var boundRight = bound/2;
13366          if(mpos.x < boundLeft || mpos.x > boundRight ) {
13367             return false;
13368           }
13369
13370       
13371       //deep check
13372       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13373         var dimi = dimArray[i];
13374
13375           
13376           
13377                 var url = Url.decode(node.getData('linkArray')[i]);
13378           acum -= dimi;  
13379           var intersec = acum;
13380           if(mpos.y >= intersec) {
13381             return {
13382               'name': node.getData('stringArray')[i],
13383               'color': node.getData('colorArray')[i],
13384               'value': node.getData('valueArray')[i],
13385               'percentage': node.getData('percentageArray')[i],
13386                           'valuelabel': node.getData('valuelabelArray')[i],
13387               'link': url,
13388               'label': node.name
13389             };
13390           }
13391         
13392       }
13393       return false;
13394     }
13395   }
13396 });
13397
13398 /*
13399   Class: FunnelChart
13400   
13401   A visualization that displays funnel charts.
13402   
13403   Constructor Options:
13404   
13405   See <Options.FunnelChart>.
13406
13407 */
13408 $jit.FunnelChart = new Class({
13409   st: null,
13410   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13411   selected: {},
13412   busy: false,
13413   
13414   initialize: function(opt) {
13415     this.controller = this.config = 
13416       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13417         Label: { type: 'Native' }
13418       }, opt);
13419     //set functions for showLabels and showAggregates
13420     var showLabels = this.config.showLabels,
13421         typeLabels = $.type(showLabels),
13422         showAggregates = this.config.showAggregates,
13423         typeAggregates = $.type(showAggregates);
13424     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13425     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13426     Options.Fx.clearCanvas = false;
13427     this.initializeViz();
13428   },
13429   
13430   initializeViz: function() {
13431     var config = this.config, that = this;
13432     var nodeType = config.type.split(":")[0],
13433         horz = config.orientation == 'horizontal',
13434         nodeLabels = {};
13435     var st = new $jit.ST({
13436       injectInto: config.injectInto,
13437       orientation: horz? 'left' : 'bottom',
13438       levelDistance: 0,
13439       background: config.background,
13440       renderBackground: config.renderBackground,
13441       backgroundColor: config.backgroundColor,
13442       colorStop1: config.colorStop1,
13443       colorStop2: config.colorStop2,
13444       siblingOffset: config.segmentOffset,
13445       subtreeOffset: 0,
13446       withLabels: config.Label.type != 'Native',      
13447       useCanvas: config.useCanvas,
13448       Label: {
13449         type: config.Label.type
13450       },
13451       Node: {
13452         overridable: true,
13453         type: 'funnelchart-' + nodeType,
13454         align: 'left',
13455         width: 1,
13456         height: 1
13457       },
13458       Edge: {
13459         type: 'none'
13460       },
13461       Tips: {
13462         enable: config.Tips.enable,
13463         type: 'Native',
13464         force: true,
13465         onShow: function(tip, node, contains) {
13466           var elem = contains;
13467           config.Tips.onShow(tip, elem, node);
13468                           if(elem.link != 'undefined' && elem.link != '') {
13469                                 document.body.style.cursor = 'pointer';
13470                           }
13471         },
13472                 onHide: function(call) {
13473                         document.body.style.cursor = 'default';
13474
13475         }
13476       },
13477       Events: {
13478         enable: true,
13479         type: 'Native',
13480         onClick: function(node, eventInfo, evt) {
13481           if(!config.Events.enable) return;
13482           var elem = eventInfo.getContains();
13483           config.Events.onClick(elem, eventInfo, evt);
13484         },
13485         onMouseMove: function(node, eventInfo, evt) {
13486           if(!config.hoveredColor) return;
13487           if(node) {
13488             var elem = eventInfo.getContains();
13489             that.select(node.id, elem.name, elem.index);
13490           } else {
13491             that.select(false, false, false);
13492           }
13493         }
13494       },
13495       onCreateLabel: function(domElement, node) {
13496         var labelConf = config.Label,
13497             valueArray = node.getData('valueArray'),
13498             idArray = node.getData('idArray'),
13499             valuelabelArray = node.getData('valuelabelArray'),
13500             stringArray = node.getData('stringArray');
13501             size = st.canvas.getSize()
13502             prefix = $.time();
13503                 
13504                 for(var i=0, l=valueArray.length; i<l; i++) {
13505         var nlbs = {
13506           wrapper: document.createElement('div'),
13507           valueLabel: document.createElement('div'),
13508           label: document.createElement('div')
13509         };
13510         var wrapper = nlbs.wrapper,
13511             label = nlbs.label,
13512             valueLabel = nlbs.valueLabel,
13513             wrapperStyle = wrapper.style,
13514             labelStyle = label.style,
13515             valueLabelStyle = valueLabel.style;
13516         //store node labels
13517         nodeLabels[idArray[i]] = nlbs;
13518         //append labels
13519         wrapper.appendChild(label);
13520         wrapper.appendChild(valueLabel);
13521
13522         wrapperStyle.position = 'relative';
13523         wrapperStyle.overflow = 'visible';
13524         wrapperStyle.fontSize = labelConf.size + 'px';
13525         wrapperStyle.fontFamily = labelConf.family;
13526         wrapperStyle.color = labelConf.color;
13527         wrapperStyle.textAlign = 'center';
13528         wrapperStyle.width = size.width + 'px';
13529         valueLabelStyle.position = labelStyle.position = 'absolute';
13530         valueLabelStyle.left = labelStyle.left =  '0px';
13531                 valueLabelStyle.width = (size.width/3) + 'px';
13532                 valueLabelStyle.textAlign = 'right';
13533         label.innerHTML = stringArray[i];
13534         valueLabel.innerHTML = valuelabelArray[i];
13535         domElement.id = prefix+'funnel';
13536         domElement.style.width = size.width + 'px';
13537         
13538                 domElement.appendChild(wrapper);
13539                 }
13540
13541       },
13542       onPlaceLabel: function(domElement, node) {
13543
13544             var dimArray = node.getData('dimArray'),
13545             idArray = node.getData('idArray'),
13546             valueArray = node.getData('valueArray'),
13547             valuelabelArray = node.getData('valuelabelArray'),
13548             stringArray = node.getData('stringArray');
13549             size = st.canvas.getSize(),
13550             pos = node.pos.getc(true),
13551              domElement.style.left = "0px",
13552              domElement.style.top = "0px",
13553              minWidth = node.getData('width') * .25,
13554              ratio = .65,
13555              pos = node.pos.getc(true),
13556              labelConf = config.Label;
13557              
13558              
13559                 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13560
13561         var labels = nodeLabels[idArray[i]],
13562             wrapperStyle = labels.wrapper.style,
13563             labelStyle = labels.label.style,
13564             valueLabelStyle = labels.valueLabel.style;
13565                 var bottomWidth = minWidth + (acum * ratio); 
13566                 
13567             font = parseInt(wrapperStyle.fontSize, 10),
13568             domStyle = domElement.style;
13569            
13570                 
13571        
13572                         wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13573             valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13574             labelStyle.left =  (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13575
13576                         acum += (dimArray[i] || 0);
13577
13578                 }
13579
13580       }
13581
13582     });
13583
13584     var size = st.canvas.getSize(),
13585         margin = config.Margin;
13586         title = config.Title;
13587         subtitle = config.Subtitle;
13588         //y offset
13589
13590       st.config.offsetY = -size.height/2 + margin.bottom 
13591         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13592
13593                 st.config.offsetX = (margin.right - margin.left)/2;
13594           
13595     
13596     this.st = st;
13597     this.canvas = this.st.canvas;
13598   },
13599   
13600   renderTitle: function() {
13601         var canvas = this.canvas,
13602         size = canvas.getSize(),
13603         config = this.config,
13604         margin = config.Margin,
13605         label = config.Label,
13606         title = config.Title;
13607         ctx = canvas.getCtx();
13608         ctx.fillStyle = title.color;
13609         ctx.textAlign = 'left';
13610         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13611         if(label.type == 'Native') {
13612                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13613         }
13614   },  
13615   
13616   renderSubtitle: function() {
13617         var canvas = this.canvas,
13618         size = canvas.getSize(),
13619         config = this.config,
13620         margin = config.Margin,
13621         label = config.Label,
13622         subtitle = config.Subtitle;
13623         ctx = canvas.getCtx();
13624         ctx.fillStyle = title.color;
13625         ctx.textAlign = 'left';
13626         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13627         if(label.type == 'Native') {
13628                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13629         }
13630   },
13631   
13632   
13633   renderDropShadow: function() {
13634         var canvas = this.canvas,
13635         size = canvas.getSize(),
13636         config = this.config,
13637         margin = config.Margin,
13638         horz = config.orientation == 'horizontal',
13639         label = config.Label,
13640         title = config.Title,
13641         shadowThickness = 4,
13642         subtitle = config.Subtitle,
13643         ctx = canvas.getCtx(),
13644         minwidth = (size.width/8) * .25,
13645         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13646         topMargin = (title.text? title.size + title.offset : 0)  + margin.top,
13647     height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13648           - (config.showLabels && (config.Label.size + config.labelOffset)),
13649     ratio = .65,
13650         topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13651         topY = (-size.height/2) + topMargin - shadowThickness;
13652         bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13653         bottomWidth = minwidth + shadowThickness;
13654         ctx.beginPath();
13655         ctx.fillStyle = "rgba(0,0,0,.2)";
13656         ctx.moveTo(0,topY);
13657         ctx.lineTo(-topWidth/2,topY); //top left
13658         ctx.lineTo(-bottomWidth/2,bottomY);  // bottom left
13659         ctx.lineTo(bottomWidth/2,bottomY);  // bottom right
13660         ctx.lineTo(topWidth/2,topY);  // top right
13661         ctx.closePath(); 
13662         ctx.fill();
13663                         
13664                         
13665   },
13666
13667    renderBackground: function() {
13668                 var canvas = this.canvas,
13669                 config = this.config,
13670                 backgroundColor = config.backgroundColor,
13671                 size = canvas.getSize(),
13672                 ctx = canvas.getCtx();
13673                 //ctx.globalCompositeOperation = "destination-over";
13674             ctx.fillStyle = backgroundColor;
13675             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
13676   },
13677   
13678   
13679   loadJSON: function(json) {
13680     if(this.busy) return;
13681     this.busy = true;
13682     var prefix = $.time(), 
13683         ch = [], 
13684         st = this.st,
13685         name = $.splat(json.label), 
13686         color = $.splat(json.color || this.colors),
13687         config = this.config,
13688         canvas = this.canvas,
13689         gradient = !!config.type.split(":")[1],
13690         animate = config.animate,
13691         title = config.Title,
13692         subtitle = config.Subtitle,
13693         renderBackground = config.renderBackground,
13694         horz = config.orientation == 'horizontal',
13695         that = this,
13696                 colorLength = color.length,
13697                 nameLength = name.length,
13698                 totalValue = 0;
13699
13700     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13701         var val = values[i];
13702         var valArray = $.splat(val.values);
13703         totalValue += parseInt(valArray.sum());
13704     }
13705     
13706     
13707     var idArray = new Array();
13708     var valArray = new Array();
13709     var valuelabelArray = new Array();
13710     var linkArray = new Array();
13711     var titleArray = new Array();
13712     var percentageArray = new Array();
13713     
13714     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13715       var val = values[i];
13716       idArray[i] = $.splat(prefix + val.label);
13717       valArray[i] = $.splat(val.values);
13718       valuelabelArray[i] = $.splat(val.valuelabels);
13719       linkArray[i] = $.splat(val.links);
13720       titleArray[i] = $.splat(val.titles);
13721       percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13722       var acum = 0;
13723     }
13724     
13725     
13726
13727     valArray.reverse();
13728     valuelabelArray.reverse();
13729     linkArray.reverse();
13730     titleArray.reverse();
13731     percentageArray.reverse();
13732     
13733      
13734      
13735       ch.push({
13736         'id': prefix + val.label,
13737         'name': val.label,
13738         
13739         'data': {
13740           'value': valArray,
13741           '$idArray': idArray,
13742           '$linkArray': linkArray,
13743           '$titleArray': titleArray,
13744           '$valueArray': valArray,
13745           '$valuelabelArray': valuelabelArray,
13746           '$colorArray': color,
13747           '$colorMono': $.splat(color[i % colorLength]),
13748           '$stringArray': name.reverse(),
13749           '$gradient': gradient,
13750           '$config': config,
13751           '$percentageArray' : percentageArray,
13752           '$canvas': canvas,
13753           '$st': st
13754         },
13755         'children': []
13756       });
13757     
13758     var root = {
13759       'id': prefix + '$root',
13760       'name': '',
13761       'data': {
13762         '$type': 'none',
13763         '$width': 1,
13764         '$height': 1
13765       },
13766       'children': ch
13767     };
13768     st.loadJSON(root);
13769     
13770     this.normalizeDims();
13771         
13772         if(renderBackground) {
13773                 this.renderBackground();        
13774         }
13775         if(!animate && title.text) {
13776                 this.renderTitle();
13777         }
13778         if(!animate && subtitle.text) {
13779                 this.renderSubtitle();
13780         }
13781         if(typeof FlashCanvas == "undefined") {
13782                 this.renderDropShadow();
13783         }
13784     st.compute();
13785     st.select(st.root);
13786     if(animate) {
13787       if(horz) {
13788         st.fx.animate({
13789           modes: ['node-property:width:dimArray'],
13790           duration:1500,
13791           onComplete: function() {
13792             that.busy = false;
13793           }
13794         });
13795       } else {
13796         st.fx.animate({
13797           modes: ['node-property:height:dimArray'],
13798           duration:1500,
13799           onComplete: function() {
13800             that.busy = false;
13801           }
13802         });
13803       }
13804     } else {
13805       this.busy = false;
13806     }
13807   },
13808   
13809   /*
13810     Method: updateJSON
13811    
13812     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.
13813     
13814     Parameters:
13815     
13816     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13817     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13818     
13819     Example:
13820     
13821     (start code js)
13822     barChart.updateJSON(json, {
13823       onComplete: function() {
13824         alert('update complete!');
13825       }
13826     });
13827     (end code)
13828  */  
13829   updateJSON: function(json, onComplete) {
13830     if(this.busy) return;
13831     this.busy = true;
13832     
13833     var st = this.st;
13834     var graph = st.graph;
13835     var values = json.values;
13836     var animate = this.config.animate;
13837     var that = this;
13838     var horz = this.config.orientation == 'horizontal';
13839     $.each(values, function(v) {
13840       var n = graph.getByName(v.label);
13841       if(n) {
13842         n.setData('valueArray', $.splat(v.values));
13843         if(json.label) {
13844           n.setData('stringArray', $.splat(json.label));
13845         }
13846       }
13847     });
13848     this.normalizeDims();
13849     st.compute();
13850     st.select(st.root);
13851     if(animate) {
13852       if(horz) {
13853         st.fx.animate({
13854           modes: ['node-property:width:dimArray'],
13855           duration:1500,
13856           onComplete: function() {
13857             that.busy = false;
13858             onComplete && onComplete.onComplete();
13859           }
13860         });
13861       } else {
13862         st.fx.animate({
13863           modes: ['node-property:height:dimArray'],
13864           duration:1500,
13865           onComplete: function() {
13866             that.busy = false;
13867             onComplete && onComplete.onComplete();
13868           }
13869         });
13870       }
13871     }
13872   },
13873   
13874   //adds the little brown bar when hovering the node
13875   select: function(id, name) {
13876
13877     if(!this.config.hoveredColor) return;
13878     var s = this.selected;
13879     if(s.id != id || s.name != name) {
13880       s.id = id;
13881       s.name = name;
13882       s.color = this.config.hoveredColor;
13883       this.st.graph.eachNode(function(n) {
13884         if(id == n.id) {
13885           n.setData('border', s);
13886         } else {
13887           n.setData('border', false);
13888         }
13889       });
13890       this.st.plot();
13891     }
13892   },
13893   
13894   /*
13895     Method: getLegend
13896    
13897     Returns an object containing as keys the legend names and as values hex strings with color values.
13898     
13899     Example:
13900     
13901     (start code js)
13902     var legend = barChart.getLegend();
13903     (end code)
13904   */  
13905   getLegend: function() {
13906     var legend = new Array();
13907     var name = new Array();
13908     var color = new Array();
13909     var n;
13910     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13911       n = adj.nodeTo;
13912     });
13913     var colors = n.getData('colorArray'),
13914         len = colors.length;
13915     $.each(n.getData('stringArray'), function(s, i) {
13916       color[i] = colors[i % len];
13917       name[i] = s;
13918     });
13919         legend['name'] = name;
13920         legend['color'] = color;
13921     return legend;
13922   },
13923   
13924   /*
13925     Method: getMaxValue
13926    
13927     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13928     
13929     Example:
13930     
13931     (start code js)
13932     var ans = barChart.getMaxValue();
13933     (end code)
13934     
13935     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13936     
13937     Example:
13938     
13939     (start code js)
13940     //will return 100 for all BarChart instances,
13941     //displaying all of them with the same scale
13942     $jit.BarChart.implement({
13943       'getMaxValue': function() {
13944         return 100;
13945       }
13946     });
13947     (end code)
13948     
13949   */  
13950   getMaxValue: function() {
13951     var maxValue = 0, stacked = true;
13952     this.st.graph.eachNode(function(n) {
13953       var valArray = n.getData('valueArray'),
13954           acum = 0;
13955       if(!valArray) return;
13956       if(stacked) {
13957         $.each(valArray, function(v) { 
13958           acum += +v;
13959         });
13960       } else {
13961         acum = Math.max.apply(null, valArray);
13962       }
13963       maxValue = maxValue>acum? maxValue:acum;
13964     });
13965     return maxValue;
13966   },
13967   
13968   setBarType: function(type) {
13969     this.config.type = type;
13970     this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
13971   },
13972   
13973   normalizeDims: function() {
13974     //number of elements
13975     var root = this.st.graph.getNode(this.st.root), l=0;
13976     root.eachAdjacency(function() {
13977       l++;
13978     });
13979     var maxValue = this.getMaxValue() || 1,
13980         size = this.st.canvas.getSize(),
13981         config = this.config,
13982         margin = config.Margin,
13983         title = config.Title,
13984         subtitle = config.Subtitle,
13985         marginWidth = margin.left + margin.right,
13986         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13987         horz = config.orientation == 'horizontal',
13988         animate = config.animate,
13989         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13990
13991           - (config.showLabels && (config.Label.size + config.labelOffset)),
13992         dim1 = horz? 'height':'width',
13993         dim2 = horz? 'width':'height';
13994         
13995
13996         minWidth = size.width/8;
13997         
13998
13999
14000     this.st.graph.eachNode(function(n) {
14001       var acum = 0, animateValue = [];
14002       $.each(n.getData('valueArray'), function(v) {
14003         acum += +v;
14004         animateValue.push(0);
14005       });
14006       n.setData(dim1, minWidth);
14007             
14008       if(animate) {
14009         n.setData(dim2, acum * height / maxValue, 'end');
14010         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14011           return n * height / maxValue; 
14012         }), 'end');
14013         var dimArray = n.getData('dimArray');
14014         if(!dimArray) {
14015           n.setData('dimArray', animateValue);
14016         }
14017       } else {
14018                         n.setData(dim2, acum * height / maxValue);
14019                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14020                           return n * height / maxValue; 
14021                         }));
14022       }
14023
14024     });
14025   }
14026 });
14027
14028
14029
14030 /*
14031  * File: Options.PieChart.js
14032  *
14033 */
14034 /*
14035   Object: Options.PieChart
14036   
14037   <PieChart> options. 
14038   Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14039   
14040   Syntax:
14041   
14042   (start code js)
14043
14044   Options.PieChart = {
14045     animate: true,
14046     offset: 25,
14047     sliceOffset:0,
14048     labelOffset: 3,
14049     type: 'stacked',
14050     hoveredColor: '#9fd4ff',
14051     showLabels: true,
14052     resizeLabels: false,
14053     updateHeights: false
14054   };  
14055
14056   (end code)
14057   
14058   Example:
14059   
14060   (start code js)
14061
14062   var pie = new $jit.PieChart({
14063     animate: true,
14064     sliceOffset: 5,
14065     type: 'stacked:gradient'
14066   });  
14067
14068   (end code)
14069   
14070   Parameters:
14071   
14072   animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14073   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14074   sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14075   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14076   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14077   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14078   showLabels - (boolean) Default's *true*. Display the name of the slots.
14079   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.
14080   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.
14081
14082 */
14083 Options.PieChart = {
14084   $extend: true,
14085
14086   animate: true,
14087   offset: 25, // page offset
14088   sliceOffset:0,
14089   labelOffset: 3, // label offset
14090   type: 'stacked', // gradient
14091   labelType: 'name',
14092   hoveredColor: '#9fd4ff',
14093   Events: {
14094     enable: false,
14095     onClick: $.empty
14096   },
14097   Tips: {
14098     enable: false,
14099     onShow: $.empty,
14100     onHide: $.empty
14101   },
14102   showLabels: true,
14103   resizeLabels: false,
14104   
14105   //only valid for mono-valued datasets
14106   updateHeights: false
14107 };
14108
14109 /*
14110  * Class: Layouts.Radial
14111  * 
14112  * Implements a Radial Layout.
14113  * 
14114  * Implemented By:
14115  * 
14116  * <RGraph>, <Hypertree>
14117  * 
14118  */
14119 Layouts.Radial = new Class({
14120
14121   /*
14122    * Method: compute
14123    * 
14124    * Computes nodes' positions.
14125    * 
14126    * Parameters:
14127    * 
14128    * property - _optional_ A <Graph.Node> position property to store the new
14129    * positions. Possible values are 'pos', 'end' or 'start'.
14130    * 
14131    */
14132   compute : function(property) {
14133     var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14134     NodeDim.compute(this.graph, prop, this.config);
14135     this.graph.computeLevels(this.root, 0, "ignore");
14136     var lengthFunc = this.createLevelDistanceFunc(); 
14137     this.computeAngularWidths(prop);
14138     this.computePositions(prop, lengthFunc);
14139   },
14140
14141   /*
14142    * computePositions
14143    * 
14144    * Performs the main algorithm for computing node positions.
14145    */
14146   computePositions : function(property, getLength) {
14147     var propArray = property;
14148     var graph = this.graph;
14149     var root = graph.getNode(this.root);
14150     var parent = this.parent;
14151     var config = this.config;
14152
14153     for ( var i=0, l=propArray.length; i < l; i++) {
14154       var pi = propArray[i];
14155       root.setPos($P(0, 0), pi);
14156       root.setData('span', Math.PI * 2, pi);
14157     }
14158
14159     root.angleSpan = {
14160       begin : 0,
14161       end : 2 * Math.PI
14162     };
14163
14164     graph.eachBFS(this.root, function(elem) {
14165       var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14166       var angleInit = elem.angleSpan.begin;
14167       var len = getLength(elem);
14168       //Calculate the sum of all angular widths
14169       var totalAngularWidths = 0, subnodes = [], maxDim = {};
14170       elem.eachSubnode(function(sib) {
14171         totalAngularWidths += sib._treeAngularWidth;
14172         //get max dim
14173         for ( var i=0, l=propArray.length; i < l; i++) {
14174           var pi = propArray[i], dim = sib.getData('dim', pi);
14175           maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14176         }
14177         subnodes.push(sib);
14178       }, "ignore");
14179       //Maintain children order
14180       //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14181       if (parent && parent.id == elem.id && subnodes.length > 0
14182           && subnodes[0].dist) {
14183         subnodes.sort(function(a, b) {
14184           return (a.dist >= b.dist) - (a.dist <= b.dist);
14185         });
14186       }
14187       //Calculate nodes positions.
14188       for (var k = 0, ls=subnodes.length; k < ls; k++) {
14189         var child = subnodes[k];
14190         if (!child._flag) {
14191           var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14192           var theta = angleInit + angleProportion / 2;
14193
14194           for ( var i=0, l=propArray.length; i < l; i++) {
14195             var pi = propArray[i];
14196             child.setPos($P(theta, len), pi);
14197             child.setData('span', angleProportion, pi);
14198             child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14199           }
14200
14201           child.angleSpan = {
14202             begin : angleInit,
14203             end : angleInit + angleProportion
14204           };
14205           angleInit += angleProportion;
14206         }
14207       }
14208     }, "ignore");
14209   },
14210
14211   /*
14212    * Method: setAngularWidthForNodes
14213    * 
14214    * Sets nodes angular widths.
14215    */
14216   setAngularWidthForNodes : function(prop) {
14217     this.graph.eachBFS(this.root, function(elem, i) {
14218       var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14219       elem._angularWidth = diamValue / i;
14220     }, "ignore");
14221   },
14222
14223   /*
14224    * Method: setSubtreesAngularWidth
14225    * 
14226    * Sets subtrees angular widths.
14227    */
14228   setSubtreesAngularWidth : function() {
14229     var that = this;
14230     this.graph.eachNode(function(elem) {
14231       that.setSubtreeAngularWidth(elem);
14232     }, "ignore");
14233   },
14234
14235   /*
14236    * Method: setSubtreeAngularWidth
14237    * 
14238    * Sets the angular width for a subtree.
14239    */
14240   setSubtreeAngularWidth : function(elem) {
14241     var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14242     elem.eachSubnode(function(child) {
14243       that.setSubtreeAngularWidth(child);
14244       sumAW += child._treeAngularWidth;
14245     }, "ignore");
14246     elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14247   },
14248
14249   /*
14250    * Method: computeAngularWidths
14251    * 
14252    * Computes nodes and subtrees angular widths.
14253    */
14254   computeAngularWidths : function(prop) {
14255     this.setAngularWidthForNodes(prop);
14256     this.setSubtreesAngularWidth();
14257   }
14258
14259 });
14260
14261
14262 /*
14263  * File: Sunburst.js
14264  */
14265
14266 /*
14267    Class: Sunburst
14268       
14269    A radial space filling tree visualization.
14270    
14271    Inspired by:
14272  
14273    Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14274    
14275    Note:
14276    
14277    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.
14278    
14279   Implements:
14280   
14281   All <Loader> methods
14282   
14283    Constructor Options:
14284    
14285    Inherits options from
14286    
14287    - <Options.Canvas>
14288    - <Options.Controller>
14289    - <Options.Node>
14290    - <Options.Edge>
14291    - <Options.Label>
14292    - <Options.Events>
14293    - <Options.Tips>
14294    - <Options.NodeStyles>
14295    - <Options.Navigation>
14296    
14297    Additionally, there are other parameters and some default values changed
14298    
14299    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14300    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
14301    Node.type - Described in <Options.Node>. Default's to *multipie*.
14302    Node.height - Described in <Options.Node>. Default's *0*.
14303    Edge.type - Described in <Options.Edge>. Default's *none*.
14304    Label.textAlign - Described in <Options.Label>. Default's *start*.
14305    Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14306      
14307    Instance Properties:
14308
14309    canvas - Access a <Canvas> instance.
14310    graph - Access a <Graph> instance.
14311    op - Access a <Sunburst.Op> instance.
14312    fx - Access a <Sunburst.Plot> instance.
14313    labels - Access a <Sunburst.Label> interface implementation.   
14314
14315 */
14316
14317 $jit.Sunburst = new Class({
14318
14319   Implements: [ Loader, Extras, Layouts.Radial ],
14320
14321   initialize: function(controller) {
14322     var $Sunburst = $jit.Sunburst;
14323
14324     var config = {
14325       interpolation: 'linear',
14326       levelDistance: 100,
14327       Node: {
14328         'type': 'multipie',
14329         'height':0
14330       },
14331       Edge: {
14332         'type': 'none'
14333       },
14334       Label: {
14335         textAlign: 'start',
14336         textBaseline: 'middle'
14337       }
14338     };
14339
14340     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14341         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14342
14343     var canvasConfig = this.config;
14344     if(canvasConfig.useCanvas) {
14345       this.canvas = canvasConfig.useCanvas;
14346       this.config.labelContainer = this.canvas.id + '-label';
14347     } else {
14348       if(canvasConfig.background) {
14349         canvasConfig.background = $.merge({
14350           type: 'Fade',
14351           colorStop1: this.config.colorStop1,
14352           colorStop2: this.config.colorStop2
14353         }, canvasConfig.background);
14354       }
14355       this.canvas = new Canvas(this, canvasConfig);
14356       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14357     }
14358
14359     this.graphOptions = {
14360       'complex': false,
14361       'Node': {
14362         'selected': false,
14363         'exist': true,
14364         'drawn': true
14365       }
14366     };
14367     this.graph = new Graph(this.graphOptions, this.config.Node,
14368         this.config.Edge);
14369     this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14370     this.fx = new $Sunburst.Plot(this, $Sunburst);
14371     this.op = new $Sunburst.Op(this);
14372     this.json = null;
14373     this.root = null;
14374     this.rotated = null;
14375     this.busy = false;
14376     // initialize extras
14377     this.initializeExtras();
14378   },
14379
14380   /* 
14381   
14382     createLevelDistanceFunc 
14383   
14384     Returns the levelDistance function used for calculating a node distance 
14385     to its origin. This function returns a function that is computed 
14386     per level and not per node, such that all nodes with the same depth will have the 
14387     same distance to the origin. The resulting function gets the 
14388     parent node as parameter and returns a float.
14389
14390    */
14391   createLevelDistanceFunc: function() {
14392     var ld = this.config.levelDistance;
14393     return function(elem) {
14394       return (elem._depth + 1) * ld;
14395     };
14396   },
14397
14398   /* 
14399      Method: refresh 
14400      
14401      Computes positions and plots the tree.
14402
14403    */
14404   refresh: function() {
14405     this.compute();
14406     this.plot();
14407   },
14408
14409   /*
14410    reposition
14411   
14412    An alias for computing new positions to _endPos_
14413
14414    See also:
14415
14416    <Sunburst.compute>
14417    
14418   */
14419   reposition: function() {
14420     this.compute('end');
14421   },
14422
14423   /*
14424   Method: rotate
14425   
14426   Rotates the graph so that the selected node is horizontal on the right.
14427
14428   Parameters:
14429   
14430   node - (object) A <Graph.Node>.
14431   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14432   opt - (object) Configuration options merged with this visualization configuration options.
14433   
14434   See also:
14435
14436   <Sunburst.rotateAngle>
14437   
14438   */
14439   rotate: function(node, method, opt) {
14440     var theta = node.getPos(opt.property || 'current').getp(true).theta;
14441     this.rotated = node;
14442     this.rotateAngle(-theta, method, opt);
14443   },
14444
14445   /*
14446   Method: rotateAngle
14447   
14448   Rotates the graph of an angle theta.
14449   
14450    Parameters:
14451    
14452    node - (object) A <Graph.Node>.
14453    method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14454    opt - (object) Configuration options merged with this visualization configuration options.
14455    
14456    See also:
14457
14458    <Sunburst.rotate>
14459   
14460   */
14461   rotateAngle: function(theta, method, opt) {
14462     var that = this;
14463     var options = $.merge(this.config, opt || {}, {
14464       modes: [ 'polar' ]
14465     });
14466     var prop = opt.property || (method === "animate" ? 'end' : 'current');
14467     if(method === 'animate') {
14468       this.fx.animation.pause();
14469     }
14470     this.graph.eachNode(function(n) {
14471       var p = n.getPos(prop);
14472       p.theta += theta;
14473       if (p.theta < 0) {
14474         p.theta += Math.PI * 2;
14475       }
14476     });
14477     if (method == 'animate') {
14478       this.fx.animate(options);
14479     } else if (method == 'replot') {
14480       this.fx.plot();
14481       this.busy = false;
14482     }
14483   },
14484
14485   /*
14486    Method: plot
14487   
14488    Plots the Sunburst. This is a shortcut to *fx.plot*.
14489   */
14490   plot: function() {
14491     this.fx.plot();
14492   }
14493 });
14494
14495 $jit.Sunburst.$extend = true;
14496
14497 (function(Sunburst) {
14498
14499   /*
14500      Class: Sunburst.Op
14501
14502      Custom extension of <Graph.Op>.
14503
14504      Extends:
14505
14506      All <Graph.Op> methods
14507      
14508      See also:
14509      
14510      <Graph.Op>
14511
14512   */
14513   Sunburst.Op = new Class( {
14514
14515     Implements: Graph.Op
14516
14517   });
14518
14519   /*
14520      Class: Sunburst.Plot
14521
14522     Custom extension of <Graph.Plot>.
14523   
14524     Extends:
14525   
14526     All <Graph.Plot> methods
14527     
14528     See also:
14529     
14530     <Graph.Plot>
14531   
14532   */
14533   Sunburst.Plot = new Class( {
14534
14535     Implements: Graph.Plot
14536
14537   });
14538
14539   /*
14540     Class: Sunburst.Label
14541
14542     Custom extension of <Graph.Label>. 
14543     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14544   
14545     Extends:
14546   
14547     All <Graph.Label> methods and subclasses.
14548   
14549     See also:
14550   
14551     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14552   
14553    */
14554   Sunburst.Label = {};
14555
14556   /*
14557      Sunburst.Label.Native
14558
14559      Custom extension of <Graph.Label.Native>.
14560
14561      Extends:
14562
14563      All <Graph.Label.Native> methods
14564
14565      See also:
14566
14567      <Graph.Label.Native>
14568   */
14569   Sunburst.Label.Native = new Class( {
14570     Implements: Graph.Label.Native,
14571
14572     initialize: function(viz) {
14573       this.viz = viz;
14574       this.label = viz.config.Label;
14575       this.config = viz.config;
14576     },
14577
14578     renderLabel: function(canvas, node, controller) {
14579       var span = node.getData('span');
14580       if(span < Math.PI /2 && Math.tan(span) * 
14581           this.config.levelDistance * node._depth < 10) {
14582         return;
14583       }
14584       var ctx = canvas.getCtx();
14585       var measure = ctx.measureText(node.name);
14586       if (node.id == this.viz.root) {
14587         var x = -measure.width / 2, y = 0, thetap = 0;
14588         var ld = 0;
14589       } else {
14590         var indent = 5;
14591         var ld = controller.levelDistance - indent;
14592         var clone = node.pos.clone();
14593         clone.rho += indent;
14594         var p = clone.getp(true);
14595         var ct = clone.getc(true);
14596         var x = ct.x, y = ct.y;
14597         // get angle in degrees
14598         var pi = Math.PI;
14599         var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14600         var thetap = cond ? p.theta + pi : p.theta;
14601         if (cond) {
14602           x -= Math.abs(Math.cos(p.theta) * measure.width);
14603           y += Math.sin(p.theta) * measure.width;
14604         } else if (node.id == this.viz.root) {
14605           x -= measure.width / 2;
14606         }
14607       }
14608       ctx.save();
14609       ctx.translate(x, y);
14610       ctx.rotate(thetap);
14611       ctx.fillText(node.name, 0, 0);
14612       ctx.restore();
14613     }
14614   });
14615
14616   /*
14617      Sunburst.Label.SVG
14618
14619     Custom extension of <Graph.Label.SVG>.
14620   
14621     Extends:
14622   
14623     All <Graph.Label.SVG> methods
14624   
14625     See also:
14626   
14627     <Graph.Label.SVG>
14628   
14629   */
14630   Sunburst.Label.SVG = new Class( {
14631     Implements: Graph.Label.SVG,
14632
14633     initialize: function(viz) {
14634       this.viz = viz;
14635     },
14636
14637     /* 
14638        placeLabel
14639
14640        Overrides abstract method placeLabel in <Graph.Plot>.
14641
14642        Parameters:
14643
14644        tag - A DOM label element.
14645        node - A <Graph.Node>.
14646        controller - A configuration/controller object passed to the visualization.
14647       
14648      */
14649     placeLabel: function(tag, node, controller) {
14650       var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14651       var radius = canvas.getSize();
14652       var labelPos = {
14653         x: Math.round(pos.x + radius.width / 2),
14654         y: Math.round(pos.y + radius.height / 2)
14655       };
14656       tag.setAttribute('x', labelPos.x);
14657       tag.setAttribute('y', labelPos.y);
14658
14659       var bb = tag.getBBox();
14660       if (bb) {
14661         // center the label
14662     var x = tag.getAttribute('x');
14663     var y = tag.getAttribute('y');
14664     // get polar coordinates
14665     var p = node.pos.getp(true);
14666     // get angle in degrees
14667     var pi = Math.PI;
14668     var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14669     if (cond) {
14670       tag.setAttribute('x', x - bb.width);
14671       tag.setAttribute('y', y - bb.height);
14672     } else if (node.id == viz.root) {
14673       tag.setAttribute('x', x - bb.width / 2);
14674     }
14675
14676     var thetap = cond ? p.theta + pi : p.theta;
14677     if(node._depth)
14678       tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14679           + ' ' + y + ')');
14680   }
14681
14682   controller.onPlaceLabel(tag, node);
14683 }
14684   });
14685
14686   /*
14687      Sunburst.Label.HTML
14688
14689      Custom extension of <Graph.Label.HTML>.
14690
14691      Extends:
14692
14693      All <Graph.Label.HTML> methods.
14694
14695      See also:
14696
14697      <Graph.Label.HTML>
14698
14699   */
14700   Sunburst.Label.HTML = new Class( {
14701     Implements: Graph.Label.HTML,
14702
14703     initialize: function(viz) {
14704       this.viz = viz;
14705     },
14706     /* 
14707        placeLabel
14708
14709        Overrides abstract method placeLabel in <Graph.Plot>.
14710
14711        Parameters:
14712
14713        tag - A DOM label element.
14714        node - A <Graph.Node>.
14715        controller - A configuration/controller object passed to the visualization.
14716       
14717      */
14718     placeLabel: function(tag, node, controller) {
14719       var pos = node.pos.clone(), 
14720           canvas = this.viz.canvas,
14721           height = node.getData('height'),
14722           ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14723           radius = canvas.getSize();
14724       pos.rho += ldist;
14725       pos = pos.getc(true);
14726       
14727       var labelPos = {
14728         x: Math.round(pos.x + radius.width / 2),
14729         y: Math.round(pos.y + radius.height / 2)
14730       };
14731
14732       var style = tag.style;
14733       style.left = labelPos.x + 'px';
14734       style.top = labelPos.y + 'px';
14735       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14736
14737       controller.onPlaceLabel(tag, node);
14738     }
14739   });
14740
14741   /*
14742     Class: Sunburst.Plot.NodeTypes
14743
14744     This class contains a list of <Graph.Node> built-in types. 
14745     Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14746
14747     You can add your custom node types, customizing your visualization to the extreme.
14748
14749     Example:
14750
14751     (start code js)
14752       Sunburst.Plot.NodeTypes.implement({
14753         'mySpecialType': {
14754           'render': function(node, canvas) {
14755             //print your custom node to canvas
14756           },
14757           //optional
14758           'contains': function(node, pos) {
14759             //return true if pos is inside the node or false otherwise
14760           }
14761         }
14762       });
14763     (end code)
14764
14765   */
14766   Sunburst.Plot.NodeTypes = new Class( {
14767     'none': {
14768       'render': $.empty,
14769       'contains': $.lambda(false),
14770       'anglecontains': function(node, pos) {
14771         var span = node.getData('span') / 2, theta = node.pos.theta;
14772         var begin = theta - span, end = theta + span;
14773         if (begin < 0)
14774           begin += Math.PI * 2;
14775         var atan = Math.atan2(pos.y, pos.x);
14776         if (atan < 0)
14777           atan += Math.PI * 2;
14778         if (begin > end) {
14779           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14780         } else {
14781           return atan > begin && atan < end;
14782         }
14783       },
14784           'anglecontainsgauge': function(node, pos) {
14785         var span = node.getData('span') / 2, theta = node.pos.theta;
14786                 var config = node.getData('config');
14787         var ld = this.config.levelDistance;
14788                 var yOffset = pos.y-(ld/2);
14789                 var begin = ((theta - span)/2)+Math.PI,
14790         end = ((theta + span)/2)+Math.PI;
14791                 
14792         if (begin < 0)
14793           begin += Math.PI * 2;
14794         var atan = Math.atan2(yOffset, pos.x);
14795
14796                 
14797         if (atan < 0)
14798           atan += Math.PI * 2;
14799                   
14800                   
14801         if (begin > end) {
14802           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14803         } else {
14804           return atan > begin && atan < end;
14805         }
14806       }
14807     },
14808
14809     'pie': {
14810       'render': function(node, canvas) {
14811         var span = node.getData('span') / 2, theta = node.pos.theta;
14812         var begin = theta - span, end = theta + span;
14813         var polarNode = node.pos.getp(true);
14814         var polar = new Polar(polarNode.rho, begin);
14815         var p1coord = polar.getc(true);
14816         polar.theta = end;
14817         var p2coord = polar.getc(true);
14818
14819         var ctx = canvas.getCtx();
14820         ctx.beginPath();
14821         ctx.moveTo(0, 0);
14822         ctx.lineTo(p1coord.x, p1coord.y);
14823         ctx.moveTo(0, 0);
14824         ctx.lineTo(p2coord.x, p2coord.y);
14825         ctx.moveTo(0, 0);
14826         ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14827             false);
14828         ctx.fill();
14829       },
14830       'contains': function(node, pos) {
14831         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14832           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14833           var ld = this.config.levelDistance, d = node._depth;
14834           return (rho <= ld * d);
14835         }
14836         return false;
14837       }
14838     },
14839     'multipie': {
14840       'render': function(node, canvas) {
14841         var height = node.getData('height');
14842         var ldist = height? height : this.config.levelDistance;
14843         var span = node.getData('span') / 2, theta = node.pos.theta;
14844         var begin = theta - span, end = theta + span;
14845         var polarNode = node.pos.getp(true);
14846
14847         var polar = new Polar(polarNode.rho, begin);
14848         var p1coord = polar.getc(true);
14849
14850         polar.theta = end;
14851         var p2coord = polar.getc(true);
14852
14853         polar.rho += ldist;
14854         var p3coord = polar.getc(true);
14855
14856         polar.theta = begin;
14857         var p4coord = polar.getc(true);
14858
14859         var ctx = canvas.getCtx();
14860         ctx.moveTo(0, 0);
14861         ctx.beginPath();
14862         ctx.arc(0, 0, polarNode.rho, begin, end, false);
14863         ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
14864         ctx.moveTo(p1coord.x, p1coord.y);
14865         ctx.lineTo(p4coord.x, p4coord.y);
14866         ctx.moveTo(p2coord.x, p2coord.y);
14867         ctx.lineTo(p3coord.x, p3coord.y);
14868         ctx.fill();
14869
14870         if (node.collapsed) {
14871           ctx.save();
14872           ctx.lineWidth = 2;
14873           ctx.moveTo(0, 0);
14874           ctx.beginPath();
14875           ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
14876               true);
14877           ctx.stroke();
14878           ctx.restore();
14879         }
14880       },
14881       'contains': function(node, pos) {
14882         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14883           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14884           var height = node.getData('height');
14885           var ldist = height? height : this.config.levelDistance;
14886           var ld = this.config.levelDistance, d = node._depth;
14887           return (rho >= ld * d) && (rho <= (ld * d + ldist));
14888         }
14889         return false;
14890       }
14891     },
14892
14893     'gradient-multipie': {
14894       'render': function(node, canvas) {
14895         var ctx = canvas.getCtx();
14896         var height = node.getData('height');
14897         var ldist = height? height : this.config.levelDistance;
14898         var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
14899             0, 0, node.getPos().rho + ldist);
14900
14901         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
14902         $.each(colorArray, function(i) {
14903           ans.push(parseInt(i * 0.5, 10));
14904         });
14905         var endColor = $.rgbToHex(ans);
14906         radialGradient.addColorStop(0, endColor);
14907         radialGradient.addColorStop(1, node.getData('color'));
14908         ctx.fillStyle = radialGradient;
14909         this.nodeTypes['multipie'].render.call(this, node, canvas);
14910       },
14911       'contains': function(node, pos) {
14912         return this.nodeTypes['multipie'].contains.call(this, node, pos);
14913       }
14914     },
14915
14916     'gradient-pie': {
14917       'render': function(node, canvas) {
14918         var ctx = canvas.getCtx();
14919         var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
14920             .getPos().rho);
14921
14922         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
14923         $.each(colorArray, function(i) {
14924           ans.push(parseInt(i * 0.5, 10));
14925         });
14926         var endColor = $.rgbToHex(ans);
14927         radialGradient.addColorStop(1, endColor);
14928         radialGradient.addColorStop(0, node.getData('color'));
14929         ctx.fillStyle = radialGradient;
14930         this.nodeTypes['pie'].render.call(this, node, canvas);
14931       },
14932       'contains': function(node, pos) {
14933         return this.nodeTypes['pie'].contains.call(this, node, pos);
14934       }
14935     }
14936   });
14937
14938   /*
14939     Class: Sunburst.Plot.EdgeTypes
14940
14941     This class contains a list of <Graph.Adjacence> built-in types. 
14942     Edge types implemented are 'none', 'line' and 'arrow'.
14943   
14944     You can add your custom edge types, customizing your visualization to the extreme.
14945   
14946     Example:
14947   
14948     (start code js)
14949       Sunburst.Plot.EdgeTypes.implement({
14950         'mySpecialType': {
14951           'render': function(adj, canvas) {
14952             //print your custom edge to canvas
14953           },
14954           //optional
14955           'contains': function(adj, pos) {
14956             //return true if pos is inside the arc or false otherwise
14957           }
14958         }
14959       });
14960     (end code)
14961   
14962   */
14963   Sunburst.Plot.EdgeTypes = new Class({
14964     'none': $.empty,
14965     'line': {
14966       'render': function(adj, canvas) {
14967         var from = adj.nodeFrom.pos.getc(true),
14968             to = adj.nodeTo.pos.getc(true);
14969         this.edgeHelper.line.render(from, to, canvas);
14970       },
14971       'contains': function(adj, pos) {
14972         var from = adj.nodeFrom.pos.getc(true),
14973             to = adj.nodeTo.pos.getc(true);
14974         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
14975       }
14976     },
14977     'arrow': {
14978       'render': function(adj, canvas) {
14979         var from = adj.nodeFrom.pos.getc(true),
14980             to = adj.nodeTo.pos.getc(true),
14981             dim = adj.getData('dim'),
14982             direction = adj.data.$direction,
14983             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
14984         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
14985       },
14986       'contains': function(adj, pos) {
14987         var from = adj.nodeFrom.pos.getc(true),
14988             to = adj.nodeTo.pos.getc(true);
14989         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
14990       }
14991     },
14992     'hyperline': {
14993       'render': function(adj, canvas) {
14994         var from = adj.nodeFrom.pos.getc(),
14995             to = adj.nodeTo.pos.getc(),
14996             dim = Math.max(from.norm(), to.norm());
14997         this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
14998       },
14999       'contains': $.lambda(false) //TODO(nico): Implement this!
15000     }
15001   });
15002
15003 })($jit.Sunburst);
15004
15005
15006 /*
15007  * File: PieChart.js
15008  *
15009 */
15010
15011 $jit.Sunburst.Plot.NodeTypes.implement({
15012   'piechart-stacked' : {
15013     'render' : function(node, canvas) {
15014       var pos = node.pos.getp(true),
15015           dimArray = node.getData('dimArray'),
15016           valueArray = node.getData('valueArray'),
15017           colorArray = node.getData('colorArray'),
15018           colorLength = colorArray.length,
15019           stringArray = node.getData('stringArray'),
15020           span = node.getData('span') / 2,
15021           theta = node.pos.theta,
15022           begin = theta - span,
15023           end = theta + span,
15024           polar = new Polar;
15025     
15026       var ctx = canvas.getCtx(), 
15027           opt = {},
15028           gradient = node.getData('gradient'),
15029           border = node.getData('border'),
15030           config = node.getData('config'),
15031           showLabels = config.showLabels,
15032           resizeLabels = config.resizeLabels,
15033           label = config.Label;
15034
15035       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15036       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15037
15038       if (colorArray && dimArray && stringArray) {
15039         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15040           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15041           if(dimi <= 0) continue;
15042           ctx.fillStyle = ctx.strokeStyle = colori;
15043           if(gradient && dimi) {
15044             var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15045                 xpos, ypos, acum + dimi + config.sliceOffset);
15046             var colorRgb = $.hexToRgb(colori), 
15047                 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15048                 endColor = $.rgbToHex(ans);
15049
15050             radialGradient.addColorStop(0, colori);
15051             radialGradient.addColorStop(0.5, colori);
15052             radialGradient.addColorStop(1, endColor);
15053             ctx.fillStyle = radialGradient;
15054           }
15055           
15056           polar.rho = acum + config.sliceOffset;
15057           polar.theta = begin;
15058           var p1coord = polar.getc(true);
15059           polar.theta = end;
15060           var p2coord = polar.getc(true);
15061           polar.rho += dimi;
15062           var p3coord = polar.getc(true);
15063           polar.theta = begin;
15064           var p4coord = polar.getc(true);
15065
15066           ctx.beginPath();
15067           //fixing FF arc method + fill
15068           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15069           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15070           ctx.fill();
15071           if(border && border.name == stringArray[i]) {
15072             opt.acum = acum;
15073             opt.dimValue = dimArray[i];
15074             opt.begin = begin;
15075             opt.end = end;
15076           }
15077           acum += (dimi || 0);
15078           valAcum += (valueArray[i] || 0);
15079         }
15080         if(border) {
15081           ctx.save();
15082           ctx.globalCompositeOperation = "source-over";
15083           ctx.lineWidth = 2;
15084           ctx.strokeStyle = border.color;
15085           var s = begin < end? 1 : -1;
15086           ctx.beginPath();
15087           //fixing FF arc method + fill
15088           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15089           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15090           ctx.closePath();
15091           ctx.stroke();
15092           ctx.restore();
15093         }
15094         if(showLabels && label.type == 'Native') {
15095           ctx.save();
15096           ctx.fillStyle = ctx.strokeStyle = label.color;
15097           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15098               fontSize = (label.size * scale) >> 0;
15099           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15100           
15101           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15102           ctx.textBaseline = 'middle';
15103           ctx.textAlign = 'center';
15104           
15105           polar.rho = acum + config.labelOffset + config.sliceOffset;
15106           polar.theta = node.pos.theta;
15107           var cart = polar.getc(true);
15108           
15109           ctx.fillText(node.name, cart.x, cart.y);
15110           ctx.restore();
15111         }
15112       }
15113     },
15114     'contains': function(node, pos) {
15115       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15116         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15117         var ld = this.config.levelDistance, d = node._depth;
15118         var config = node.getData('config');
15119         if(rho <=ld * d + config.sliceOffset) {
15120           var dimArray = node.getData('dimArray');
15121           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15122             var dimi = dimArray[i];
15123             if(rho >= acum && rho <= acum + dimi) {
15124               return {
15125                 name: node.getData('stringArray')[i],
15126                 color: node.getData('colorArray')[i],
15127                 value: node.getData('valueArray')[i],
15128                 label: node.name
15129               };
15130             }
15131             acum += dimi;
15132           }
15133         }
15134         return false;
15135         
15136       }
15137       return false;
15138     }
15139   },
15140     'piechart-basic' : {
15141     'render' : function(node, canvas) {
15142       var pos = node.pos.getp(true),
15143           dimArray = node.getData('dimArray'),
15144           valueArray = node.getData('valueArray'),
15145           colorArray = node.getData('colorMono'),
15146           colorLength = colorArray.length,
15147           stringArray = node.getData('stringArray'),
15148                   percentage = node.getData('percentage'),
15149           span = node.getData('span') / 2,
15150           theta = node.pos.theta,
15151           begin = theta - span,
15152           end = theta + span,
15153           polar = new Polar;
15154     
15155       var ctx = canvas.getCtx(), 
15156           opt = {},
15157           gradient = node.getData('gradient'),
15158           border = node.getData('border'),
15159           config = node.getData('config'),
15160           showLabels = config.showLabels,
15161           resizeLabels = config.resizeLabels,
15162           label = config.Label;
15163
15164       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15165       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15166
15167       if (colorArray && dimArray && stringArray) {
15168         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15169           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15170           if(dimi <= 0) continue;
15171           ctx.fillStyle = ctx.strokeStyle = colori;
15172           
15173           polar.rho = acum + config.sliceOffset;
15174           polar.theta = begin;
15175           var p1coord = polar.getc(true);
15176           polar.theta = end;
15177           var p2coord = polar.getc(true);
15178           polar.rho += dimi;
15179           var p3coord = polar.getc(true);
15180           polar.theta = begin;
15181           var p4coord = polar.getc(true);
15182           
15183           if(typeof FlashCanvas == "undefined") {
15184                   //drop shadow
15185                   ctx.beginPath();
15186                   ctx.fillStyle = "rgba(0,0,0,.2)";
15187                   ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15188                   ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);    
15189                   ctx.fill();
15190                   
15191                   if(gradient && dimi) {
15192                     var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15193                         xpos, ypos, acum + dimi + config.sliceOffset);
15194                     var colorRgb = $.hexToRgb(colori), 
15195                         endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15196                         endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15197         
15198                     radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15199                     radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15200                                 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15201                     radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15202                     ctx.fillStyle = radialGradient;
15203                   }
15204           }
15205
15206           
15207           //fixing FF arc method + fill
15208           ctx.beginPath();
15209           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15210           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15211           ctx.fill();
15212           if(border && border.name == stringArray[i]) {
15213             opt.acum = acum;
15214             opt.dimValue = dimArray[i];
15215             opt.begin = begin;
15216             opt.end = end;
15217             opt.sliceValue = valueArray[i];
15218           }
15219           acum += (dimi || 0);
15220           valAcum += (valueArray[i] || 0);
15221         }
15222         if(border) {
15223           ctx.save();
15224           ctx.globalCompositeOperation = "source-over";
15225           ctx.lineWidth = 2;
15226           ctx.strokeStyle = border.color;
15227           var s = begin < end? 1 : -1;
15228           ctx.beginPath();
15229           //fixing FF arc method + fill
15230           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15231           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15232           ctx.closePath();
15233           ctx.stroke();
15234           ctx.restore();
15235         }
15236         if(showLabels && label.type == 'Native') {
15237           ctx.save();
15238           ctx.fillStyle = ctx.strokeStyle = label.color;
15239           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15240               fontSize = (label.size * scale) >> 0;
15241           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15242           
15243           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15244           ctx.textBaseline = 'middle';
15245           ctx.textAlign = 'center';
15246           pi = Math.PI;
15247           angle = theta * 360 / (2 * pi);
15248           polar.rho = acum + config.labelOffset + config.sliceOffset;
15249           polar.theta = node.pos.theta;
15250           var cart = polar.getc(true);
15251           if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15252                 
15253                 } else {
15254                   if(config.labelType == 'name') {
15255                                 ctx.fillText(node.name, cart.x, cart.y);
15256                           } else {
15257                                 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15258                           }
15259           }
15260           ctx.restore();
15261         }
15262       }
15263     },
15264     'contains': function(node, pos) {
15265       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15266         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15267         var ld = this.config.levelDistance, d = node._depth;
15268         var config = node.getData('config');
15269
15270         if(rho <=ld * d + config.sliceOffset) {
15271           var dimArray = node.getData('dimArray');
15272           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15273             var dimi = dimArray[i];
15274             if(rho >= acum && rho <= acum + dimi) {
15275                           var url = Url.decode(node.getData('linkArray')[i]);
15276               return {
15277                 name: node.getData('stringArray')[i],
15278                 link: url,
15279                 color: node.getData('colorArray')[i],
15280                 value: node.getData('valueArray')[i],
15281                 percentage: node.getData('percentage'),
15282                 valuelabel: node.getData('valuelabelsArray')[i],
15283                 label: node.name
15284               };
15285             }
15286             acum += dimi;
15287           }
15288         }
15289         return false;
15290         
15291       }
15292       return false;
15293     }
15294   }
15295 });
15296
15297 /*
15298   Class: PieChart
15299   
15300   A visualization that displays stacked bar charts.
15301   
15302   Constructor Options:
15303   
15304   See <Options.PieChart>.
15305
15306 */
15307 $jit.PieChart = new Class({
15308   sb: null,
15309   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15310   selected: {},
15311   busy: false,
15312   
15313   initialize: function(opt) {
15314     this.controller = this.config = 
15315       $.merge(Options("Canvas", "PieChart", "Label"), {
15316         Label: { type: 'Native' }
15317       }, opt);
15318     this.initializeViz();
15319   },
15320   
15321   initializeViz: function() {
15322     var config = this.config, that = this;
15323     var nodeType = config.type.split(":")[0];
15324     var sb = new $jit.Sunburst({
15325       injectInto: config.injectInto,
15326       useCanvas: config.useCanvas,
15327       withLabels: config.Label.type != 'Native',
15328       background: config.background,
15329       renderBackground: config.renderBackground,
15330       backgroundColor: config.backgroundColor,
15331       colorStop1: config.colorStop1,
15332       colorStop2: config.colorStop2,
15333       Label: {
15334         type: config.Label.type
15335       },
15336       Node: {
15337         overridable: true,
15338         type: 'piechart-' + nodeType,
15339         width: 1,
15340         height: 1
15341       },
15342       Edge: {
15343         type: 'none'
15344       },
15345       Tips: {
15346         enable: config.Tips.enable,
15347         type: 'Native',
15348         force: true,
15349         onShow: function(tip, node, contains) {
15350           var elem = contains;
15351           config.Tips.onShow(tip, elem, node);
15352                           if(elem.link != 'undefined' && elem.link != '') {
15353                                 document.body.style.cursor = 'pointer';
15354                           }
15355         },
15356                 onHide: function() {
15357                                 document.body.style.cursor = 'default';
15358         }
15359       },
15360       Events: {
15361         enable: true,
15362         type: 'Native',
15363         onClick: function(node, eventInfo, evt) {
15364           if(!config.Events.enable) return;
15365           var elem = eventInfo.getContains();
15366           config.Events.onClick(elem, eventInfo, evt);
15367         },
15368         onMouseMove: function(node, eventInfo, evt) {
15369           if(!config.hoveredColor) return;
15370           if(node) {
15371             var elem = eventInfo.getContains();
15372             that.select(node.id, elem.name, elem.index);
15373           } else {
15374             that.select(false, false, false);
15375           }
15376         }
15377       },
15378       onCreateLabel: function(domElement, node) {
15379         var labelConf = config.Label;
15380         if(config.showLabels) {
15381           var style = domElement.style;
15382           style.fontSize = labelConf.size + 'px';
15383           style.fontFamily = labelConf.family;
15384           style.color = labelConf.color;
15385           style.textAlign = 'center';
15386           if(config.labelType == 'name') {
15387                 domElement.innerHTML = node.name;
15388           } else {
15389                 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15390           }
15391           domElement.style.width = '400px';
15392         }
15393       },
15394       onPlaceLabel: function(domElement, node) {
15395         if(!config.showLabels) return;
15396         var pos = node.pos.getp(true),
15397             dimArray = node.getData('dimArray'),
15398             span = node.getData('span') / 2,
15399             theta = node.pos.theta,
15400             begin = theta - span,
15401             end = theta + span,
15402             polar = new Polar;
15403
15404         var showLabels = config.showLabels,
15405             resizeLabels = config.resizeLabels,
15406             label = config.Label;
15407         
15408         if (dimArray) {
15409           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15410             acum += dimArray[i];
15411           }
15412           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15413               fontSize = (label.size * scale) >> 0;
15414           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15415           domElement.style.fontSize = fontSize + 'px';
15416           polar.rho = acum + config.labelOffset + config.sliceOffset;
15417           polar.theta = (begin + end) / 2;
15418           var pos = polar.getc(true);
15419           var radius = that.canvas.getSize();
15420           var labelPos = {
15421             x: Math.round(pos.x + radius.width / 2),
15422             y: Math.round(pos.y + radius.height / 2)
15423           };
15424           domElement.style.left = (labelPos.x - 200) + 'px';
15425           domElement.style.top = labelPos.y + 'px';
15426         }
15427       }
15428     });
15429     
15430     var size = sb.canvas.getSize(),
15431         min = Math.min;
15432     sb.config.levelDistance = min(size.width, size.height)/2 
15433       - config.offset - config.sliceOffset;
15434     this.sb = sb;
15435     this.canvas = this.sb.canvas;
15436     this.canvas.getCtx().globalCompositeOperation = 'lighter';
15437   },
15438     renderBackground: function() {
15439                 var canvas = this.canvas,
15440                 config = this.config,
15441                 backgroundColor = config.backgroundColor,
15442                 size = canvas.getSize(),
15443                 ctx = canvas.getCtx();
15444                 ctx.globalCompositeOperation = "destination-over";
15445             ctx.fillStyle = backgroundColor;
15446             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
15447   },
15448   /*
15449     Method: loadJSON
15450    
15451     Loads JSON data into the visualization. 
15452     
15453     Parameters:
15454     
15455     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>.
15456     
15457     Example:
15458     (start code js)
15459     var pieChart = new $jit.PieChart(options);
15460     pieChart.loadJSON(json);
15461     (end code)
15462   */  
15463   loadJSON: function(json) {
15464     var prefix = $.time(), 
15465         ch = [], 
15466         sb = this.sb,
15467         name = $.splat(json.label),
15468         nameLength = name.length,
15469         color = $.splat(json.color || this.colors),
15470         colorLength = color.length,
15471         config = this.config,
15472         renderBackground = config.renderBackground,
15473         gradient = !!config.type.split(":")[1],
15474         animate = config.animate,
15475         mono = nameLength == 1;
15476         totalValue = 0;
15477     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15478         var val = values[i];
15479         var valArray = $.splat(val.values);
15480         totalValue += parseInt(valArray.sum());
15481     }
15482
15483     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15484       var val = values[i];
15485       var valArray = $.splat(val.values);
15486           var percentage = (valArray.sum()/totalValue) * 100;
15487
15488       var linkArray = $.splat(val.links);
15489       var valuelabelsArray = $.splat(val.valuelabels);
15490       
15491  
15492       ch.push({
15493         'id': prefix + val.label,
15494         'name': val.label,
15495         'data': {
15496           'value': valArray,
15497           'valuelabel': valuelabelsArray,
15498           '$linkArray': linkArray,
15499           '$valuelabelsArray': valuelabelsArray,
15500           '$valueArray': valArray,
15501           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15502           '$colorMono': $.splat(color[i % colorLength]),
15503           '$stringArray': name,
15504           '$gradient': gradient,
15505           '$config': config,
15506           '$percentage': percentage.toFixed(1),
15507           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15508         },
15509         'children': []
15510       });
15511     }
15512     var root = {
15513       'id': prefix + '$root',
15514       'name': '',
15515       'data': {
15516         '$type': 'none',
15517         '$width': 1,
15518         '$height': 1
15519       },
15520       'children': ch
15521     };
15522     sb.loadJSON(root);
15523     
15524     
15525
15526     
15527     this.normalizeDims();
15528
15529     
15530     sb.refresh();
15531     
15532         if(renderBackground) {
15533         this.renderBackground();        
15534     }
15535     
15536     
15537     if(animate) {
15538       sb.fx.animate({
15539         modes: ['node-property:dimArray'],
15540         duration:1500
15541       });
15542     }
15543   },
15544   
15545   /*
15546     Method: updateJSON
15547    
15548     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.
15549     
15550     Parameters:
15551     
15552     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15553     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15554     
15555     Example:
15556     
15557     (start code js)
15558     pieChart.updateJSON(json, {
15559       onComplete: function() {
15560         alert('update complete!');
15561       }
15562     });
15563     (end code)
15564   */  
15565   updateJSON: function(json, onComplete) {
15566     if(this.busy) return;
15567     this.busy = true;
15568     
15569     var sb = this.sb;
15570     var graph = sb.graph;
15571     var values = json.values;
15572     var animate = this.config.animate;
15573     var that = this;
15574     $.each(values, function(v) {
15575       var n = graph.getByName(v.label),
15576           vals = $.splat(v.values);
15577       if(n) {
15578         n.setData('valueArray', vals);
15579         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15580         if(json.label) {
15581           n.setData('stringArray', $.splat(json.label));
15582         }
15583       }
15584     });
15585     this.normalizeDims();
15586     if(animate) {
15587       sb.compute('end');
15588       sb.fx.animate({
15589         modes: ['node-property:dimArray:span', 'linear'],
15590         duration:1500,
15591         onComplete: function() {
15592           that.busy = false;
15593           onComplete && onComplete.onComplete();
15594         }
15595       });
15596     } else {
15597       sb.refresh();
15598     }
15599   },
15600     
15601   //adds the little brown bar when hovering the node
15602   select: function(id, name) {
15603     if(!this.config.hoveredColor) return;
15604     var s = this.selected;
15605     if(s.id != id || s.name != name) {
15606       s.id = id;
15607       s.name = name;
15608       s.color = this.config.hoveredColor;
15609       this.sb.graph.eachNode(function(n) {
15610         if(id == n.id) {
15611           n.setData('border', s);
15612         } else {
15613           n.setData('border', false);
15614         }
15615       });
15616       this.sb.plot();
15617     }
15618   },
15619   
15620   /*
15621     Method: getLegend
15622    
15623     Returns an object containing as keys the legend names and as values hex strings with color values.
15624     
15625     Example:
15626     
15627     (start code js)
15628     var legend = pieChart.getLegend();
15629     (end code)
15630   */  
15631   getLegend: function() {
15632     var legend = new Array();
15633     var name = new Array();
15634     var color = new Array();
15635     var n;
15636     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15637       n = adj.nodeTo;
15638     });
15639     var colors = n.getData('colorArray'),
15640         len = colors.length;
15641     $.each(n.getData('stringArray'), function(s, i) {
15642       color[i] = colors[i % len];
15643       name[i] = s;
15644     });
15645         legend['name'] = name;
15646         legend['color'] = color;
15647     return legend;
15648   },
15649   
15650   /*
15651     Method: getMaxValue
15652    
15653     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15654     
15655     Example:
15656     
15657     (start code js)
15658     var ans = pieChart.getMaxValue();
15659     (end code)
15660     
15661     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15662     
15663     Example:
15664     
15665     (start code js)
15666     //will return 100 for all PieChart instances,
15667     //displaying all of them with the same scale
15668     $jit.PieChart.implement({
15669       'getMaxValue': function() {
15670         return 100;
15671       }
15672     });
15673     (end code)
15674     
15675   */  
15676   getMaxValue: function() {
15677     var maxValue = 0;
15678     this.sb.graph.eachNode(function(n) {
15679       var valArray = n.getData('valueArray'),
15680           acum = 0;
15681       $.each(valArray, function(v) { 
15682         acum += +v;
15683       });
15684       maxValue = maxValue>acum? maxValue:acum;
15685     });
15686     return maxValue;
15687   },
15688   
15689   normalizeDims: function() {
15690     //number of elements
15691     var root = this.sb.graph.getNode(this.sb.root), l=0;
15692     root.eachAdjacency(function() {
15693       l++;
15694     });
15695     var maxValue = this.getMaxValue() || 1,
15696         config = this.config,
15697         animate = config.animate,
15698         rho = this.sb.config.levelDistance;
15699     this.sb.graph.eachNode(function(n) {
15700       var acum = 0, animateValue = [];
15701       $.each(n.getData('valueArray'), function(v) {
15702         acum += +v;
15703         animateValue.push(1);
15704       });
15705       var stat = (animateValue.length == 1) && !config.updateHeights;
15706       if(animate) {
15707         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15708           return stat? rho: (n * rho / maxValue); 
15709         }), 'end');
15710         var dimArray = n.getData('dimArray');
15711         if(!dimArray) {
15712           n.setData('dimArray', animateValue);
15713         }
15714       } else {
15715         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15716           return stat? rho : (n * rho / maxValue); 
15717         }));
15718       }
15719       n.setData('normalizedDim', acum / maxValue);
15720     });
15721   }
15722 });
15723
15724
15725 //Gauge Chart
15726
15727 Options.GaugeChart = {
15728   $extend: true,
15729
15730   animate: true,
15731   offset: 25, // page offset
15732   sliceOffset:0,
15733   labelOffset: 3, // label offset
15734   type: 'stacked', // gradient
15735   labelType: 'name',
15736   hoveredColor: '#9fd4ff',
15737   Events: {
15738     enable: false,
15739     onClick: $.empty
15740   },
15741   Tips: {
15742     enable: false,
15743     onShow: $.empty,
15744     onHide: $.empty
15745   },
15746   showLabels: true,
15747   resizeLabels: false,
15748   
15749   //only valid for mono-valued datasets
15750   updateHeights: false
15751 };
15752
15753
15754
15755 $jit.Sunburst.Plot.NodeTypes.implement({
15756     'gaugechart-basic' : {
15757     'render' : function(node, canvas) {
15758       var pos = node.pos.getp(true),
15759           dimArray = node.getData('dimArray'),
15760           valueArray = node.getData('valueArray'),
15761           valuelabelsArray = node.getData('valuelabelsArray'),
15762           gaugeTarget = node.getData('gaugeTarget'),
15763           nodeIteration = node.getData('nodeIteration'),
15764           nodeLength = node.getData('nodeLength'),
15765           colorArray = node.getData('colorMono'),
15766           colorLength = colorArray.length,
15767           stringArray = node.getData('stringArray'),
15768           span = node.getData('span') / 2,
15769           theta = node.pos.theta,
15770           begin = ((theta - span)/2)+Math.PI,
15771           end = ((theta + span)/2)+Math.PI,
15772           polar = new Polar;
15773
15774   
15775       var ctx = canvas.getCtx(), 
15776           opt = {},
15777           gradient = node.getData('gradient'),
15778           border = node.getData('border'),
15779           config = node.getData('config'),
15780           showLabels = config.showLabels,
15781           resizeLabels = config.resizeLabels,
15782           label = config.Label;
15783
15784       var xpos = Math.cos((begin + end) /2);
15785       var ypos = Math.sin((begin + end) /2);
15786
15787       if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
15788         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15789           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15790           if(dimi <= 0) continue;
15791           ctx.fillStyle = ctx.strokeStyle = colori;
15792           if(gradient && dimi) {
15793             var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
15794                 xpos, (ypos + dimi/2), acum + dimi);
15795             var colorRgb = $.hexToRgb(colori), 
15796                 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
15797                 endColor = $.rgbToHex(ans);
15798
15799             radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15800             radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
15801             radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
15802             radialGradient.addColorStop(1,  'rgba('+ans+',1)');
15803             ctx.fillStyle = radialGradient;
15804           }
15805           
15806           polar.rho = acum;
15807           polar.theta = begin;
15808           var p1coord = polar.getc(true);
15809           polar.theta = end;
15810           var p2coord = polar.getc(true);
15811           polar.rho += dimi;
15812           var p3coord = polar.getc(true);
15813           polar.theta = begin;
15814           var p4coord = polar.getc(true);
15815
15816                   
15817           ctx.beginPath();
15818           //fixing FF arc method + fill
15819           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
15820           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
15821           ctx.fill();
15822                   
15823
15824           acum += (dimi || 0);
15825           valAcum += (valueArray[i] || 0);                
15826         }
15827                 
15828                 if(showLabels && label.type == 'Native') {
15829                           ctx.save();
15830                           ctx.fillStyle = ctx.strokeStyle = label.color;
15831
15832                           
15833                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
15834                           ctx.textBaseline = 'bottom';
15835                           ctx.textAlign = 'center';
15836
15837                           polar.rho = acum * .65;
15838                           polar.theta = begin;
15839                           var cart = polar.getc(true);
15840                           
15841                           //changes y pos of first label
15842                           if(nodeIteration == 1) {
15843                                 textY = cart.y - (label.size/2) + acum /2;
15844                           } else {
15845                                 textY = cart.y + acum/2;
15846                           }
15847                           
15848                           if(config.labelType == 'name') {
15849                                 ctx.fillText(node.name, cart.x, textY);
15850                           } else {
15851                                 ctx.fillText(valuelabelsArray[0], cart.x, textY);
15852                           }
15853                           
15854                           //adds final label
15855                           if(nodeIteration == nodeLength) {
15856                                 polar.theta = end;
15857                                 var cart = polar.getc(true);
15858                                 if(config.labelType == 'name') {
15859                                         ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
15860                                 } else {
15861                                         ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
15862                                 }
15863                                 
15864                           }
15865                           ctx.restore();
15866                 }
15867
15868       }
15869       },
15870     'contains': function(node, pos) {
15871                 
15872                 
15873                 
15874       if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
15875                 var config = node.getData('config');
15876         var ld = this.config.levelDistance , d = node._depth;
15877                 var yOffset = pos.y - (ld/2);
15878                 var xOffset = pos.x;
15879         var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
15880         if(rho <=parseInt(ld * d)) {
15881           var dimArray = node.getData('dimArray');
15882           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15883                         var dimi = dimArray[i];
15884             if(rho >= ld * .8 && rho <= acum + dimi) {
15885                 
15886                           var url = Url.decode(node.getData('linkArray')[i]);
15887               return {
15888                 name: node.getData('stringArray')[i],
15889                 link: url,
15890                 color: node.getData('colorArray')[i],
15891                 value: node.getData('valueArray')[i],
15892                 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
15893                 label: node.name
15894               };
15895             }
15896             acum += dimi;
15897                         
15898                         
15899           }
15900         }
15901         return false;
15902         
15903       }
15904       return false;
15905     }
15906   }
15907 });
15908
15909 /*
15910   Class: GaugeChart
15911   
15912   A visualization that displays gauge charts
15913   
15914   Constructor Options:
15915   
15916   See <Options.Gauge>.
15917
15918 */
15919 $jit.GaugeChart = new Class({
15920   sb: null,
15921   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15922   selected: {},
15923   busy: false,
15924   
15925   initialize: function(opt) {
15926     this.controller = this.config = 
15927       $.merge(Options("Canvas", "GaugeChart", "Label"), {
15928         Label: { type: 'Native' }
15929       }, opt);
15930     this.initializeViz();
15931   },
15932   
15933   initializeViz: function() {
15934     var config = this.config, that = this;
15935     var nodeType = config.type.split(":")[0];
15936     var sb = new $jit.Sunburst({
15937       injectInto: config.injectInto,
15938       useCanvas: config.useCanvas,
15939       withLabels: config.Label.type != 'Native',
15940       background: config.background,
15941       renderBackground: config.renderBackground,
15942       backgroundColor: config.backgroundColor,
15943       colorStop1: config.colorStop1,
15944       colorStop2: config.colorStop2,
15945       Label: {
15946         type: config.Label.type
15947       },
15948       Node: {
15949         overridable: true,
15950         type: 'gaugechart-' + nodeType,
15951         width: 1,
15952         height: 1
15953       },
15954       Edge: {
15955         type: 'none'
15956       },
15957       Tips: {
15958         enable: config.Tips.enable,
15959         type: 'Native',
15960         force: true,
15961         onShow: function(tip, node, contains) {
15962           var elem = contains;
15963           config.Tips.onShow(tip, elem, node);
15964                           if(elem.link != 'undefined' && elem.link != '') {
15965                                 document.body.style.cursor = 'pointer';
15966                           }
15967         },
15968                 onHide: function() {
15969                                 document.body.style.cursor = 'default';
15970         }
15971       },
15972       Events: {
15973         enable: true,
15974         type: 'Native',
15975         onClick: function(node, eventInfo, evt) {
15976           if(!config.Events.enable) return;
15977           var elem = eventInfo.getContains();
15978           config.Events.onClick(elem, eventInfo, evt);
15979         }
15980         },
15981       onCreateLabel: function(domElement, node) {
15982         var labelConf = config.Label;
15983         if(config.showLabels) {
15984           var style = domElement.style;
15985           style.fontSize = labelConf.size + 'px';
15986           style.fontFamily = labelConf.family;
15987           style.color = labelConf.color;
15988           style.textAlign = 'center';
15989           valuelabelsArray = node.getData('valuelabelsArray'),
15990           nodeIteration = node.getData('nodeIteration'),
15991           nodeLength = node.getData('nodeLength'),
15992           canvas = sb.canvas,
15993           prefix = $.time();
15994           
15995           if(config.labelType == 'name') {
15996                 domElement.innerHTML = node.name;
15997           } else {
15998                 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
15999           }
16000           
16001           domElement.style.width = '400px';
16002           
16003           //adds final label
16004                   if(nodeIteration == nodeLength && nodeLength != 0) {
16005                   idLabel = canvas.id + "-label";
16006                   container = document.getElementById(idLabel);
16007                   finalLabel = document.createElement('div');
16008                   finalLabelStyle = finalLabel.style;
16009                   finalLabel.id = prefix + "finalLabel";
16010                   finalLabelStyle.position = "absolute";
16011                   finalLabelStyle.width = "400px";
16012                   finalLabelStyle.left = "0px";
16013                   container.appendChild(finalLabel);
16014                         if(config.labelType == 'name') {
16015                                 finalLabel.innerHTML = node.name;
16016                         } else {
16017                                 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16018                         }
16019                         
16020                   }
16021         }
16022       },
16023       onPlaceLabel: function(domElement, node) {
16024         if(!config.showLabels) return;
16025         var pos = node.pos.getp(true),
16026             dimArray = node.getData('dimArray'),
16027             nodeIteration = node.getData('nodeIteration'),
16028             nodeLength = node.getData('nodeLength'),
16029             span = node.getData('span') / 2,
16030             theta = node.pos.theta,
16031             begin = ((theta - span)/2)+Math.PI,
16032             end = ((theta + span)/2)+Math.PI,
16033             polar = new Polar;
16034
16035         var showLabels = config.showLabels,
16036             resizeLabels = config.resizeLabels,
16037             label = config.Label,
16038             radiusOffset = sb.config.levelDistance;
16039
16040         if (dimArray) {
16041           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16042             acum += dimArray[i];
16043           }
16044           var scale = resizeLabels? node.getData('normalizedDim') : 1,
16045               fontSize = (label.size * scale) >> 0;
16046           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16047           domElement.style.fontSize = fontSize + 'px';
16048           polar.rho = acum * .65;
16049           polar.theta = begin;
16050           var pos = polar.getc(true);
16051           var radius = that.canvas.getSize();
16052           var labelPos = {
16053             x: Math.round(pos.x + radius.width / 2),
16054             y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16055           };
16056           
16057
16058           
16059           domElement.style.left = (labelPos.x - 200) + 'px';
16060           domElement.style.top = labelPos.y + 'px';
16061           
16062           //reposition first label
16063           if(nodeIteration == 1) {
16064                  domElement.style.top = labelPos.y - label.size + 'px';
16065           }
16066           
16067           
16068           //position final label
16069                 if(nodeIteration == nodeLength && nodeLength != 0) {
16070                         polar.theta = end;
16071                         var final = polar.getc(true);
16072                         var finalPos = {
16073                                         x: Math.round(final.x + radius.width / 2),
16074                                 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16075                                 };
16076                         finalLabel.style.left = (finalPos.x - 200) + "px";
16077                         finalLabel.style.top = finalPos.y - label.size + "px";
16078                     }
16079           
16080         }
16081       }
16082    
16083     });
16084     this.sb = sb;
16085     this.canvas = this.sb.canvas;
16086     var size = sb.canvas.getSize(),
16087         min = Math.min;
16088         sb.config.levelDistance = min(size.width, size.height)/2 
16089       - config.offset - config.sliceOffset;
16090
16091
16092   },
16093         
16094   renderBackground: function() {
16095         var canvas = this.sb.canvas,
16096         config = this.config,
16097         style = config.gaugeStyle,
16098         ctx = canvas.getCtx(),
16099         size = canvas.getSize(),
16100         radius = this.sb.config.levelDistance,
16101         startAngle = (Math.PI/180)*1,
16102         endAngle = (Math.PI/180)*179;
16103         
16104
16105         //background border
16106         ctx.fillStyle = style.borderColor;      
16107         ctx.beginPath();
16108         ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true); 
16109         ctx.fill(); 
16110         
16111         
16112         var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16113         radialGradient.addColorStop(0, '#ffffff');  
16114         radialGradient.addColorStop(0.3, style.backgroundColor);  
16115         radialGradient.addColorStop(0.6, style.backgroundColor);  
16116         radialGradient.addColorStop(1, '#FFFFFF'); 
16117         ctx.fillStyle = radialGradient;
16118         
16119         //background
16120         startAngle = (Math.PI/180)*0;
16121         endAngle = (Math.PI/180)*180;
16122         ctx.beginPath();
16123         ctx.arc(0,radius/2,radius,startAngle,endAngle, true); 
16124         ctx.fill();     
16125         
16126         
16127  
16128   },
16129   
16130   
16131   renderNeedle: function(gaugePosition,target) {
16132         var canvas = this.sb.canvas,
16133         config = this.config,
16134         style = config.gaugeStyle,
16135         ctx = canvas.getCtx(),
16136         size = canvas.getSize(),
16137         radius = this.sb.config.levelDistance;
16138         gaugeCenter = (radius/2);
16139         startAngle = 0;
16140         endAngle = (Math.PI/180)*180;
16141         
16142         
16143         // needle
16144         ctx.fillStyle = style.needleColor;
16145         var segments = 180/target;
16146         needleAngle = gaugePosition * segments;
16147         ctx.translate(0, gaugeCenter);
16148         ctx.save();
16149         ctx.rotate(needleAngle * Math.PI / 180);  
16150         ctx.beginPath();
16151         ctx.moveTo(0,0); 
16152         ctx.lineTo(0,-4);  
16153         ctx.lineTo(-radius*.9,-1);  
16154         ctx.lineTo(-radius*.9,1);  
16155         ctx.lineTo(0,4);  
16156         ctx.lineTo(0,0);  
16157         ctx.closePath(); 
16158         ctx.fill();
16159         ctx.restore(); 
16160         
16161         
16162         // stroke needle
16163         ctx.lineWidth = 1;
16164         ctx.strokeStyle = '#aa0000';
16165         ctx.save();
16166         ctx.rotate(needleAngle * Math.PI / 180);  
16167         ctx.beginPath();
16168         ctx.moveTo(0,0); 
16169         ctx.lineTo(0,-4);  
16170         ctx.lineTo(-radius*.8,-1);  
16171         ctx.lineTo(-radius*.8,1);  
16172         ctx.lineTo(0,4);  
16173         ctx.lineTo(0,0);  
16174         ctx.closePath(); 
16175         ctx.stroke();
16176         ctx.restore(); 
16177
16178         //needle cap
16179         ctx.fillStyle = "#000000";
16180         ctx.lineWidth = style.borderSize;
16181     ctx.strokeStyle = style.borderColor;
16182         var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16183         radialGradient.addColorStop(0, '#666666');  
16184         radialGradient.addColorStop(0.8, '#444444');  
16185         radialGradient.addColorStop(1, 'rgba(0,0,0,0)'); 
16186         ctx.fillStyle = radialGradient;
16187         ctx.translate(0,5);
16188         ctx.save();
16189         ctx.beginPath();
16190         ctx.arc(0,0,radius*.2,startAngle,endAngle, true); 
16191         ctx.fill();     
16192         ctx.restore();
16193
16194         
16195   },
16196   
16197   renderTicks: function(values) {
16198         var canvas = this.sb.canvas,
16199         config = this.config,
16200         style = config.gaugeStyle,
16201         ctx = canvas.getCtx(),
16202         size = canvas.getSize(),
16203         radius = this.sb.config.levelDistance,
16204         gaugeCenter = (radius/2);
16205         
16206         
16207         ctx.strokeStyle = style.borderColor;
16208         ctx.lineWidth = 5;
16209         ctx.lineCap = "round";
16210                 for(var i=0, total = 0, l=values.length; i<l; i++) {
16211                         var val = values[i];
16212                         if(val.label != 'GaugePosition') {
16213                         total += (parseInt(val.values) || 0);
16214                         }
16215                 }
16216         
16217                 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16218                         var val = values[i];
16219                         if(val.label != 'GaugePosition') {
16220                         acum += (parseInt(val.values) || 0);
16221
16222                            var segments = 180/total;
16223                         angle = acum * segments;
16224
16225                           //alert(acum);
16226                                  ctx.save();
16227                                  ctx.translate(0, gaugeCenter);
16228                                  ctx.beginPath();
16229                                 ctx.rotate(angle * (Math.PI/180));
16230                                 ctx.moveTo(-radius,0);
16231                                 ctx.lineTo(-radius*.75,0);
16232                                 ctx.stroke();
16233                                  ctx.restore();
16234                         
16235                         }
16236                 }
16237         },
16238         
16239         renderPositionLabel: function(position) {
16240                 var canvas = this.sb.canvas,
16241                 config = this.config,
16242                 label = config.Label,
16243                 style = config.gaugeStyle,
16244                 ctx = canvas.getCtx(),
16245                 size = canvas.getSize(),
16246                 radius = this.sb.config.levelDistance,
16247                 gaugeCenter = (radius/2);
16248                 ctx.textBaseline = 'middle';
16249                 ctx.textAlign = 'center';
16250                 ctx.font = style.positionFontSize + 'px ' + label.family;
16251                 ctx.fillStyle = "#ffffff";
16252                 ctx.lineWidth = 2;
16253                 height = style.positionFontSize + 10,
16254                 cornerRadius = 8,
16255                 idLabel = canvas.id + "-label";
16256                 container = document.getElementById(idLabel);
16257                 if(label.type == 'Native') {
16258                         var m = ctx.measureText(position),
16259                         width = m.width + 40;
16260                 } else {
16261                         var width = 70;
16262                 }
16263                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16264                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16265                 if(label.type == 'Native') {
16266                         ctx.fillStyle = label.color;
16267                         ctx.fillText(position, 0, (height/2) + style.positionOffset);
16268                 } else {
16269                         var labelDiv =  document.createElement('div');
16270                         labelDivStyle = labelDiv.style;
16271                         labelDivStyle.color =  label.color;
16272                         labelDivStyle.fontSize =  style.positionFontSize + "px";
16273                         labelDivStyle.position = "absolute";
16274                         labelDivStyle.width = width + "px";
16275                         labelDivStyle.left = (size.width/2) - (width/2) + "px";
16276                         labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16277                         labelDiv.innerHTML = position;
16278                         container.appendChild(labelDiv);
16279                 }
16280         
16281     },
16282     
16283    renderSubtitle: function() {
16284         var canvas = this.canvas,
16285         size = canvas.getSize(),
16286         config = this.config,
16287         margin = config.Margin,
16288         radius = this.sb.config.levelDistance,
16289         title = config.Title,
16290         label = config.Label,
16291         subtitle = config.Subtitle;
16292         ctx = canvas.getCtx();
16293         ctx.fillStyle = title.color;
16294         ctx.textAlign = 'left';
16295         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16296         ctx.moveTo(0,0);
16297         if(label.type == 'Native') {
16298                 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset); 
16299         }
16300   },
16301   
16302   renderChartBackground: function() {
16303                 var canvas = this.canvas,
16304                 config = this.config,
16305                 backgroundColor = config.backgroundColor,
16306                 size = canvas.getSize(),
16307                 ctx = canvas.getCtx();
16308                 //ctx.globalCompositeOperation = "destination-over";
16309             ctx.fillStyle = backgroundColor;
16310             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
16311   },
16312   
16313   loadJSON: function(json) {
16314   
16315      var prefix = $.time(), 
16316         ch = [], 
16317         sb = this.sb,
16318         name = $.splat(json.label),
16319         nameLength = name.length,
16320         color = $.splat(json.color || this.colors),
16321         colorLength = color.length,
16322         config = this.config,
16323         renderBackground = config.renderBackground,
16324         gradient = !!config.type.split(":")[1],
16325         animate = config.animate,
16326         mono = nameLength == 1;
16327                 var props = $.splat(json.properties)[0];
16328
16329     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16330         
16331       var val = values[i];
16332           if(val.label != 'GaugePosition') {
16333                   var valArray = $.splat(val.values);
16334                   var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16335                   var valuelabelsArray = $.splat(val.valuelabels);
16336
16337                   ch.push({
16338                         'id': prefix + val.label,
16339                         'name': val.label,
16340                         'data': {
16341                           'value': valArray,
16342                           'valuelabel': valuelabelsArray,
16343                           '$linkArray': linkArray,
16344                           '$valuelabelsArray': valuelabelsArray,
16345                           '$valueArray': valArray,
16346                           '$nodeIteration': i,
16347                           '$nodeLength': l-1,
16348                           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16349                           '$colorMono': $.splat(color[i % colorLength]),
16350                           '$stringArray': name,
16351                           '$gradient': gradient,
16352                           '$config': config,
16353                           '$gaugeTarget': props['gaugeTarget'],
16354                           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16355                         },
16356                         'children': []
16357                   });
16358           } else {
16359                 var gaugePosition = val.gvalue;
16360                 var gaugePositionLabel = val.gvaluelabel;
16361           }
16362     }
16363     var root = {
16364       'id': prefix + '$root',
16365       'name': '',
16366       'data': {
16367         '$type': 'none',
16368         '$width': 1,
16369         '$height': 1
16370       },
16371       'children': ch
16372     };
16373         
16374         
16375     sb.loadJSON(root);
16376     
16377     if(renderBackground) {
16378         this.renderChartBackground();   
16379     }
16380     
16381     this.renderBackground();
16382     
16383     
16384     this.normalizeDims();
16385         
16386     sb.refresh();
16387     if(animate) {
16388       sb.fx.animate({
16389         modes: ['node-property:dimArray'],
16390         duration:1500
16391       });
16392     }
16393         
16394
16395         this.renderPositionLabel(gaugePositionLabel);
16396         if (props['gaugeTarget'] != 0) {
16397                 this.renderTicks(json.values);
16398                 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16399         }
16400         
16401         this.renderSubtitle();
16402
16403   },
16404   
16405   updateJSON: function(json, onComplete) {
16406     if(this.busy) return;
16407     this.busy = true;
16408     
16409     var sb = this.sb;
16410     var graph = sb.graph;
16411     var values = json.values;
16412     var animate = this.config.animate;
16413     var that = this;
16414     $.each(values, function(v) {
16415       var n = graph.getByName(v.label),
16416           vals = $.splat(v.values);
16417       if(n) {
16418         n.setData('valueArray', vals);
16419         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16420         if(json.label) {
16421           n.setData('stringArray', $.splat(json.label));
16422         }
16423       }
16424     });
16425     this.normalizeDims();
16426     if(animate) {
16427       sb.compute('end');
16428       sb.fx.animate({
16429         modes: ['node-property:dimArray:span', 'linear'],
16430         duration:1500,
16431         onComplete: function() {
16432           that.busy = false;
16433           onComplete && onComplete.onComplete();
16434         }
16435       });
16436     } else {
16437       sb.refresh();
16438     }
16439   },
16440     
16441   //adds the little brown bar when hovering the node
16442   select: function(id, name) {
16443     if(!this.config.hoveredColor) return;
16444     var s = this.selected;
16445     if(s.id != id || s.name != name) {
16446       s.id = id;
16447       s.name = name;
16448       s.color = this.config.hoveredColor;
16449       this.sb.graph.eachNode(function(n) {
16450         if(id == n.id) {
16451           n.setData('border', s);
16452         } else {
16453           n.setData('border', false);
16454         }
16455       });
16456       this.sb.plot();
16457     }
16458   },
16459   
16460   /*
16461     Method: getLegend
16462    
16463     Returns an object containing as keys the legend names and as values hex strings with color values.
16464     
16465     Example:
16466     
16467     (start code js)
16468     var legend = pieChart.getLegend();
16469     (end code)
16470   */  
16471   getLegend: function() {
16472     var legend = new Array();
16473     var name = new Array();
16474     var color = new Array();
16475     var n;
16476     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16477       n = adj.nodeTo;
16478     });
16479     var colors = n.getData('colorArray'),
16480         len = colors.length;
16481     $.each(n.getData('stringArray'), function(s, i) {
16482       color[i] = colors[i % len];
16483       name[i] = s;
16484     });
16485         legend['name'] = name;
16486         legend['color'] = color;
16487     return legend;
16488   },
16489   
16490   /*
16491     Method: getMaxValue
16492    
16493     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16494     
16495     Example:
16496     
16497     (start code js)
16498     var ans = pieChart.getMaxValue();
16499     (end code)
16500     
16501     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16502     
16503     Example:
16504     
16505     (start code js)
16506     //will return 100 for all PieChart instances,
16507     //displaying all of them with the same scale
16508     $jit.PieChart.implement({
16509       'getMaxValue': function() {
16510         return 100;
16511       }
16512     });
16513     (end code)
16514     
16515   */  
16516   getMaxValue: function() {
16517     var maxValue = 0;
16518     this.sb.graph.eachNode(function(n) {
16519       var valArray = n.getData('valueArray'),
16520           acum = 0;
16521       $.each(valArray, function(v) { 
16522         acum += +v;
16523       });
16524       maxValue = maxValue>acum? maxValue:acum;
16525     });
16526     return maxValue;
16527   },
16528   
16529   normalizeDims: function() {
16530     //number of elements
16531     var root = this.sb.graph.getNode(this.sb.root), l=0;
16532     root.eachAdjacency(function() {
16533       l++;
16534     });
16535     var maxValue = this.getMaxValue() || 1,
16536         config = this.config,
16537         animate = config.animate,
16538         rho = this.sb.config.levelDistance;
16539     this.sb.graph.eachNode(function(n) {
16540       var acum = 0, animateValue = [];
16541       $.each(n.getData('valueArray'), function(v) {
16542         acum += +v;
16543         animateValue.push(1);
16544       });
16545       var stat = (animateValue.length == 1) && !config.updateHeights;
16546       if(animate) {
16547         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16548           return stat? rho: (n * rho / maxValue); 
16549         }), 'end');
16550         var dimArray = n.getData('dimArray');
16551         if(!dimArray) {
16552           n.setData('dimArray', animateValue);
16553         }
16554       } else {
16555         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16556           return stat? rho : (n * rho / maxValue); 
16557         }));
16558       }
16559       n.setData('normalizedDim', acum / maxValue);
16560     });
16561   }
16562 });
16563
16564
16565 /*
16566  * Class: Layouts.TM
16567  * 
16568  * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16569  * 
16570  * Implemented By:
16571  * 
16572  * <TM>
16573  * 
16574  */
16575 Layouts.TM = {};
16576
16577 Layouts.TM.SliceAndDice = new Class({
16578   compute: function(prop) {
16579     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16580     this.controller.onBeforeCompute(root);
16581     var size = this.canvas.getSize(),
16582         config = this.config,
16583         width = size.width,
16584         height = size.height;
16585     this.graph.computeLevels(this.root, 0, "ignore");
16586     //set root position and dimensions
16587     root.getPos(prop).setc(-width/2, -height/2);
16588     root.setData('width', width, prop);
16589     root.setData('height', height + config.titleHeight, prop);
16590     this.computePositions(root, root, this.layout.orientation, prop);
16591     this.controller.onAfterCompute(root);
16592   },
16593   
16594   computePositions: function(par, ch, orn, prop) {
16595     //compute children areas
16596     var totalArea = 0;
16597     par.eachSubnode(function(n) {
16598       totalArea += n.getData('area', prop);
16599     });
16600     
16601     var config = this.config,
16602         offst = config.offset,
16603         width  = par.getData('width', prop),
16604         height = par.getData('height', prop) - config.titleHeight,
16605         fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16606     
16607     var otherSize, size, dim, pos, pos2, posth, pos2th;
16608     var horizontal = (orn == "h");
16609     if(horizontal) {
16610       orn = 'v';    
16611       otherSize = height;
16612       size = width * fact;
16613       dim = 'height';
16614       pos = 'y';
16615       pos2 = 'x';
16616       posth = config.titleHeight;
16617       pos2th = 0;
16618     } else {
16619       orn = 'h';    
16620       otherSize = height * fact;
16621       size = width;
16622       dim = 'width';
16623       pos = 'x';
16624       pos2 = 'y';
16625       posth = 0;
16626       pos2th = config.titleHeight;
16627     }
16628     var cpos = ch.getPos(prop);
16629     ch.setData('width', size, prop);
16630     ch.setData('height', otherSize, prop);
16631     var offsetSize = 0, tm = this;
16632     ch.eachSubnode(function(n) {
16633       var p = n.getPos(prop);
16634       p[pos] = offsetSize + cpos[pos] + posth;
16635       p[pos2] = cpos[pos2] + pos2th;
16636       tm.computePositions(ch, n, orn, prop);
16637       offsetSize += n.getData(dim, prop);
16638     });
16639   }
16640
16641 });
16642
16643 Layouts.TM.Area = {
16644  /*
16645     Method: compute
16646  
16647    Called by loadJSON to calculate recursively all node positions and lay out the tree.
16648  
16649     Parameters:
16650
16651        json - A JSON tree. See also <Loader.loadJSON>.
16652        coord - A coordinates object specifying width, height, left and top style properties.
16653  */
16654  compute: function(prop) {
16655     prop = prop || "current";
16656     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16657     this.controller.onBeforeCompute(root);
16658     var config = this.config,
16659         size = this.canvas.getSize(),
16660         width = size.width,
16661         height = size.height,
16662         offst = config.offset,
16663         offwdth = width - offst,
16664         offhght = height - offst;
16665     this.graph.computeLevels(this.root, 0, "ignore");
16666     //set root position and dimensions
16667     root.getPos(prop).setc(-width/2, -height/2);
16668     root.setData('width', width, prop);
16669     root.setData('height', height, prop);
16670     //create a coordinates object
16671     var coord = {
16672         'top': -height/2 + config.titleHeight,
16673         'left': -width/2,
16674         'width': offwdth,
16675         'height': offhght - config.titleHeight
16676     };
16677     this.computePositions(root, coord, prop);
16678     this.controller.onAfterCompute(root);
16679  }, 
16680  
16681  /*
16682     Method: computeDim
16683  
16684    Computes dimensions and positions of a group of nodes
16685    according to a custom layout row condition. 
16686  
16687     Parameters:
16688
16689        tail - An array of nodes.  
16690        initElem - An array of nodes (containing the initial node to be laid).
16691        w - A fixed dimension where nodes will be layed out.
16692        coord - A coordinates object specifying width, height, left and top style properties.
16693        comp - A custom comparison function
16694  */
16695  computeDim: function(tail, initElem, w, coord, comp, prop) {
16696    if(tail.length + initElem.length == 1) {
16697      var l = (tail.length == 1)? tail : initElem;
16698      this.layoutLast(l, w, coord, prop);
16699      return;
16700    }
16701    if(tail.length >= 2 && initElem.length == 0) {
16702      initElem = [tail.shift()];
16703    }
16704    if(tail.length == 0) {
16705      if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
16706      return;
16707    }
16708    var c = tail[0];
16709    if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
16710      this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
16711    } else {
16712      var newCoords = this.layoutRow(initElem, w, coord, prop);
16713      this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
16714    }
16715  },
16716
16717  
16718  /*
16719     Method: worstAspectRatio
16720  
16721    Calculates the worst aspect ratio of a group of rectangles. 
16722        
16723     See also:
16724        
16725        <http://en.wikipedia.org/wiki/Aspect_ratio>
16726    
16727     Parameters:
16728
16729      ch - An array of nodes.  
16730      w  - The fixed dimension where rectangles are being laid out.
16731
16732     Returns:
16733  
16734         The worst aspect ratio.
16735
16736
16737  */
16738  worstAspectRatio: function(ch, w) {
16739    if(!ch || ch.length == 0) return Number.MAX_VALUE;
16740    var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
16741    for(var i=0, l=ch.length; i<l; i++) {
16742      var area = ch[i]._area;
16743      areaSum += area; 
16744      minArea = minArea < area? minArea : area;
16745      maxArea = maxArea > area? maxArea : area; 
16746    }
16747    var sqw = w * w, sqAreaSum = areaSum * areaSum;
16748    return Math.max(sqw * maxArea / sqAreaSum,
16749            sqAreaSum / (sqw * minArea));
16750  },
16751  
16752  /*
16753     Method: avgAspectRatio
16754  
16755    Calculates the average aspect ratio of a group of rectangles. 
16756        
16757        See also:
16758        
16759        <http://en.wikipedia.org/wiki/Aspect_ratio>
16760    
16761     Parameters:
16762
16763      ch - An array of nodes.  
16764        w - The fixed dimension where rectangles are being laid out.
16765
16766     Returns:
16767  
16768         The average aspect ratio.
16769
16770
16771  */
16772  avgAspectRatio: function(ch, w) {
16773    if(!ch || ch.length == 0) return Number.MAX_VALUE;
16774    var arSum = 0;
16775    for(var i=0, l=ch.length; i<l; i++) {
16776      var area = ch[i]._area;
16777      var h = area / w;
16778      arSum += w > h? w / h : h / w;
16779    }
16780    return arSum / l;
16781  },
16782
16783  /*
16784     layoutLast
16785  
16786    Performs the layout of the last computed sibling.
16787  
16788     Parameters:
16789
16790        ch - An array of nodes.  
16791        w - A fixed dimension where nodes will be layed out.
16792      coord - A coordinates object specifying width, height, left and top style properties.
16793  */
16794  layoutLast: function(ch, w, coord, prop) {
16795    var child = ch[0];
16796    child.getPos(prop).setc(coord.left, coord.top);
16797    child.setData('width', coord.width, prop);
16798    child.setData('height', coord.height, prop);
16799  }
16800 };
16801
16802
16803 Layouts.TM.Squarified = new Class({
16804  Implements: Layouts.TM.Area,
16805  
16806  computePositions: function(node, coord, prop) {
16807    var config = this.config;
16808    
16809    if (coord.width >= coord.height) 
16810      this.layout.orientation = 'h';
16811    else
16812      this.layout.orientation = 'v';
16813    
16814    var ch = node.getSubnodes([1, 1], "ignore");
16815    if(ch.length > 0) {
16816      this.processChildrenLayout(node, ch, coord, prop);
16817      for(var i=0, l=ch.length; i<l; i++) {
16818        var chi = ch[i]; 
16819        var offst = config.offset,
16820            height = chi.getData('height', prop) - offst - config.titleHeight,
16821            width = chi.getData('width', prop) - offst;
16822        var chipos = chi.getPos(prop);
16823        coord = {
16824          'width': width,
16825          'height': height,
16826          'top': chipos.y + config.titleHeight,
16827          'left': chipos.x
16828        };
16829        this.computePositions(chi, coord, prop);
16830      }
16831    }
16832  },
16833
16834  /*
16835     Method: processChildrenLayout
16836  
16837    Computes children real areas and other useful parameters for performing the Squarified algorithm.
16838  
16839     Parameters:
16840
16841        par - The parent node of the json subtree.  
16842        ch - An Array of nodes
16843      coord - A coordinates object specifying width, height, left and top style properties.
16844  */
16845  processChildrenLayout: function(par, ch, coord, prop) {
16846    //compute children real areas
16847    var parentArea = coord.width * coord.height;
16848    var i, l=ch.length, totalChArea=0, chArea = [];
16849    for(i=0; i<l; i++) {
16850      chArea[i] = parseFloat(ch[i].getData('area', prop));
16851      totalChArea += chArea[i];
16852    }
16853    for(i=0; i<l; i++) {
16854      ch[i]._area = parentArea * chArea[i] / totalChArea;
16855    }
16856    var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
16857    ch.sort(function(a, b) { 
16858      var diff = b._area - a._area; 
16859      return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1)); 
16860    });
16861    var initElem = [ch[0]];
16862    var tail = ch.slice(1);
16863    this.squarify(tail, initElem, minimumSideValue, coord, prop);
16864  },
16865
16866  /*
16867    Method: squarify
16868  
16869    Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
16870  
16871     Parameters:
16872
16873        tail - An array of nodes.  
16874        initElem - An array of nodes, containing the initial node to be laid out.
16875        w - A fixed dimension where nodes will be laid out.
16876        coord - A coordinates object specifying width, height, left and top style properties.
16877  */
16878  squarify: function(tail, initElem, w, coord, prop) {
16879    this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
16880  },
16881  
16882  /*
16883     Method: layoutRow
16884  
16885    Performs the layout of an array of nodes.
16886  
16887     Parameters:
16888
16889        ch - An array of nodes.  
16890        w - A fixed dimension where nodes will be laid out.
16891        coord - A coordinates object specifying width, height, left and top style properties.
16892  */
16893  layoutRow: function(ch, w, coord, prop) {
16894    if(this.layout.horizontal()) {
16895      return this.layoutV(ch, w, coord, prop);
16896    } else {
16897      return this.layoutH(ch, w, coord, prop);
16898    }
16899  },
16900  
16901  layoutV: function(ch, w, coord, prop) {
16902    var totalArea = 0, rnd = function(x) { return x; }; 
16903    $.each(ch, function(elem) { totalArea += elem._area; });
16904    var width = rnd(totalArea / w), top =  0; 
16905    for(var i=0, l=ch.length; i<l; i++) {
16906      var h = rnd(ch[i]._area / width);
16907      var chi = ch[i];
16908      chi.getPos(prop).setc(coord.left, coord.top + top);
16909      chi.setData('width', width, prop);
16910      chi.setData('height', h, prop);
16911      top += h;
16912    }
16913    var ans = {
16914      'height': coord.height,
16915      'width': coord.width - width,
16916      'top': coord.top,
16917      'left': coord.left + width
16918    };
16919    //take minimum side value.
16920    ans.dim = Math.min(ans.width, ans.height);
16921    if(ans.dim != ans.height) this.layout.change();
16922    return ans;
16923  },
16924  
16925  layoutH: function(ch, w, coord, prop) {
16926    var totalArea = 0; 
16927    $.each(ch, function(elem) { totalArea += elem._area; });
16928    var height = totalArea / w,
16929        top = coord.top, 
16930        left = 0;
16931    
16932    for(var i=0, l=ch.length; i<l; i++) {
16933      var chi = ch[i];
16934      var w = chi._area / height;
16935      chi.getPos(prop).setc(coord.left + left, top);
16936      chi.setData('width', w, prop);
16937      chi.setData('height', height, prop);
16938      left += w;
16939    }
16940    var ans = {
16941      'height': coord.height - height,
16942      'width': coord.width,
16943      'top': coord.top + height,
16944      'left': coord.left
16945    };
16946    ans.dim = Math.min(ans.width, ans.height);
16947    if(ans.dim != ans.width) this.layout.change();
16948    return ans;
16949  }
16950 });
16951
16952 Layouts.TM.Strip = new Class({
16953   Implements: Layouts.TM.Area,
16954
16955     /*
16956       Method: compute
16957     
16958      Called by loadJSON to calculate recursively all node positions and lay out the tree.
16959     
16960       Parameters:
16961     
16962          json - A JSON subtree. See also <Loader.loadJSON>. 
16963        coord - A coordinates object specifying width, height, left and top style properties.
16964     */
16965     computePositions: function(node, coord, prop) {
16966      var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
16967      if(ch.length > 0) {
16968        this.processChildrenLayout(node, ch, coord, prop);
16969        for(var i=0, l=ch.length; i<l; i++) {
16970          var chi = ch[i];
16971          var offst = config.offset,
16972              height = chi.getData('height', prop) - offst - config.titleHeight,
16973              width  = chi.getData('width', prop)  - offst;
16974          var chipos = chi.getPos(prop);
16975          coord = {
16976            'width': width,
16977            'height': height,
16978            'top': chipos.y + config.titleHeight,
16979            'left': chipos.x
16980          };
16981          this.computePositions(chi, coord, prop);
16982        }
16983      }
16984     },
16985     
16986     /*
16987       Method: processChildrenLayout
16988     
16989      Computes children real areas and other useful parameters for performing the Strip algorithm.
16990     
16991       Parameters:
16992     
16993          par - The parent node of the json subtree.  
16994          ch - An Array of nodes
16995          coord - A coordinates object specifying width, height, left and top style properties.
16996     */
16997     processChildrenLayout: function(par, ch, coord, prop) {
16998      //compute children real areas
16999       var parentArea = coord.width * coord.height;
17000       var i, l=ch.length, totalChArea=0, chArea = [];
17001       for(i=0; i<l; i++) {
17002         chArea[i] = +ch[i].getData('area', prop);
17003         totalChArea += chArea[i];
17004       }
17005       for(i=0; i<l; i++) {
17006         ch[i]._area = parentArea * chArea[i] / totalChArea;
17007       }
17008      var side = this.layout.horizontal()? coord.width : coord.height;
17009      var initElem = [ch[0]];
17010      var tail = ch.slice(1);
17011      this.stripify(tail, initElem, side, coord, prop);
17012     },
17013     
17014     /*
17015       Method: stripify
17016     
17017      Performs an heuristic method to calculate div elements sizes in order to have 
17018      a good compromise between aspect ratio and order.
17019     
17020       Parameters:
17021     
17022          tail - An array of nodes.  
17023          initElem - An array of nodes.
17024          w - A fixed dimension where nodes will be layed out.
17025        coord - A coordinates object specifying width, height, left and top style properties.
17026     */
17027     stripify: function(tail, initElem, w, coord, prop) {
17028      this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17029     },
17030     
17031     /*
17032       Method: layoutRow
17033     
17034      Performs the layout of an array of nodes.
17035     
17036       Parameters:
17037     
17038          ch - An array of nodes.  
17039          w - A fixed dimension where nodes will be laid out.
17040          coord - A coordinates object specifying width, height, left and top style properties.
17041     */
17042     layoutRow: function(ch, w, coord, prop) {
17043      if(this.layout.horizontal()) {
17044        return this.layoutH(ch, w, coord, prop);
17045      } else {
17046        return this.layoutV(ch, w, coord, prop);
17047      }
17048     },
17049     
17050     layoutV: function(ch, w, coord, prop) {
17051      var totalArea = 0; 
17052      $.each(ch, function(elem) { totalArea += elem._area; });
17053      var width = totalArea / w, top =  0; 
17054      for(var i=0, l=ch.length; i<l; i++) {
17055        var chi = ch[i];
17056        var h = chi._area / width;
17057        chi.getPos(prop).setc(coord.left, 
17058            coord.top + (w - h - top));
17059        chi.setData('width', width, prop);
17060        chi.setData('height', h, prop);
17061        top += h;
17062      }
17063     
17064      return {
17065        'height': coord.height,
17066        'width': coord.width - width,
17067        'top': coord.top,
17068        'left': coord.left + width,
17069        'dim': w
17070      };
17071     },
17072     
17073     layoutH: function(ch, w, coord, prop) {
17074      var totalArea = 0; 
17075      $.each(ch, function(elem) { totalArea += elem._area; });
17076      var height = totalArea / w,
17077          top = coord.height - height, 
17078          left = 0;
17079      
17080      for(var i=0, l=ch.length; i<l; i++) {
17081        var chi = ch[i];
17082        var s = chi._area / height;
17083        chi.getPos(prop).setc(coord.left + left, coord.top + top);
17084        chi.setData('width', s, prop);
17085        chi.setData('height', height, prop);
17086        left += s;
17087      }
17088      return {
17089        'height': coord.height - height,
17090        'width': coord.width,
17091        'top': coord.top,
17092        'left': coord.left,
17093        'dim': w
17094      };
17095     }
17096  });
17097
17098 /*
17099  * Class: Layouts.Icicle
17100  *
17101  * Implements the icicle tree layout.
17102  *
17103  * Implemented By:
17104  *
17105  * <Icicle>
17106  *
17107  */
17108
17109 Layouts.Icicle = new Class({
17110  /*
17111   * Method: compute
17112   *
17113   * Called by loadJSON to calculate all node positions.
17114   *
17115   * Parameters:
17116   *
17117   * posType - The nodes' position to compute. Either "start", "end" or
17118   *            "current". Defaults to "current".
17119   */
17120   compute: function(posType) {
17121     posType = posType || "current";
17122     var root = this.graph.getNode(this.root),
17123         config = this.config,
17124         size = this.canvas.getSize(),
17125         width = size.width,
17126         height = size.height,
17127         offset = config.offset,
17128         levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17129
17130     this.controller.onBeforeCompute(root);
17131
17132     Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17133
17134     var treeDepth = 0;
17135
17136     Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17137
17138     var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17139     var maxDepth = Math.min(treeDepth, levelsToShow-1);
17140     var initialDepth = startNode._depth;
17141     if(this.layout.horizontal()) {
17142       this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17143     } else {
17144       this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17145     }
17146   },
17147
17148   computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17149     root.getPos(posType).setc(x, y);
17150     root.setData('width', width, posType);
17151     root.setData('height', height, posType);
17152
17153     var nodeLength, prevNodeLength = 0, totalDim = 0;
17154     var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17155
17156     if(!children.length)
17157       return;
17158
17159     $.each(children, function(e) { totalDim += e.getData('dim'); });
17160
17161     for(var i=0, l=children.length; i < l; i++) {
17162       if(this.layout.horizontal()) {
17163         nodeLength = height * children[i].getData('dim') / totalDim;
17164         this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17165         y += nodeLength;
17166       } else {
17167         nodeLength = width * children[i].getData('dim') / totalDim;
17168         this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17169         x += nodeLength;
17170       }
17171     }
17172   }
17173 });
17174
17175
17176
17177 /*
17178  * File: Icicle.js
17179  *
17180 */
17181
17182 /*
17183   Class: Icicle
17184   
17185   Icicle space filling visualization.
17186   
17187   Implements:
17188   
17189   All <Loader> methods
17190   
17191   Constructor Options:
17192   
17193   Inherits options from
17194   
17195   - <Options.Canvas>
17196   - <Options.Controller>
17197   - <Options.Node>
17198   - <Options.Edge>
17199   - <Options.Label>
17200   - <Options.Events>
17201   - <Options.Tips>
17202   - <Options.NodeStyles>
17203   - <Options.Navigation>
17204   
17205   Additionally, there are other parameters and some default values changed
17206
17207   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17208   offset - (number) Default's *2*. Boxes offset.
17209   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17210   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17211   animate - (boolean) Default's *false*. Whether to animate transitions.
17212   Node.type - Described in <Options.Node>. Default's *rectangle*.
17213   Label.type - Described in <Options.Label>. Default's *Native*.
17214   duration - Described in <Options.Fx>. Default's *700*.
17215   fps - Described in <Options.Fx>. Default's *45*.
17216   
17217   Instance Properties:
17218   
17219   canvas - Access a <Canvas> instance.
17220   graph - Access a <Graph> instance.
17221   op - Access a <Icicle.Op> instance.
17222   fx - Access a <Icicle.Plot> instance.
17223   labels - Access a <Icicle.Label> interface implementation.
17224
17225 */
17226
17227 $jit.Icicle = new Class({
17228   Implements: [ Loader, Extras, Layouts.Icicle ],
17229
17230   layout: {
17231     orientation: "h",
17232     vertical: function(){
17233       return this.orientation == "v";
17234     },
17235     horizontal: function(){
17236       return this.orientation == "h";
17237     },
17238     change: function(){
17239       this.orientation = this.vertical()? "h" : "v";
17240     }
17241   },
17242
17243   initialize: function(controller) {
17244     var config = {
17245       animate: false,
17246       orientation: "h",
17247       offset: 2,
17248       levelsToShow: Number.MAX_VALUE,
17249       constrained: false,
17250       Node: {
17251         type: 'rectangle',
17252         overridable: true
17253       },
17254       Edge: {
17255         type: 'none'
17256       },
17257       Label: {
17258         type: 'Native'
17259       },
17260       duration: 700,
17261       fps: 45
17262     };
17263
17264     var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17265                        "Events", "Navigation", "Controller", "Label");
17266     this.controller = this.config = $.merge(opts, config, controller);
17267     this.layout.orientation = this.config.orientation;
17268
17269     var canvasConfig = this.config;
17270     if (canvasConfig.useCanvas) {
17271       this.canvas = canvasConfig.useCanvas;
17272       this.config.labelContainer = this.canvas.id + '-label';
17273     } else {
17274       this.canvas = new Canvas(this, canvasConfig);
17275       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17276     }
17277
17278     this.graphOptions = {
17279       'complex': true,
17280       'Node': {
17281         'selected': false,
17282         'exist': true,
17283         'drawn': true
17284       }
17285     };
17286
17287     this.graph = new Graph(
17288       this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17289
17290     this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17291     this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17292     this.op = new $jit.Icicle.Op(this);
17293     this.group = new $jit.Icicle.Group(this);
17294     this.clickedNode = null;
17295
17296     this.initializeExtras();
17297   },
17298
17299   /* 
17300     Method: refresh 
17301     
17302     Computes positions and plots the tree.
17303   */
17304   refresh: function(){
17305     var labelType = this.config.Label.type;
17306     if(labelType != 'Native') {
17307       var that = this;
17308       this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17309     }
17310     this.compute();
17311     this.plot();
17312   },
17313
17314   /* 
17315     Method: plot 
17316     
17317     Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
17318   
17319    */
17320   plot: function(){
17321     this.fx.plot(this.config);
17322   },
17323
17324   /* 
17325     Method: enter 
17326     
17327     Sets the node as root.
17328     
17329      Parameters:
17330      
17331      node - (object) A <Graph.Node>.
17332   
17333    */
17334   enter: function (node) {
17335     if (this.busy)
17336       return;
17337     this.busy = true;
17338
17339     var that = this,
17340         config = this.config;
17341
17342     var callback = {
17343       onComplete: function() {
17344         //compute positions of newly inserted nodes
17345         if(config.request)
17346           that.compute();
17347
17348         if(config.animate) {
17349           that.graph.nodeList.setDataset(['current', 'end'], {
17350             'alpha': [1, 0] //fade nodes
17351           });
17352
17353           Graph.Util.eachSubgraph(node, function(n) {
17354             n.setData('alpha', 1, 'end');
17355           }, "ignore");
17356
17357           that.fx.animate({
17358             duration: 500,
17359             modes:['node-property:alpha'],
17360             onComplete: function() {
17361               that.clickedNode = node;
17362               that.compute('end');
17363
17364               that.fx.animate({
17365                 modes:['linear', 'node-property:width:height'],
17366                 duration: 1000,
17367                 onComplete: function() {
17368                   that.busy = false;
17369                   that.clickedNode = node;
17370                 }
17371               });
17372             }
17373           });
17374         } else {
17375           that.clickedNode = node;
17376           that.busy = false;
17377           that.refresh();
17378         }
17379       }
17380     };
17381
17382     if(config.request) {
17383       this.requestNodes(clickedNode, callback);
17384     } else {
17385       callback.onComplete();
17386     }
17387   },
17388
17389   /* 
17390     Method: out 
17391     
17392     Sets the parent node of the current selected node as root.
17393   
17394    */
17395   out: function(){
17396     if(this.busy)
17397       return;
17398
17399     var that = this,
17400         GUtil = Graph.Util,
17401         config = this.config,
17402         graph = this.graph,
17403         parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17404         parent = parents[0],
17405         clickedNode = parent,
17406         previousClickedNode = this.clickedNode;
17407
17408     this.busy = true;
17409     this.events.hoveredNode = false;
17410
17411     if(!parent) {
17412       this.busy = false;
17413       return;
17414     }
17415
17416     //final plot callback
17417     callback = {
17418       onComplete: function() {
17419         that.clickedNode = parent;
17420         if(config.request) {
17421           that.requestNodes(parent, {
17422             onComplete: function() {
17423               that.compute();
17424               that.plot();
17425               that.busy = false;
17426             }
17427           });
17428         } else {
17429           that.compute();
17430           that.plot();
17431           that.busy = false;
17432         }
17433       }
17434     };
17435
17436     //animate node positions
17437     if(config.animate) {
17438       this.clickedNode = clickedNode;
17439       this.compute('end');
17440       //animate the visible subtree only
17441       this.clickedNode = previousClickedNode;
17442       this.fx.animate({
17443         modes:['linear', 'node-property:width:height'],
17444         duration: 1000,
17445         onComplete: function() {
17446           //animate the parent subtree
17447           that.clickedNode = clickedNode;
17448           //change nodes alpha
17449           graph.nodeList.setDataset(['current', 'end'], {
17450             'alpha': [0, 1]
17451           });
17452           GUtil.eachSubgraph(previousClickedNode, function(node) {
17453             node.setData('alpha', 1);
17454           }, "ignore");
17455           that.fx.animate({
17456             duration: 500,
17457             modes:['node-property:alpha'],
17458             onComplete: function() {
17459               callback.onComplete();
17460             }
17461           });
17462         }
17463       });
17464     } else {
17465       callback.onComplete();
17466     }
17467   },
17468   requestNodes: function(node, onComplete){
17469     var handler = $.merge(this.controller, onComplete),
17470         levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17471
17472     if (handler.request) {
17473       var leaves = [], d = node._depth;
17474       Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17475         if (n.drawn && !Graph.Util.anySubnode(n)) {
17476           leaves.push(n);
17477           n._level = n._depth - d;
17478           if (this.config.constrained)
17479             n._level = levelsToShow - n._level;
17480
17481         }
17482       });
17483       this.group.requestNodes(leaves, handler);
17484     } else {
17485       handler.onComplete();
17486     }
17487   }
17488 });
17489
17490 /*
17491   Class: Icicle.Op
17492   
17493   Custom extension of <Graph.Op>.
17494   
17495   Extends:
17496   
17497   All <Graph.Op> methods
17498   
17499   See also:
17500   
17501   <Graph.Op>
17502   
17503   */
17504 $jit.Icicle.Op = new Class({
17505
17506   Implements: Graph.Op
17507
17508 });
17509
17510 /*
17511  * Performs operations on group of nodes.
17512  */
17513 $jit.Icicle.Group = new Class({
17514
17515   initialize: function(viz){
17516     this.viz = viz;
17517     this.canvas = viz.canvas;
17518     this.config = viz.config;
17519   },
17520
17521   /*
17522    * Calls the request method on the controller to request a subtree for each node.
17523    */
17524   requestNodes: function(nodes, controller){
17525     var counter = 0, len = nodes.length, nodeSelected = {};
17526     var complete = function(){
17527       controller.onComplete();
17528     };
17529     var viz = this.viz;
17530     if (len == 0)
17531       complete();
17532     for(var i = 0; i < len; i++) {
17533       nodeSelected[nodes[i].id] = nodes[i];
17534       controller.request(nodes[i].id, nodes[i]._level, {
17535         onComplete: function(nodeId, data){
17536           if (data && data.children) {
17537             data.id = nodeId;
17538             viz.op.sum(data, {
17539               type: 'nothing'
17540             });
17541           }
17542           if (++counter == len) {
17543             Graph.Util.computeLevels(viz.graph, viz.root, 0);
17544             complete();
17545           }
17546         }
17547       });
17548     }
17549   }
17550 });
17551
17552 /*
17553   Class: Icicle.Plot
17554   
17555   Custom extension of <Graph.Plot>.
17556   
17557   Extends:
17558   
17559   All <Graph.Plot> methods
17560   
17561   See also:
17562   
17563   <Graph.Plot>
17564   
17565   */
17566 $jit.Icicle.Plot = new Class({
17567   Implements: Graph.Plot,
17568
17569   plot: function(opt, animating){
17570     opt = opt || this.viz.controller;
17571     var viz = this.viz,
17572         graph = viz.graph,
17573         root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17574         initialDepth = root._depth;
17575
17576     viz.canvas.clear();
17577     this.plotTree(root, $.merge(opt, {
17578       'withLabels': true,
17579       'hideLabels': false,
17580       'plotSubtree': function(root, node) {
17581         return !viz.config.constrained ||
17582                (node._depth - initialDepth < viz.config.levelsToShow);
17583       }
17584     }), animating);
17585   }
17586 });
17587
17588 /*
17589   Class: Icicle.Label
17590   
17591   Custom extension of <Graph.Label>. 
17592   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17593   
17594   Extends:
17595   
17596   All <Graph.Label> methods and subclasses.
17597   
17598   See also:
17599   
17600   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17601   
17602   */
17603 $jit.Icicle.Label = {};
17604
17605 /*
17606   Icicle.Label.Native
17607   
17608   Custom extension of <Graph.Label.Native>.
17609   
17610   Extends:
17611   
17612   All <Graph.Label.Native> methods
17613   
17614   See also:
17615   
17616   <Graph.Label.Native>
17617
17618   */
17619 $jit.Icicle.Label.Native = new Class({
17620   Implements: Graph.Label.Native,
17621
17622   renderLabel: function(canvas, node, controller) {
17623     var ctx = canvas.getCtx(),
17624         width = node.getData('width'),
17625         height = node.getData('height'),
17626         size = node.getLabelData('size'),
17627         m = ctx.measureText(node.name);
17628
17629     // Guess as much as possible if the label will fit in the node
17630     if(height < (size * 1.5) || width < m.width)
17631       return;
17632
17633     var pos = node.pos.getc(true);
17634     ctx.fillText(node.name,
17635                  pos.x + width / 2,
17636                  pos.y + height / 2);
17637   }
17638 });
17639
17640 /*
17641   Icicle.Label.SVG
17642   
17643   Custom extension of <Graph.Label.SVG>.
17644   
17645   Extends:
17646   
17647   All <Graph.Label.SVG> methods
17648   
17649   See also:
17650   
17651   <Graph.Label.SVG>
17652 */
17653 $jit.Icicle.Label.SVG = new Class( {
17654   Implements: Graph.Label.SVG,
17655
17656   initialize: function(viz){
17657     this.viz = viz;
17658   },
17659
17660   /*
17661     placeLabel
17662    
17663     Overrides abstract method placeLabel in <Graph.Plot>.
17664    
17665     Parameters:
17666    
17667     tag - A DOM label element.
17668     node - A <Graph.Node>.
17669     controller - A configuration/controller object passed to the visualization.
17670    */
17671   placeLabel: function(tag, node, controller){
17672     var pos = node.pos.getc(true), canvas = this.viz.canvas;
17673     var radius = canvas.getSize();
17674     var labelPos = {
17675       x: Math.round(pos.x + radius.width / 2),
17676       y: Math.round(pos.y + radius.height / 2)
17677     };
17678     tag.setAttribute('x', labelPos.x);
17679     tag.setAttribute('y', labelPos.y);
17680
17681     controller.onPlaceLabel(tag, node);
17682   }
17683 });
17684
17685 /*
17686   Icicle.Label.HTML
17687   
17688   Custom extension of <Graph.Label.HTML>.
17689   
17690   Extends:
17691   
17692   All <Graph.Label.HTML> methods.
17693   
17694   See also:
17695   
17696   <Graph.Label.HTML>
17697   
17698   */
17699 $jit.Icicle.Label.HTML = new Class( {
17700   Implements: Graph.Label.HTML,
17701
17702   initialize: function(viz){
17703     this.viz = viz;
17704   },
17705
17706   /*
17707     placeLabel
17708    
17709     Overrides abstract method placeLabel in <Graph.Plot>.
17710    
17711     Parameters:
17712    
17713     tag - A DOM label element.
17714     node - A <Graph.Node>.
17715     controller - A configuration/controller object passed to the visualization.
17716    */
17717   placeLabel: function(tag, node, controller){
17718     var pos = node.pos.getc(true), canvas = this.viz.canvas;
17719     var radius = canvas.getSize();
17720     var labelPos = {
17721       x: Math.round(pos.x + radius.width / 2),
17722       y: Math.round(pos.y + radius.height / 2)
17723     };
17724
17725     var style = tag.style;
17726     style.left = labelPos.x + 'px';
17727     style.top = labelPos.y + 'px';
17728     style.display = '';
17729
17730     controller.onPlaceLabel(tag, node);
17731   }
17732 });
17733
17734 /*
17735   Class: Icicle.Plot.NodeTypes
17736   
17737   This class contains a list of <Graph.Node> built-in types. 
17738   Node types implemented are 'none', 'rectangle'.
17739   
17740   You can add your custom node types, customizing your visualization to the extreme.
17741   
17742   Example:
17743   
17744   (start code js)
17745     Icicle.Plot.NodeTypes.implement({
17746       'mySpecialType': {
17747         'render': function(node, canvas) {
17748           //print your custom node to canvas
17749         },
17750         //optional
17751         'contains': function(node, pos) {
17752           //return true if pos is inside the node or false otherwise
17753         }
17754       }
17755     });
17756   (end code)
17757   
17758   */
17759 $jit.Icicle.Plot.NodeTypes = new Class( {
17760   'none': {
17761     'render': $.empty
17762   },
17763
17764   'rectangle': {
17765     'render': function(node, canvas, animating) {
17766       var config = this.viz.config;
17767       var offset = config.offset;
17768       var width = node.getData('width');
17769       var height = node.getData('height');
17770       var border = node.getData('border');
17771       var pos = node.pos.getc(true);
17772       var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
17773       var ctx = canvas.getCtx();
17774       
17775       if(width - offset < 2 || height - offset < 2) return;
17776       
17777       if(config.cushion) {
17778         var color = node.getData('color');
17779         var lg = ctx.createRadialGradient(posx + (width - offset)/2, 
17780                                           posy + (height - offset)/2, 1, 
17781                                           posx + (width-offset)/2, posy + (height-offset)/2, 
17782                                           width < height? height : width);
17783         var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
17784             function(r) { return r * 0.3 >> 0; }));
17785         lg.addColorStop(0, color);
17786         lg.addColorStop(1, colorGrad);
17787         ctx.fillStyle = lg;
17788       }
17789
17790       if (border) {
17791         ctx.strokeStyle = border;
17792         ctx.lineWidth = 3;
17793       }
17794
17795       ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
17796       border && ctx.strokeRect(pos.x, pos.y, width, height);
17797     },
17798
17799     'contains': function(node, pos) {
17800       if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
17801       var npos = node.pos.getc(true),
17802           width = node.getData('width'),
17803           height = node.getData('height');
17804       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
17805     }
17806   }
17807 });
17808
17809 $jit.Icicle.Plot.EdgeTypes = new Class( {
17810   'none': $.empty
17811 });
17812
17813
17814
17815 /*
17816  * File: Layouts.ForceDirected.js
17817  *
17818 */
17819
17820 /*
17821  * Class: Layouts.ForceDirected
17822  * 
17823  * Implements a Force Directed Layout.
17824  * 
17825  * Implemented By:
17826  * 
17827  * <ForceDirected>
17828  * 
17829  * Credits:
17830  * 
17831  * Marcus Cobden <http://marcuscobden.co.uk>
17832  * 
17833  */
17834 Layouts.ForceDirected = new Class({
17835
17836   getOptions: function(random) {
17837     var s = this.canvas.getSize();
17838     var w = s.width, h = s.height;
17839     //count nodes
17840     var count = 0;
17841     this.graph.eachNode(function(n) { 
17842       count++;
17843     });
17844     var k2 = w * h / count, k = Math.sqrt(k2);
17845     var l = this.config.levelDistance;
17846     
17847     return {
17848       width: w,
17849       height: h,
17850       tstart: w * 0.1,
17851       nodef: function(x) { return k2 / (x || 1); },
17852       edgef: function(x) { return /* x * x / k; */ k * (x - l); }
17853     };
17854   },
17855   
17856   compute: function(property, incremental) {
17857     var prop = $.splat(property || ['current', 'start', 'end']);
17858     var opt = this.getOptions();
17859     NodeDim.compute(this.graph, prop, this.config);
17860     this.graph.computeLevels(this.root, 0, "ignore");
17861     this.graph.eachNode(function(n) {
17862       $.each(prop, function(p) {
17863         var pos = n.getPos(p);
17864         if(pos.equals(Complex.KER)) {
17865           pos.x = opt.width/5 * (Math.random() - 0.5);
17866           pos.y = opt.height/5 * (Math.random() - 0.5);
17867         }
17868         //initialize disp vector
17869         n.disp = {};
17870         $.each(prop, function(p) {
17871           n.disp[p] = $C(0, 0);
17872         });
17873       });
17874     });
17875     this.computePositions(prop, opt, incremental);
17876   },
17877   
17878   computePositions: function(property, opt, incremental) {
17879     var times = this.config.iterations, i = 0, that = this;
17880     if(incremental) {
17881       (function iter() {
17882         for(var total=incremental.iter, j=0; j<total; j++) {
17883           opt.t = opt.tstart * (1 - i++/(times -1));
17884           that.computePositionStep(property, opt);
17885           if(i >= times) {
17886             incremental.onComplete();
17887             return;
17888           }
17889         }
17890         incremental.onStep(Math.round(i / (times -1) * 100));
17891         setTimeout(iter, 1);
17892       })();
17893     } else {
17894       for(; i < times; i++) {
17895         opt.t = opt.tstart * (1 - i/(times -1));
17896         this.computePositionStep(property, opt);
17897       }
17898     }
17899   },
17900   
17901   computePositionStep: function(property, opt) {
17902     var graph = this.graph;
17903     var min = Math.min, max = Math.max;
17904     var dpos = $C(0, 0);
17905     //calculate repulsive forces
17906     graph.eachNode(function(v) {
17907       //initialize disp
17908       $.each(property, function(p) {
17909         v.disp[p].x = 0; v.disp[p].y = 0;
17910       });
17911       graph.eachNode(function(u) {
17912         if(u.id != v.id) {
17913           $.each(property, function(p) {
17914             var vp = v.getPos(p), up = u.getPos(p);
17915             dpos.x = vp.x - up.x;
17916             dpos.y = vp.y - up.y;
17917             var norm = dpos.norm() || 1;
17918             v.disp[p].$add(dpos
17919                 .$scale(opt.nodef(norm) / norm));
17920           });
17921         }
17922       });
17923     });
17924     //calculate attractive forces
17925     var T = !!graph.getNode(this.root).visited;
17926     graph.eachNode(function(node) {
17927       node.eachAdjacency(function(adj) {
17928         var nodeTo = adj.nodeTo;
17929         if(!!nodeTo.visited === T) {
17930           $.each(property, function(p) {
17931             var vp = node.getPos(p), up = nodeTo.getPos(p);
17932             dpos.x = vp.x - up.x;
17933             dpos.y = vp.y - up.y;
17934             var norm = dpos.norm() || 1;
17935             node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
17936             nodeTo.disp[p].$add(dpos.$scale(-1));
17937           });
17938         }
17939       });
17940       node.visited = !T;
17941     });
17942     //arrange positions to fit the canvas
17943     var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
17944     graph.eachNode(function(u) {
17945       $.each(property, function(p) {
17946         var disp = u.disp[p];
17947         var norm = disp.norm() || 1;
17948         var p = u.getPos(p);
17949         p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
17950             disp.y * min(Math.abs(disp.y), t) / norm));
17951         p.x = min(w2, max(-w2, p.x));
17952         p.y = min(h2, max(-h2, p.y));
17953       });
17954     });
17955   }
17956 });
17957
17958 /*
17959  * File: ForceDirected.js
17960  */
17961
17962 /*
17963    Class: ForceDirected
17964       
17965    A visualization that lays graphs using a Force-Directed layout algorithm.
17966    
17967    Inspired by:
17968   
17969    Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
17970    
17971   Implements:
17972   
17973   All <Loader> methods
17974   
17975    Constructor Options:
17976    
17977    Inherits options from
17978    
17979    - <Options.Canvas>
17980    - <Options.Controller>
17981    - <Options.Node>
17982    - <Options.Edge>
17983    - <Options.Label>
17984    - <Options.Events>
17985    - <Options.Tips>
17986    - <Options.NodeStyles>
17987    - <Options.Navigation>
17988    
17989    Additionally, there are two parameters
17990    
17991    levelDistance - (number) Default's *50*. The natural length desired for the edges.
17992    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*. 
17993      
17994    Instance Properties:
17995
17996    canvas - Access a <Canvas> instance.
17997    graph - Access a <Graph> instance.
17998    op - Access a <ForceDirected.Op> instance.
17999    fx - Access a <ForceDirected.Plot> instance.
18000    labels - Access a <ForceDirected.Label> interface implementation.
18001
18002 */
18003
18004 $jit.ForceDirected = new Class( {
18005
18006   Implements: [ Loader, Extras, Layouts.ForceDirected ],
18007
18008   initialize: function(controller) {
18009     var $ForceDirected = $jit.ForceDirected;
18010
18011     var config = {
18012       iterations: 50,
18013       levelDistance: 50
18014     };
18015
18016     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18017         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18018
18019     var canvasConfig = this.config;
18020     if(canvasConfig.useCanvas) {
18021       this.canvas = canvasConfig.useCanvas;
18022       this.config.labelContainer = this.canvas.id + '-label';
18023     } else {
18024       if(canvasConfig.background) {
18025         canvasConfig.background = $.merge({
18026           type: 'Circles'
18027         }, canvasConfig.background);
18028       }
18029       this.canvas = new Canvas(this, canvasConfig);
18030       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18031     }
18032
18033     this.graphOptions = {
18034       'complex': true,
18035       'Node': {
18036         'selected': false,
18037         'exist': true,
18038         'drawn': true
18039       }
18040     };
18041     this.graph = new Graph(this.graphOptions, this.config.Node,
18042         this.config.Edge);
18043     this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18044     this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18045     this.op = new $ForceDirected.Op(this);
18046     this.json = null;
18047     this.busy = false;
18048     // initialize extras
18049     this.initializeExtras();
18050   },
18051
18052   /* 
18053     Method: refresh 
18054     
18055     Computes positions and plots the tree.
18056   */
18057   refresh: function() {
18058     this.compute();
18059     this.plot();
18060   },
18061
18062   reposition: function() {
18063     this.compute('end');
18064   },
18065
18066 /*
18067   Method: computeIncremental
18068   
18069   Performs the Force Directed algorithm incrementally.
18070   
18071   Description:
18072   
18073   ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
18074   This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
18075   avoiding browser messages such as "This script is taking too long to complete".
18076   
18077   Parameters:
18078   
18079   opt - (object) The object properties are described below
18080   
18081   iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
18082   of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18083   
18084   property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
18085   You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
18086   computations for final animation positions then you can just choose 'end'.
18087   
18088   onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
18089   parameter a percentage value.
18090   
18091   onComplete - A callback function called when the algorithm completed.
18092   
18093   Example:
18094   
18095   In this example I calculate the end positions and then animate the graph to those positions
18096   
18097   (start code js)
18098   var fd = new $jit.ForceDirected(...);
18099   fd.computeIncremental({
18100     iter: 20,
18101     property: 'end',
18102     onStep: function(perc) {
18103       Log.write("loading " + perc + "%");
18104     },
18105     onComplete: function() {
18106       Log.write("done");
18107       fd.animate();
18108     }
18109   });
18110   (end code)
18111   
18112   In this example I calculate all positions and (re)plot the graph
18113   
18114   (start code js)
18115   var fd = new ForceDirected(...);
18116   fd.computeIncremental({
18117     iter: 20,
18118     property: ['end', 'start', 'current'],
18119     onStep: function(perc) {
18120       Log.write("loading " + perc + "%");
18121     },
18122     onComplete: function() {
18123       Log.write("done");
18124       fd.plot();
18125     }
18126   });
18127   (end code)
18128   
18129   */
18130   computeIncremental: function(opt) {
18131     opt = $.merge( {
18132       iter: 20,
18133       property: 'end',
18134       onStep: $.empty,
18135       onComplete: $.empty
18136     }, opt || {});
18137
18138     this.config.onBeforeCompute(this.graph.getNode(this.root));
18139     this.compute(opt.property, opt);
18140   },
18141
18142   /*
18143     Method: plot
18144    
18145     Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18146    */
18147   plot: function() {
18148     this.fx.plot();
18149   },
18150
18151   /*
18152      Method: animate
18153     
18154      Animates the graph from the current positions to the 'end' node positions.
18155   */
18156   animate: function(opt) {
18157     this.fx.animate($.merge( {
18158       modes: [ 'linear' ]
18159     }, opt || {}));
18160   }
18161 });
18162
18163 $jit.ForceDirected.$extend = true;
18164
18165 (function(ForceDirected) {
18166
18167   /*
18168      Class: ForceDirected.Op
18169      
18170      Custom extension of <Graph.Op>.
18171
18172      Extends:
18173
18174      All <Graph.Op> methods
18175      
18176      See also:
18177      
18178      <Graph.Op>
18179
18180   */
18181   ForceDirected.Op = new Class( {
18182
18183     Implements: Graph.Op
18184
18185   });
18186
18187   /*
18188     Class: ForceDirected.Plot
18189     
18190     Custom extension of <Graph.Plot>.
18191   
18192     Extends:
18193   
18194     All <Graph.Plot> methods
18195     
18196     See also:
18197     
18198     <Graph.Plot>
18199   
18200   */
18201   ForceDirected.Plot = new Class( {
18202
18203     Implements: Graph.Plot
18204
18205   });
18206
18207   /*
18208     Class: ForceDirected.Label
18209     
18210     Custom extension of <Graph.Label>. 
18211     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18212   
18213     Extends:
18214   
18215     All <Graph.Label> methods and subclasses.
18216   
18217     See also:
18218   
18219     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18220   
18221   */
18222   ForceDirected.Label = {};
18223
18224   /*
18225      ForceDirected.Label.Native
18226      
18227      Custom extension of <Graph.Label.Native>.
18228
18229      Extends:
18230
18231      All <Graph.Label.Native> methods
18232
18233      See also:
18234
18235      <Graph.Label.Native>
18236
18237   */
18238   ForceDirected.Label.Native = new Class( {
18239     Implements: Graph.Label.Native
18240   });
18241
18242   /*
18243     ForceDirected.Label.SVG
18244     
18245     Custom extension of <Graph.Label.SVG>.
18246   
18247     Extends:
18248   
18249     All <Graph.Label.SVG> methods
18250   
18251     See also:
18252   
18253     <Graph.Label.SVG>
18254   
18255   */
18256   ForceDirected.Label.SVG = new Class( {
18257     Implements: Graph.Label.SVG,
18258
18259     initialize: function(viz) {
18260       this.viz = viz;
18261     },
18262
18263     /* 
18264        placeLabel
18265
18266        Overrides abstract method placeLabel in <Graph.Label>.
18267
18268        Parameters:
18269
18270        tag - A DOM label element.
18271        node - A <Graph.Node>.
18272        controller - A configuration/controller object passed to the visualization.
18273       
18274      */
18275     placeLabel: function(tag, node, controller) {
18276       var pos = node.pos.getc(true), 
18277           canvas = this.viz.canvas,
18278           ox = canvas.translateOffsetX,
18279           oy = canvas.translateOffsetY,
18280           sx = canvas.scaleOffsetX,
18281           sy = canvas.scaleOffsetY,
18282           radius = canvas.getSize();
18283       var labelPos = {
18284         x: Math.round(pos.x * sx + ox + radius.width / 2),
18285         y: Math.round(pos.y * sy + oy + radius.height / 2)
18286       };
18287       tag.setAttribute('x', labelPos.x);
18288       tag.setAttribute('y', labelPos.y);
18289
18290       controller.onPlaceLabel(tag, node);
18291     }
18292   });
18293
18294   /*
18295      ForceDirected.Label.HTML
18296      
18297      Custom extension of <Graph.Label.HTML>.
18298
18299      Extends:
18300
18301      All <Graph.Label.HTML> methods.
18302
18303      See also:
18304
18305      <Graph.Label.HTML>
18306
18307   */
18308   ForceDirected.Label.HTML = new Class( {
18309     Implements: Graph.Label.HTML,
18310
18311     initialize: function(viz) {
18312       this.viz = viz;
18313     },
18314     /* 
18315        placeLabel
18316
18317        Overrides abstract method placeLabel in <Graph.Plot>.
18318
18319        Parameters:
18320
18321        tag - A DOM label element.
18322        node - A <Graph.Node>.
18323        controller - A configuration/controller object passed to the visualization.
18324       
18325      */
18326     placeLabel: function(tag, node, controller) {
18327       var pos = node.pos.getc(true), 
18328           canvas = this.viz.canvas,
18329           ox = canvas.translateOffsetX,
18330           oy = canvas.translateOffsetY,
18331           sx = canvas.scaleOffsetX,
18332           sy = canvas.scaleOffsetY,
18333           radius = canvas.getSize();
18334       var labelPos = {
18335         x: Math.round(pos.x * sx + ox + radius.width / 2),
18336         y: Math.round(pos.y * sy + oy + radius.height / 2)
18337       };
18338       var style = tag.style;
18339       style.left = labelPos.x + 'px';
18340       style.top = labelPos.y + 'px';
18341       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18342
18343       controller.onPlaceLabel(tag, node);
18344     }
18345   });
18346
18347   /*
18348     Class: ForceDirected.Plot.NodeTypes
18349
18350     This class contains a list of <Graph.Node> built-in types. 
18351     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18352
18353     You can add your custom node types, customizing your visualization to the extreme.
18354
18355     Example:
18356
18357     (start code js)
18358       ForceDirected.Plot.NodeTypes.implement({
18359         'mySpecialType': {
18360           'render': function(node, canvas) {
18361             //print your custom node to canvas
18362           },
18363           //optional
18364           'contains': function(node, pos) {
18365             //return true if pos is inside the node or false otherwise
18366           }
18367         }
18368       });
18369     (end code)
18370
18371   */
18372   ForceDirected.Plot.NodeTypes = new Class({
18373     'none': {
18374       'render': $.empty,
18375       'contains': $.lambda(false)
18376     },
18377     'circle': {
18378       'render': function(node, canvas){
18379         var pos = node.pos.getc(true), 
18380             dim = node.getData('dim');
18381         this.nodeHelper.circle.render('fill', pos, dim, canvas);
18382       },
18383       'contains': function(node, pos){
18384         var npos = node.pos.getc(true), 
18385             dim = node.getData('dim');
18386         return this.nodeHelper.circle.contains(npos, pos, dim);
18387       }
18388     },
18389     'ellipse': {
18390       'render': function(node, canvas){
18391         var pos = node.pos.getc(true), 
18392             width = node.getData('width'), 
18393             height = node.getData('height');
18394         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18395         },
18396       // TODO(nico): be more precise...
18397       'contains': function(node, pos){
18398         var npos = node.pos.getc(true), 
18399             width = node.getData('width'), 
18400             height = node.getData('height');
18401         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18402       }
18403     },
18404     'square': {
18405       'render': function(node, canvas){
18406         var pos = node.pos.getc(true), 
18407             dim = node.getData('dim');
18408         this.nodeHelper.square.render('fill', pos, dim, canvas);
18409       },
18410       'contains': function(node, pos){
18411         var npos = node.pos.getc(true), 
18412             dim = node.getData('dim');
18413         return this.nodeHelper.square.contains(npos, pos, dim);
18414       }
18415     },
18416     'rectangle': {
18417       'render': function(node, canvas){
18418         var pos = node.pos.getc(true), 
18419             width = node.getData('width'), 
18420             height = node.getData('height');
18421         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18422       },
18423       'contains': function(node, pos){
18424         var npos = node.pos.getc(true), 
18425             width = node.getData('width'), 
18426             height = node.getData('height');
18427         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18428       }
18429     },
18430     'triangle': {
18431       'render': function(node, canvas){
18432         var pos = node.pos.getc(true), 
18433             dim = node.getData('dim');
18434         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18435       },
18436       'contains': function(node, pos) {
18437         var npos = node.pos.getc(true), 
18438             dim = node.getData('dim');
18439         return this.nodeHelper.triangle.contains(npos, pos, dim);
18440       }
18441     },
18442     'star': {
18443       'render': function(node, canvas){
18444         var pos = node.pos.getc(true),
18445             dim = node.getData('dim');
18446         this.nodeHelper.star.render('fill', pos, dim, canvas);
18447       },
18448       'contains': function(node, pos) {
18449         var npos = node.pos.getc(true),
18450             dim = node.getData('dim');
18451         return this.nodeHelper.star.contains(npos, pos, dim);
18452       }
18453     }
18454   });
18455
18456   /*
18457     Class: ForceDirected.Plot.EdgeTypes
18458   
18459     This class contains a list of <Graph.Adjacence> built-in types. 
18460     Edge types implemented are 'none', 'line' and 'arrow'.
18461   
18462     You can add your custom edge types, customizing your visualization to the extreme.
18463   
18464     Example:
18465   
18466     (start code js)
18467       ForceDirected.Plot.EdgeTypes.implement({
18468         'mySpecialType': {
18469           'render': function(adj, canvas) {
18470             //print your custom edge to canvas
18471           },
18472           //optional
18473           'contains': function(adj, pos) {
18474             //return true if pos is inside the arc or false otherwise
18475           }
18476         }
18477       });
18478     (end code)
18479   
18480   */
18481   ForceDirected.Plot.EdgeTypes = new Class({
18482     'none': $.empty,
18483     'line': {
18484       'render': function(adj, canvas) {
18485         var from = adj.nodeFrom.pos.getc(true),
18486             to = adj.nodeTo.pos.getc(true);
18487         this.edgeHelper.line.render(from, to, canvas);
18488       },
18489       'contains': function(adj, pos) {
18490         var from = adj.nodeFrom.pos.getc(true),
18491             to = adj.nodeTo.pos.getc(true);
18492         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18493       }
18494     },
18495     'arrow': {
18496       'render': function(adj, canvas) {
18497         var from = adj.nodeFrom.pos.getc(true),
18498             to = adj.nodeTo.pos.getc(true),
18499             dim = adj.getData('dim'),
18500             direction = adj.data.$direction,
18501             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18502         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18503       },
18504       'contains': function(adj, pos) {
18505         var from = adj.nodeFrom.pos.getc(true),
18506             to = adj.nodeTo.pos.getc(true);
18507         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18508       }
18509     }
18510   });
18511
18512 })($jit.ForceDirected);
18513
18514
18515 /*
18516  * File: Treemap.js
18517  *
18518 */
18519
18520 $jit.TM = {};
18521
18522 var TM = $jit.TM;
18523
18524 $jit.TM.$extend = true;
18525
18526 /*
18527   Class: TM.Base
18528   
18529   Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18530   
18531   Implements:
18532   
18533   All <Loader> methods
18534   
18535   Constructor Options:
18536   
18537   Inherits options from
18538   
18539   - <Options.Canvas>
18540   - <Options.Controller>
18541   - <Options.Node>
18542   - <Options.Edge>
18543   - <Options.Label>
18544   - <Options.Events>
18545   - <Options.Tips>
18546   - <Options.NodeStyles>
18547   - <Options.Navigation>
18548   
18549   Additionally, there are other parameters and some default values changed
18550
18551   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18552   titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18553   offset - (number) Default's *2*. Boxes offset.
18554   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18555   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18556   animate - (boolean) Default's *false*. Whether to animate transitions.
18557   Node.type - Described in <Options.Node>. Default's *rectangle*.
18558   duration - Described in <Options.Fx>. Default's *700*.
18559   fps - Described in <Options.Fx>. Default's *45*.
18560   
18561   Instance Properties:
18562   
18563   canvas - Access a <Canvas> instance.
18564   graph - Access a <Graph> instance.
18565   op - Access a <TM.Op> instance.
18566   fx - Access a <TM.Plot> instance.
18567   labels - Access a <TM.Label> interface implementation.
18568
18569   Inspired by:
18570   
18571   Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18572   
18573   Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18574   
18575    Note:
18576    
18577    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.
18578
18579 */
18580 TM.Base = {
18581   layout: {
18582     orientation: "h",
18583     vertical: function(){
18584       return this.orientation == "v";
18585     },
18586     horizontal: function(){
18587       return this.orientation == "h";
18588     },
18589     change: function(){
18590       this.orientation = this.vertical()? "h" : "v";
18591     }
18592   },
18593
18594   initialize: function(controller){
18595     var config = {
18596       orientation: "h",
18597       titleHeight: 13,
18598       offset: 2,
18599       levelsToShow: 0,
18600       constrained: false,
18601       animate: false,
18602       Node: {
18603         type: 'rectangle',
18604         overridable: true,
18605         //we all know why this is not zero,
18606         //right, Firefox?
18607         width: 3,
18608         height: 3,
18609         color: '#444'
18610       },
18611       Label: {
18612         textAlign: 'center',
18613         textBaseline: 'top'
18614       },
18615       Edge: {
18616         type: 'none'
18617       },
18618       duration: 700,
18619       fps: 45
18620     };
18621
18622     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18623         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18624     this.layout.orientation = this.config.orientation;
18625
18626     var canvasConfig = this.config;
18627     if (canvasConfig.useCanvas) {
18628       this.canvas = canvasConfig.useCanvas;
18629       this.config.labelContainer = this.canvas.id + '-label';
18630     } else {
18631       if(canvasConfig.background) {
18632         canvasConfig.background = $.merge({
18633           type: 'Circles'
18634         }, canvasConfig.background);
18635       }
18636       this.canvas = new Canvas(this, canvasConfig);
18637       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18638     }
18639
18640     this.graphOptions = {
18641       'complex': true,
18642       'Node': {
18643         'selected': false,
18644         'exist': true,
18645         'drawn': true
18646       }
18647     };
18648     this.graph = new Graph(this.graphOptions, this.config.Node,
18649         this.config.Edge);
18650     this.labels = new TM.Label[canvasConfig.Label.type](this);
18651     this.fx = new TM.Plot(this);
18652     this.op = new TM.Op(this);
18653     this.group = new TM.Group(this);
18654     this.geom = new TM.Geom(this);
18655     this.clickedNode = null;
18656     this.busy = false;
18657     // initialize extras
18658     this.initializeExtras();
18659   },
18660
18661   /* 
18662     Method: refresh 
18663     
18664     Computes positions and plots the tree.
18665   */
18666   refresh: function(){
18667     if(this.busy) return;
18668     this.busy = true;
18669     var that = this;
18670     if(this.config.animate) {
18671       this.compute('end');
18672       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18673           && this.clickedNode.id || this.root));
18674       this.fx.animate($.merge(this.config, {
18675         modes: ['linear', 'node-property:width:height'],
18676         onComplete: function() {
18677           that.busy = false;
18678         }
18679       }));
18680     } else {
18681       var labelType = this.config.Label.type;
18682       if(labelType != 'Native') {
18683         var that = this;
18684         this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18685       }
18686       this.busy = false;
18687       this.compute();
18688       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18689           && this.clickedNode.id || this.root));
18690       this.plot();
18691     }
18692   },
18693
18694   /* 
18695     Method: plot 
18696     
18697     Plots the TreeMap. This is a shortcut to *fx.plot*. 
18698   
18699    */
18700   plot: function(){
18701     this.fx.plot();
18702   },
18703
18704   /* 
18705   Method: leaf 
18706   
18707   Returns whether the node is a leaf.
18708   
18709    Parameters:
18710    
18711    n - (object) A <Graph.Node>.
18712
18713  */
18714   leaf: function(n){
18715     return n.getSubnodes([
18716         1, 1
18717     ], "ignore").length == 0;
18718   },
18719   
18720   /* 
18721   Method: enter 
18722   
18723   Sets the node as root.
18724   
18725    Parameters:
18726    
18727    n - (object) A <Graph.Node>.
18728
18729  */
18730   enter: function(n){
18731     if(this.busy) return;
18732     this.busy = true;
18733     
18734     var that = this,
18735         config = this.config,
18736         graph = this.graph,
18737         clickedNode = n,
18738         previousClickedNode = this.clickedNode;
18739
18740     var callback = {
18741       onComplete: function() {
18742         //ensure that nodes are shown for that level
18743         if(config.levelsToShow > 0) {
18744           that.geom.setRightLevelToShow(n);
18745         }
18746         //compute positions of newly inserted nodes
18747         if(config.levelsToShow > 0 || config.request) that.compute();
18748         if(config.animate) {
18749           //fade nodes
18750           graph.nodeList.setData('alpha', 0, 'end');
18751           n.eachSubgraph(function(n) {
18752             n.setData('alpha', 1, 'end');
18753           }, "ignore");
18754           that.fx.animate({
18755             duration: 500,
18756             modes:['node-property:alpha'],
18757             onComplete: function() {
18758               //compute end positions
18759               that.clickedNode = clickedNode;
18760               that.compute('end');
18761               //animate positions
18762               //TODO(nico) commenting this line didn't seem to throw errors...
18763               that.clickedNode = previousClickedNode;
18764               that.fx.animate({
18765                 modes:['linear', 'node-property:width:height'],
18766                 duration: 1000,
18767                 onComplete: function() { 
18768                   that.busy = false;
18769                   //TODO(nico) check comment above
18770                   that.clickedNode = clickedNode;
18771                 }
18772               });
18773             }
18774           });
18775         } else {
18776           that.busy = false;
18777           that.clickedNode = n;
18778           that.refresh();
18779         }
18780       }
18781     };
18782     if(config.request) {
18783       this.requestNodes(clickedNode, callback);
18784     } else {
18785       callback.onComplete();
18786     }
18787   },
18788
18789   /* 
18790   Method: out 
18791   
18792   Sets the parent node of the current selected node as root.
18793
18794  */
18795   out: function(){
18796     if(this.busy) return;
18797     this.busy = true;
18798     this.events.hoveredNode = false;
18799     var that = this,
18800         config = this.config,
18801         graph = this.graph,
18802         parents = graph.getNode(this.clickedNode 
18803             && this.clickedNode.id || this.root).getParents(),
18804         parent = parents[0],
18805         clickedNode = parent,
18806         previousClickedNode = this.clickedNode;
18807     
18808     //if no parents return
18809     if(!parent) {
18810       this.busy = false;
18811       return;
18812     }
18813     //final plot callback
18814     callback = {
18815       onComplete: function() {
18816         that.clickedNode = parent;
18817         if(config.request) {
18818           that.requestNodes(parent, {
18819             onComplete: function() {
18820               that.compute();
18821               that.plot();
18822               that.busy = false;
18823             }
18824           });
18825         } else {
18826           that.compute();
18827           that.plot();
18828           that.busy = false;
18829         }
18830       }
18831     };
18832     //prune tree
18833     if (config.levelsToShow > 0)
18834       this.geom.setRightLevelToShow(parent);
18835     //animate node positions
18836     if(config.animate) {
18837       this.clickedNode = clickedNode;
18838       this.compute('end');
18839       //animate the visible subtree only
18840       this.clickedNode = previousClickedNode;
18841       this.fx.animate({
18842         modes:['linear', 'node-property:width:height'],
18843         duration: 1000,
18844         onComplete: function() {
18845           //animate the parent subtree
18846           that.clickedNode = clickedNode;
18847           //change nodes alpha
18848           graph.eachNode(function(n) {
18849             n.setDataset(['current', 'end'], {
18850               'alpha': [0, 1]
18851             });
18852           }, "ignore");
18853           previousClickedNode.eachSubgraph(function(node) {
18854             node.setData('alpha', 1);
18855           }, "ignore");
18856           that.fx.animate({
18857             duration: 500,
18858             modes:['node-property:alpha'],
18859             onComplete: function() {
18860               callback.onComplete();
18861             }
18862           });
18863         }
18864       });
18865     } else {
18866       callback.onComplete();
18867     }
18868   },
18869
18870   requestNodes: function(node, onComplete){
18871     var handler = $.merge(this.controller, onComplete), 
18872         lev = this.config.levelsToShow;
18873     if (handler.request) {
18874       var leaves = [], d = node._depth;
18875       node.eachLevel(0, lev, function(n){
18876         var nodeLevel = lev - (n._depth - d);
18877         if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
18878           leaves.push(n);
18879           n._level = nodeLevel;
18880         }
18881       });
18882       this.group.requestNodes(leaves, handler);
18883     } else {
18884       handler.onComplete();
18885     }
18886   }
18887 };
18888
18889 /*
18890   Class: TM.Op
18891   
18892   Custom extension of <Graph.Op>.
18893   
18894   Extends:
18895   
18896   All <Graph.Op> methods
18897   
18898   See also:
18899   
18900   <Graph.Op>
18901   
18902   */
18903 TM.Op = new Class({
18904   Implements: Graph.Op,
18905
18906   initialize: function(viz){
18907     this.viz = viz;
18908   }
18909 });
18910
18911 //extend level methods of Graph.Geom
18912 TM.Geom = new Class({
18913   Implements: Graph.Geom,
18914   
18915   getRightLevelToShow: function() {
18916     return this.viz.config.levelsToShow;
18917   },
18918   
18919   setRightLevelToShow: function(node) {
18920     var level = this.getRightLevelToShow(), 
18921         fx = this.viz.labels;
18922     node.eachLevel(0, level+1, function(n) {
18923       var d = n._depth - node._depth;
18924       if(d > level) {
18925         n.drawn = false; 
18926         n.exist = false;
18927         n.ignore = true;
18928         fx.hideLabel(n, false);
18929       } else {
18930         n.drawn = true;
18931         n.exist = true;
18932         delete n.ignore;
18933       }
18934     });
18935     node.drawn = true;
18936     delete node.ignore;
18937   }
18938 });
18939
18940 /*
18941
18942 Performs operations on group of nodes.
18943
18944 */
18945 TM.Group = new Class( {
18946
18947   initialize: function(viz){
18948     this.viz = viz;
18949     this.canvas = viz.canvas;
18950     this.config = viz.config;
18951   },
18952
18953   /*
18954   
18955     Calls the request method on the controller to request a subtree for each node. 
18956   */
18957   requestNodes: function(nodes, controller){
18958     var counter = 0, len = nodes.length, nodeSelected = {};
18959     var complete = function(){
18960       controller.onComplete();
18961     };
18962     var viz = this.viz;
18963     if (len == 0)
18964       complete();
18965     for ( var i = 0; i < len; i++) {
18966       nodeSelected[nodes[i].id] = nodes[i];
18967       controller.request(nodes[i].id, nodes[i]._level, {
18968         onComplete: function(nodeId, data){
18969           if (data && data.children) {
18970             data.id = nodeId;
18971             viz.op.sum(data, {
18972               type: 'nothing'
18973             });
18974           }
18975           if (++counter == len) {
18976             viz.graph.computeLevels(viz.root, 0);
18977             complete();
18978           }
18979         }
18980       });
18981     }
18982   }
18983 });
18984
18985 /*
18986   Class: TM.Plot
18987   
18988   Custom extension of <Graph.Plot>.
18989   
18990   Extends:
18991   
18992   All <Graph.Plot> methods
18993   
18994   See also:
18995   
18996   <Graph.Plot>
18997   
18998   */
18999 TM.Plot = new Class({
19000
19001   Implements: Graph.Plot,
19002
19003   initialize: function(viz){
19004     this.viz = viz;
19005     this.config = viz.config;
19006     this.node = this.config.Node;
19007     this.edge = this.config.Edge;
19008     this.animation = new Animation;
19009     this.nodeTypes = new TM.Plot.NodeTypes;
19010     this.edgeTypes = new TM.Plot.EdgeTypes;
19011     this.labels = viz.labels;
19012   },
19013
19014   plot: function(opt, animating){
19015     var viz = this.viz, 
19016         graph = viz.graph;
19017     viz.canvas.clear();
19018     this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19019       'withLabels': true,
19020       'hideLabels': false,
19021       'plotSubtree': function(n, ch){
19022         return n.anySubnode("exist");
19023       }
19024     }), animating);
19025   }
19026 });
19027
19028 /*
19029   Class: TM.Label
19030   
19031   Custom extension of <Graph.Label>. 
19032   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19033
19034   Extends:
19035
19036   All <Graph.Label> methods and subclasses.
19037
19038   See also:
19039
19040   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19041   
19042 */
19043 TM.Label = {};
19044
19045 /*
19046  TM.Label.Native
19047
19048  Custom extension of <Graph.Label.Native>.
19049
19050  Extends:
19051
19052  All <Graph.Label.Native> methods
19053
19054  See also:
19055
19056  <Graph.Label.Native>
19057 */
19058 TM.Label.Native = new Class({
19059   Implements: Graph.Label.Native,
19060
19061   initialize: function(viz) {
19062     this.config = viz.config;
19063     this.leaf = viz.leaf;
19064   },
19065   
19066   renderLabel: function(canvas, node, controller){
19067     if(!this.leaf(node) && !this.config.titleHeight) return;
19068     var pos = node.pos.getc(true), 
19069         ctx = canvas.getCtx(),
19070         width = node.getData('width'),
19071         height = node.getData('height'),
19072         x = pos.x + width/2,
19073         y = pos.y;
19074         
19075     ctx.fillText(node.name, x, y, width);
19076   }
19077 });
19078
19079 /*
19080  TM.Label.SVG
19081
19082   Custom extension of <Graph.Label.SVG>.
19083
19084   Extends:
19085
19086   All <Graph.Label.SVG> methods
19087
19088   See also:
19089
19090   <Graph.Label.SVG>
19091 */
19092 TM.Label.SVG = new Class( {
19093   Implements: Graph.Label.SVG,
19094
19095   initialize: function(viz){
19096     this.viz = viz;
19097     this.leaf = viz.leaf;
19098     this.config = viz.config;
19099   },
19100
19101   /* 
19102   placeLabel
19103
19104   Overrides abstract method placeLabel in <Graph.Plot>.
19105
19106   Parameters:
19107
19108   tag - A DOM label element.
19109   node - A <Graph.Node>.
19110   controller - A configuration/controller object passed to the visualization.
19111   
19112   */
19113   placeLabel: function(tag, node, controller){
19114     var pos = node.pos.getc(true), 
19115         canvas = this.viz.canvas,
19116         ox = canvas.translateOffsetX,
19117         oy = canvas.translateOffsetY,
19118         sx = canvas.scaleOffsetX,
19119         sy = canvas.scaleOffsetY,
19120         radius = canvas.getSize();
19121     var labelPos = {
19122       x: Math.round(pos.x * sx + ox + radius.width / 2),
19123       y: Math.round(pos.y * sy + oy + radius.height / 2)
19124     };
19125     tag.setAttribute('x', labelPos.x);
19126     tag.setAttribute('y', labelPos.y);
19127
19128     if(!this.leaf(node) && !this.config.titleHeight) {
19129       tag.style.display = 'none';
19130     }
19131     controller.onPlaceLabel(tag, node);
19132   }
19133 });
19134
19135 /*
19136  TM.Label.HTML
19137
19138  Custom extension of <Graph.Label.HTML>.
19139
19140  Extends:
19141
19142  All <Graph.Label.HTML> methods.
19143
19144  See also:
19145
19146  <Graph.Label.HTML>
19147
19148 */
19149 TM.Label.HTML = new Class( {
19150   Implements: Graph.Label.HTML,
19151
19152   initialize: function(viz){
19153     this.viz = viz;
19154     this.leaf = viz.leaf;
19155     this.config = viz.config;
19156   },
19157
19158   /* 
19159     placeLabel
19160   
19161     Overrides abstract method placeLabel in <Graph.Plot>.
19162   
19163     Parameters:
19164   
19165     tag - A DOM label element.
19166     node - A <Graph.Node>.
19167     controller - A configuration/controller object passed to the visualization.
19168   
19169   */
19170   placeLabel: function(tag, node, controller){
19171     var pos = node.pos.getc(true), 
19172         canvas = this.viz.canvas,
19173         ox = canvas.translateOffsetX,
19174         oy = canvas.translateOffsetY,
19175         sx = canvas.scaleOffsetX,
19176         sy = canvas.scaleOffsetY,
19177         radius = canvas.getSize();
19178     var labelPos = {
19179       x: Math.round(pos.x * sx + ox + radius.width / 2),
19180       y: Math.round(pos.y * sy + oy + radius.height / 2)
19181     };
19182
19183     var style = tag.style;
19184     style.left = labelPos.x + 'px';
19185     style.top = labelPos.y + 'px';
19186     style.width = node.getData('width') * sx + 'px';
19187     style.height = node.getData('height') * sy + 'px';
19188     style.zIndex = node._depth * 100;
19189     style.display = '';
19190
19191     if(!this.leaf(node) && !this.config.titleHeight) {
19192       tag.style.display = 'none';
19193     }
19194     controller.onPlaceLabel(tag, node);
19195   }
19196 });
19197
19198 /*
19199   Class: TM.Plot.NodeTypes
19200
19201   This class contains a list of <Graph.Node> built-in types. 
19202   Node types implemented are 'none', 'rectangle'.
19203
19204   You can add your custom node types, customizing your visualization to the extreme.
19205
19206   Example:
19207
19208   (start code js)
19209     TM.Plot.NodeTypes.implement({
19210       'mySpecialType': {
19211         'render': function(node, canvas) {
19212           //print your custom node to canvas
19213         },
19214         //optional
19215         'contains': function(node, pos) {
19216           //return true if pos is inside the node or false otherwise
19217         }
19218       }
19219     });
19220   (end code)
19221
19222 */
19223 TM.Plot.NodeTypes = new Class( {
19224   'none': {
19225     'render': $.empty
19226   },
19227
19228   'rectangle': {
19229     'render': function(node, canvas, animating){
19230       var leaf = this.viz.leaf(node),
19231           config = this.config,
19232           offst = config.offset,
19233           titleHeight = config.titleHeight,
19234           pos = node.pos.getc(true),
19235           width = node.getData('width'),
19236           height = node.getData('height'),
19237           border = node.getData('border'),
19238           ctx = canvas.getCtx(),
19239           posx = pos.x + offst / 2, 
19240           posy = pos.y + offst / 2;
19241       if(width <= offst || height <= offst) return;
19242       if (leaf) {
19243         if(config.cushion) {
19244           var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1, 
19245               posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19246           var color = node.getData('color');
19247           var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
19248               function(r) { return r * 0.2 >> 0; }));
19249           lg.addColorStop(0, color);
19250           lg.addColorStop(1, colorGrad);
19251           ctx.fillStyle = lg;
19252         }
19253         ctx.fillRect(posx, posy, width - offst, height - offst);
19254         if(border) {
19255           ctx.save();
19256           ctx.strokeStyle = border;
19257           ctx.strokeRect(posx, posy, width - offst, height - offst);
19258           ctx.restore();
19259         }
19260       } else if(titleHeight > 0){
19261         ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19262             titleHeight - offst);
19263         if(border) {
19264           ctx.save();
19265           ctx.strokeStyle = border;
19266           ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19267               height - offst);
19268           ctx.restore();
19269         }
19270       }
19271     },
19272     'contains': function(node, pos) {
19273       if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19274       var npos = node.pos.getc(true),
19275           width = node.getData('width'), 
19276           leaf = this.viz.leaf(node),
19277           height = leaf? node.getData('height') : this.config.titleHeight;
19278       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19279     }
19280   }
19281 });
19282
19283 TM.Plot.EdgeTypes = new Class( {
19284   'none': $.empty
19285 });
19286
19287 /*
19288   Class: TM.SliceAndDice
19289   
19290   A slice and dice TreeMap visualization.
19291   
19292   Implements:
19293   
19294   All <TM.Base> methods and properties.
19295 */
19296 TM.SliceAndDice = new Class( {
19297   Implements: [
19298       Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19299   ]
19300 });
19301
19302 /*
19303   Class: TM.Squarified
19304   
19305   A squarified TreeMap visualization.
19306
19307   Implements:
19308   
19309   All <TM.Base> methods and properties.
19310 */
19311 TM.Squarified = new Class( {
19312   Implements: [
19313       Loader, Extras, TM.Base, Layouts.TM.Squarified
19314   ]
19315 });
19316
19317 /*
19318   Class: TM.Strip
19319   
19320   A strip TreeMap visualization.
19321
19322   Implements:
19323   
19324   All <TM.Base> methods and properties.
19325 */
19326 TM.Strip = new Class( {
19327   Implements: [
19328       Loader, Extras, TM.Base, Layouts.TM.Strip
19329   ]
19330 });
19331
19332
19333 /*
19334  * File: RGraph.js
19335  *
19336  */
19337
19338 /*
19339    Class: RGraph
19340    
19341    A radial graph visualization with advanced animations.
19342    
19343    Inspired by:
19344  
19345    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>
19346    
19347    Note:
19348    
19349    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.
19350    
19351   Implements:
19352   
19353   All <Loader> methods
19354   
19355    Constructor Options:
19356    
19357    Inherits options from
19358    
19359    - <Options.Canvas>
19360    - <Options.Controller>
19361    - <Options.Node>
19362    - <Options.Edge>
19363    - <Options.Label>
19364    - <Options.Events>
19365    - <Options.Tips>
19366    - <Options.NodeStyles>
19367    - <Options.Navigation>
19368    
19369    Additionally, there are other parameters and some default values changed
19370    
19371    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19372    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
19373      
19374    Instance Properties:
19375
19376    canvas - Access a <Canvas> instance.
19377    graph - Access a <Graph> instance.
19378    op - Access a <RGraph.Op> instance.
19379    fx - Access a <RGraph.Plot> instance.
19380    labels - Access a <RGraph.Label> interface implementation.   
19381 */
19382
19383 $jit.RGraph = new Class( {
19384
19385   Implements: [
19386       Loader, Extras, Layouts.Radial
19387   ],
19388
19389   initialize: function(controller){
19390     var $RGraph = $jit.RGraph;
19391
19392     var config = {
19393       interpolation: 'linear',
19394       levelDistance: 100
19395     };
19396
19397     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19398         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19399
19400     var canvasConfig = this.config;
19401     if(canvasConfig.useCanvas) {
19402       this.canvas = canvasConfig.useCanvas;
19403       this.config.labelContainer = this.canvas.id + '-label';
19404     } else {
19405       if(canvasConfig.background) {
19406         canvasConfig.background = $.merge({
19407           type: 'Circles'
19408         }, canvasConfig.background);
19409       }
19410       this.canvas = new Canvas(this, canvasConfig);
19411       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19412     }
19413
19414     this.graphOptions = {
19415       'complex': false,
19416       'Node': {
19417         'selected': false,
19418         'exist': true,
19419         'drawn': true
19420       }
19421     };
19422     this.graph = new Graph(this.graphOptions, this.config.Node,
19423         this.config.Edge);
19424     this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19425     this.fx = new $RGraph.Plot(this, $RGraph);
19426     this.op = new $RGraph.Op(this);
19427     this.json = null;
19428     this.root = null;
19429     this.busy = false;
19430     this.parent = false;
19431     // initialize extras
19432     this.initializeExtras();
19433   },
19434
19435   /* 
19436   
19437     createLevelDistanceFunc 
19438   
19439     Returns the levelDistance function used for calculating a node distance 
19440     to its origin. This function returns a function that is computed 
19441     per level and not per node, such that all nodes with the same depth will have the 
19442     same distance to the origin. The resulting function gets the 
19443     parent node as parameter and returns a float.
19444
19445    */
19446   createLevelDistanceFunc: function(){
19447     var ld = this.config.levelDistance;
19448     return function(elem){
19449       return (elem._depth + 1) * ld;
19450     };
19451   },
19452
19453   /* 
19454      Method: refresh 
19455      
19456      Computes positions and plots the tree.
19457
19458    */
19459   refresh: function(){
19460     this.compute();
19461     this.plot();
19462   },
19463
19464   reposition: function(){
19465     this.compute('end');
19466   },
19467
19468   /*
19469    Method: plot
19470   
19471    Plots the RGraph. This is a shortcut to *fx.plot*.
19472   */
19473   plot: function(){
19474     this.fx.plot();
19475   },
19476   /*
19477    getNodeAndParentAngle
19478   
19479    Returns the _parent_ of the given node, also calculating its angle span.
19480   */
19481   getNodeAndParentAngle: function(id){
19482     var theta = false;
19483     var n = this.graph.getNode(id);
19484     var ps = n.getParents();
19485     var p = (ps.length > 0)? ps[0] : false;
19486     if (p) {
19487       var posParent = p.pos.getc(), posChild = n.pos.getc();
19488       var newPos = posParent.add(posChild.scale(-1));
19489       theta = Math.atan2(newPos.y, newPos.x);
19490       if (theta < 0)
19491         theta += 2 * Math.PI;
19492     }
19493     return {
19494       parent: p,
19495       theta: theta
19496     };
19497   },
19498   /*
19499    tagChildren
19500   
19501    Enumerates the children in order to maintain child ordering (second constraint of the paper).
19502   */
19503   tagChildren: function(par, id){
19504     if (par.angleSpan) {
19505       var adjs = [];
19506       par.eachAdjacency(function(elem){
19507         adjs.push(elem.nodeTo);
19508       }, "ignore");
19509       var len = adjs.length;
19510       for ( var i = 0; i < len && id != adjs[i].id; i++)
19511         ;
19512       for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19513         adjs[j].dist = k++;
19514       }
19515     }
19516   },
19517   /* 
19518   Method: onClick 
19519   
19520   Animates the <RGraph> to center the node specified by *id*.
19521
19522    Parameters:
19523
19524    id - A <Graph.Node> id.
19525    opt - (optional|object) An object containing some extra properties described below
19526    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19527
19528    Example:
19529
19530    (start code js)
19531      rgraph.onClick('someid');
19532      //or also...
19533      rgraph.onClick('someid', {
19534       hideLabels: false
19535      });
19536     (end code)
19537     
19538   */
19539   onClick: function(id, opt){
19540     if (this.root != id && !this.busy) {
19541       this.busy = true;
19542       this.root = id;
19543       that = this;
19544       this.controller.onBeforeCompute(this.graph.getNode(id));
19545       var obj = this.getNodeAndParentAngle(id);
19546
19547       // second constraint
19548       this.tagChildren(obj.parent, id);
19549       this.parent = obj.parent;
19550       this.compute('end');
19551
19552       // first constraint
19553       var thetaDiff = obj.theta - obj.parent.endPos.theta;
19554       this.graph.eachNode(function(elem){
19555         elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19556       });
19557
19558       var mode = this.config.interpolation;
19559       opt = $.merge( {
19560         onComplete: $.empty
19561       }, opt || {});
19562
19563       this.fx.animate($.merge( {
19564         hideLabels: true,
19565         modes: [
19566           mode
19567         ]
19568       }, opt, {
19569         onComplete: function(){
19570           that.busy = false;
19571           opt.onComplete();
19572         }
19573       }));
19574     }
19575   }
19576 });
19577
19578 $jit.RGraph.$extend = true;
19579
19580 (function(RGraph){
19581
19582   /*
19583      Class: RGraph.Op
19584      
19585      Custom extension of <Graph.Op>.
19586
19587      Extends:
19588
19589      All <Graph.Op> methods
19590      
19591      See also:
19592      
19593      <Graph.Op>
19594
19595   */
19596   RGraph.Op = new Class( {
19597
19598     Implements: Graph.Op
19599
19600   });
19601
19602   /*
19603      Class: RGraph.Plot
19604     
19605     Custom extension of <Graph.Plot>.
19606   
19607     Extends:
19608   
19609     All <Graph.Plot> methods
19610     
19611     See also:
19612     
19613     <Graph.Plot>
19614   
19615   */
19616   RGraph.Plot = new Class( {
19617
19618     Implements: Graph.Plot
19619
19620   });
19621
19622   /*
19623     Object: RGraph.Label
19624
19625     Custom extension of <Graph.Label>. 
19626     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19627   
19628     Extends:
19629   
19630     All <Graph.Label> methods and subclasses.
19631   
19632     See also:
19633   
19634     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19635   
19636    */
19637   RGraph.Label = {};
19638
19639   /*
19640      RGraph.Label.Native
19641
19642      Custom extension of <Graph.Label.Native>.
19643
19644      Extends:
19645
19646      All <Graph.Label.Native> methods
19647
19648      See also:
19649
19650      <Graph.Label.Native>
19651
19652   */
19653   RGraph.Label.Native = new Class( {
19654     Implements: Graph.Label.Native
19655   });
19656
19657   /*
19658      RGraph.Label.SVG
19659     
19660     Custom extension of <Graph.Label.SVG>.
19661   
19662     Extends:
19663   
19664     All <Graph.Label.SVG> methods
19665   
19666     See also:
19667   
19668     <Graph.Label.SVG>
19669   
19670   */
19671   RGraph.Label.SVG = new Class( {
19672     Implements: Graph.Label.SVG,
19673
19674     initialize: function(viz){
19675       this.viz = viz;
19676     },
19677
19678     /* 
19679        placeLabel
19680
19681        Overrides abstract method placeLabel in <Graph.Plot>.
19682
19683        Parameters:
19684
19685        tag - A DOM label element.
19686        node - A <Graph.Node>.
19687        controller - A configuration/controller object passed to the visualization.
19688       
19689      */
19690     placeLabel: function(tag, node, controller){
19691       var pos = node.pos.getc(true), 
19692           canvas = this.viz.canvas,
19693           ox = canvas.translateOffsetX,
19694           oy = canvas.translateOffsetY,
19695           sx = canvas.scaleOffsetX,
19696           sy = canvas.scaleOffsetY,
19697           radius = canvas.getSize();
19698       var labelPos = {
19699         x: Math.round(pos.x * sx + ox + radius.width / 2),
19700         y: Math.round(pos.y * sy + oy + radius.height / 2)
19701       };
19702       tag.setAttribute('x', labelPos.x);
19703       tag.setAttribute('y', labelPos.y);
19704
19705       controller.onPlaceLabel(tag, node);
19706     }
19707   });
19708
19709   /*
19710      RGraph.Label.HTML
19711
19712      Custom extension of <Graph.Label.HTML>.
19713
19714      Extends:
19715
19716      All <Graph.Label.HTML> methods.
19717
19718      See also:
19719
19720      <Graph.Label.HTML>
19721
19722   */
19723   RGraph.Label.HTML = new Class( {
19724     Implements: Graph.Label.HTML,
19725
19726     initialize: function(viz){
19727       this.viz = viz;
19728     },
19729     /* 
19730        placeLabel
19731
19732        Overrides abstract method placeLabel in <Graph.Plot>.
19733
19734        Parameters:
19735
19736        tag - A DOM label element.
19737        node - A <Graph.Node>.
19738        controller - A configuration/controller object passed to the visualization.
19739       
19740      */
19741     placeLabel: function(tag, node, controller){
19742       var pos = node.pos.getc(true), 
19743           canvas = this.viz.canvas,
19744           ox = canvas.translateOffsetX,
19745           oy = canvas.translateOffsetY,
19746           sx = canvas.scaleOffsetX,
19747           sy = canvas.scaleOffsetY,
19748           radius = canvas.getSize();
19749       var labelPos = {
19750         x: Math.round(pos.x * sx + ox + radius.width / 2),
19751         y: Math.round(pos.y * sy + oy + radius.height / 2)
19752       };
19753
19754       var style = tag.style;
19755       style.left = labelPos.x + 'px';
19756       style.top = labelPos.y + 'px';
19757       style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
19758
19759       controller.onPlaceLabel(tag, node);
19760     }
19761   });
19762
19763   /*
19764     Class: RGraph.Plot.NodeTypes
19765
19766     This class contains a list of <Graph.Node> built-in types. 
19767     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
19768
19769     You can add your custom node types, customizing your visualization to the extreme.
19770
19771     Example:
19772
19773     (start code js)
19774       RGraph.Plot.NodeTypes.implement({
19775         'mySpecialType': {
19776           'render': function(node, canvas) {
19777             //print your custom node to canvas
19778           },
19779           //optional
19780           'contains': function(node, pos) {
19781             //return true if pos is inside the node or false otherwise
19782           }
19783         }
19784       });
19785     (end code)
19786
19787   */
19788   RGraph.Plot.NodeTypes = new Class({
19789     'none': {
19790       'render': $.empty,
19791       'contains': $.lambda(false)
19792     },
19793     'circle': {
19794       'render': function(node, canvas){
19795         var pos = node.pos.getc(true), 
19796             dim = node.getData('dim');
19797         this.nodeHelper.circle.render('fill', pos, dim, canvas);
19798       },
19799       'contains': function(node, pos){
19800         var npos = node.pos.getc(true), 
19801             dim = node.getData('dim');
19802         return this.nodeHelper.circle.contains(npos, pos, dim);
19803       }
19804     },
19805     'ellipse': {
19806       'render': function(node, canvas){
19807         var pos = node.pos.getc(true), 
19808             width = node.getData('width'), 
19809             height = node.getData('height');
19810         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
19811         },
19812       // TODO(nico): be more precise...
19813       'contains': function(node, pos){
19814         var npos = node.pos.getc(true), 
19815             width = node.getData('width'), 
19816             height = node.getData('height');
19817         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
19818       }
19819     },
19820     'square': {
19821       'render': function(node, canvas){
19822         var pos = node.pos.getc(true), 
19823             dim = node.getData('dim');
19824         this.nodeHelper.square.render('fill', pos, dim, canvas);
19825       },
19826       'contains': function(node, pos){
19827         var npos = node.pos.getc(true), 
19828             dim = node.getData('dim');
19829         return this.nodeHelper.square.contains(npos, pos, dim);
19830       }
19831     },
19832     'rectangle': {
19833       'render': function(node, canvas){
19834         var pos = node.pos.getc(true), 
19835             width = node.getData('width'), 
19836             height = node.getData('height');
19837         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
19838       },
19839       'contains': function(node, pos){
19840         var npos = node.pos.getc(true), 
19841             width = node.getData('width'), 
19842             height = node.getData('height');
19843         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
19844       }
19845     },
19846     'triangle': {
19847       'render': function(node, canvas){
19848         var pos = node.pos.getc(true), 
19849             dim = node.getData('dim');
19850         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
19851       },
19852       'contains': function(node, pos) {
19853         var npos = node.pos.getc(true), 
19854             dim = node.getData('dim');
19855         return this.nodeHelper.triangle.contains(npos, pos, dim);
19856       }
19857     },
19858     'star': {
19859       'render': function(node, canvas){
19860         var pos = node.pos.getc(true),
19861             dim = node.getData('dim');
19862         this.nodeHelper.star.render('fill', pos, dim, canvas);
19863       },
19864       'contains': function(node, pos) {
19865         var npos = node.pos.getc(true),
19866             dim = node.getData('dim');
19867         return this.nodeHelper.star.contains(npos, pos, dim);
19868       }
19869     }
19870   });
19871
19872   /*
19873     Class: RGraph.Plot.EdgeTypes
19874
19875     This class contains a list of <Graph.Adjacence> built-in types. 
19876     Edge types implemented are 'none', 'line' and 'arrow'.
19877   
19878     You can add your custom edge types, customizing your visualization to the extreme.
19879   
19880     Example:
19881   
19882     (start code js)
19883       RGraph.Plot.EdgeTypes.implement({
19884         'mySpecialType': {
19885           'render': function(adj, canvas) {
19886             //print your custom edge to canvas
19887           },
19888           //optional
19889           'contains': function(adj, pos) {
19890             //return true if pos is inside the arc or false otherwise
19891           }
19892         }
19893       });
19894     (end code)
19895   
19896   */
19897   RGraph.Plot.EdgeTypes = new Class({
19898     'none': $.empty,
19899     'line': {
19900       'render': function(adj, canvas) {
19901         var from = adj.nodeFrom.pos.getc(true),
19902             to = adj.nodeTo.pos.getc(true);
19903         this.edgeHelper.line.render(from, to, canvas);
19904       },
19905       'contains': function(adj, pos) {
19906         var from = adj.nodeFrom.pos.getc(true),
19907             to = adj.nodeTo.pos.getc(true);
19908         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
19909       }
19910     },
19911     'arrow': {
19912       'render': function(adj, canvas) {
19913         var from = adj.nodeFrom.pos.getc(true),
19914             to = adj.nodeTo.pos.getc(true),
19915             dim = adj.getData('dim'),
19916             direction = adj.data.$direction,
19917             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
19918         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
19919       },
19920       'contains': function(adj, pos) {
19921         var from = adj.nodeFrom.pos.getc(true),
19922             to = adj.nodeTo.pos.getc(true);
19923         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
19924       }
19925     }
19926   });
19927
19928 })($jit.RGraph);
19929
19930
19931 /*
19932  * File: Hypertree.js
19933  * 
19934 */
19935
19936 /* 
19937      Complex 
19938      
19939      A multi-purpose Complex Class with common methods. Extended for the Hypertree. 
19940  
19941 */
19942 /* 
19943    moebiusTransformation 
19944  
19945    Calculates a moebius transformation for this point / complex. 
19946     For more information go to: 
19947         http://en.wikipedia.org/wiki/Moebius_transformation. 
19948  
19949    Parameters: 
19950  
19951       c - An initialized Complex instance representing a translation Vector. 
19952 */
19953
19954 Complex.prototype.moebiusTransformation = function(c) {
19955   var num = this.add(c);
19956   var den = c.$conjugate().$prod(this);
19957   den.x++;
19958   return num.$div(den);
19959 };
19960
19961 /* 
19962     moebiusTransformation 
19963      
19964     Calculates a moebius transformation for the hyperbolic tree. 
19965      
19966     <http://en.wikipedia.org/wiki/Moebius_transformation> 
19967       
19968      Parameters: 
19969      
19970         graph - A <Graph> instance.
19971         pos - A <Complex>.
19972         prop - A property array.
19973         theta - Rotation angle. 
19974         startPos - _optional_ start position. 
19975 */
19976 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
19977   this.eachNode(graph, function(elem) {
19978     for ( var i = 0; i < prop.length; i++) {
19979       var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
19980       elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
19981     }
19982   }, flags);
19983 };
19984
19985 /* 
19986    Class: Hypertree 
19987    
19988    A Hyperbolic Tree/Graph visualization.
19989    
19990    Inspired by:
19991  
19992    A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli). 
19993    <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
19994  
19995   Note:
19996  
19997   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.
19998
19999   Implements:
20000   
20001   All <Loader> methods
20002   
20003   Constructor Options:
20004   
20005   Inherits options from
20006   
20007   - <Options.Canvas>
20008   - <Options.Controller>
20009   - <Options.Node>
20010   - <Options.Edge>
20011   - <Options.Label>
20012   - <Options.Events>
20013   - <Options.Tips>
20014   - <Options.NodeStyles>
20015   - <Options.Navigation>
20016   
20017   Additionally, there are other parameters and some default values changed
20018   
20019   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*.
20020   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.
20021   fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20022   duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20023   Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
20024   
20025   Instance Properties:
20026   
20027   canvas - Access a <Canvas> instance.
20028   graph - Access a <Graph> instance.
20029   op - Access a <Hypertree.Op> instance.
20030   fx - Access a <Hypertree.Plot> instance.
20031   labels - Access a <Hypertree.Label> interface implementation.
20032
20033 */
20034
20035 $jit.Hypertree = new Class( {
20036
20037   Implements: [ Loader, Extras, Layouts.Radial ],
20038
20039   initialize: function(controller) {
20040     var $Hypertree = $jit.Hypertree;
20041
20042     var config = {
20043       radius: "auto",
20044       offset: 0,
20045       Edge: {
20046         type: 'hyperline'
20047       },
20048       duration: 1500,
20049       fps: 35
20050     };
20051     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20052         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20053
20054     var canvasConfig = this.config;
20055     if(canvasConfig.useCanvas) {
20056       this.canvas = canvasConfig.useCanvas;
20057       this.config.labelContainer = this.canvas.id + '-label';
20058     } else {
20059       if(canvasConfig.background) {
20060         canvasConfig.background = $.merge({
20061           type: 'Circles'
20062         }, canvasConfig.background);
20063       }
20064       this.canvas = new Canvas(this, canvasConfig);
20065       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20066     }
20067
20068     this.graphOptions = {
20069       'complex': false,
20070       'Node': {
20071         'selected': false,
20072         'exist': true,
20073         'drawn': true
20074       }
20075     };
20076     this.graph = new Graph(this.graphOptions, this.config.Node,
20077         this.config.Edge);
20078     this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20079     this.fx = new $Hypertree.Plot(this, $Hypertree);
20080     this.op = new $Hypertree.Op(this);
20081     this.json = null;
20082     this.root = null;
20083     this.busy = false;
20084     // initialize extras
20085     this.initializeExtras();
20086   },
20087
20088   /* 
20089   
20090   createLevelDistanceFunc 
20091
20092   Returns the levelDistance function used for calculating a node distance 
20093   to its origin. This function returns a function that is computed 
20094   per level and not per node, such that all nodes with the same depth will have the 
20095   same distance to the origin. The resulting function gets the 
20096   parent node as parameter and returns a float.
20097
20098   */
20099   createLevelDistanceFunc: function() {
20100     // get max viz. length.
20101     var r = this.getRadius();
20102     // get max depth.
20103     var depth = 0, max = Math.max, config = this.config;
20104     this.graph.eachNode(function(node) {
20105       depth = max(node._depth, depth);
20106     }, "ignore");
20107     depth++;
20108     // node distance generator
20109     var genDistFunc = function(a) {
20110       return function(node) {
20111         node.scale = r;
20112         var d = node._depth + 1;
20113         var acum = 0, pow = Math.pow;
20114         while (d) {
20115           acum += pow(a, d--);
20116         }
20117         return acum - config.offset;
20118       };
20119     };
20120     // estimate better edge length.
20121     for ( var i = 0.51; i <= 1; i += 0.01) {
20122       var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20123       if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20124     }
20125     return genDistFunc(0.75);
20126   },
20127
20128   /* 
20129     Method: getRadius 
20130     
20131     Returns the current radius of the visualization. If *config.radius* is *auto* then it 
20132     calculates the radius by taking the smaller size of the <Canvas> widget.
20133     
20134     See also:
20135     
20136     <Canvas.getSize>
20137    
20138   */
20139   getRadius: function() {
20140     var rad = this.config.radius;
20141     if (rad !== "auto") { return rad; }
20142     var s = this.canvas.getSize();
20143     return Math.min(s.width, s.height) / 2;
20144   },
20145
20146   /* 
20147     Method: refresh 
20148     
20149     Computes positions and plots the tree.
20150
20151     Parameters:
20152
20153     reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20154
20155    */
20156   refresh: function(reposition) {
20157     if (reposition) {
20158       this.reposition();
20159       this.graph.eachNode(function(node) {
20160         node.startPos.rho = node.pos.rho = node.endPos.rho;
20161         node.startPos.theta = node.pos.theta = node.endPos.theta;
20162       });
20163     } else {
20164       this.compute();
20165     }
20166     this.plot();
20167   },
20168
20169   /* 
20170    reposition 
20171    
20172    Computes nodes' positions and restores the tree to its previous position.
20173
20174    For calculating nodes' positions the root must be placed on its origin. This method does this 
20175      and then attemps to restore the hypertree to its previous position.
20176     
20177   */
20178   reposition: function() {
20179     this.compute('end');
20180     var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20181     Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20182         'end', "ignore");
20183     this.graph.eachNode(function(node) {
20184       if (node.ignore) {
20185         node.endPos.rho = node.pos.rho;
20186         node.endPos.theta = node.pos.theta;
20187       }
20188     });
20189   },
20190
20191   /* 
20192    Method: plot 
20193    
20194    Plots the <Hypertree>. This is a shortcut to *fx.plot*. 
20195
20196   */
20197   plot: function() {
20198     this.fx.plot();
20199   },
20200
20201   /* 
20202    Method: onClick 
20203    
20204    Animates the <Hypertree> to center the node specified by *id*.
20205
20206    Parameters:
20207
20208    id - A <Graph.Node> id.
20209    opt - (optional|object) An object containing some extra properties described below
20210    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20211
20212    Example:
20213
20214    (start code js)
20215      ht.onClick('someid');
20216      //or also...
20217      ht.onClick('someid', {
20218       hideLabels: false
20219      });
20220     (end code)
20221     
20222   */
20223   onClick: function(id, opt) {
20224     var pos = this.graph.getNode(id).pos.getc(true);
20225     this.move(pos, opt);
20226   },
20227
20228   /* 
20229    Method: move 
20230
20231    Translates the tree to the given position. 
20232
20233    Parameters:
20234
20235    pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20236    opt - This object has been defined in <Hypertree.onClick>
20237    
20238    Example:
20239    
20240    (start code js)
20241      ht.move({ x: 0, y: 0.7 }, {
20242        hideLabels: false
20243      });
20244    (end code)
20245
20246   */
20247   move: function(pos, opt) {
20248     var versor = $C(pos.x, pos.y);
20249     if (this.busy === false && versor.norm() < 1) {
20250       this.busy = true;
20251       var root = this.graph.getClosestNodeToPos(versor), that = this;
20252       this.graph.computeLevels(root.id, 0);
20253       this.controller.onBeforeCompute(root);
20254       opt = $.merge( {
20255         onComplete: $.empty
20256       }, opt || {});
20257       this.fx.animate($.merge( {
20258         modes: [ 'moebius' ],
20259         hideLabels: true
20260       }, opt, {
20261         onComplete: function() {
20262           that.busy = false;
20263           opt.onComplete();
20264         }
20265       }), versor);
20266     }
20267   }
20268 });
20269
20270 $jit.Hypertree.$extend = true;
20271
20272 (function(Hypertree) {
20273
20274   /* 
20275      Class: Hypertree.Op 
20276    
20277      Custom extension of <Graph.Op>.
20278
20279      Extends:
20280
20281      All <Graph.Op> methods
20282      
20283      See also:
20284      
20285      <Graph.Op>
20286
20287   */
20288   Hypertree.Op = new Class( {
20289
20290     Implements: Graph.Op
20291
20292   });
20293
20294   /* 
20295      Class: Hypertree.Plot 
20296    
20297     Custom extension of <Graph.Plot>.
20298   
20299     Extends:
20300   
20301     All <Graph.Plot> methods
20302     
20303     See also:
20304     
20305     <Graph.Plot>
20306   
20307   */
20308   Hypertree.Plot = new Class( {
20309
20310     Implements: Graph.Plot
20311
20312   });
20313
20314   /*
20315     Object: Hypertree.Label
20316
20317     Custom extension of <Graph.Label>. 
20318     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20319   
20320     Extends:
20321   
20322     All <Graph.Label> methods and subclasses.
20323   
20324     See also:
20325   
20326     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20327
20328    */
20329   Hypertree.Label = {};
20330
20331   /*
20332      Hypertree.Label.Native
20333
20334      Custom extension of <Graph.Label.Native>.
20335
20336      Extends:
20337
20338      All <Graph.Label.Native> methods
20339
20340      See also:
20341
20342      <Graph.Label.Native>
20343
20344   */
20345   Hypertree.Label.Native = new Class( {
20346     Implements: Graph.Label.Native,
20347
20348     initialize: function(viz) {
20349       this.viz = viz;
20350     },
20351
20352     renderLabel: function(canvas, node, controller) {
20353       var ctx = canvas.getCtx();
20354       var coord = node.pos.getc(true);
20355       var s = this.viz.getRadius();
20356       ctx.fillText(node.name, coord.x * s, coord.y * s);
20357     }
20358   });
20359
20360   /*
20361      Hypertree.Label.SVG
20362
20363     Custom extension of <Graph.Label.SVG>.
20364   
20365     Extends:
20366   
20367     All <Graph.Label.SVG> methods
20368   
20369     See also:
20370   
20371     <Graph.Label.SVG>
20372   
20373   */
20374   Hypertree.Label.SVG = new Class( {
20375     Implements: Graph.Label.SVG,
20376
20377     initialize: function(viz) {
20378       this.viz = viz;
20379     },
20380
20381     /* 
20382        placeLabel
20383
20384        Overrides abstract method placeLabel in <Graph.Plot>.
20385
20386        Parameters:
20387
20388        tag - A DOM label element.
20389        node - A <Graph.Node>.
20390        controller - A configuration/controller object passed to the visualization.
20391       
20392      */
20393     placeLabel: function(tag, node, controller) {
20394       var pos = node.pos.getc(true), 
20395           canvas = this.viz.canvas,
20396           ox = canvas.translateOffsetX,
20397           oy = canvas.translateOffsetY,
20398           sx = canvas.scaleOffsetX,
20399           sy = canvas.scaleOffsetY,
20400           radius = canvas.getSize(),
20401           r = this.viz.getRadius();
20402       var labelPos = {
20403         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20404         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20405       };
20406       tag.setAttribute('x', labelPos.x);
20407       tag.setAttribute('y', labelPos.y);
20408       controller.onPlaceLabel(tag, node);
20409     }
20410   });
20411
20412   /*
20413      Hypertree.Label.HTML
20414
20415      Custom extension of <Graph.Label.HTML>.
20416
20417      Extends:
20418
20419      All <Graph.Label.HTML> methods.
20420
20421      See also:
20422
20423      <Graph.Label.HTML>
20424
20425   */
20426   Hypertree.Label.HTML = new Class( {
20427     Implements: Graph.Label.HTML,
20428
20429     initialize: function(viz) {
20430       this.viz = viz;
20431     },
20432     /* 
20433        placeLabel
20434
20435        Overrides abstract method placeLabel in <Graph.Plot>.
20436
20437        Parameters:
20438
20439        tag - A DOM label element.
20440        node - A <Graph.Node>.
20441        controller - A configuration/controller object passed to the visualization.
20442       
20443      */
20444     placeLabel: function(tag, node, controller) {
20445       var pos = node.pos.getc(true), 
20446           canvas = this.viz.canvas,
20447           ox = canvas.translateOffsetX,
20448           oy = canvas.translateOffsetY,
20449           sx = canvas.scaleOffsetX,
20450           sy = canvas.scaleOffsetY,
20451           radius = canvas.getSize(),
20452           r = this.viz.getRadius();
20453       var labelPos = {
20454         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20455         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20456       };
20457       var style = tag.style;
20458       style.left = labelPos.x + 'px';
20459       style.top = labelPos.y + 'px';
20460       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20461
20462       controller.onPlaceLabel(tag, node);
20463     }
20464   });
20465
20466   /*
20467     Class: Hypertree.Plot.NodeTypes
20468
20469     This class contains a list of <Graph.Node> built-in types. 
20470     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20471
20472     You can add your custom node types, customizing your visualization to the extreme.
20473
20474     Example:
20475
20476     (start code js)
20477       Hypertree.Plot.NodeTypes.implement({
20478         'mySpecialType': {
20479           'render': function(node, canvas) {
20480             //print your custom node to canvas
20481           },
20482           //optional
20483           'contains': function(node, pos) {
20484             //return true if pos is inside the node or false otherwise
20485           }
20486         }
20487       });
20488     (end code)
20489
20490   */
20491   Hypertree.Plot.NodeTypes = new Class({
20492     'none': {
20493       'render': $.empty,
20494       'contains': $.lambda(false)
20495     },
20496     'circle': {
20497       'render': function(node, canvas) {
20498         var nconfig = this.node,
20499             dim = node.getData('dim'),
20500             p = node.pos.getc();
20501         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20502         p.$scale(node.scale);
20503         if (dim > 0.2) {
20504           this.nodeHelper.circle.render('fill', p, dim, canvas);
20505         }
20506       },
20507       'contains': function(node, pos) {
20508         var dim = node.getData('dim'),
20509             npos = node.pos.getc().$scale(node.scale);
20510         return this.nodeHelper.circle.contains(npos, pos, dim);
20511       }
20512     },
20513     'ellipse': {
20514       'render': function(node, canvas) {
20515         var pos = node.pos.getc().$scale(node.scale),
20516             width = node.getData('width'),
20517             height = node.getData('height');
20518         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20519       },
20520       'contains': function(node, pos) {
20521         var width = node.getData('width'),
20522             height = node.getData('height'),
20523             npos = node.pos.getc().$scale(node.scale);
20524         return this.nodeHelper.circle.contains(npos, pos, width, height);
20525       }
20526     },
20527     'square': {
20528       'render': function(node, canvas) {
20529         var nconfig = this.node,
20530             dim = node.getData('dim'),
20531             p = node.pos.getc();
20532         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20533         p.$scale(node.scale);
20534         if (dim > 0.2) {
20535           this.nodeHelper.square.render('fill', p, dim, canvas);
20536         }
20537       },
20538       'contains': function(node, pos) {
20539         var dim = node.getData('dim'),
20540             npos = node.pos.getc().$scale(node.scale);
20541         return this.nodeHelper.square.contains(npos, pos, dim);
20542       }
20543     },
20544     'rectangle': {
20545       'render': function(node, canvas) {
20546         var nconfig = this.node,
20547             width = node.getData('width'),
20548             height = node.getData('height'),
20549             pos = node.pos.getc();
20550         width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20551         height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20552         pos.$scale(node.scale);
20553         if (width > 0.2 && height > 0.2) {
20554           this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20555         }
20556       },
20557       'contains': function(node, pos) {
20558         var width = node.getData('width'),
20559             height = node.getData('height'),
20560             npos = node.pos.getc().$scale(node.scale);
20561         return this.nodeHelper.square.contains(npos, pos, width, height);
20562       }
20563     },
20564     'triangle': {
20565       'render': function(node, canvas) {
20566         var nconfig = this.node,
20567             dim = node.getData('dim'),
20568             p = node.pos.getc();
20569         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20570         p.$scale(node.scale);
20571         if (dim > 0.2) {
20572           this.nodeHelper.triangle.render('fill', p, dim, canvas);
20573         }
20574       },
20575       'contains': function(node, pos) {
20576         var dim = node.getData('dim'),
20577             npos = node.pos.getc().$scale(node.scale);
20578         return this.nodeHelper.triangle.contains(npos, pos, dim);
20579       }
20580     },
20581     'star': {
20582       'render': function(node, canvas) {
20583         var nconfig = this.node,
20584             dim = node.getData('dim'),
20585             p = node.pos.getc();
20586         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20587         p.$scale(node.scale);
20588         if (dim > 0.2) {
20589           this.nodeHelper.star.render('fill', p, dim, canvas);
20590         }
20591       },
20592       'contains': function(node, pos) {
20593         var dim = node.getData('dim'),
20594             npos = node.pos.getc().$scale(node.scale);
20595         return this.nodeHelper.star.contains(npos, pos, dim);
20596       }
20597     }
20598   });
20599
20600   /*
20601    Class: Hypertree.Plot.EdgeTypes
20602
20603     This class contains a list of <Graph.Adjacence> built-in types. 
20604     Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20605   
20606     You can add your custom edge types, customizing your visualization to the extreme.
20607   
20608     Example:
20609   
20610     (start code js)
20611       Hypertree.Plot.EdgeTypes.implement({
20612         'mySpecialType': {
20613           'render': function(adj, canvas) {
20614             //print your custom edge to canvas
20615           },
20616           //optional
20617           'contains': function(adj, pos) {
20618             //return true if pos is inside the arc or false otherwise
20619           }
20620         }
20621       });
20622     (end code)
20623   
20624   */
20625   Hypertree.Plot.EdgeTypes = new Class({
20626     'none': $.empty,
20627     'line': {
20628       'render': function(adj, canvas) {
20629         var from = adj.nodeFrom.pos.getc(true),
20630           to = adj.nodeTo.pos.getc(true),
20631           r = adj.nodeFrom.scale;
20632           this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20633       },
20634       'contains': function(adj, pos) {
20635         var from = adj.nodeFrom.pos.getc(true),
20636             to = adj.nodeTo.pos.getc(true),
20637             r = adj.nodeFrom.scale;
20638             this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20639       }
20640     },
20641     'arrow': {
20642       'render': function(adj, canvas) {
20643         var from = adj.nodeFrom.pos.getc(true),
20644             to = adj.nodeTo.pos.getc(true),
20645             r = adj.nodeFrom.scale,
20646             dim = adj.getData('dim'),
20647             direction = adj.data.$direction,
20648             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20649         this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20650       },
20651       'contains': function(adj, pos) {
20652         var from = adj.nodeFrom.pos.getc(true),
20653             to = adj.nodeTo.pos.getc(true),
20654             r = adj.nodeFrom.scale;
20655         this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20656       }
20657     },
20658     'hyperline': {
20659       'render': function(adj, canvas) {
20660         var from = adj.nodeFrom.pos.getc(),
20661             to = adj.nodeTo.pos.getc(),
20662             dim = this.viz.getRadius();
20663         this.edgeHelper.hyperline.render(from, to, dim, canvas);
20664       },
20665       'contains': $.lambda(false)
20666     }
20667   });
20668
20669 })($jit.Hypertree);
20670
20671
20672
20673
20674  })();