]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/SugarCharts/Jit/js/Jit/jit.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / SugarCharts / Jit / js / Jit / jit.js
1 /*
2   Copyright (c) 2010, Nicolas Garcia Belmonte
3   All rights reserved
4
5   > Redistribution and use in source and binary forms, with or without
6   > modification, are permitted provided that the following conditions are met:
7   >      * Redistributions of source code must retain the above copyright
8   >        notice, this list of conditions and the following disclaimer.
9   >      * Redistributions in binary form must reproduce the above copyright
10   >        notice, this list of conditions and the following disclaimer in the
11   >        documentation and/or other materials provided with the distribution.
12   >      * Neither the name of the organization nor the
13   >        names of its contributors may be used to endorse or promote products
14   >        derived from this software without specific prior written permission.
15   >
16   >  THIS SOFTWARE IS PROVIDED BY NICOLAS GARCIA BELMONTE ``AS IS'' AND ANY
17   >  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   >  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   >  DISCLAIMED. IN NO EVENT SHALL NICOLAS GARCIA BELMONTE BE LIABLE FOR ANY
20   >  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   >  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   >  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   >  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   >  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   >  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27  
28  /** Lam Huynh on 10/10/2010 added funnel,guage charts **/
29  /** Lam Huynh on 02/27/2011 added image exporting **/
30  /** Lam Huynh on 02/23/2011 added line charts **/
31  
32  (function () { 
33
34 /*
35   File: Core.js
36
37  */
38
39 /*
40  Object: $jit
41  
42  Defines the namespace for all library Classes and Objects. 
43  This variable is the *only* global variable defined in the Toolkit. 
44  There are also other interesting properties attached to this variable described below.
45  */
46 window.$jit = function(w) {
47   w = w || window;
48   for(var k in $jit) {
49     if($jit[k].$extend) {
50       w[k] = $jit[k];
51     }
52   }
53 };
54
55 $jit.version = '2.0.0b';
56 /*
57   Object: $jit.id
58   
59   Works just like *document.getElementById*
60   
61   Example:
62   (start code js)
63   var element = $jit.id('elementId');
64   (end code)
65
66 */
67
68 /*
69  Object: $jit.util
70  
71  Contains utility functions.
72  
73  Some of the utility functions and the Class system were based in the MooTools Framework 
74  <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. 
75  MIT license <http://mootools.net/license.txt>.
76  
77  These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
78  I'd suggest you to use the functions from those libraries instead of using these, since their functions 
79  are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
80  
81  */
82 var $ = function(d) {
83   return document.getElementById(d);
84 };
85
86 $.empty = function() {
87 };
88
89 function pad(number, length) {
90    
91     var str = '' + number;
92     while (str.length < length) {
93         str =  str + '0';
94     }
95    
96     return str;
97
98 };
99
100 var Url = {
101  
102         // public method for url encoding
103         encode : function (string) {
104                 return escape(this._utf8_encode(string));
105         },
106  
107         // public method for url decoding
108         decode : function (string) {
109                 return this._utf8_decode(unescape(string));
110         },
111  
112         // private method for UTF-8 encoding
113         _utf8_encode : function (string) {
114                 string = string.replace(/\r\n/g,"\n");
115                 var utftext = "";
116  
117                 for (var n = 0; n < string.length; n++) {
118  
119                         var c = string.charCodeAt(n);
120  
121                         if (c < 128) {
122                                 utftext += String.fromCharCode(c);
123                         }
124                         else if((c > 127) && (c < 2048)) {
125                                 utftext += String.fromCharCode((c >> 6) | 192);
126                                 utftext += String.fromCharCode((c & 63) | 128);
127                         }
128                         else {
129                                 utftext += String.fromCharCode((c >> 12) | 224);
130                                 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
131                                 utftext += String.fromCharCode((c & 63) | 128);
132                         }
133  
134                 }
135  
136                 return utftext;
137         },
138  
139         // private method for UTF-8 decoding
140         _utf8_decode : function (utftext) {
141                 var string = "";
142                 var i = 0;
143                 var c = c1 = c2 = 0;
144  
145                 while ( i < utftext.length ) {
146  
147                         c = utftext.charCodeAt(i);
148  
149                         if (c < 128) {
150                                 string += String.fromCharCode(c);
151                                 i++;
152                         }
153                         else if((c > 191) && (c < 224)) {
154                                 c2 = utftext.charCodeAt(i+1);
155                                 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
156                                 i += 2;
157                         }
158                         else {
159                                 c2 = utftext.charCodeAt(i+1);
160                                 c3 = utftext.charCodeAt(i+2);
161                                 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
162                                 i += 3;
163                         }
164  
165                 }
166  
167                 return string;
168         }
169  
170 };
171
172 Array.prototype.sum = function() {
173   return (! this.length) ? 0 : this.slice(1).sum() +
174       ((typeof this[0] == 'number') ? this[0] : 0);
175 };
176
177 function array_match(needle, haystack) {
178     var length = haystack.length;
179     var indexValue = new Array();
180     for(var i = 0, count = 0; i < length; i++) {
181         if(haystack[i] == needle) {
182                 indexValue[count] = i;
183                 count++;
184         }
185     }
186     return new Array(count,indexValue);
187 };
188
189 $.roundedRect = function (ctx,x,y,width,height,radius,fillType){
190   ctx.beginPath();
191   ctx.moveTo(x,y+radius);
192   ctx.lineTo(x,y+height-radius);
193   ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
194   ctx.lineTo(x+width-radius,y+height);
195   ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
196   ctx.lineTo(x+width,y+radius);
197   ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
198   ctx.lineTo(x+radius,y);
199   ctx.quadraticCurveTo(x,y,x,y+radius);
200   if(fillType=="fill") {
201         ctx.fill();
202   } else {
203         ctx.stroke();
204         }
205 };
206
207 $.saveImageFile = function (id,jsonfilename,imageExt) {
208         var parts = jsonfilename.split("/");
209         var filename = parts[2].replace(".js","."+imageExt);
210         var oCanvas = document.getElementById(id+"-canvas");
211         
212         if(oCanvas) {
213                 if(imageExt == "jpg") {
214                         var strDataURI = oCanvas.toDataURL("image/jpeg"); 
215                 } else {
216                         var strDataURI = oCanvas.toDataURL("image/png");
217                 }
218                 var handleFailure = function(o){
219                         //alert('failed to write image' + filename);
220                         //remove alert since chrome triggers this function when user navigates away from page before image gets written.
221                 }       
222                 var handleSuccess = function(o){
223                 }                       
224                 var callback =
225                 {
226                   success:handleSuccess,
227                   failure:handleFailure,
228                   argument: { foo:'foo', bar:''}
229                 };
230                 var path = "index.php?action=DynamicAction&DynamicAction=saveImage&module=Charts&to_pdf=1";
231                 var postData = "imageStr=" + strDataURI + "&filename=" + filename;
232                 var request = YAHOO.util.Connect.asyncRequest('POST', path, callback, postData);
233         }
234 };
235
236 $.saveImageTest = function (id,jsonfilename,imageExt) {
237                 if(typeof FlashCanvas != "undefined") {
238                         setTimeout(function(){$.saveImageFile(id,jsonfilename,imageExt)},10000);
239                 } else {
240                         $.saveImageFile(id,jsonfilename,imageExt);
241                 }
242         };
243 /*
244   Method: extend
245   
246   Augment an object by appending another object's properties.
247   
248   Parameters:
249   
250   original - (object) The object to be extended.
251   extended - (object) An object which properties are going to be appended to the original object.
252   
253   Example:
254   (start code js)
255   $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
256   (end code)
257 */
258 $.extend = function(original, extended) {
259   for ( var key in (extended || {}))
260     original[key] = extended[key];
261   return original;
262 };
263
264 $.lambda = function(value) {
265   return (typeof value == 'function') ? value : function() {
266     return value;
267   };
268 };
269
270 $.time = Date.now || function() {
271   return +new Date;
272 };
273
274 /*
275   Method: splat
276   
277   Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
278   
279   Parameters:
280   
281   obj - (mixed) The object to be wrapped in an array.
282   
283   Example:
284   (start code js)
285   $jit.util.splat(3);   //[3]
286   $jit.util.splat([3]); //[3]
287   (end code)
288 */
289 $.splat = function(obj) {
290   var type = $.type(obj);
291   return type ? ((type != 'array') ? [ obj ] : obj) : [];
292 };
293
294 $.type = function(elem) {
295   var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
296   if(type != 'object') return type;
297   if(elem && elem.$$family) return elem.$$family;
298   return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
299 };
300 $.type.s = Object.prototype.toString;
301
302 /*
303   Method: each
304   
305   Iterates through an iterable applying *f*.
306   
307   Parameters:
308   
309   iterable - (array) The original array.
310   fn - (function) The function to apply to the array elements.
311   
312   Example:
313   (start code js)
314   $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
315   (end code)
316 */
317 $.each = function(iterable, fn) {
318   var type = $.type(iterable);
319   if (type == 'object') {
320     for ( var key in iterable)
321       fn(iterable[key], key);
322   } else {
323     for ( var i = 0, l = iterable.length; i < l; i++)
324       fn(iterable[i], i);
325   }
326 };
327
328 $.indexOf = function(array, item) {
329   if(Array.indexOf) return array.indexOf(item);
330   for(var i=0,l=array.length; i<l; i++) {
331     if(array[i] === item) return i;
332   }
333   return -1;
334 };
335
336 /*
337   Method: map
338   
339   Maps or collects an array by applying *f*.
340   
341   Parameters:
342   
343   array - (array) The original array.
344   f - (function) The function to apply to the array elements.
345   
346   Example:
347   (start code js)
348   $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
349   (end code)
350 */
351 $.map = function(array, f) {
352   var ans = [];
353   $.each(array, function(elem, i) {
354     ans.push(f(elem, i));
355   });
356   return ans;
357 };
358
359 /*
360   Method: reduce
361   
362   Iteratively applies the binary function *f* storing the result in an accumulator.
363   
364   Parameters:
365   
366   array - (array) The original array.
367   f - (function) The function to apply to the array elements.
368   opt - (optional|mixed) The starting value for the acumulator.
369   
370   Example:
371   (start code js)
372   $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
373   (end code)
374 */
375 $.reduce = function(array, f, opt) {
376   var l = array.length;
377   if(l==0) return opt;
378   var acum = arguments.length == 3? opt : array[--l];
379   while(l--) {
380     acum = f(acum, array[l]);
381   }
382   return acum;
383 };
384
385 /*
386   Method: merge
387   
388   Merges n-objects and their sub-objects creating a new, fresh object.
389   
390   Parameters:
391   
392   An arbitrary number of objects.
393   
394   Example:
395   (start code js)
396   $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
397   (end code)
398 */
399 $.merge = function() {
400   var mix = {};
401   for ( var i = 0, l = arguments.length; i < l; i++) {
402     var object = arguments[i];
403     if ($.type(object) != 'object')
404       continue;
405     for ( var key in object) {
406       var op = object[key], mp = mix[key];
407       mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
408           .merge(mp, op) : $.unlink(op);
409     }
410   }
411   return mix;
412 };
413
414 $.unlink = function(object) {
415   var unlinked;
416   switch ($.type(object)) {
417   case 'object':
418     unlinked = {};
419     for ( var p in object)
420       unlinked[p] = $.unlink(object[p]);
421     break;
422   case 'array':
423     unlinked = [];
424     for ( var i = 0, l = object.length; i < l; i++)
425       unlinked[i] = $.unlink(object[i]);
426     break;
427   default:
428     return object;
429   }
430   return unlinked;
431 };
432
433 $.zip = function() {
434   if(arguments.length === 0) return [];
435   for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
436     for(var i=0, row=[]; i<l; i++) {
437       row.push(arguments[i][j]);
438     }
439     ans.push(row);
440   }
441   return ans;
442 };
443
444 /*
445   Method: rgbToHex
446   
447   Converts an RGB array into a Hex string.
448   
449   Parameters:
450   
451   srcArray - (array) An array with R, G and B values
452   
453   Example:
454   (start code js)
455   $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
456   (end code)
457 */
458 $.rgbToHex = function(srcArray, array) {
459   if (srcArray.length < 3)
460     return null;
461   if (srcArray.length == 4 && srcArray[3] == 0 && !array)
462     return 'transparent';
463   var hex = [];
464   for ( var i = 0; i < 3; i++) {
465     var bit = (srcArray[i] - 0).toString(16);
466     hex.push(bit.length == 1 ? '0' + bit : bit);
467   }
468   return array ? hex : '#' + hex.join('');
469 };
470
471 /*
472   Method: hexToRgb
473   
474   Converts an Hex color string into an RGB array.
475   
476   Parameters:
477   
478   hex - (string) A color hex string.
479   
480   Example:
481   (start code js)
482   $jit.util.hexToRgb('#fff'); //[255, 255, 255]
483   (end code)
484 */
485 $.hexToRgb = function(hex) {
486   if (hex.length != 7) {
487     hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
488     hex.shift();
489     if (hex.length != 3)
490       return null;
491     var rgb = [];
492     for ( var i = 0; i < 3; i++) {
493       var value = hex[i];
494       if (value.length == 1)
495         value += value;
496       rgb.push(parseInt(value, 16));
497     }
498     return rgb;
499   } else {
500     hex = parseInt(hex.slice(1), 16);
501     return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
502   }
503 };
504
505 $.destroy = function(elem) {
506   $.clean(elem);
507   if (elem.parentNode)
508     elem.parentNode.removeChild(elem);
509   if (elem.clearAttributes)
510     elem.clearAttributes();
511 };
512
513 $.clean = function(elem) {
514   for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
515     $.destroy(ch[i]);
516   }
517 };
518
519 /*
520   Method: addEvent
521   
522   Cross-browser add event listener.
523   
524   Parameters:
525   
526   obj - (obj) The Element to attach the listener to.
527   type - (string) The listener type. For example 'click', or 'mousemove'.
528   fn - (function) The callback function to be used when the event is fired.
529   
530   Example:
531   (start code js)
532   $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
533   (end code)
534 */
535 $.addEvent = function(obj, type, fn) {
536   if (obj.addEventListener)
537     obj.addEventListener(type, fn, false);
538   else
539     obj.attachEvent('on' + type, fn);
540 };
541
542 $.addEvents = function(obj, typeObj) {
543   for(var type in typeObj) {
544     $.addEvent(obj, type, typeObj[type]);
545   }
546 };
547
548 $.hasClass = function(obj, klass) {
549   return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
550 };
551
552 $.addClass = function(obj, klass) {
553   if (!$.hasClass(obj, klass))
554     obj.className = (obj.className + " " + klass);
555 };
556
557 $.removeClass = function(obj, klass) {
558   obj.className = obj.className.replace(new RegExp(
559       '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
560 };
561
562 $.getPos = function(elem) {
563   var offset = getOffsets(elem);
564   var scroll = getScrolls(elem);
565   return {
566     x: offset.x - scroll.x,
567     y: offset.y - scroll.y
568   };
569
570   function getOffsets(elem) {
571     var position = {
572       x: 0,
573       y: 0
574     };
575     while (elem && !isBody(elem)) {
576       position.x += elem.offsetLeft;
577       position.y += elem.offsetTop;
578       try{elem=elem.offsetParent;}catch(e){elem=document.body;}
579     }
580     return position;
581   }
582
583   function getScrolls(elem) {
584     var position = {
585       x: 0,
586       y: 0
587     };
588     while (elem && !isBody(elem)) {
589       position.x += elem.scrollLeft;
590       position.y += elem.scrollTop;
591       elem = elem.parentNode;
592     }
593     return position;
594   }
595
596   function isBody(element) {
597     return (/^(?:body|html)$/i).test(element.tagName);
598   }
599 };
600
601 $.event = {
602   get: function(e, win) {
603     win = win || window;
604     return e || win.event;
605   },
606   getWheel: function(e) {
607     return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
608   },
609   isRightClick: function(e) {
610     return (e.which == 3 || e.button == 2);
611   },
612   getPos: function(e, win) {
613     // get mouse position
614     win = win || window;
615     e = e || win.event;
616     var doc = win.document;
617     doc = doc.documentElement || doc.body;
618     //TODO(nico): make touch event handling better
619     if(e.touches && e.touches.length) {
620       e = e.touches[0];
621     }
622     var page = {
623       x: e.pageX || (e.clientX + doc.scrollLeft),
624       y: e.pageY || (e.clientY + doc.scrollTop)
625     };
626     return page;
627   },
628   stop: function(e) {
629     if (e.stopPropagation) e.stopPropagation();
630     e.cancelBubble = true;
631     if (e.preventDefault) e.preventDefault();
632     else e.returnValue = false;
633   }
634 };
635
636 $jit.util = $jit.id = $;
637
638 var Class = function(properties) {
639   properties = properties || {};
640   var klass = function() {
641     for ( var key in this) {
642       if (typeof this[key] != 'function')
643         this[key] = $.unlink(this[key]);
644     }
645     this.constructor = klass;
646     if (Class.prototyping)
647       return this;
648     var instance = this.initialize ? this.initialize.apply(this, arguments)
649         : this;
650     //typize
651     this.$$family = 'class';
652     return instance;
653   };
654
655   for ( var mutator in Class.Mutators) {
656     if (!properties[mutator])
657       continue;
658     properties = Class.Mutators[mutator](properties, properties[mutator]);
659     delete properties[mutator];
660   }
661
662   $.extend(klass, this);
663   klass.constructor = Class;
664   klass.prototype = properties;
665   return klass;
666 };
667
668 Class.Mutators = {
669
670   Implements: function(self, klasses) {
671     $.each($.splat(klasses), function(klass) {
672       Class.prototyping = klass;
673       var instance = (typeof klass == 'function') ? new klass : klass;
674       for ( var prop in instance) {
675         if (!(prop in self)) {
676           self[prop] = instance[prop];
677         }
678       }
679       delete Class.prototyping;
680     });
681     return self;
682   }
683
684 };
685
686 $.extend(Class, {
687
688   inherit: function(object, properties) {
689     for ( var key in properties) {
690       var override = properties[key];
691       var previous = object[key];
692       var type = $.type(override);
693       if (previous && type == 'function') {
694         if (override != previous) {
695           Class.override(object, key, override);
696         }
697       } else if (type == 'object') {
698         object[key] = $.merge(previous, override);
699       } else {
700         object[key] = override;
701       }
702     }
703     return object;
704   },
705
706   override: function(object, name, method) {
707     var parent = Class.prototyping;
708     if (parent && object[name] != parent[name])
709       parent = null;
710     var override = function() {
711       var previous = this.parent;
712       this.parent = parent ? parent[name] : object[name];
713       var value = method.apply(this, arguments);
714       this.parent = previous;
715       return value;
716     };
717     object[name] = override;
718   }
719
720 });
721
722 Class.prototype.implement = function() {
723   var proto = this.prototype;
724   $.each(Array.prototype.slice.call(arguments || []), function(properties) {
725     Class.inherit(proto, properties);
726   });
727   return this;
728 };
729
730 $jit.Class = Class;
731
732 /*
733   Object: $jit.json
734   
735   Provides JSON utility functions.
736   
737   Most of these functions are JSON-tree traversal and manipulation functions.
738 */
739 $jit.json = {
740   /*
741      Method: prune
742   
743      Clears all tree nodes having depth greater than maxLevel.
744   
745      Parameters:
746   
747         tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
748         maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
749
750   */
751   prune: function(tree, maxLevel) {
752     this.each(tree, function(elem, i) {
753       if (i == maxLevel && elem.children) {
754         delete elem.children;
755         elem.children = [];
756       }
757     });
758   },
759   /*
760      Method: getParent
761   
762      Returns the parent node of the node having _id_ as id.
763   
764      Parameters:
765   
766         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
767         id - (string) The _id_ of the child node whose parent will be returned.
768
769     Returns:
770
771         A tree JSON node if any, or false otherwise.
772   
773   */
774   getParent: function(tree, id) {
775     if (tree.id == id)
776       return false;
777     var ch = tree.children;
778     if (ch && ch.length > 0) {
779       for ( var i = 0; i < ch.length; i++) {
780         if (ch[i].id == id)
781           return tree;
782         else {
783           var ans = this.getParent(ch[i], id);
784           if (ans)
785             return ans;
786         }
787       }
788     }
789     return false;
790   },
791   /*
792      Method: getSubtree
793   
794      Returns the subtree that matches the given id.
795   
796      Parameters:
797   
798         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
799         id - (string) A node *unique* identifier.
800   
801      Returns:
802   
803         A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
804
805   */
806   getSubtree: function(tree, id) {
807     if (tree.id == id)
808       return tree;
809     for ( var i = 0, ch = tree.children; i < ch.length; i++) {
810       var t = this.getSubtree(ch[i], id);
811       if (t != null)
812         return t;
813     }
814     return null;
815   },
816   /*
817      Method: eachLevel
818   
819       Iterates on tree nodes with relative depth less or equal than a specified level.
820   
821      Parameters:
822   
823         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
824         initLevel - (number) An integer specifying the initial relative level. Usually zero.
825         toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
826         action - (function) A function that receives a node and an integer specifying the actual level of the node.
827           
828     Example:
829    (start code js)
830      $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
831         alert(node.name + ' ' + depth);
832      });
833    (end code)
834   */
835   eachLevel: function(tree, initLevel, toLevel, action) {
836     if (initLevel <= toLevel) {
837       action(tree, initLevel);
838       if(!tree.children) return;
839       for ( var i = 0, ch = tree.children; i < ch.length; i++) {
840         this.eachLevel(ch[i], initLevel + 1, toLevel, action);
841       }
842     }
843   },
844   /*
845      Method: each
846   
847       A JSON tree iterator.
848   
849      Parameters:
850   
851         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
852         action - (function) A function that receives a node.
853
854     Example:
855     (start code js)
856       $jit.json.each(tree, function(node) {
857         alert(node.name);
858       });
859     (end code)
860           
861   */
862   each: function(tree, action) {
863     this.eachLevel(tree, 0, Number.MAX_VALUE, action);
864   }
865 };
866
867
868 /*
869      An object containing multiple type of transformations. 
870 */
871
872 $jit.Trans = {
873   $extend: true,
874   
875   linear: function(p){
876     return p;
877   }
878 };
879
880 var Trans = $jit.Trans;
881
882 (function(){
883
884   var makeTrans = function(transition, params){
885     params = $.splat(params);
886     return $.extend(transition, {
887       easeIn: function(pos){
888         return transition(pos, params);
889       },
890       easeOut: function(pos){
891         return 1 - transition(1 - pos, params);
892       },
893       easeInOut: function(pos){
894         return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
895             2 * (1 - pos), params)) / 2;
896       }
897     });
898   };
899
900   var transitions = {
901
902     Pow: function(p, x){
903       return Math.pow(p, x[0] || 6);
904     },
905
906     Expo: function(p){
907       return Math.pow(2, 8 * (p - 1));
908     },
909
910     Circ: function(p){
911       return 1 - Math.sin(Math.acos(p));
912     },
913
914     Sine: function(p){
915       return 1 - Math.sin((1 - p) * Math.PI / 2);
916     },
917
918     Back: function(p, x){
919       x = x[0] || 1.618;
920       return Math.pow(p, 2) * ((x + 1) * p - x);
921     },
922
923     Bounce: function(p){
924       var value;
925       for ( var a = 0, b = 1; 1; a += b, b /= 2) {
926         if (p >= (7 - 4 * a) / 11) {
927           value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
928           break;
929         }
930       }
931       return value;
932     },
933
934     Elastic: function(p, x){
935       return Math.pow(2, 10 * --p)
936           * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
937     }
938
939   };
940
941   $.each(transitions, function(val, key){
942     Trans[key] = makeTrans(val);
943   });
944
945   $.each( [
946       'Quad', 'Cubic', 'Quart', 'Quint'
947   ], function(elem, i){
948     Trans[elem] = makeTrans(function(p){
949       return Math.pow(p, [
950         i + 2
951       ]);
952     });
953   });
954
955 })();
956
957 /*
958    A Class that can perform animations for generic objects.
959
960    If you are looking for animation transitions please take a look at the <Trans> object.
961
962    Used by:
963
964    <Graph.Plot>
965    
966    Based on:
967    
968    The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
969
970 */
971
972 var Animation = new Class( {
973
974   initialize: function(options){
975     this.setOptions(options);
976   },
977
978   setOptions: function(options){
979     var opt = {
980       duration: 2500,
981       fps: 40,
982       transition: Trans.Quart.easeInOut,
983       compute: $.empty,
984       complete: $.empty,
985       link: 'ignore'
986     };
987     this.opt = $.merge(opt, options || {});
988     return this;
989   },
990
991   step: function(){
992     var time = $.time(), opt = this.opt;
993     if (time < this.time + opt.duration) {
994       var delta = opt.transition((time - this.time) / opt.duration);
995       opt.compute(delta);
996     } else {
997       this.timer = clearInterval(this.timer);
998       opt.compute(1);
999       opt.complete();
1000     }
1001   },
1002
1003   start: function(){
1004     if (!this.check())
1005       return this;
1006     this.time = 0;
1007     this.startTimer();
1008     return this;
1009   },
1010
1011   startTimer: function(){
1012     var that = this, fps = this.opt.fps;
1013     if (this.timer)
1014       return false;
1015     this.time = $.time() - this.time;
1016     this.timer = setInterval((function(){
1017       that.step();
1018     }), Math.round(1000 / fps));
1019     return true;
1020   },
1021
1022   pause: function(){
1023     this.stopTimer();
1024     return this;
1025   },
1026
1027   resume: function(){
1028     this.startTimer();
1029     return this;
1030   },
1031
1032   stopTimer: function(){
1033     if (!this.timer)
1034       return false;
1035     this.time = $.time() - this.time;
1036     this.timer = clearInterval(this.timer);
1037     return true;
1038   },
1039
1040   check: function(){
1041     if (!this.timer)
1042       return true;
1043     if (this.opt.link == 'cancel') {
1044       this.stopTimer();
1045       return true;
1046     }
1047     return false;
1048   }
1049 });
1050
1051
1052 var Options = function() {
1053   var args = arguments;
1054   for(var i=0, l=args.length, ans={}; i<l; i++) {
1055     var opt = Options[args[i]];
1056     if(opt.$extend) {
1057       $.extend(ans, opt);
1058     } else {
1059       ans[args[i]] = opt;  
1060     }
1061   }
1062   return ans;
1063 };
1064
1065 /*
1066  * File: Options.AreaChart.js
1067  *
1068 */
1069
1070 /*
1071   Object: Options.AreaChart
1072   
1073   <AreaChart> options. 
1074   Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1075   
1076   Syntax:
1077   
1078   (start code js)
1079
1080   Options.AreaChart = {
1081     animate: true,
1082     labelOffset: 3,
1083     type: 'stacked',
1084     selectOnHover: true,
1085     showAggregates: true,
1086     showLabels: true,
1087     filterOnClick: false,
1088     restoreOnRightClick: false
1089   };
1090   
1091   (end code)
1092   
1093   Example:
1094   
1095   (start code js)
1096
1097   var areaChart = new $jit.AreaChart({
1098     animate: true,
1099     type: 'stacked:gradient',
1100     selectOnHover: true,
1101     filterOnClick: true,
1102     restoreOnRightClick: true
1103   });
1104   
1105   (end code)
1106
1107   Parameters:
1108   
1109   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1110   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1111   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1112   selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1113   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1114   showLabels - (boolean) Default's *true*. Display the name of the slots.
1115   filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1116   restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1117   
1118 */
1119   
1120 Options.AreaChart = {
1121   $extend: true,
1122
1123   animate: true,
1124   labelOffset: 3, // label offset
1125   type: 'stacked', // gradient
1126   Tips: {
1127     enable: false,
1128     onShow: $.empty,
1129     onHide: $.empty
1130   },
1131   Events: {
1132     enable: false,
1133     onClick: $.empty
1134   },
1135   selectOnHover: true,
1136   showAggregates: true,
1137   showLabels: true,
1138   filterOnClick: false,
1139   restoreOnRightClick: false
1140 };
1141
1142 /*
1143  * File: Options.Margin.js
1144  *
1145 */
1146
1147 /*
1148   Object: Options.Margin
1149   
1150   Canvas drawing margins. 
1151   
1152   Syntax:
1153   
1154   (start code js)
1155
1156   Options.Margin = {
1157     top: 0,
1158     left: 0,
1159     right: 0,
1160     bottom: 0
1161   };
1162   
1163   (end code)
1164   
1165   Example:
1166   
1167   (start code js)
1168
1169   var viz = new $jit.Viz({
1170     Margin: {
1171       right: 10,
1172       bottom: 20
1173     }
1174   });
1175   
1176   (end code)
1177
1178   Parameters:
1179   
1180   top - (number) Default's *0*. Top margin.
1181   left - (number) Default's *0*. Left margin.
1182   right - (number) Default's *0*. Right margin.
1183   bottom - (number) Default's *0*. Bottom margin.
1184   
1185 */
1186
1187 Options.Margin = {
1188   $extend: false,
1189   
1190   top: 0,
1191   left: 0,
1192   right: 0,
1193   bottom: 0
1194 };
1195
1196 /*
1197  * File: Options.Canvas.js
1198  *
1199 */
1200
1201 /*
1202   Object: Options.Canvas
1203   
1204   These are Canvas general options, like where to append it in the DOM, its dimensions, background, 
1205   and other more advanced options.
1206   
1207   Syntax:
1208   
1209   (start code js)
1210
1211   Options.Canvas = {
1212     injectInto: 'id',
1213     width: false,
1214     height: false,
1215     useCanvas: false,
1216     withLabels: true,
1217     background: false
1218   };  
1219   (end code)
1220   
1221   Example:
1222   
1223   (start code js)
1224   var viz = new $jit.Viz({
1225     injectInto: 'someContainerId',
1226     width: 500,
1227     height: 700
1228   });
1229   (end code)
1230   
1231   Parameters:
1232   
1233   injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
1234   width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1235   height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1236   useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1237   withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1238   background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1239 */
1240
1241 Options.Canvas = {
1242     $extend: true,
1243     
1244     injectInto: 'id',
1245     width: false,
1246     height: false,
1247     useCanvas: false,
1248     withLabels: true,
1249     background: false,
1250     colorStop1: 'rgba(255,255,255,1)',
1251     colorStop2: 'rgba(255,255,255,0)'
1252 };
1253
1254 /*
1255  * File: Options.Tree.js
1256  *
1257 */
1258
1259 /*
1260   Object: Options.Tree
1261   
1262   Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1263   
1264   Syntax:
1265   
1266   (start code js)
1267   Options.Tree = {
1268     orientation: "left",
1269     subtreeOffset: 8,
1270     siblingOffset: 5,
1271     indent:10,
1272     multitree: false,
1273     align:"center"
1274   };
1275   (end code)
1276   
1277   Example:
1278   
1279   (start code js)
1280   var st = new $jit.ST({
1281     orientation: 'left',
1282     subtreeOffset: 1,
1283     siblingOFfset: 5,
1284     multitree: true
1285   });
1286   (end code)
1287
1288   Parameters:
1289     
1290   subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1291   siblingOffset - (number) Default's 5. Separation offset between siblings.
1292   orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1293   align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1294   indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1295   multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1296      
1297 */
1298 Options.Tree = {
1299     $extend: true,
1300     
1301     orientation: "left",
1302     subtreeOffset: 8,
1303     siblingOffset: 5,
1304     indent:10,
1305     multitree: false,
1306     align:"center"
1307 };
1308
1309
1310 /*
1311  * File: Options.Node.js
1312  *
1313 */
1314
1315 /*
1316   Object: Options.Node
1317
1318   Provides Node rendering options for Tree and Graph based visualizations.
1319
1320   Syntax:
1321     
1322   (start code js)
1323   Options.Node = {
1324     overridable: false,
1325     type: 'circle',
1326     color: '#ccb',
1327     alpha: 1,
1328     dim: 3,
1329     height: 20,
1330     width: 90,
1331     autoHeight: false,
1332     autoWidth: false,
1333     lineWidth: 1,
1334     transform: true,
1335     align: "center",
1336     angularWidth:1,
1337     span:1,
1338     CanvasStyles: {}
1339   };
1340   (end code)
1341   
1342   Example:
1343   
1344   (start code js)
1345   var viz = new $jit.Viz({
1346     Node: {
1347       overridable: true,
1348       width: 30,
1349       autoHeight: true,
1350       type: 'rectangle'
1351     }
1352   });
1353   (end code)
1354   
1355   Parameters:
1356
1357   overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1358   type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
1359   color - (string) Default's *#ccb*. Node color.
1360   alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1361   dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1362   height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1363   width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1364   autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1365   autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1366   lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1367   transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1368   align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
1369   angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1370   span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1371   CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
1372
1373 */
1374 Options.Node = {
1375   $extend: false,
1376   
1377   overridable: false,
1378   type: 'circle',
1379   color: '#ccb',
1380   alpha: 1,
1381   dim: 3,
1382   height: 20,
1383   width: 90,
1384   autoHeight: false,
1385   autoWidth: false,
1386   lineWidth: 1,
1387   transform: true,
1388   align: "center",
1389   angularWidth:1,
1390   span:1,
1391   //Raw canvas styles to be
1392   //applied to the context instance
1393   //before plotting a node
1394   CanvasStyles: {}
1395 };
1396
1397
1398 /*
1399  * File: Options.Edge.js
1400  *
1401 */
1402
1403 /*
1404   Object: Options.Edge
1405
1406   Provides Edge rendering options for Tree and Graph based visualizations.
1407
1408   Syntax:
1409     
1410   (start code js)
1411   Options.Edge = {
1412     overridable: false,
1413     type: 'line',
1414     color: '#ccb',
1415     lineWidth: 1,
1416     dim:15,
1417     alpha: 1,
1418     CanvasStyles: {}
1419   };
1420   (end code)
1421   
1422   Example:
1423   
1424   (start code js)
1425   var viz = new $jit.Viz({
1426     Edge: {
1427       overridable: true,
1428       type: 'line',
1429       color: '#fff',
1430       CanvasStyles: {
1431         shadowColor: '#ccc',
1432         shadowBlur: 10
1433       }
1434     }
1435   });
1436   (end code)
1437   
1438   Parameters:
1439     
1440    overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1441    type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
1442    color - (string) Default's '#ccb'. Edge color.
1443    lineWidth - (number) Default's *1*. Line/Edge width.
1444    alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1445    dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
1446    epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
1447    CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
1448
1449   See also:
1450    
1451    If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
1452 */
1453 Options.Edge = {
1454   $extend: false,
1455   
1456   overridable: false,
1457   type: 'line',
1458   color: '#ccb',
1459   lineWidth: 1,
1460   dim:15,
1461   alpha: 1,
1462   epsilon: 7,
1463
1464   //Raw canvas styles to be
1465   //applied to the context instance
1466   //before plotting an edge
1467   CanvasStyles: {}
1468 };
1469
1470
1471 /*
1472  * File: Options.Fx.js
1473  *
1474 */
1475
1476 /*
1477   Object: Options.Fx
1478
1479   Provides animation options like duration of the animations, frames per second and animation transitions.  
1480
1481   Syntax:
1482   
1483   (start code js)
1484     Options.Fx = {
1485       fps:40,
1486       duration: 2500,
1487       transition: $jit.Trans.Quart.easeInOut,
1488       clearCanvas: true
1489     };
1490   (end code)
1491   
1492   Example:
1493   
1494   (start code js)
1495   var viz = new $jit.Viz({
1496     duration: 1000,
1497     fps: 35,
1498     transition: $jit.Trans.linear
1499   });
1500   (end code)
1501   
1502   Parameters:
1503   
1504   clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1505   duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1506   fps - (number) Default's *40*. Frames per second.
1507   transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1508   
1509   Object: $jit.Trans
1510   
1511   This object is used for specifying different animation transitions in all visualizations.
1512
1513   There are many different type of animation transitions.
1514
1515   linear:
1516
1517   Displays a linear transition
1518
1519   >Trans.linear
1520   
1521   (see Linear.png)
1522
1523   Quad:
1524
1525   Displays a Quadratic transition.
1526
1527   >Trans.Quad.easeIn
1528   >Trans.Quad.easeOut
1529   >Trans.Quad.easeInOut
1530   
1531  (see Quad.png)
1532
1533  Cubic:
1534
1535  Displays a Cubic transition.
1536
1537  >Trans.Cubic.easeIn
1538  >Trans.Cubic.easeOut
1539  >Trans.Cubic.easeInOut
1540
1541  (see Cubic.png)
1542
1543  Quart:
1544
1545  Displays a Quartetic transition.
1546
1547  >Trans.Quart.easeIn
1548  >Trans.Quart.easeOut
1549  >Trans.Quart.easeInOut
1550
1551  (see Quart.png)
1552
1553  Quint:
1554
1555  Displays a Quintic transition.
1556
1557  >Trans.Quint.easeIn
1558  >Trans.Quint.easeOut
1559  >Trans.Quint.easeInOut
1560
1561  (see Quint.png)
1562
1563  Expo:
1564
1565  Displays an Exponential transition.
1566
1567  >Trans.Expo.easeIn
1568  >Trans.Expo.easeOut
1569  >Trans.Expo.easeInOut
1570
1571  (see Expo.png)
1572
1573  Circ:
1574
1575  Displays a Circular transition.
1576
1577  >Trans.Circ.easeIn
1578  >Trans.Circ.easeOut
1579  >Trans.Circ.easeInOut
1580
1581  (see Circ.png)
1582
1583  Sine:
1584
1585  Displays a Sineousidal transition.
1586
1587  >Trans.Sine.easeIn
1588  >Trans.Sine.easeOut
1589  >Trans.Sine.easeInOut
1590
1591  (see Sine.png)
1592
1593  Back:
1594
1595  >Trans.Back.easeIn
1596  >Trans.Back.easeOut
1597  >Trans.Back.easeInOut
1598
1599  (see Back.png)
1600
1601  Bounce:
1602
1603  Bouncy transition.
1604
1605  >Trans.Bounce.easeIn
1606  >Trans.Bounce.easeOut
1607  >Trans.Bounce.easeInOut
1608
1609  (see Bounce.png)
1610
1611  Elastic:
1612
1613  Elastic curve.
1614
1615  >Trans.Elastic.easeIn
1616  >Trans.Elastic.easeOut
1617  >Trans.Elastic.easeInOut
1618
1619  (see Elastic.png)
1620  
1621  Based on:
1622      
1623  Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
1624
1625
1626 */
1627 Options.Fx = {
1628   $extend: true,
1629   
1630   fps:40,
1631   duration: 2500,
1632   transition: $jit.Trans.Quart.easeInOut,
1633   clearCanvas: true
1634 };
1635
1636 /*
1637  * File: Options.Label.js
1638  *
1639 */
1640 /*
1641   Object: Options.Label
1642
1643   Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.  
1644
1645   Syntax:
1646   
1647   (start code js)
1648     Options.Label = {
1649       overridable: false,
1650       type: 'HTML', //'SVG', 'Native'
1651       style: ' ',
1652       size: 10,
1653       family: 'sans-serif',
1654       textAlign: 'center',
1655       textBaseline: 'alphabetic',
1656       color: '#fff'
1657     };
1658   (end code)
1659   
1660   Example:
1661   
1662   (start code js)
1663   var viz = new $jit.Viz({
1664     Label: {
1665       type: 'Native',
1666       size: 11,
1667       color: '#ccc'
1668     }
1669   });
1670   (end code)
1671   
1672   Parameters:
1673     
1674   overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1675   type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1676   style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1677   size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1678   family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1679   color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1680 */
1681 Options.Label = {
1682   $extend: false,
1683   
1684   overridable: false,
1685   type: 'HTML', //'SVG', 'Native'
1686   style: ' ',
1687   size: 10,
1688   family: 'sans-serif',
1689   textAlign: 'center',
1690   textBaseline: 'alphabetic',
1691   color: '#fff'
1692 };
1693
1694
1695 /*
1696  * File: Options.Tips.js
1697  *
1698  */
1699
1700 /*
1701   Object: Options.Tips
1702   
1703   Tips options
1704   
1705   Syntax:
1706     
1707   (start code js)
1708   Options.Tips = {
1709     enable: false,
1710     type: 'auto',
1711     offsetX: 20,
1712     offsetY: 20,
1713     onShow: $.empty,
1714     onHide: $.empty
1715   };
1716   (end code)
1717   
1718   Example:
1719   
1720   (start code js)
1721   var viz = new $jit.Viz({
1722     Tips: {
1723       enable: true,
1724       type: 'Native',
1725       offsetX: 10,
1726       offsetY: 10,
1727       onShow: function(tip, node) {
1728         tip.innerHTML = node.name;
1729       }
1730     }
1731   });
1732   (end code)
1733
1734   Parameters:
1735
1736   enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class. 
1737   type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
1738   offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
1739   offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
1740   onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
1741   onHide() - This callack is used when hiding a tooltip.
1742
1743 */
1744 Options.Tips = {
1745   $extend: false,
1746   
1747   enable: false,
1748   type: 'auto',
1749   offsetX: 20,
1750   offsetY: 20,
1751   force: false,
1752   onShow: $.empty,
1753   onHide: $.empty
1754 };
1755
1756
1757 /*
1758  * File: Options.NodeStyles.js
1759  *
1760  */
1761
1762 /*
1763   Object: Options.NodeStyles
1764   
1765   Apply different styles when a node is hovered or selected.
1766   
1767   Syntax:
1768     
1769   (start code js)
1770   Options.NodeStyles = {
1771     enable: false,
1772     type: 'auto',
1773     stylesHover: false,
1774     stylesClick: false
1775   };
1776   (end code)
1777   
1778   Example:
1779   
1780   (start code js)
1781   var viz = new $jit.Viz({
1782     NodeStyles: {
1783       enable: true,
1784       type: 'Native',
1785       stylesHover: {
1786         dim: 30,
1787         color: '#fcc'
1788       },
1789       duration: 600
1790     }
1791   });
1792   (end code)
1793
1794   Parameters:
1795   
1796   enable - (boolean) Default's *false*. Whether to enable this option.
1797   type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
1798   stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799   stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1800 */
1801
1802 Options.NodeStyles = {
1803   $extend: false,
1804   
1805   enable: false,
1806   type: 'auto',
1807   stylesHover: false,
1808   stylesClick: false
1809 };
1810
1811
1812 /*
1813  * File: Options.Events.js
1814  *
1815 */
1816
1817 /*
1818   Object: Options.Events
1819   
1820   Configuration for adding mouse/touch event handlers to Nodes.
1821   
1822   Syntax:
1823   
1824   (start code js)
1825   Options.Events = {
1826     enable: false,
1827     enableForEdges: false,
1828     type: 'auto',
1829     onClick: $.empty,
1830     onRightClick: $.empty,
1831     onMouseMove: $.empty,
1832     onMouseEnter: $.empty,
1833     onMouseLeave: $.empty,
1834     onDragStart: $.empty,
1835     onDragMove: $.empty,
1836     onDragCancel: $.empty,
1837     onDragEnd: $.empty,
1838     onTouchStart: $.empty,
1839     onTouchMove: $.empty,
1840     onTouchEnd: $.empty,
1841     onTouchCancel: $.empty,
1842     onMouseWheel: $.empty
1843   };
1844   (end code)
1845   
1846   Example:
1847   
1848   (start code js)
1849   var viz = new $jit.Viz({
1850     Events: {
1851       enable: true,
1852       onClick: function(node, eventInfo, e) {
1853         viz.doSomething();
1854       },
1855       onMouseEnter: function(node, eventInfo, e) {
1856         viz.canvas.getElement().style.cursor = 'pointer';
1857       },
1858       onMouseLeave: function(node, eventInfo, e) {
1859         viz.canvas.getElement().style.cursor = '';
1860       }
1861     }
1862   });
1863   (end code)
1864   
1865   Parameters:
1866   
1867   enable - (boolean) Default's *false*. Whether to enable the Event system.
1868   enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
1869   type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
1870   onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1871   onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1872   onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner).  *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1873   onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1874   onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1875   onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1876   onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1877   onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1878   onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1879   onTouchStart(node, eventInfo, e) - Behaves just like onDragStart. 
1880   onTouchMove(node, eventInfo, e) - Behaves just like onDragMove. 
1881   onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd. 
1882   onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1883   onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
1884 */
1885
1886 Options.Events = {
1887   $extend: false,
1888   
1889   enable: false,
1890   enableForEdges: false,
1891   type: 'auto',
1892   onClick: $.empty,
1893   onRightClick: $.empty,
1894   onMouseMove: $.empty,
1895   onMouseEnter: $.empty,
1896   onMouseLeave: $.empty,
1897   onDragStart: $.empty,
1898   onDragMove: $.empty,
1899   onDragCancel: $.empty,
1900   onDragEnd: $.empty,
1901   onTouchStart: $.empty,
1902   onTouchMove: $.empty,
1903   onTouchEnd: $.empty,
1904   onMouseWheel: $.empty
1905 };
1906
1907 /*
1908  * File: Options.Navigation.js
1909  *
1910 */
1911
1912 /*
1913   Object: Options.Navigation
1914   
1915   Panning and zooming options for Graph/Tree based visualizations. These options are implemented 
1916   by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1917   
1918   Syntax:
1919   
1920   (start code js)
1921
1922   Options.Navigation = {
1923     enable: false,
1924     type: 'auto',
1925     panning: false, //true, 'avoid nodes'
1926     zooming: false
1927   };
1928   
1929   (end code)
1930   
1931   Example:
1932     
1933   (start code js)
1934   var viz = new $jit.Viz({
1935     Navigation: {
1936       enable: true,
1937       panning: 'avoid nodes',
1938       zooming: 20
1939     }
1940   });
1941   (end code)
1942   
1943   Parameters:
1944   
1945   enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1946   panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
1947   zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
1948   
1949 */
1950
1951 Options.Navigation = {
1952   $extend: false,
1953   
1954   enable: false,
1955   type: 'auto',
1956   panning: false, //true | 'avoid nodes'
1957   zooming: false
1958 };
1959
1960 /*
1961  * File: Options.Controller.js
1962  *
1963 */
1964
1965 /*
1966   Object: Options.Controller
1967   
1968   Provides controller methods. Controller methods are callback functions that get called at different stages 
1969   of the animation, computing or plotting of the visualization.
1970   
1971   Implemented by:
1972     
1973   All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1974   
1975   Syntax:
1976   
1977   (start code js)
1978
1979   Options.Controller = {
1980     onBeforeCompute: $.empty,
1981     onAfterCompute:  $.empty,
1982     onCreateLabel:   $.empty,
1983     onPlaceLabel:    $.empty,
1984     onComplete:      $.empty,
1985     onBeforePlotLine:$.empty,
1986     onAfterPlotLine: $.empty,
1987     onBeforePlotNode:$.empty,
1988     onAfterPlotNode: $.empty,
1989     request:         false
1990   };
1991   
1992   (end code)
1993   
1994   Example:
1995     
1996   (start code js)
1997   var viz = new $jit.Viz({
1998     onBeforePlotNode: function(node) {
1999       if(node.selected) {
2000         node.setData('color', '#ffc');
2001       } else {
2002         node.removeData('color');
2003       }
2004     },
2005     onBeforePlotLine: function(adj) {
2006       if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2007         adj.setData('color', '#ffc');
2008       } else {
2009         adj.removeData('color');
2010       }
2011     },
2012     onAfterCompute: function() {
2013       alert("computed!");
2014     }
2015   });
2016   (end code)
2017   
2018   Parameters:
2019
2020    onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2021    onAfterCompute() - This method is triggered after all animations or computations ended.
2022    onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
2023    onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
2024    onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
2025    onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2026    onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
2027    onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2028
2029     *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2030     
2031     request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.  
2032  
2033  */
2034 Options.Controller = {
2035   $extend: true,
2036   
2037   onBeforeCompute: $.empty,
2038   onAfterCompute:  $.empty,
2039   onCreateLabel:   $.empty,
2040   onPlaceLabel:    $.empty,
2041   onComplete:      $.empty,
2042   onBeforePlotLine:$.empty,
2043   onAfterPlotLine: $.empty,
2044   onBeforePlotNode:$.empty,
2045   onAfterPlotNode: $.empty,
2046   request:         false
2047 };
2048
2049
2050 /*
2051  * File: Extras.js
2052  * 
2053  * Provides Extras such as Tips and Style Effects.
2054  * 
2055  * Description:
2056  * 
2057  * Provides the <Tips> and <NodeStyles> classes and functions.
2058  *
2059  */
2060
2061 /*
2062  * Manager for mouse events (clicking and mouse moving).
2063  * 
2064  * This class is used for registering objects implementing onClick
2065  * and onMousemove methods. These methods are called when clicking or
2066  * moving the mouse around  the Canvas.
2067  * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2068  * 
2069  */
2070 var ExtrasInitializer = {
2071   initialize: function(className, viz) {
2072     this.viz = viz;
2073     this.canvas = viz.canvas;
2074     this.config = viz.config[className];
2075     this.nodeTypes = viz.fx.nodeTypes;
2076     var type = this.config.type;
2077     this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2078     this.labelContainer = this.dom && viz.labels.getLabelContainer();
2079     this.isEnabled() && this.initializePost();
2080   },
2081   initializePost: $.empty,
2082   setAsProperty: $.lambda(false),
2083   isEnabled: function() {
2084     return this.config.enable;
2085   },
2086   isLabel: function(e, win) {
2087     e = $.event.get(e, win);
2088     var labelContainer = this.labelContainer,
2089         target = e.target || e.srcElement;
2090     if(target && target.parentNode == labelContainer)
2091       return target;
2092     return false;
2093   }
2094 };
2095
2096 var EventsInterface = {
2097   onMouseUp: $.empty,
2098   onMouseDown: $.empty,
2099   onMouseMove: $.empty,
2100   onMouseOver: $.empty,
2101   onMouseOut: $.empty,
2102   onMouseWheel: $.empty,
2103   onTouchStart: $.empty,
2104   onTouchMove: $.empty,
2105   onTouchEnd: $.empty,
2106   onTouchCancel: $.empty
2107 };
2108
2109 var MouseEventsManager = new Class({
2110   initialize: function(viz) {
2111     this.viz = viz;
2112     this.canvas = viz.canvas;
2113     this.node = false;
2114     this.edge = false;
2115     this.registeredObjects = [];
2116     this.attachEvents();
2117   },
2118   
2119   attachEvents: function() {
2120     var htmlCanvas = this.canvas.getElement(), 
2121         that = this;
2122     htmlCanvas.oncontextmenu = $.lambda(false);
2123     $.addEvents(htmlCanvas, {
2124       'mouseup': function(e, win) {
2125         var event = $.event.get(e, win);
2126         that.handleEvent('MouseUp', e, win, 
2127             that.makeEventObject(e, win), 
2128             $.event.isRightClick(event));
2129       },
2130       'mousedown': function(e, win) {
2131         var event = $.event.get(e, win);
2132         that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win), 
2133             $.event.isRightClick(event));
2134       },
2135       'mousemove': function(e, win) {
2136         that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2137       },
2138       'mouseover': function(e, win) {
2139         that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2140       },
2141       'mouseout': function(e, win) {
2142         that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2143       },
2144       'touchstart': function(e, win) {
2145         that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2146       },
2147       'touchmove': function(e, win) {
2148         that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2149       },
2150       'touchend': function(e, win) {
2151         that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2152       }
2153     });
2154     //attach mousewheel event
2155     var handleMouseWheel = function(e, win) {
2156       var event = $.event.get(e, win);
2157       var wheel = $.event.getWheel(event);
2158       that.handleEvent('MouseWheel', e, win, wheel);
2159     };
2160     //TODO(nico): this is a horrible check for non-gecko browsers!
2161     if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2162       $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2163     } else {
2164       htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2165     }
2166   },
2167   
2168   register: function(obj) {
2169     this.registeredObjects.push(obj);
2170   },
2171   
2172   handleEvent: function() {
2173     var args = Array.prototype.slice.call(arguments),
2174         type = args.shift();
2175     for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2176       regs[i]['on' + type].apply(regs[i], args);
2177     }
2178   },
2179   
2180   makeEventObject: function(e, win) {
2181     var that = this,
2182         graph = this.viz.graph,
2183         fx = this.viz.fx,
2184         ntypes = fx.nodeTypes,
2185         etypes = fx.edgeTypes;
2186     return {
2187       pos: false,
2188       node: false,
2189       edge: false,
2190       contains: false,
2191       getNodeCalled: false,
2192       getEdgeCalled: false,
2193       getPos: function() {
2194         //TODO(nico): check why this can't be cache anymore when using edge detection
2195         //if(this.pos) return this.pos;
2196         var canvas = that.viz.canvas,
2197             s = canvas.getSize(),
2198             p = canvas.getPos(),
2199             ox = canvas.translateOffsetX,
2200             oy = canvas.translateOffsetY,
2201             sx = canvas.scaleOffsetX,
2202             sy = canvas.scaleOffsetY,
2203             pos = $.event.getPos(e, win);
2204         this.pos = {
2205           x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2206           y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2207         };
2208         return this.pos;
2209       },
2210       getNode: function() {
2211         if(this.getNodeCalled) return this.node;
2212         this.getNodeCalled = true;
2213         for(var id in graph.nodes) {
2214           var n = graph.nodes[id],
2215               geom = n && ntypes[n.getData('type')],
2216               contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2217           if(contains) {
2218             this.contains = contains;
2219             return that.node = this.node = n;
2220           }
2221         }
2222         return that.node = this.node = false;
2223       },
2224       getEdge: function() {
2225         if(this.getEdgeCalled) return this.edge;
2226         this.getEdgeCalled = true;
2227         var hashset = {};
2228         for(var id in graph.edges) {
2229           var edgeFrom = graph.edges[id];
2230           hashset[id] = true;
2231           for(var edgeId in edgeFrom) {
2232             if(edgeId in hashset) continue;
2233             var e = edgeFrom[edgeId],
2234                 geom = e && etypes[e.getData('type')],
2235                 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2236             if(contains) {
2237               this.contains = contains;
2238               return that.edge = this.edge = e;
2239             }
2240           }
2241         }
2242         return that.edge = this.edge = false;
2243       },
2244       getContains: function() {
2245         if(this.getNodeCalled) return this.contains;
2246         this.getNode();
2247         return this.contains;
2248       }
2249     };
2250   }
2251 });
2252
2253 /* 
2254  * Provides the initialization function for <NodeStyles> and <Tips> implemented 
2255  * by all main visualizations.
2256  *
2257  */
2258 var Extras = {
2259   initializeExtras: function() {
2260     var mem = new MouseEventsManager(this), that = this;
2261     $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2262       var obj = new Extras.Classes[k](k, that);
2263       if(obj.isEnabled()) {
2264         mem.register(obj);
2265       }
2266       if(obj.setAsProperty()) {
2267         that[k.toLowerCase()] = obj;
2268       }
2269     });
2270   }   
2271 };
2272
2273 Extras.Classes = {};
2274 /*
2275   Class: Events
2276    
2277   This class defines an Event API to be accessed by the user.
2278   The methods implemented are the ones defined in the <Options.Events> object.
2279 */
2280
2281 Extras.Classes.Events = new Class({
2282   Implements: [ExtrasInitializer, EventsInterface],
2283   
2284   initializePost: function() {
2285     this.fx = this.viz.fx;
2286     this.ntypes = this.viz.fx.nodeTypes;
2287     this.etypes = this.viz.fx.edgeTypes;
2288     
2289     this.hovered = false;
2290     this.pressed = false;
2291     this.touched = false;
2292
2293     this.touchMoved = false;
2294     this.moved = false;
2295     
2296   },
2297   
2298   setAsProperty: $.lambda(true),
2299   
2300   onMouseUp: function(e, win, event, isRightClick) {
2301     var evt = $.event.get(e, win);
2302     if(!this.moved) {
2303       if(isRightClick) {
2304         this.config.onRightClick(this.hovered, event, evt);
2305       } else {
2306         this.config.onClick(this.pressed, event, evt);
2307       }
2308     }
2309     if(this.pressed) {
2310       if(this.moved) {
2311         this.config.onDragEnd(this.pressed, event, evt);
2312       } else {
2313         this.config.onDragCancel(this.pressed, event, evt);
2314       }
2315       this.pressed = this.moved = false;
2316     }
2317   },
2318
2319   onMouseOut: function(e, win, event) {
2320    //mouseout a label
2321    var evt = $.event.get(e, win), label;
2322    if(this.dom && (label = this.isLabel(e, win))) {
2323      this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2324                               event, evt);
2325      this.hovered = false;
2326      return;
2327    }
2328    //mouseout canvas
2329    var rt = evt.relatedTarget,
2330        canvasWidget = this.canvas.getElement();
2331    while(rt && rt.parentNode) {
2332      if(canvasWidget == rt.parentNode) return;
2333      rt = rt.parentNode;
2334    }
2335    if(this.hovered) {
2336      this.config.onMouseLeave(this.hovered,
2337          event, evt);
2338      this.hovered = false;
2339    }
2340   },
2341   
2342   onMouseOver: function(e, win, event) {
2343     //mouseover a label
2344     var evt = $.event.get(e, win), label;
2345     if(this.dom && (label = this.isLabel(e, win))) {
2346       this.hovered = this.viz.graph.getNode(label.id);
2347       this.config.onMouseEnter(this.hovered,
2348                                event, evt);
2349     }
2350   },
2351   
2352   onMouseMove: function(e, win, event) {
2353    var label, evt = $.event.get(e, win);
2354    if(this.pressed) {
2355      this.moved = true;
2356      this.config.onDragMove(this.pressed, event, evt);
2357      return;
2358    }
2359    if(this.dom) {
2360      this.config.onMouseMove(this.hovered,
2361          event, evt);
2362    } else {
2363      if(this.hovered) {
2364        var hn = this.hovered;
2365        var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2366        var contains = geom && geom.contains 
2367          && geom.contains.call(this.fx, hn, event.getPos());
2368        if(contains) {
2369          this.config.onMouseMove(hn, event, evt);
2370          return;
2371        } else {
2372          this.config.onMouseLeave(hn, event, evt);
2373          this.hovered = false;
2374        }
2375      }
2376      if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2377        this.config.onMouseEnter(this.hovered, event, evt);
2378      } else {
2379        this.config.onMouseMove(false, event, evt);
2380      }
2381    }
2382   },
2383   
2384   onMouseWheel: function(e, win, delta) {
2385     this.config.onMouseWheel(delta, $.event.get(e, win));
2386   },
2387   
2388   onMouseDown: function(e, win, event) {
2389     var evt = $.event.get(e, win);
2390     this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2391     this.config.onDragStart(this.pressed, event, evt);
2392   },
2393   
2394   onTouchStart: function(e, win, event) {
2395     var evt = $.event.get(e, win);
2396     this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2397     this.config.onTouchStart(this.touched, event, evt);
2398   },
2399   
2400   onTouchMove: function(e, win, event) {
2401     var evt = $.event.get(e, win);
2402     if(this.touched) {
2403       this.touchMoved = true;
2404       this.config.onTouchMove(this.touched, event, evt);
2405     }
2406   },
2407   
2408   onTouchEnd: function(e, win, event) {
2409     var evt = $.event.get(e, win);
2410     if(this.touched) {
2411       if(this.touchMoved) {
2412         this.config.onTouchEnd(this.touched, event, evt);
2413       } else {
2414         this.config.onTouchCancel(this.touched, event, evt);
2415       }
2416       this.touched = this.touchMoved = false;
2417     }
2418   }
2419 });
2420
2421 /*
2422    Class: Tips
2423     
2424    A class containing tip related functions. This class is used internally.
2425    
2426    Used by:
2427    
2428    <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2429    
2430    See also:
2431    
2432    <Options.Tips>
2433 */
2434
2435 Extras.Classes.Tips = new Class({
2436   Implements: [ExtrasInitializer, EventsInterface],
2437   
2438   initializePost: function() {
2439     //add DOM tooltip
2440     if(document.body) {
2441       var tip = $('_tooltip') || document.createElement('div');
2442       tip.id = '_tooltip';
2443       tip.className = 'tip';
2444       $.extend(tip.style, {
2445         position: 'absolute',
2446         display: 'none',
2447         zIndex: 13000
2448       });
2449       document.body.appendChild(tip);
2450       this.tip = tip;
2451       this.node = false;
2452     }
2453   },
2454   
2455   setAsProperty: $.lambda(true),
2456   
2457   onMouseOut: function(e, win) {
2458     //mouseout a label
2459     if(this.dom && this.isLabel(e, win)) {
2460       this.hide(true);
2461       return;
2462     }
2463     //mouseout canvas
2464     var rt = e.relatedTarget,
2465         canvasWidget = this.canvas.getElement();
2466     while(rt && rt.parentNode) {
2467       if(canvasWidget == rt.parentNode) return;
2468       rt = rt.parentNode;
2469     }
2470     this.hide(false);
2471   },
2472   
2473   onMouseOver: function(e, win) {
2474     //mouseover a label
2475     var label;
2476     if(this.dom && (label = this.isLabel(e, win))) {
2477       this.node = this.viz.graph.getNode(label.id);
2478       this.config.onShow(this.tip, this.node, label);
2479     }
2480   },
2481   
2482   onMouseMove: function(e, win, opt) {
2483     if(this.dom && this.isLabel(e, win)) {
2484       this.setTooltipPosition($.event.getPos(e, win));
2485     }
2486     if(!this.dom) {
2487       var node = opt.getNode();
2488       if(!node) {
2489         this.hide(true);
2490         return;
2491       }
2492       if(this.config.force || !this.node || this.node.id != node.id) {
2493         this.node = node;
2494         this.config.onShow(this.tip, node, opt.getContains());
2495       }
2496       this.setTooltipPosition($.event.getPos(e, win));
2497     }
2498   },
2499   
2500   setTooltipPosition: function(pos) {
2501     var tip = this.tip, 
2502         style = tip.style, 
2503         cont = this.config;
2504     style.display = '';
2505     //get window dimensions
2506     var win = {
2507       'height': document.body.clientHeight,
2508       'width': document.body.clientWidth
2509     };
2510     //get tooltip dimensions
2511     var obj = {
2512       'width': tip.offsetWidth,
2513       'height': tip.offsetHeight  
2514     };
2515     //set tooltip position
2516     var x = cont.offsetX, y = cont.offsetY;
2517     style.top = ((pos.y + y + obj.height > win.height)?  
2518         (pos.y - obj.height - y) : pos.y + y) + 'px';
2519     style.left = ((pos.x + obj.width + x > win.width)? 
2520         (pos.x - obj.width - x) : pos.x + x) + 'px';
2521   },
2522   
2523   hide: function(triggerCallback) {
2524     if(!SUGAR.util.isTouchScreen()) {
2525         this.tip.style.display = 'none';
2526     }
2527     triggerCallback && this.config.onHide();
2528   }
2529 });
2530
2531 /*
2532   Class: NodeStyles
2533    
2534   Change node styles when clicking or hovering a node. This class is used internally.
2535   
2536   Used by:
2537   
2538   <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2539   
2540   See also:
2541   
2542   <Options.NodeStyles>
2543 */
2544 Extras.Classes.NodeStyles = new Class({
2545   Implements: [ExtrasInitializer, EventsInterface],
2546   
2547   initializePost: function() {
2548     this.fx = this.viz.fx;
2549     this.types = this.viz.fx.nodeTypes;
2550     this.nStyles = this.config;
2551     this.nodeStylesOnHover = this.nStyles.stylesHover;
2552     this.nodeStylesOnClick = this.nStyles.stylesClick;
2553     this.hoveredNode = false;
2554     this.fx.nodeFxAnimation = new Animation();
2555     
2556     this.down = false;
2557     this.move = false;
2558   },
2559   
2560   onMouseOut: function(e, win) {
2561     this.down = this.move = false;
2562     if(!this.hoveredNode) return;
2563     //mouseout a label
2564     if(this.dom && this.isLabel(e, win)) {
2565       this.toggleStylesOnHover(this.hoveredNode, false);
2566     }
2567     //mouseout canvas
2568     var rt = e.relatedTarget,
2569         canvasWidget = this.canvas.getElement();
2570     while(rt && rt.parentNode) {
2571       if(canvasWidget == rt.parentNode) return;
2572       rt = rt.parentNode;
2573     }
2574     this.toggleStylesOnHover(this.hoveredNode, false);
2575     this.hoveredNode = false;
2576   },
2577   
2578   onMouseOver: function(e, win) {
2579     //mouseover a label
2580     var label;
2581     if(this.dom && (label = this.isLabel(e, win))) {
2582       var node = this.viz.graph.getNode(label.id);
2583       if(node.selected) return;
2584       this.hoveredNode = node;
2585       this.toggleStylesOnHover(this.hoveredNode, true);
2586     }
2587   },
2588   
2589   onMouseDown: function(e, win, event, isRightClick) {
2590     if(isRightClick) return;
2591     var label;
2592     if(this.dom && (label = this.isLabel(e, win))) {
2593       this.down = this.viz.graph.getNode(label.id);
2594     } else if(!this.dom) {
2595       this.down = event.getNode();
2596     }
2597     this.move = false;
2598   },
2599   
2600   onMouseUp: function(e, win, event, isRightClick) {
2601     if(isRightClick) return;
2602     if(!this.move) {
2603       this.onClick(event.getNode());
2604     }
2605     this.down = this.move = false;
2606   },
2607   
2608   getRestoredStyles: function(node, type) {
2609     var restoredStyles = {}, 
2610         nStyles = this['nodeStylesOn' + type];
2611     for(var prop in nStyles) {
2612       restoredStyles[prop] = node.styles['$' + prop];
2613     }
2614     return restoredStyles;
2615   },
2616   
2617   toggleStylesOnHover: function(node, set) {
2618     if(this.nodeStylesOnHover) {
2619       this.toggleStylesOn('Hover', node, set);
2620     }
2621   },
2622
2623   toggleStylesOnClick: function(node, set) {
2624     if(this.nodeStylesOnClick) {
2625       this.toggleStylesOn('Click', node, set);
2626     }
2627   },
2628   
2629   toggleStylesOn: function(type, node, set) {
2630     var viz = this.viz;
2631     var nStyles = this.nStyles;
2632     if(set) {
2633       var that = this;
2634       if(!node.styles) {
2635         node.styles = $.merge(node.data, {});
2636       }
2637       for(var s in this['nodeStylesOn' + type]) {
2638         var $s = '$' + s;
2639         if(!($s in node.styles)) {
2640             node.styles[$s] = node.getData(s); 
2641         }
2642       }
2643       viz.fx.nodeFx($.extend({
2644         'elements': {
2645           'id': node.id,
2646           'properties': that['nodeStylesOn' + type]
2647          },
2648          transition: Trans.Quart.easeOut,
2649          duration:300,
2650          fps:40
2651       }, this.config));
2652     } else {
2653       var restoredStyles = this.getRestoredStyles(node, type);
2654       viz.fx.nodeFx($.extend({
2655         'elements': {
2656           'id': node.id,
2657           'properties': restoredStyles
2658          },
2659          transition: Trans.Quart.easeOut,
2660          duration:300,
2661          fps:40
2662       }, this.config));
2663     }
2664   },
2665
2666   onClick: function(node) {
2667     if(!node) return;
2668     var nStyles = this.nodeStylesOnClick;
2669     if(!nStyles) return;
2670     //if the node is selected then unselect it
2671     if(node.selected) {
2672       this.toggleStylesOnClick(node, false);
2673       delete node.selected;
2674     } else {
2675       //unselect all selected nodes...
2676       this.viz.graph.eachNode(function(n) {
2677         if(n.selected) {
2678           for(var s in nStyles) {
2679             n.setData(s, n.styles['$' + s], 'end');
2680           }
2681           delete n.selected;
2682         }
2683       });
2684       //select clicked node
2685       this.toggleStylesOnClick(node, true);
2686       node.selected = true;
2687       delete node.hovered;
2688       this.hoveredNode = false;
2689     }
2690   },
2691   
2692   onMouseMove: function(e, win, event) {
2693     //if mouse button is down and moving set move=true
2694     if(this.down) this.move = true;
2695     //already handled by mouseover/out
2696     if(this.dom && this.isLabel(e, win)) return;
2697     var nStyles = this.nodeStylesOnHover;
2698     if(!nStyles) return;
2699     
2700     if(!this.dom) {
2701       if(this.hoveredNode) {
2702         var geom = this.types[this.hoveredNode.getData('type')];
2703         var contains = geom && geom.contains && geom.contains.call(this.fx, 
2704             this.hoveredNode, event.getPos());
2705         if(contains) return;
2706       }
2707       var node = event.getNode();
2708       //if no node is being hovered then just exit
2709       if(!this.hoveredNode && !node) return;
2710       //if the node is hovered then exit
2711       if(node.hovered) return;
2712       //select hovered node
2713       if(node && !node.selected) {
2714         //check if an animation is running and exit it
2715         this.fx.nodeFxAnimation.stopTimer();
2716         //unselect all hovered nodes...
2717         this.viz.graph.eachNode(function(n) {
2718           if(n.hovered && !n.selected) {
2719             for(var s in nStyles) {
2720               n.setData(s, n.styles['$' + s], 'end');
2721             }
2722             delete n.hovered;
2723           }
2724         });
2725         //select hovered node
2726         node.hovered = true;
2727         this.hoveredNode = node;
2728         this.toggleStylesOnHover(node, true);
2729       } else if(this.hoveredNode && !this.hoveredNode.selected) {
2730         //check if an animation is running and exit it
2731         this.fx.nodeFxAnimation.stopTimer();
2732         //unselect hovered node
2733         this.toggleStylesOnHover(this.hoveredNode, false);
2734         delete this.hoveredNode.hovered;
2735         this.hoveredNode = false;
2736       }
2737     }
2738   }
2739 });
2740
2741 Extras.Classes.Navigation = new Class({
2742   Implements: [ExtrasInitializer, EventsInterface],
2743   
2744   initializePost: function() {
2745     this.pos = false;
2746     this.pressed = false;
2747   },
2748   
2749   onMouseWheel: function(e, win, scroll) {
2750     if(!this.config.zooming) return;
2751     $.event.stop($.event.get(e, win));
2752     var val = this.config.zooming / 1000,
2753         ans = 1 + scroll * val;
2754     this.canvas.scale(ans, ans);
2755   },
2756   
2757   onMouseDown: function(e, win, eventInfo) {
2758     if(!this.config.panning) return;
2759     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2760     this.pressed = true;
2761     this.pos = eventInfo.getPos();
2762     var canvas = this.canvas,
2763         ox = canvas.translateOffsetX,
2764         oy = canvas.translateOffsetY,
2765         sx = canvas.scaleOffsetX,
2766         sy = canvas.scaleOffsetY;
2767     this.pos.x *= sx;
2768     this.pos.x += ox;
2769     this.pos.y *= sy;
2770     this.pos.y += oy;
2771   },
2772   
2773   onMouseMove: function(e, win, eventInfo) {
2774     if(!this.config.panning) return;
2775     if(!this.pressed) return;
2776     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2777     var thispos = this.pos, 
2778         currentPos = eventInfo.getPos(),
2779         canvas = this.canvas,
2780         ox = canvas.translateOffsetX,
2781         oy = canvas.translateOffsetY,
2782         sx = canvas.scaleOffsetX,
2783         sy = canvas.scaleOffsetY;
2784     currentPos.x *= sx;
2785     currentPos.y *= sy;
2786     currentPos.x += ox;
2787     currentPos.y += oy;
2788     var x = currentPos.x - thispos.x,
2789         y = currentPos.y - thispos.y;
2790     this.pos = currentPos;
2791     this.canvas.translate(x * 1/sx, y * 1/sy);
2792   },
2793   
2794   onMouseUp: function(e, win, eventInfo, isRightClick) {
2795     if(!this.config.panning) return;
2796     this.pressed = false;
2797   }
2798 });
2799
2800
2801 /*
2802  * File: Canvas.js
2803  *
2804  */
2805
2806 /*
2807  Class: Canvas
2808  
2809         A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to 
2810         know more about <Canvas> options take a look at <Options.Canvas>.
2811  
2812  A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior 
2813  across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2814  
2815  Example:
2816  
2817  Suppose we have this HTML
2818  
2819  (start code xml)
2820         <div id="infovis"></div>
2821  (end code)
2822  
2823  Now we create a new Visualization
2824  
2825  (start code js)
2826         var viz = new $jit.Viz({
2827                 //Where to inject the canvas. Any div container will do.
2828                 'injectInto':'infovis',
2829                  //width and height for canvas. 
2830                  //Default's to the container offsetWidth and Height.
2831                  'width': 900,
2832                  'height':500
2833          });
2834  (end code)
2835
2836  The generated HTML will look like this
2837  
2838  (start code xml)
2839  <div id="infovis">
2840         <div id="infovis-canvaswidget" style="position:relative;">
2841         <canvas id="infovis-canvas" width=900 height=500
2842         style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2843         <div id="infovis-label"
2844         style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2845         </div>
2846         </div>
2847  </div>
2848  (end code)
2849  
2850  As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2851  of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2852  */
2853
2854 var Canvas;
2855 (function() {
2856   //check for native canvas support
2857   var canvasType = typeof HTMLCanvasElement,
2858       supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2859   //create element function
2860   function $E(tag, props) {
2861     var elem = document.createElement(tag);
2862     for(var p in props) {
2863       if(typeof props[p] == "object") {
2864         $.extend(elem[p], props[p]);
2865       } else {
2866         elem[p] = props[p];
2867       }
2868     }
2869     if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2870       elem = G_vmlCanvasManager.initElement(elem);
2871     }
2872     return elem;
2873   }
2874   //canvas widget which we will call just Canvas
2875   $jit.Canvas = Canvas = new Class({
2876     canvases: [],
2877     pos: false,
2878     element: false,
2879     labelContainer: false,
2880     translateOffsetX: 0,
2881     translateOffsetY: 0,
2882     scaleOffsetX: 1,
2883     scaleOffsetY: 1,
2884     
2885     initialize: function(viz, opt) {
2886       this.viz = viz;
2887       this.opt = opt;
2888       var id = $.type(opt.injectInto) == 'string'? 
2889           opt.injectInto:opt.injectInto.id,
2890           idLabel = id + "-label", 
2891           wrapper = $(id),
2892           width = opt.width || wrapper.offsetWidth,
2893           height = opt.height || wrapper.offsetHeight;
2894       this.id = id;
2895       //canvas options
2896       var canvasOptions = {
2897         injectInto: id,
2898         width: width,
2899         height: height
2900       };
2901       //create main wrapper
2902       this.element = $E('div', {
2903         'id': id + '-canvaswidget',
2904         'style': {
2905           'position': 'relative',
2906           'width': width + 'px',
2907           'height': height + 'px'
2908         }
2909       });
2910       //create label container
2911       this.labelContainer = this.createLabelContainer(opt.Label.type, 
2912           idLabel, canvasOptions);
2913       //create primary canvas
2914       this.canvases.push(new Canvas.Base({
2915         config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2916         plot: function(base) {
2917           viz.fx.plot();
2918         },
2919         resize: function() {
2920           viz.refresh();
2921         }
2922       }));
2923       //create secondary canvas
2924       var back = opt.background;
2925       if(back) {
2926         var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2927         this.canvases.push(new Canvas.Base(backCanvas));
2928       }
2929       //insert canvases
2930       var len = this.canvases.length;
2931       while(len--) {
2932         this.element.appendChild(this.canvases[len].canvas);
2933         if(len > 0) {
2934           this.canvases[len].plot();
2935         }
2936       }
2937       this.element.appendChild(this.labelContainer);
2938       wrapper.appendChild(this.element);
2939       //Update canvas position when the page is scrolled.
2940       var timer = null, that = this;
2941       $.addEvent(window, 'scroll', function() {
2942         clearTimeout(timer);
2943         timer = setTimeout(function() {
2944           that.getPos(true); //update canvas position
2945         }, 500);
2946       });
2947       $.addEvent(window, 'click', function() {
2948         clearTimeout(timer);
2949         timer = setTimeout(function() {
2950           that.getPos(true); //update canvas position
2951         }, 500);
2952       });
2953       sb = document.getElementById('sb'+id);
2954       $.addEvent(sb, 'scroll', function() {
2955         clearTimeout(timer);
2956         timer = setTimeout(function() {
2957           that.getPos(true); //update canvas position
2958         }, 500);
2959       });
2960     },
2961     /*
2962       Method: getCtx
2963       
2964       Returns the main canvas context object
2965       
2966       Example:
2967       
2968       (start code js)
2969        var ctx = canvas.getCtx();
2970        //Now I can use the native canvas context
2971        //and for example change some canvas styles
2972        ctx.globalAlpha = 1;
2973       (end code)
2974     */
2975     getCtx: function(i) {
2976       return this.canvases[i || 0].getCtx();
2977     },
2978     /*
2979       Method: getConfig
2980       
2981       Returns the current Configuration for this Canvas Widget.
2982       
2983       Example:
2984       
2985       (start code js)
2986        var config = canvas.getConfig();
2987       (end code)
2988     */
2989     getConfig: function() {
2990       return this.opt;
2991     },
2992     /*
2993       Method: getElement
2994
2995       Returns the main Canvas DOM wrapper
2996       
2997       Example:
2998       
2999       (start code js)
3000        var wrapper = canvas.getElement();
3001        //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3002       (end code)
3003     */
3004     getElement: function() {
3005       return this.element;
3006     },
3007     /*
3008       Method: getSize
3009       
3010       Returns canvas dimensions.
3011       
3012       Returns:
3013       
3014       An object with *width* and *height* properties.
3015       
3016       Example:
3017       (start code js)
3018       canvas.getSize(); //returns { width: 900, height: 500 }
3019       (end code)
3020     */
3021     getSize: function(i) {
3022       return this.canvases[i || 0].getSize();
3023     },
3024     /*
3025       Method: resize
3026       
3027       Resizes the canvas.
3028       
3029       Parameters:
3030       
3031       width - New canvas width.
3032       height - New canvas height.
3033       
3034       Example:
3035       
3036       (start code js)
3037        canvas.resize(width, height);
3038       (end code)
3039     
3040     */
3041     resize: function(width, height) {
3042       this.getPos(true);
3043       this.translateOffsetX = this.translateOffsetY = 0;
3044       this.scaleOffsetX = this.scaleOffsetY = 1;
3045       for(var i=0, l=this.canvases.length; i<l; i++) {
3046         this.canvases[i].resize(width, height);
3047       }
3048       var style = this.element.style;
3049       style.width = width + 'px';
3050       style.height = height + 'px';
3051       if(this.labelContainer)
3052         this.labelContainer.style.width = width + 'px';
3053     },
3054     /*
3055       Method: translate
3056       
3057       Applies a translation to the canvas.
3058       
3059       Parameters:
3060       
3061       x - (number) x offset.
3062       y - (number) y offset.
3063       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3064       
3065       Example:
3066       
3067       (start code js)
3068        canvas.translate(30, 30);
3069       (end code)
3070     
3071     */
3072     translate: function(x, y, disablePlot) {
3073       this.translateOffsetX += x*this.scaleOffsetX;
3074       this.translateOffsetY += y*this.scaleOffsetY;
3075       for(var i=0, l=this.canvases.length; i<l; i++) {
3076         this.canvases[i].translate(x, y, disablePlot);
3077       }
3078     },
3079     /*
3080       Method: scale
3081       
3082       Scales the canvas.
3083       
3084       Parameters:
3085       
3086       x - (number) scale value.
3087       y - (number) scale value.
3088       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3089       
3090       Example:
3091       
3092       (start code js)
3093        canvas.scale(0.5, 0.5);
3094       (end code)
3095     
3096     */
3097     scale: function(x, y, disablePlot) {
3098         if(!disablePlot) {
3099                 disablePlot = false;
3100                 }
3101       var px = this.scaleOffsetX * x,
3102           py = this.scaleOffsetY * y;
3103       var dx = this.translateOffsetX * (x -1) / px,
3104           dy = this.translateOffsetY * (y -1) / py;
3105       this.scaleOffsetX = px;
3106       this.scaleOffsetY = py;
3107       for(var i=0, l=this.canvases.length; i<l; i++) {
3108         this.canvases[i].scale(x, y, true);
3109       }
3110       this.translate(dx, dy, disablePlot);
3111     },
3112     /*
3113       Method: getPos
3114       
3115       Returns the canvas position as an *x, y* object.
3116       
3117       Parameters:
3118       
3119       force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3120       
3121       Returns:
3122       
3123       An object with *x* and *y* properties.
3124       
3125       Example:
3126       (start code js)
3127       canvas.getPos(true); //returns { x: 900, y: 500 }
3128       (end code)
3129     */
3130     getPos: function(force){
3131       if(force || !this.pos) {
3132         return this.pos = $.getPos(this.getElement());
3133       }
3134       return this.pos;
3135     },
3136     /*
3137        Method: clear
3138        
3139        Clears the canvas.
3140     */
3141     clear: function(i){
3142       this.canvases[i||0].clear();
3143     },
3144     
3145     path: function(type, action){
3146       var ctx = this.canvases[0].getCtx();
3147       ctx.beginPath();
3148       action(ctx);
3149       ctx[type]();
3150       ctx.closePath();
3151     },
3152     
3153     createLabelContainer: function(type, idLabel, dim) {
3154       var NS = 'http://www.w3.org/2000/svg';
3155       if(type == 'HTML' || type == 'Native') {
3156         return $E('div', {
3157           'id': idLabel,
3158           'style': {
3159             'overflow': 'visible',
3160             'position': 'absolute',
3161             'top': 0,
3162             'left': 0,
3163             'width': dim.width + 'px',
3164             'height': 0
3165           }
3166         });
3167       } else if(type == 'SVG') {
3168         var svgContainer = document.createElementNS(NS, 'svg:svg');
3169         svgContainer.setAttribute("width", dim.width);
3170         svgContainer.setAttribute('height', dim.height);
3171         var style = svgContainer.style;
3172         style.position = 'absolute';
3173         style.left = style.top = '0px';
3174         var labelContainer = document.createElementNS(NS, 'svg:g');
3175         labelContainer.setAttribute('width', dim.width);
3176         labelContainer.setAttribute('height', dim.height);
3177         labelContainer.setAttribute('x', 0);
3178         labelContainer.setAttribute('y', 0);
3179         labelContainer.setAttribute('id', idLabel);
3180         svgContainer.appendChild(labelContainer);
3181         return svgContainer;
3182       }
3183     }
3184   });
3185   //base canvas wrapper
3186   Canvas.Base = new Class({
3187     translateOffsetX: 0,
3188     translateOffsetY: 0,
3189     scaleOffsetX: 1,
3190     scaleOffsetY: 1,
3191
3192     initialize: function(viz) {
3193       this.viz = viz;
3194       this.opt = viz.config;
3195       this.size = false;
3196       this.createCanvas();
3197       this.translateToCenter();
3198     },
3199     createCanvas: function() {
3200       var opt = this.opt,
3201           width = opt.width,
3202           height = opt.height;
3203       this.canvas = $E('canvas', {
3204         'id': opt.injectInto + opt.idSuffix,
3205         'width': width,
3206         'height': height,
3207         'style': {
3208           'position': 'absolute',
3209           'top': 0,
3210           'left': 0,
3211           'width': width + 'px',
3212           'height': height + 'px'
3213         }
3214       });
3215     },
3216     getCtx: function() {
3217       if(!this.ctx) 
3218         return this.ctx = this.canvas.getContext('2d');
3219       return this.ctx;
3220     },
3221     getSize: function() {
3222       if(this.size) return this.size;
3223       var canvas = this.canvas;
3224       return this.size = {
3225         width: canvas.width,
3226         height: canvas.height
3227       };
3228     },
3229     translateToCenter: function(ps) {
3230       var size = this.getSize(),
3231           width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3232           height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3233       var ctx = this.getCtx();
3234       ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3235       ctx.translate(width/2, height/2);
3236     },
3237     resize: function(width, height) {
3238       var size = this.getSize(),
3239           canvas = this.canvas,
3240           styles = canvas.style;
3241       this.size = false;
3242       canvas.width = width;
3243       canvas.height = height;
3244       styles.width = width + "px";
3245       styles.height = height + "px";
3246       //small ExCanvas fix
3247       if (!supportsCanvas) {
3248         // for some unknown reason _resize() method is not being called in some
3249         // IE browser instances, so call it manually
3250         // in obfuscated version of flashcanvas.js the method is renamed to I()
3251         this.getCtx().I(width, height);
3252       }
3253       this.translateToCenter();
3254       this.translateOffsetX =
3255         this.translateOffsetY = 0;
3256       this.scaleOffsetX = 
3257         this.scaleOffsetY = 1;
3258       this.clear();
3259       this.viz.resize(width, height, this);
3260     },
3261     translate: function(x, y, disablePlot) {
3262       var sx = this.scaleOffsetX,
3263           sy = this.scaleOffsetY;
3264       this.translateOffsetX += x*sx;
3265       this.translateOffsetY += y*sy;
3266       this.getCtx().translate(x, y);
3267       !disablePlot && this.plot();
3268     },
3269     scale: function(x, y, disablePlot) {
3270       this.scaleOffsetX *= x;
3271       this.scaleOffsetY *= y;
3272       this.getCtx().scale(x, y);
3273       !disablePlot && this.plot();
3274     },
3275     clear: function(){
3276       var size = this.getSize(),
3277           ox = this.translateOffsetX,
3278           oy = this.translateOffsetY,
3279           sx = this.scaleOffsetX,
3280           sy = this.scaleOffsetY;
3281       this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx, 
3282                               (-size.height / 2 - oy) * 1/sy, 
3283                               size.width * 1/sx, size.height * 1/sy);
3284     },
3285     plot: function() {
3286       this.clear();
3287       this.viz.plot(this);
3288     }
3289   });
3290   //background canvases
3291   //TODO(nico): document this!
3292   Canvas.Background = {};
3293   Canvas.Background.Circles = new Class({
3294     initialize: function(viz, options) {
3295       this.viz = viz;
3296       this.config = $.merge({
3297         idSuffix: '-bkcanvas',
3298         levelDistance: 100,
3299         numberOfCircles: 6,
3300         CanvasStyles: {},
3301         offset: 0
3302       }, options);
3303     },
3304     resize: function(width, height, base) {
3305       this.plot(base);
3306     },
3307     plot: function(base) {
3308       var canvas = base.canvas,
3309           ctx = base.getCtx(),
3310           conf = this.config,
3311           styles = conf.CanvasStyles;
3312       //set canvas styles
3313       for(var s in styles) ctx[s] = styles[s];
3314       var n = conf.numberOfCircles,
3315           rho = conf.levelDistance;
3316       for(var i=1; i<=n; i++) {
3317         ctx.beginPath();
3318         ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3319         ctx.stroke();
3320         ctx.closePath();
3321       }
3322       //TODO(nico): print labels too!
3323     }
3324   });
3325   Canvas.Background.Fade = new Class({
3326     initialize: function(viz, options) {
3327       this.viz = viz;
3328       this.config = $.merge({
3329         idSuffix: '-bkcanvas',
3330         CanvasStyles: {},
3331         offset: 0
3332       }, options);
3333     },
3334     resize: function(width, height, base) {
3335       this.plot(base);
3336     },
3337     plot: function(base) {
3338       var canvas = base.canvas,
3339           ctx = base.getCtx(),
3340           conf = this.config,
3341           styles = conf.CanvasStyles,
3342           size = base.getSize();
3343                   ctx.fillStyle = 'rgb(255,255,255)';
3344                   ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3345       //TODO(nico): print labels too!
3346     }
3347   });
3348 })();
3349
3350
3351 /*
3352  * File: Polar.js
3353  * 
3354  * Defines the <Polar> class.
3355  *
3356  * Description:
3357  *
3358  * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3359  *
3360  * See also:
3361  *
3362  * <http://en.wikipedia.org/wiki/Polar_coordinates>
3363  *
3364 */
3365
3366 /*
3367    Class: Polar
3368
3369    A multi purpose polar representation.
3370
3371    Description:
3372  
3373    The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3374  
3375    See also:
3376  
3377    <http://en.wikipedia.org/wiki/Polar_coordinates>
3378  
3379    Parameters:
3380
3381       theta - An angle.
3382       rho - The norm.
3383 */
3384
3385 var Polar = function(theta, rho) {
3386   this.theta = theta;
3387   this.rho = rho;
3388 };
3389
3390 $jit.Polar = Polar;
3391
3392 Polar.prototype = {
3393     /*
3394        Method: getc
3395     
3396        Returns a complex number.
3397     
3398        Parameters:
3399
3400        simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3401
3402       Returns:
3403     
3404           A complex number.
3405     */
3406     getc: function(simple) {
3407         return this.toComplex(simple);
3408     },
3409
3410     /*
3411        Method: getp
3412     
3413        Returns a <Polar> representation.
3414     
3415        Returns:
3416     
3417           A variable in polar coordinates.
3418     */
3419     getp: function() {
3420         return this;
3421     },
3422
3423
3424     /*
3425        Method: set
3426     
3427        Sets a number.
3428
3429        Parameters:
3430
3431        v - A <Complex> or <Polar> instance.
3432     
3433     */
3434     set: function(v) {
3435         v = v.getp();
3436         this.theta = v.theta; this.rho = v.rho;
3437     },
3438
3439     /*
3440        Method: setc
3441     
3442        Sets a <Complex> number.
3443
3444        Parameters:
3445
3446        x - A <Complex> number real part.
3447        y - A <Complex> number imaginary part.
3448     
3449     */
3450     setc: function(x, y) {
3451         this.rho = Math.sqrt(x * x + y * y);
3452         this.theta = Math.atan2(y, x);
3453         if(this.theta < 0) this.theta += Math.PI * 2;
3454     },
3455
3456     /*
3457        Method: setp
3458     
3459        Sets a polar number.
3460
3461        Parameters:
3462
3463        theta - A <Polar> number angle property.
3464        rho - A <Polar> number rho property.
3465     
3466     */
3467     setp: function(theta, rho) {
3468         this.theta = theta; 
3469         this.rho = rho;
3470     },
3471
3472     /*
3473        Method: clone
3474     
3475        Returns a copy of the current object.
3476     
3477        Returns:
3478     
3479           A copy of the real object.
3480     */
3481     clone: function() {
3482         return new Polar(this.theta, this.rho);
3483     },
3484
3485     /*
3486        Method: toComplex
3487     
3488         Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3489     
3490         Parameters:
3491
3492         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*.
3493  
3494         Returns:
3495     
3496           A new <Complex> instance.
3497     */
3498     toComplex: function(simple) {
3499         var x = Math.cos(this.theta) * this.rho;
3500         var y = Math.sin(this.theta) * this.rho;
3501         if(simple) return { 'x': x, 'y': y};
3502         return new Complex(x, y);
3503     },
3504
3505     /*
3506        Method: add
3507     
3508         Adds two <Polar> instances.
3509     
3510        Parameters:
3511
3512        polar - A <Polar> number.
3513
3514        Returns:
3515     
3516           A new Polar instance.
3517     */
3518     add: function(polar) {
3519         return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3520     },
3521     
3522     /*
3523        Method: scale
3524     
3525         Scales a polar norm.
3526     
3527         Parameters:
3528
3529         number - A scale factor.
3530         
3531         Returns:
3532     
3533           A new Polar instance.
3534     */
3535     scale: function(number) {
3536         return new Polar(this.theta, this.rho * number);
3537     },
3538     
3539     /*
3540        Method: equals
3541     
3542        Comparison method.
3543
3544        Returns *true* if the theta and rho properties are equal.
3545
3546        Parameters:
3547
3548        c - A <Polar> number.
3549
3550        Returns:
3551
3552        *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3553     */
3554     equals: function(c) {
3555         return this.theta == c.theta && this.rho == c.rho;
3556     },
3557     
3558     /*
3559        Method: $add
3560     
3561         Adds two <Polar> instances affecting the current object.
3562     
3563        Paramters:
3564
3565        polar - A <Polar> instance.
3566
3567        Returns:
3568     
3569           The changed object.
3570     */
3571     $add: function(polar) {
3572         this.theta = this.theta + polar.theta; this.rho += polar.rho;
3573         return this;
3574     },
3575
3576     /*
3577        Method: $madd
3578     
3579         Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3580     
3581        Parameters:
3582
3583        polar - A <Polar> instance.
3584
3585        Returns:
3586     
3587           The changed object.
3588     */
3589     $madd: function(polar) {
3590         this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3591         return this;
3592     },
3593
3594     
3595     /*
3596        Method: $scale
3597     
3598         Scales a polar instance affecting the object.
3599     
3600       Parameters:
3601
3602       number - A scaling factor.
3603
3604       Returns:
3605     
3606           The changed object.
3607     */
3608     $scale: function(number) {
3609         this.rho *= number;
3610         return this;
3611     },
3612     
3613     /*
3614        Method: interpolate
3615     
3616         Calculates a polar interpolation between two points at a given delta moment.
3617
3618         Parameters:
3619       
3620         elem - A <Polar> instance.
3621         delta - A delta factor ranging [0, 1].
3622     
3623        Returns:
3624     
3625           A new <Polar> instance representing an interpolation between _this_ and _elem_
3626     */
3627     interpolate: function(elem, delta) {
3628         var pi = Math.PI, pi2 = pi * 2;
3629         var ch = function(t) {
3630             var a =  (t < 0)? (t % pi2) + pi2 : t % pi2;
3631             return a;
3632         };
3633         var tt = this.theta, et = elem.theta;
3634         var sum, diff = Math.abs(tt - et);
3635         if(diff == pi) {
3636           if(tt > et) {
3637             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3638           } else {
3639             sum = ch((et - pi2 + (tt - (et)) * delta));
3640           }
3641         } else if(diff >= pi) {
3642           if(tt > et) {
3643             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3644           } else {
3645             sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3646           }
3647         } else {  
3648           sum = ch((et + (tt - et) * delta)) ;
3649         }
3650         var r = (this.rho - elem.rho) * delta + elem.rho;
3651         return {
3652           'theta': sum,
3653           'rho': r
3654         };
3655     }
3656 };
3657
3658
3659 var $P = function(a, b) { return new Polar(a, b); };
3660
3661 Polar.KER = $P(0, 0);
3662
3663
3664
3665 /*
3666  * File: Complex.js
3667  * 
3668  * Defines the <Complex> class.
3669  *
3670  * Description:
3671  *
3672  * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3673  *
3674  * See also:
3675  *
3676  * <http://en.wikipedia.org/wiki/Complex_number>
3677  *
3678 */
3679
3680 /*
3681    Class: Complex
3682     
3683    A multi-purpose Complex Class with common methods.
3684  
3685    Description:
3686  
3687    The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3688  
3689    See also:
3690  
3691    <http://en.wikipedia.org/wiki/Complex_number>
3692
3693    Parameters:
3694
3695    x - _optional_ A Complex number real part.
3696    y - _optional_ A Complex number imaginary part.
3697  
3698 */
3699
3700 var Complex = function(x, y) {
3701   this.x = x;
3702   this.y = y;
3703 };
3704
3705 $jit.Complex = Complex;
3706
3707 Complex.prototype = {
3708     /*
3709        Method: getc
3710     
3711        Returns a complex number.
3712     
3713        Returns:
3714     
3715           A complex number.
3716     */
3717     getc: function() {
3718         return this;
3719     },
3720
3721     /*
3722        Method: getp
3723     
3724        Returns a <Polar> representation of this number.
3725     
3726        Parameters:
3727
3728        simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3729
3730        Returns:
3731     
3732           A variable in <Polar> coordinates.
3733     */
3734     getp: function(simple) {
3735         return this.toPolar(simple);
3736     },
3737
3738
3739     /*
3740        Method: set
3741     
3742        Sets a number.
3743
3744        Parameters:
3745
3746        c - A <Complex> or <Polar> instance.
3747     
3748     */
3749     set: function(c) {
3750       c = c.getc(true);
3751       this.x = c.x; 
3752       this.y = c.y;
3753     },
3754
3755     /*
3756        Method: setc
3757     
3758        Sets a complex number.
3759
3760        Parameters:
3761
3762        x - A <Complex> number Real part.
3763        y - A <Complex> number Imaginary part.
3764     
3765     */
3766     setc: function(x, y) {
3767         this.x = x; 
3768         this.y = y;
3769     },
3770
3771     /*
3772        Method: setp
3773     
3774        Sets a polar number.
3775
3776        Parameters:
3777
3778        theta - A <Polar> number theta property.
3779        rho - A <Polar> number rho property.
3780     
3781     */
3782     setp: function(theta, rho) {
3783         this.x = Math.cos(theta) * rho;
3784         this.y = Math.sin(theta) * rho;
3785     },
3786
3787     /*
3788        Method: clone
3789     
3790        Returns a copy of the current object.
3791     
3792        Returns:
3793     
3794           A copy of the real object.
3795     */
3796     clone: function() {
3797         return new Complex(this.x, this.y);
3798     },
3799
3800     /*
3801        Method: toPolar
3802     
3803        Transforms cartesian to polar coordinates.
3804     
3805        Parameters:
3806
3807        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*.
3808        
3809        Returns:
3810     
3811           A new <Polar> instance.
3812     */
3813     
3814     toPolar: function(simple) {
3815         var rho = this.norm();
3816         var atan = Math.atan2(this.y, this.x);
3817         if(atan < 0) atan += Math.PI * 2;
3818         if(simple) return { 'theta': atan, 'rho': rho };
3819         return new Polar(atan, rho);
3820     },
3821     /*
3822        Method: norm
3823     
3824        Calculates a <Complex> number norm.
3825     
3826        Returns:
3827     
3828           A real number representing the complex norm.
3829     */
3830     norm: function () {
3831         return Math.sqrt(this.squaredNorm());
3832     },
3833     
3834     /*
3835        Method: squaredNorm
3836     
3837        Calculates a <Complex> number squared norm.
3838     
3839        Returns:
3840     
3841           A real number representing the complex squared norm.
3842     */
3843     squaredNorm: function () {
3844         return this.x*this.x + this.y*this.y;
3845     },
3846
3847     /*
3848        Method: add
3849     
3850        Returns the result of adding two complex numbers.
3851        
3852        Does not alter the original object.
3853
3854        Parameters:
3855     
3856           pos - A <Complex> instance.
3857     
3858        Returns:
3859     
3860          The result of adding two complex numbers.
3861     */
3862     add: function(pos) {
3863         return new Complex(this.x + pos.x, this.y + pos.y);
3864     },
3865
3866     /*
3867        Method: prod
3868     
3869        Returns the result of multiplying two <Complex> numbers.
3870        
3871        Does not alter the original object.
3872
3873        Parameters:
3874     
3875           pos - A <Complex> instance.
3876     
3877        Returns:
3878     
3879          The result of multiplying two complex numbers.
3880     */
3881     prod: function(pos) {
3882         return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3883     },
3884
3885     /*
3886        Method: conjugate
3887     
3888        Returns the conjugate of this <Complex> number.
3889
3890        Does not alter the original object.
3891
3892        Returns:
3893     
3894          The conjugate of this <Complex> number.
3895     */
3896     conjugate: function() {
3897         return new Complex(this.x, -this.y);
3898     },
3899
3900
3901     /*
3902        Method: scale
3903     
3904        Returns the result of scaling a <Complex> instance.
3905        
3906        Does not alter the original object.
3907
3908        Parameters:
3909     
3910           factor - A scale factor.
3911     
3912        Returns:
3913     
3914          The result of scaling this complex to a factor.
3915     */
3916     scale: function(factor) {
3917         return new Complex(this.x * factor, this.y * factor);
3918     },
3919
3920     /*
3921        Method: equals
3922     
3923        Comparison method.
3924
3925        Returns *true* if both real and imaginary parts are equal.
3926
3927        Parameters:
3928
3929        c - A <Complex> instance.
3930
3931        Returns:
3932
3933        A boolean instance indicating if both <Complex> numbers are equal.
3934     */
3935     equals: function(c) {
3936         return this.x == c.x && this.y == c.y;
3937     },
3938
3939     /*
3940        Method: $add
3941     
3942        Returns the result of adding two <Complex> numbers.
3943        
3944        Alters the original object.
3945
3946        Parameters:
3947     
3948           pos - A <Complex> instance.
3949     
3950        Returns:
3951     
3952          The result of adding two complex numbers.
3953     */
3954     $add: function(pos) {
3955         this.x += pos.x; this.y += pos.y;
3956         return this;    
3957     },
3958     
3959     /*
3960        Method: $prod
3961     
3962        Returns the result of multiplying two <Complex> numbers.
3963        
3964        Alters the original object.
3965
3966        Parameters:
3967     
3968           pos - A <Complex> instance.
3969     
3970        Returns:
3971     
3972          The result of multiplying two complex numbers.
3973     */
3974     $prod:function(pos) {
3975         var x = this.x, y = this.y;
3976         this.x = x*pos.x - y*pos.y;
3977         this.y = y*pos.x + x*pos.y;
3978         return this;
3979     },
3980     
3981     /*
3982        Method: $conjugate
3983     
3984        Returns the conjugate for this <Complex>.
3985        
3986        Alters the original object.
3987
3988        Returns:
3989     
3990          The conjugate for this complex.
3991     */
3992     $conjugate: function() {
3993         this.y = -this.y;
3994         return this;
3995     },
3996     
3997     /*
3998        Method: $scale
3999     
4000        Returns the result of scaling a <Complex> instance.
4001        
4002        Alters the original object.
4003
4004        Parameters:
4005     
4006           factor - A scale factor.
4007     
4008        Returns:
4009     
4010          The result of scaling this complex to a factor.
4011     */
4012     $scale: function(factor) {
4013         this.x *= factor; this.y *= factor;
4014         return this;
4015     },
4016     
4017     /*
4018        Method: $div
4019     
4020        Returns the division of two <Complex> numbers.
4021        
4022        Alters the original object.
4023
4024        Parameters:
4025     
4026           pos - A <Complex> number.
4027     
4028        Returns:
4029     
4030          The result of scaling this complex to a factor.
4031     */
4032     $div: function(pos) {
4033         var x = this.x, y = this.y;
4034         var sq = pos.squaredNorm();
4035         this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4036         return this.$scale(1 / sq);
4037     }
4038 };
4039
4040 var $C = function(a, b) { return new Complex(a, b); };
4041
4042 Complex.KER = $C(0, 0);
4043
4044
4045
4046 /*
4047  * File: Graph.js
4048  *
4049 */
4050
4051 /*
4052  Class: Graph
4053
4054  A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4055
4056  An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4057  
4058  Example:
4059
4060  (start code js)
4061    //create new visualization
4062    var viz = new $jit.Viz(options);
4063    //load JSON data
4064    viz.loadJSON(json);
4065    //access model
4066    viz.graph; //<Graph> instance
4067  (end code)
4068  
4069  Implements:
4070  
4071  The following <Graph.Util> methods are implemented in <Graph>
4072  
4073   - <Graph.Util.getNode>
4074   - <Graph.Util.eachNode>
4075   - <Graph.Util.computeLevels>
4076   - <Graph.Util.eachBFS>
4077   - <Graph.Util.clean>
4078   - <Graph.Util.getClosestNodeToPos>
4079   - <Graph.Util.getClosestNodeToOrigin>
4080  
4081 */  
4082
4083 $jit.Graph = new Class({
4084
4085   initialize: function(opt, Node, Edge, Label) {
4086     var innerOptions = {
4087     'complex': false,
4088     'Node': {}
4089     };
4090     this.Node = Node;
4091     this.Edge = Edge;
4092     this.Label = Label;
4093     this.opt = $.merge(innerOptions, opt || {});
4094     this.nodes = {};
4095     this.edges = {};
4096     
4097     //add nodeList methods
4098     var that = this;
4099     this.nodeList = {};
4100     for(var p in Accessors) {
4101       that.nodeList[p] = (function(p) {
4102         return function() {
4103           var args = Array.prototype.slice.call(arguments);
4104           that.eachNode(function(n) {
4105             n[p].apply(n, args);
4106           });
4107         };
4108       })(p);
4109     }
4110
4111  },
4112
4113 /*
4114      Method: getNode
4115     
4116      Returns a <Graph.Node> by *id*.
4117
4118      Parameters:
4119
4120      id - (string) A <Graph.Node> id.
4121
4122      Example:
4123
4124      (start code js)
4125        var node = graph.getNode('nodeId');
4126      (end code)
4127 */  
4128  getNode: function(id) {
4129     if(this.hasNode(id)) return this.nodes[id];
4130     return false;
4131  },
4132
4133  /*
4134    Method: getByName
4135   
4136    Returns a <Graph.Node> by *name*.
4137   
4138    Parameters:
4139   
4140    name - (string) A <Graph.Node> name.
4141   
4142    Example:
4143   
4144    (start code js)
4145      var node = graph.getByName('someName');
4146    (end code)
4147   */  
4148   getByName: function(name) {
4149     for(var id in this.nodes) {
4150       var n = this.nodes[id];
4151       if(n.name == name) return n;
4152     }
4153     return false;
4154   },
4155
4156 /*
4157    Method: getAdjacence
4158   
4159    Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4160
4161    Parameters:
4162
4163    id - (string) A <Graph.Node> id.
4164    id2 - (string) A <Graph.Node> id.
4165 */  
4166   getAdjacence: function (id, id2) {
4167     if(id in this.edges) {
4168       return this.edges[id][id2];
4169     }
4170     return false;
4171  },
4172
4173     /*
4174      Method: addNode
4175     
4176      Adds a node.
4177      
4178      Parameters:
4179     
4180       obj - An object with the properties described below
4181
4182       id - (string) A node id
4183       name - (string) A node's name
4184       data - (object) A node's data hash
4185
4186     See also:
4187     <Graph.Node>
4188
4189   */  
4190   addNode: function(obj) { 
4191    if(!this.nodes[obj.id]) {  
4192      var edges = this.edges[obj.id] = {};
4193      this.nodes[obj.id] = new Graph.Node($.extend({
4194         'id': obj.id,
4195         'name': obj.name,
4196         'data': $.merge(obj.data || {}, {}),
4197         'adjacencies': edges 
4198       }, this.opt.Node), 
4199       this.opt.complex, 
4200       this.Node, 
4201       this.Edge,
4202       this.Label);
4203     }
4204     return this.nodes[obj.id];
4205   },
4206   
4207     /*
4208      Method: addAdjacence
4209     
4210      Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4211      
4212      Parameters:
4213     
4214       obj - (object) A <Graph.Node> object.
4215       obj2 - (object) Another <Graph.Node> object.
4216       data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4217
4218     See also:
4219
4220     <Graph.Node>, <Graph.Adjacence>
4221     */  
4222   addAdjacence: function (obj, obj2, data) {
4223     if(!this.hasNode(obj.id)) { this.addNode(obj); }
4224     if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4225     obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4226     if(!obj.adjacentTo(obj2)) {
4227       var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4228       var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4229       adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4230       return adjsObj[obj2.id];
4231     }
4232     return this.edges[obj.id][obj2.id];
4233  },
4234
4235     /*
4236      Method: removeNode
4237     
4238      Removes a <Graph.Node> matching the specified *id*.
4239
4240      Parameters:
4241
4242      id - (string) A node's id.
4243
4244     */  
4245   removeNode: function(id) {
4246     if(this.hasNode(id)) {
4247       delete this.nodes[id];
4248       var adjs = this.edges[id];
4249       for(var to in adjs) {
4250         delete this.edges[to][id];
4251       }
4252       delete this.edges[id];
4253     }
4254   },
4255   
4256 /*
4257      Method: removeAdjacence
4258     
4259      Removes a <Graph.Adjacence> matching *id1* and *id2*.
4260
4261      Parameters:
4262
4263      id1 - (string) A <Graph.Node> id.
4264      id2 - (string) A <Graph.Node> id.
4265 */  
4266   removeAdjacence: function(id1, id2) {
4267     delete this.edges[id1][id2];
4268     delete this.edges[id2][id1];
4269   },
4270
4271    /*
4272      Method: hasNode
4273     
4274      Returns a boolean indicating if the node belongs to the <Graph> or not.
4275      
4276      Parameters:
4277     
4278         id - (string) Node id.
4279    */  
4280   hasNode: function(id) {
4281     return id in this.nodes;
4282   },
4283   
4284   /*
4285     Method: empty
4286
4287     Empties the Graph
4288
4289   */
4290   empty: function() { this.nodes = {}; this.edges = {};}
4291
4292 });
4293
4294 var Graph = $jit.Graph;
4295
4296 /*
4297  Object: Accessors
4298  
4299  Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4300  
4301  */
4302 var Accessors;
4303
4304 (function () {
4305   var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4306     var data;
4307     type = type || 'current';
4308     prefix = "$" + (prefix ? prefix + "-" : "");
4309
4310     if(type == 'current') {
4311       data = this.data;
4312     } else if(type == 'start') {
4313       data = this.startData;
4314     } else if(type == 'end') {
4315       data = this.endData;
4316     }
4317
4318     var dollar = prefix + prop;
4319
4320     if(force) {
4321       return data[dollar];
4322     }
4323
4324     if(!this.Config.overridable)
4325       return prefixConfig[prop] || 0;
4326
4327     return (dollar in data) ?
4328       data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4329   }
4330
4331   var setDataInternal = function(prefix, prop, value, type) {
4332     type = type || 'current';
4333     prefix = '$' + (prefix ? prefix + '-' : '');
4334
4335     var data;
4336
4337     if(type == 'current') {
4338       data = this.data;
4339     } else if(type == 'start') {
4340       data = this.startData;
4341     } else if(type == 'end') {
4342       data = this.endData;
4343     }
4344
4345     data[prefix + prop] = value;
4346   }
4347
4348   var removeDataInternal = function(prefix, properties) {
4349     prefix = '$' + (prefix ? prefix + '-' : '');
4350     var that = this;
4351     $.each(properties, function(prop) {
4352       var pref = prefix + prop;
4353       delete that.data[pref];
4354       delete that.endData[pref];
4355       delete that.startData[pref];
4356     });
4357   }
4358
4359   Accessors = {
4360     /*
4361     Method: getData
4362
4363     Returns the specified data value property.
4364     This is useful for querying special/reserved <Graph.Node> data properties
4365     (i.e dollar prefixed properties).
4366
4367     Parameters:
4368
4369       prop  - (string) The name of the property. The dollar sign is not needed. For
4370               example *getData(width)* will return *data.$width*.
4371       type  - (string) The type of the data property queried. Default's "current". You can access *start* and *end* 
4372               data properties also. These properties are used when making animations.
4373       force - (boolean) Whether to obtain the true value of the property (equivalent to
4374               *data.$prop*) or to check for *node.overridable = true* first.
4375
4376     Returns:
4377
4378       The value of the dollar prefixed property or the global Node/Edge property
4379       value if *overridable=false*
4380
4381     Example:
4382     (start code js)
4383      node.getData('width'); //will return node.data.$width if Node.overridable=true;
4384     (end code)
4385     */
4386     getData: function(prop, type, force) {
4387       return getDataInternal.call(this, "", prop, type, force, this.Config);
4388     },
4389
4390
4391     /*
4392     Method: setData
4393
4394     Sets the current data property with some specific value.
4395     This method is only useful for reserved (dollar prefixed) properties.
4396
4397     Parameters:
4398
4399       prop  - (string) The name of the property. The dollar sign is not necessary. For
4400               example *setData(width)* will set *data.$width*.
4401       value - (mixed) The value to store.
4402       type  - (string) The type of the data property to store. Default's "current" but
4403               can also be "start" or "end".
4404
4405     Example:
4406     
4407     (start code js)
4408      node.setData('width', 30);
4409     (end code)
4410     
4411     If we were to make an animation of a node/edge width then we could do
4412     
4413     (start code js)
4414       var node = viz.getNode('nodeId');
4415       //set start and end values
4416       node.setData('width', 10, 'start');
4417       node.setData('width', 30, 'end');
4418       //will animate nodes width property
4419       viz.fx.animate({
4420         modes: ['node-property:width'],
4421         duration: 1000
4422       });
4423     (end code)
4424     */
4425     setData: function(prop, value, type) {
4426       setDataInternal.call(this, "", prop, value, type);
4427     },
4428
4429     /*
4430     Method: setDataset
4431
4432     Convenience method to set multiple data values at once.
4433     
4434     Parameters:
4435     
4436     types - (array|string) A set of 'current', 'end' or 'start' values.
4437     obj - (object) A hash containing the names and values of the properties to be altered.
4438
4439     Example:
4440     (start code js)
4441       node.setDataset(['current', 'end'], {
4442         'width': [100, 5],
4443         'color': ['#fff', '#ccc']
4444       });
4445       //...or also
4446       node.setDataset('end', {
4447         'width': 5,
4448         'color': '#ccc'
4449       });
4450     (end code)
4451     
4452     See also: 
4453     
4454     <Accessors.setData>
4455     
4456     */
4457     setDataset: function(types, obj) {
4458       types = $.splat(types);
4459       for(var attr in obj) {
4460         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4461           this.setData(attr, val[i], types[i]);
4462         }
4463       }
4464     },
4465     
4466     /*
4467     Method: removeData
4468
4469     Remove data properties.
4470
4471     Parameters:
4472
4473     One or more property names as arguments. The dollar sign is not needed.
4474
4475     Example:
4476     (start code js)
4477     node.removeData('width'); //now the default width value is returned
4478     (end code)
4479     */
4480     removeData: function() {
4481       removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4482     },
4483
4484     /*
4485     Method: getCanvasStyle
4486
4487     Returns the specified canvas style data value property. This is useful for
4488     querying special/reserved <Graph.Node> canvas style data properties (i.e.
4489     dollar prefixed properties that match with $canvas-<name of canvas style>).
4490
4491     Parameters:
4492
4493       prop  - (string) The name of the property. The dollar sign is not needed. For
4494               example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4495       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4496               data properties also.
4497               
4498     Example:
4499     (start code js)
4500       node.getCanvasStyle('shadowBlur');
4501     (end code)
4502     
4503     See also:
4504     
4505     <Accessors.getData>
4506     */
4507     getCanvasStyle: function(prop, type, force) {
4508       return getDataInternal.call(
4509           this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4510     },
4511
4512     /*
4513     Method: setCanvasStyle
4514
4515     Sets the canvas style data property with some specific value.
4516     This method is only useful for reserved (dollar prefixed) properties.
4517     
4518     Parameters:
4519     
4520     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4521     value - (mixed) The value to set to the property.
4522     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4523     
4524     Example:
4525     
4526     (start code js)
4527      node.setCanvasStyle('shadowBlur', 30);
4528     (end code)
4529     
4530     If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4531     
4532     (start code js)
4533       var node = viz.getNode('nodeId');
4534       //set start and end values
4535       node.setCanvasStyle('shadowBlur', 10, 'start');
4536       node.setCanvasStyle('shadowBlur', 30, 'end');
4537       //will animate nodes canvas style property for nodes
4538       viz.fx.animate({
4539         modes: ['node-style:shadowBlur'],
4540         duration: 1000
4541       });
4542     (end code)
4543     
4544     See also:
4545     
4546     <Accessors.setData>.
4547     */
4548     setCanvasStyle: function(prop, value, type) {
4549       setDataInternal.call(this, 'canvas', prop, value, type);
4550     },
4551
4552     /*
4553     Method: setCanvasStyles
4554
4555     Convenience method to set multiple styles at once.
4556
4557     Parameters:
4558     
4559     types - (array|string) A set of 'current', 'end' or 'start' values.
4560     obj - (object) A hash containing the names and values of the properties to be altered.
4561
4562     See also:
4563     
4564     <Accessors.setDataset>.
4565     */
4566     setCanvasStyles: function(types, obj) {
4567       types = $.splat(types);
4568       for(var attr in obj) {
4569         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4570           this.setCanvasStyle(attr, val[i], types[i]);
4571         }
4572       }
4573     },
4574
4575     /*
4576     Method: removeCanvasStyle
4577
4578     Remove canvas style properties from data.
4579
4580     Parameters:
4581     
4582     A variable number of canvas style strings.
4583
4584     See also:
4585     
4586     <Accessors.removeData>.
4587     */
4588     removeCanvasStyle: function() {
4589       removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4590     },
4591
4592     /*
4593     Method: getLabelData
4594
4595     Returns the specified label data value property. This is useful for
4596     querying special/reserved <Graph.Node> label options (i.e.
4597     dollar prefixed properties that match with $label-<name of label style>).
4598
4599     Parameters:
4600
4601       prop  - (string) The name of the property. The dollar sign prefix is not needed. For
4602               example *getLabelData(size)* will return *data[$label-size]*.
4603       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4604               data properties also.
4605               
4606     See also:
4607     
4608     <Accessors.getData>.
4609     */
4610     getLabelData: function(prop, type, force) {
4611       return getDataInternal.call(
4612           this, 'label', prop, type, force, this.Label);
4613     },
4614
4615     /*
4616     Method: setLabelData
4617
4618     Sets the current label data with some specific value.
4619     This method is only useful for reserved (dollar prefixed) properties.
4620
4621     Parameters:
4622     
4623     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4624     value - (mixed) The value to set to the property.
4625     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4626     
4627     Example:
4628     
4629     (start code js)
4630      node.setLabelData('size', 30);
4631     (end code)
4632     
4633     If we were to make an animation of a node label size then we could do
4634     
4635     (start code js)
4636       var node = viz.getNode('nodeId');
4637       //set start and end values
4638       node.setLabelData('size', 10, 'start');
4639       node.setLabelData('size', 30, 'end');
4640       //will animate nodes label size
4641       viz.fx.animate({
4642         modes: ['label-property:size'],
4643         duration: 1000
4644       });
4645     (end code)
4646     
4647     See also:
4648     
4649     <Accessors.setData>.
4650     */
4651     setLabelData: function(prop, value, type) {
4652       setDataInternal.call(this, 'label', prop, value, type);
4653     },
4654
4655     /*
4656     Method: setLabelDataset
4657
4658     Convenience function to set multiple label data at once.
4659
4660     Parameters:
4661     
4662     types - (array|string) A set of 'current', 'end' or 'start' values.
4663     obj - (object) A hash containing the names and values of the properties to be altered.
4664
4665     See also:
4666     
4667     <Accessors.setDataset>.
4668     */
4669     setLabelDataset: function(types, obj) {
4670       types = $.splat(types);
4671       for(var attr in obj) {
4672         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4673           this.setLabelData(attr, val[i], types[i]);
4674         }
4675       }
4676     },
4677
4678     /*
4679     Method: removeLabelData
4680
4681     Remove label properties from data.
4682     
4683     Parameters:
4684     
4685     A variable number of label property strings.
4686
4687     See also:
4688     
4689     <Accessors.removeData>.
4690     */
4691     removeLabelData: function() {
4692       removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4693     }
4694   };
4695 })();
4696
4697 /*
4698      Class: Graph.Node
4699
4700      A <Graph> node.
4701      
4702      Implements:
4703      
4704      <Accessors> methods.
4705      
4706      The following <Graph.Util> methods are implemented by <Graph.Node>
4707      
4708     - <Graph.Util.eachAdjacency>
4709     - <Graph.Util.eachLevel>
4710     - <Graph.Util.eachSubgraph>
4711     - <Graph.Util.eachSubnode>
4712     - <Graph.Util.anySubnode>
4713     - <Graph.Util.getSubnodes>
4714     - <Graph.Util.getParents>
4715     - <Graph.Util.isDescendantOf>     
4716 */
4717 Graph.Node = new Class({
4718     
4719   initialize: function(opt, complex, Node, Edge, Label) {
4720     var innerOptions = {
4721       'id': '',
4722       'name': '',
4723       'data': {},
4724       'startData': {},
4725       'endData': {},
4726       'adjacencies': {},
4727
4728       'selected': false,
4729       'drawn': false,
4730       'exist': false,
4731
4732       'angleSpan': {
4733         'begin': 0,
4734         'end' : 0
4735       },
4736
4737       'pos': (complex && $C(0, 0)) || $P(0, 0),
4738       'startPos': (complex && $C(0, 0)) || $P(0, 0),
4739       'endPos': (complex && $C(0, 0)) || $P(0, 0)
4740     };
4741     
4742     $.extend(this, $.extend(innerOptions, opt));
4743     this.Config = this.Node = Node;
4744     this.Edge = Edge;
4745     this.Label = Label;
4746   },
4747
4748     /*
4749        Method: adjacentTo
4750     
4751        Indicates if the node is adjacent to the node specified by id
4752
4753        Parameters:
4754     
4755           id - (string) A node id.
4756     
4757        Example:
4758        (start code js)
4759         node.adjacentTo('nodeId') == true;
4760        (end code)
4761     */
4762     adjacentTo: function(node) {
4763         return node.id in this.adjacencies;
4764     },
4765
4766     /*
4767        Method: getAdjacency
4768     
4769        Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4770
4771        Parameters:
4772     
4773           id - (string) A node id.
4774     */  
4775     getAdjacency: function(id) {
4776         return this.adjacencies[id];
4777     },
4778
4779     /*
4780       Method: getPos
4781    
4782       Returns the position of the node.
4783   
4784       Parameters:
4785    
4786          type - (string) Default's *current*. Possible values are "start", "end" or "current".
4787    
4788       Returns:
4789    
4790         A <Complex> or <Polar> instance.
4791   
4792       Example:
4793       (start code js)
4794        var pos = node.getPos('end');
4795       (end code)
4796    */
4797    getPos: function(type) {
4798        type = type || "current";
4799        if(type == "current") {
4800          return this.pos;
4801        } else if(type == "end") {
4802          return this.endPos;
4803        } else if(type == "start") {
4804          return this.startPos;
4805        }
4806    },
4807    /*
4808      Method: setPos
4809   
4810      Sets the node's position.
4811   
4812      Parameters:
4813   
4814         value - (object) A <Complex> or <Polar> instance.
4815         type - (string) Default's *current*. Possible values are "start", "end" or "current".
4816   
4817      Example:
4818      (start code js)
4819       node.setPos(new $jit.Complex(0, 0), 'end');
4820      (end code)
4821   */
4822   setPos: function(value, type) {
4823       type = type || "current";
4824       var pos;
4825       if(type == "current") {
4826         pos = this.pos;
4827       } else if(type == "end") {
4828         pos = this.endPos;
4829       } else if(type == "start") {
4830         pos = this.startPos;
4831       }
4832       pos.set(value);
4833   }
4834 });
4835
4836 Graph.Node.implement(Accessors);
4837
4838 /*
4839      Class: Graph.Adjacence
4840
4841      A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4842      
4843      Implements:
4844      
4845      <Accessors> methods.
4846
4847      See also:
4848
4849      <Graph>, <Graph.Node>
4850
4851      Properties:
4852      
4853       nodeFrom - A <Graph.Node> connected by this edge.
4854       nodeTo - Another  <Graph.Node> connected by this edge.
4855       data - Node data property containing a hash (i.e {}) with custom options.
4856 */
4857 Graph.Adjacence = new Class({
4858   
4859   initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4860     this.nodeFrom = nodeFrom;
4861     this.nodeTo = nodeTo;
4862     this.data = data || {};
4863     this.startData = {};
4864     this.endData = {};
4865     this.Config = this.Edge = Edge;
4866     this.Label = Label;
4867   }
4868 });
4869
4870 Graph.Adjacence.implement(Accessors);
4871
4872 /*
4873    Object: Graph.Util
4874
4875    <Graph> traversal and processing utility object.
4876    
4877    Note:
4878    
4879    For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4880 */
4881 Graph.Util = {
4882     /*
4883        filter
4884     
4885        For internal use only. Provides a filtering function based on flags.
4886     */
4887     filter: function(param) {
4888         if(!param || !($.type(param) == 'string')) return function() { return true; };
4889         var props = param.split(" ");
4890         return function(elem) {
4891             for(var i=0; i<props.length; i++) { 
4892               if(elem[props[i]]) { 
4893                 return false; 
4894               }
4895             }
4896             return true;
4897         };
4898     },
4899     /*
4900        Method: getNode
4901     
4902        Returns a <Graph.Node> by *id*.
4903        
4904        Also implemented by:
4905        
4906        <Graph>
4907
4908        Parameters:
4909
4910        graph - (object) A <Graph> instance.
4911        id - (string) A <Graph.Node> id.
4912
4913        Example:
4914
4915        (start code js)
4916          $jit.Graph.Util.getNode(graph, 'nodeid');
4917          //or...
4918          graph.getNode('nodeid');
4919        (end code)
4920     */
4921     getNode: function(graph, id) {
4922         return graph.nodes[id];
4923     },
4924     
4925     /*
4926        Method: eachNode
4927     
4928        Iterates over <Graph> nodes performing an *action*.
4929        
4930        Also implemented by:
4931        
4932        <Graph>.
4933
4934        Parameters:
4935
4936        graph - (object) A <Graph> instance.
4937        action - (function) A callback function having a <Graph.Node> as first formal parameter.
4938
4939        Example:
4940        (start code js)
4941          $jit.Graph.Util.eachNode(graph, function(node) {
4942           alert(node.name);
4943          });
4944          //or...
4945          graph.eachNode(function(node) {
4946            alert(node.name);
4947          });
4948        (end code)
4949     */
4950     eachNode: function(graph, action, flags) {
4951         var filter = this.filter(flags);
4952         for(var i in graph.nodes) {
4953           if(filter(graph.nodes[i])) action(graph.nodes[i]);
4954         } 
4955     },
4956     
4957     /*
4958        Method: eachAdjacency
4959     
4960        Iterates over <Graph.Node> adjacencies applying the *action* function.
4961        
4962        Also implemented by:
4963        
4964        <Graph.Node>.
4965
4966        Parameters:
4967
4968        node - (object) A <Graph.Node>.
4969        action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4970
4971        Example:
4972        (start code js)
4973          $jit.Graph.Util.eachAdjacency(node, function(adj) {
4974           alert(adj.nodeTo.name);
4975          });
4976          //or...
4977          node.eachAdjacency(function(adj) {
4978            alert(adj.nodeTo.name);
4979          });
4980        (end code)
4981     */
4982     eachAdjacency: function(node, action, flags) {
4983         var adj = node.adjacencies, filter = this.filter(flags);
4984         for(var id in adj) {
4985           var a = adj[id];
4986           if(filter(a)) {
4987             if(a.nodeFrom != node) {
4988               var tmp = a.nodeFrom;
4989               a.nodeFrom = a.nodeTo;
4990               a.nodeTo = tmp;
4991             }
4992             action(a, id);
4993           }
4994         }
4995     },
4996
4997      /*
4998        Method: computeLevels
4999     
5000        Performs a BFS traversal setting the correct depth for each node.
5001         
5002        Also implemented by:
5003        
5004        <Graph>.
5005        
5006        Note:
5007        
5008        The depth of each node can then be accessed by 
5009        >node._depth
5010
5011        Parameters:
5012
5013        graph - (object) A <Graph>.
5014        id - (string) A starting node id for the BFS traversal.
5015        startDepth - (optional|number) A minimum depth value. Default's 0.
5016
5017     */
5018     computeLevels: function(graph, id, startDepth, flags) {
5019         startDepth = startDepth || 0;
5020         var filter = this.filter(flags);
5021         this.eachNode(graph, function(elem) {
5022             elem._flag = false;
5023             elem._depth = -1;
5024         }, flags);
5025         var root = graph.getNode(id);
5026         root._depth = startDepth;
5027         var queue = [root];
5028         while(queue.length != 0) {
5029             var node = queue.pop();
5030             node._flag = true;
5031             this.eachAdjacency(node, function(adj) {
5032                 var n = adj.nodeTo;
5033                 if(n._flag == false && filter(n)) {
5034                     if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5035                     queue.unshift(n);
5036                 }
5037             }, flags);
5038         }
5039     },
5040
5041     /*
5042        Method: eachBFS
5043     
5044        Performs a BFS traversal applying *action* to each <Graph.Node>.
5045        
5046        Also implemented by:
5047        
5048        <Graph>.
5049
5050        Parameters:
5051
5052        graph - (object) A <Graph>.
5053        id - (string) A starting node id for the BFS traversal.
5054        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5055
5056        Example:
5057        (start code js)
5058          $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5059           alert(node.name);
5060          });
5061          //or...
5062          graph.eachBFS('mynodeid', function(node) {
5063            alert(node.name);
5064          });
5065        (end code)
5066     */
5067     eachBFS: function(graph, id, action, flags) {
5068         var filter = this.filter(flags);
5069         this.clean(graph);
5070         var queue = [graph.getNode(id)];
5071         while(queue.length != 0) {
5072             var node = queue.pop();
5073             node._flag = true;
5074             action(node, node._depth);
5075             this.eachAdjacency(node, function(adj) {
5076                 var n = adj.nodeTo;
5077                 if(n._flag == false && filter(n)) {
5078                     n._flag = true;
5079                     queue.unshift(n);
5080                 }
5081             }, flags);
5082         }
5083     },
5084     
5085     /*
5086        Method: eachLevel
5087     
5088        Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5089        
5090        Also implemented by:
5091        
5092        <Graph.Node>.
5093
5094        Parameters:
5095        
5096        node - (object) A <Graph.Node>.
5097        levelBegin - (number) A relative level value.
5098        levelEnd - (number) A relative level value.
5099        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5100
5101     */
5102     eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5103         var d = node._depth, filter = this.filter(flags), that = this;
5104         levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5105         (function loopLevel(node, levelBegin, levelEnd) {
5106             var d = node._depth;
5107             if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5108             if(d < levelEnd) {
5109                 that.eachAdjacency(node, function(adj) {
5110                     var n = adj.nodeTo;
5111                     if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5112                 });
5113             }
5114         })(node, levelBegin + d, levelEnd + d);      
5115     },
5116
5117     /*
5118        Method: eachSubgraph
5119     
5120        Iterates over a node's children recursively.
5121        
5122        Also implemented by:
5123        
5124        <Graph.Node>.
5125
5126        Parameters:
5127        node - (object) A <Graph.Node>.
5128        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5129
5130        Example:
5131        (start code js)
5132          $jit.Graph.Util.eachSubgraph(node, function(node) {
5133            alert(node.name);
5134          });
5135          //or...
5136          node.eachSubgraph(function(node) {
5137            alert(node.name);
5138          });
5139        (end code)
5140     */
5141     eachSubgraph: function(node, action, flags) {
5142       this.eachLevel(node, 0, false, action, flags);
5143     },
5144
5145     /*
5146        Method: eachSubnode
5147     
5148        Iterates over a node's children (without deeper recursion).
5149        
5150        Also implemented by:
5151        
5152        <Graph.Node>.
5153        
5154        Parameters:
5155        node - (object) A <Graph.Node>.
5156        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5157
5158        Example:
5159        (start code js)
5160          $jit.Graph.Util.eachSubnode(node, function(node) {
5161           alert(node.name);
5162          });
5163          //or...
5164          node.eachSubnode(function(node) {
5165            alert(node.name);
5166          });
5167        (end code)
5168     */
5169     eachSubnode: function(node, action, flags) {
5170         this.eachLevel(node, 1, 1, action, flags);
5171     },
5172
5173     /*
5174        Method: anySubnode
5175     
5176        Returns *true* if any subnode matches the given condition.
5177        
5178        Also implemented by:
5179        
5180        <Graph.Node>.
5181
5182        Parameters:
5183        node - (object) A <Graph.Node>.
5184        cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5185
5186        Example:
5187        (start code js)
5188          $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5189          //or...
5190          node.anySubnode(function(node) { return node.name == 'mynodename'; });
5191        (end code)
5192     */
5193     anySubnode: function(node, cond, flags) {
5194       var flag = false;
5195       cond = cond || $.lambda(true);
5196       var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5197       this.eachSubnode(node, function(elem) {
5198         if(c(elem)) flag = true;
5199       }, flags);
5200       return flag;
5201     },
5202   
5203     /*
5204        Method: getSubnodes
5205     
5206        Collects all subnodes for a specified node. 
5207        The *level* parameter filters nodes having relative depth of *level* from the root node. 
5208        
5209        Also implemented by:
5210        
5211        <Graph.Node>.
5212
5213        Parameters:
5214        node - (object) A <Graph.Node>.
5215        level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5216
5217        Returns:
5218        An array of nodes.
5219
5220     */
5221     getSubnodes: function(node, level, flags) {
5222         var ans = [], that = this;
5223         level = level || 0;
5224         var levelStart, levelEnd;
5225         if($.type(level) == 'array') {
5226             levelStart = level[0];
5227             levelEnd = level[1];
5228         } else {
5229             levelStart = level;
5230             levelEnd = Number.MAX_VALUE - node._depth;
5231         }
5232         this.eachLevel(node, levelStart, levelEnd, function(n) {
5233             ans.push(n);
5234         }, flags);
5235         return ans;
5236     },
5237   
5238   
5239     /*
5240        Method: getParents
5241     
5242        Returns an Array of <Graph.Nodes> which are parents of the given node.
5243        
5244        Also implemented by:
5245        
5246        <Graph.Node>.
5247
5248        Parameters:
5249        node - (object) A <Graph.Node>.
5250
5251        Returns:
5252        An Array of <Graph.Nodes>.
5253
5254        Example:
5255        (start code js)
5256          var pars = $jit.Graph.Util.getParents(node);
5257          //or...
5258          var pars = node.getParents();
5259          
5260          if(pars.length > 0) {
5261            //do stuff with parents
5262          }
5263        (end code)
5264     */
5265     getParents: function(node) {
5266         var ans = [];
5267         this.eachAdjacency(node, function(adj) {
5268             var n = adj.nodeTo;
5269             if(n._depth < node._depth) ans.push(n);
5270         });
5271         return ans;
5272     },
5273     
5274     /*
5275     Method: isDescendantOf
5276  
5277     Returns a boolean indicating if some node is descendant of the node with the given id. 
5278
5279     Also implemented by:
5280     
5281     <Graph.Node>.
5282     
5283     
5284     Parameters:
5285     node - (object) A <Graph.Node>.
5286     id - (string) A <Graph.Node> id.
5287
5288     Example:
5289     (start code js)
5290       $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5291       //or...
5292       node.isDescendantOf('nodeid');//true|false
5293     (end code)
5294  */
5295  isDescendantOf: function(node, id) {
5296     if(node.id == id) return true;
5297     var pars = this.getParents(node), ans = false;
5298     for ( var i = 0; !ans && i < pars.length; i++) {
5299     ans = ans || this.isDescendantOf(pars[i], id);
5300   }
5301     return ans;
5302  },
5303
5304  /*
5305      Method: clean
5306   
5307      Cleans flags from nodes.
5308
5309      Also implemented by:
5310      
5311      <Graph>.
5312      
5313      Parameters:
5314      graph - A <Graph> instance.
5315   */
5316   clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5317   
5318   /* 
5319     Method: getClosestNodeToOrigin 
5320   
5321     Returns the closest node to the center of canvas.
5322   
5323     Also implemented by:
5324     
5325     <Graph>.
5326     
5327     Parameters:
5328    
5329      graph - (object) A <Graph> instance.
5330      prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5331   
5332   */
5333   getClosestNodeToOrigin: function(graph, prop, flags) {
5334    return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5335   },
5336   
5337   /* 
5338     Method: getClosestNodeToPos
5339   
5340     Returns the closest node to the given position.
5341   
5342     Also implemented by:
5343     
5344     <Graph>.
5345     
5346     Parameters:
5347    
5348      graph - (object) A <Graph> instance.
5349      pos - (object) A <Complex> or <Polar> instance.
5350      prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5351   
5352   */
5353   getClosestNodeToPos: function(graph, pos, prop, flags) {
5354    var node = null;
5355    prop = prop || 'current';
5356    pos = pos && pos.getc(true) || Complex.KER;
5357    var distance = function(a, b) {
5358      var d1 = a.x - b.x, d2 = a.y - b.y;
5359      return d1 * d1 + d2 * d2;
5360    };
5361    this.eachNode(graph, function(elem) {
5362      node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5363          node.getPos(prop).getc(true), pos)) ? elem : node;
5364    }, flags);
5365    return node;
5366   } 
5367 };
5368
5369 //Append graph methods to <Graph>
5370 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5371   Graph.prototype[m] = function() {
5372     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5373   };
5374 });
5375
5376 //Append node methods to <Graph.Node>
5377 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5378   Graph.Node.prototype[m] = function() {
5379     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5380   };
5381 });
5382
5383 /*
5384  * File: Graph.Op.js
5385  *
5386 */
5387
5388 /*
5389    Object: Graph.Op
5390
5391    Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>, 
5392    morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5393
5394 */
5395 Graph.Op = {
5396
5397     options: {
5398       type: 'nothing',
5399       duration: 2000,
5400       hideLabels: true,
5401       fps:30
5402     },
5403     
5404     initialize: function(viz) {
5405       this.viz = viz;
5406     },
5407
5408     /*
5409        Method: removeNode
5410     
5411        Removes one or more <Graph.Nodes> from the visualization. 
5412        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5413
5414        Parameters:
5415     
5416         node - (string|array) The node's id. Can also be an array having many ids.
5417         opt - (object) Animation options. It's an object with optional properties described below
5418         type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5419         duration - Described in <Options.Fx>.
5420         fps - Described in <Options.Fx>.
5421         transition - Described in <Options.Fx>.
5422         hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5423    
5424       Example:
5425       (start code js)
5426         var viz = new $jit.Viz(options);
5427         viz.op.removeNode('nodeId', {
5428           type: 'fade:seq',
5429           duration: 1000,
5430           hideLabels: false,
5431           transition: $jit.Trans.Quart.easeOut
5432         });
5433         //or also
5434         viz.op.removeNode(['someId', 'otherId'], {
5435           type: 'fade:con',
5436           duration: 1500
5437         });
5438       (end code)
5439     */
5440   
5441     removeNode: function(node, opt) {
5442         var viz = this.viz;
5443         var options = $.merge(this.options, viz.controller, opt);
5444         var n = $.splat(node);
5445         var i, that, nodeObj;
5446         switch(options.type) {
5447             case 'nothing':
5448                 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5449                 break;
5450             
5451             case 'replot':
5452                 this.removeNode(n, { type: 'nothing' });
5453                 viz.labels.clearLabels();
5454                 viz.refresh(true);
5455                 break;
5456             
5457             case 'fade:seq': case 'fade':
5458                 that = this;
5459                 //set alpha to 0 for nodes to remove.
5460                 for(i=0; i<n.length; i++) {
5461                     nodeObj = viz.graph.getNode(n[i]);
5462                     nodeObj.setData('alpha', 0, 'end');
5463                 }
5464                 viz.fx.animate($.merge(options, {
5465                     modes: ['node-property:alpha'],
5466                     onComplete: function() {
5467                         that.removeNode(n, { type: 'nothing' });
5468                         viz.labels.clearLabels();
5469                         viz.reposition();
5470                         viz.fx.animate($.merge(options, {
5471                             modes: ['linear']
5472                         }));
5473                     }
5474                 }));
5475                 break;
5476             
5477             case 'fade:con':
5478                 that = this;
5479                 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5480                 for(i=0; i<n.length; i++) {
5481                     nodeObj = viz.graph.getNode(n[i]);
5482                     nodeObj.setData('alpha', 0, 'end');
5483                     nodeObj.ignore = true;
5484                 }
5485                 viz.reposition();
5486                 viz.fx.animate($.merge(options, {
5487                     modes: ['node-property:alpha', 'linear'],
5488                     onComplete: function() {
5489                         that.removeNode(n, { type: 'nothing' });
5490                     }
5491                 }));
5492                 break;
5493             
5494             case 'iter':
5495                 that = this;
5496                 viz.fx.sequence({
5497                     condition: function() { return n.length != 0; },
5498                     step: function() { that.removeNode(n.shift(), { type: 'nothing' });  viz.labels.clearLabels(); },
5499                     onComplete: function() { options.onComplete(); },
5500                     duration: Math.ceil(options.duration / n.length)
5501                 });
5502                 break;
5503                 
5504             default: this.doError();
5505         }
5506     },
5507     
5508     /*
5509        Method: removeEdge
5510     
5511        Removes one or more <Graph.Adjacences> from the visualization. 
5512        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5513
5514        Parameters:
5515     
5516        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'], ...]).
5517        opt - (object) Animation options. It's an object with optional properties described below
5518        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5519        duration - Described in <Options.Fx>.
5520        fps - Described in <Options.Fx>.
5521        transition - Described in <Options.Fx>.
5522        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5523    
5524       Example:
5525       (start code js)
5526         var viz = new $jit.Viz(options);
5527         viz.op.removeEdge(['nodeId', 'otherId'], {
5528           type: 'fade:seq',
5529           duration: 1000,
5530           hideLabels: false,
5531           transition: $jit.Trans.Quart.easeOut
5532         });
5533         //or also
5534         viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5535           type: 'fade:con',
5536           duration: 1500
5537         });
5538       (end code)
5539     
5540     */
5541     removeEdge: function(vertex, opt) {
5542         var viz = this.viz;
5543         var options = $.merge(this.options, viz.controller, opt);
5544         var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5545         var i, that, adj;
5546         switch(options.type) {
5547             case 'nothing':
5548                 for(i=0; i<v.length; i++)   viz.graph.removeAdjacence(v[i][0], v[i][1]);
5549                 break;
5550             
5551             case 'replot':
5552                 this.removeEdge(v, { type: 'nothing' });
5553                 viz.refresh(true);
5554                 break;
5555             
5556             case 'fade:seq': case 'fade':
5557                 that = this;
5558                 //set alpha to 0 for edges to remove.
5559                 for(i=0; i<v.length; i++) {
5560                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5561                     if(adj) {
5562                         adj.setData('alpha', 0,'end');
5563                     }
5564                 }
5565                 viz.fx.animate($.merge(options, {
5566                     modes: ['edge-property:alpha'],
5567                     onComplete: function() {
5568                         that.removeEdge(v, { type: 'nothing' });
5569                         viz.reposition();
5570                         viz.fx.animate($.merge(options, {
5571                             modes: ['linear']
5572                         }));
5573                     }
5574                 }));
5575                 break;
5576             
5577             case 'fade:con':
5578                 that = this;
5579                 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5580                 for(i=0; i<v.length; i++) {
5581                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5582                     if(adj) {
5583                         adj.setData('alpha',0 ,'end');
5584                         adj.ignore = true;
5585                     }
5586                 }
5587                 viz.reposition();
5588                 viz.fx.animate($.merge(options, {
5589                     modes: ['edge-property:alpha', 'linear'],
5590                     onComplete: function() {
5591                         that.removeEdge(v, { type: 'nothing' });
5592                     }
5593                 }));
5594                 break;
5595             
5596             case 'iter':
5597                 that = this;
5598                 viz.fx.sequence({
5599                     condition: function() { return v.length != 0; },
5600                     step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5601                     onComplete: function() { options.onComplete(); },
5602                     duration: Math.ceil(options.duration / v.length)
5603                 });
5604                 break;
5605                 
5606             default: this.doError();
5607         }
5608     },
5609     
5610     /*
5611        Method: sum
5612     
5613        Adds a new graph to the visualization. 
5614        The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization. 
5615        The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5616
5617        Parameters:
5618     
5619        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5620        opt - (object) Animation options. It's an object with optional properties described below
5621        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con".
5622        duration - Described in <Options.Fx>.
5623        fps - Described in <Options.Fx>.
5624        transition - Described in <Options.Fx>.
5625        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5626    
5627       Example:
5628       (start code js)
5629         //...json contains a tree or graph structure...
5630
5631         var viz = new $jit.Viz(options);
5632         viz.op.sum(json, {
5633           type: 'fade:seq',
5634           duration: 1000,
5635           hideLabels: false,
5636           transition: $jit.Trans.Quart.easeOut
5637         });
5638         //or also
5639         viz.op.sum(json, {
5640           type: 'fade:con',
5641           duration: 1500
5642         });
5643       (end code)
5644     
5645     */
5646     sum: function(json, opt) {
5647         var viz = this.viz;
5648         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5649         var graph;
5650         viz.root = opt.id || viz.root;
5651         switch(options.type) {
5652             case 'nothing':
5653                 graph = viz.construct(json);
5654                 graph.eachNode(function(elem) {
5655                     elem.eachAdjacency(function(adj) {
5656                         viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5657                     });
5658                 });
5659                 break;
5660             
5661             case 'replot':
5662                 viz.refresh(true);
5663                 this.sum(json, { type: 'nothing' });
5664                 viz.refresh(true);
5665                 break;
5666             
5667             case 'fade:seq': case 'fade': case 'fade:con':
5668                 that = this;
5669                 graph = viz.construct(json);
5670
5671                 //set alpha to 0 for nodes to add.
5672                 var fadeEdges = this.preprocessSum(graph);
5673                 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5674                 viz.reposition();
5675                 if(options.type != 'fade:con') {
5676                     viz.fx.animate($.merge(options, {
5677                         modes: ['linear'],
5678                         onComplete: function() {
5679                             viz.fx.animate($.merge(options, {
5680                                 modes: modes,
5681                                 onComplete: function() {
5682                                     options.onComplete();
5683                                 }
5684                             }));
5685                         }
5686                     }));
5687                 } else {
5688                     viz.graph.eachNode(function(elem) {
5689                         if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5690                           elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5691                         }
5692                     });
5693                     viz.fx.animate($.merge(options, {
5694                         modes: ['linear'].concat(modes)
5695                     }));
5696                 }
5697                 break;
5698
5699             default: this.doError();
5700         }
5701     },
5702     
5703     /*
5704        Method: morph
5705     
5706        This method will transform the current visualized graph into the new JSON representation passed in the method. 
5707        The JSON object must at least have the root node in common with the current visualized graph.
5708
5709        Parameters:
5710     
5711        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5712        opt - (object) Animation options. It's an object with optional properties described below
5713        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5714        duration - Described in <Options.Fx>.
5715        fps - Described in <Options.Fx>.
5716        transition - Described in <Options.Fx>.
5717        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5718        id - (string) The shared <Graph.Node> id between both graphs.
5719        
5720        extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to 
5721                     *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation. 
5722                     For animating these extra-parameters you have to specify an object that has animation groups as keys and animation 
5723                     properties as values, just like specified in <Graph.Plot.animate>.
5724    
5725       Example:
5726       (start code js)
5727         //...json contains a tree or graph structure...
5728
5729         var viz = new $jit.Viz(options);
5730         viz.op.morph(json, {
5731           type: 'fade',
5732           duration: 1000,
5733           hideLabels: false,
5734           transition: $jit.Trans.Quart.easeOut
5735         });
5736         //or also
5737         viz.op.morph(json, {
5738           type: 'fade',
5739           duration: 1500
5740         });
5741         //if the json data contains dollar prefixed params
5742         //like $width or $height these too can be animated
5743         viz.op.morph(json, {
5744           type: 'fade',
5745           duration: 1500
5746         }, {
5747           'node-property': ['width', 'height']
5748         });
5749       (end code)
5750     
5751     */
5752     morph: function(json, opt, extraModes) {
5753         var viz = this.viz;
5754         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5755         var graph;
5756         //TODO(nico) this hack makes morphing work with the Hypertree. 
5757         //Need to check if it has been solved and this can be removed.
5758         viz.root = opt.id || viz.root;
5759         switch(options.type) {
5760             case 'nothing':
5761                 graph = viz.construct(json);
5762                 graph.eachNode(function(elem) {
5763                   var nodeExists = viz.graph.hasNode(elem.id);  
5764                   elem.eachAdjacency(function(adj) {
5765                     var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5766                     viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5767                     //Update data properties if the node existed
5768                     if(adjExists) {
5769                       var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5770                       for(var prop in (adj.data || {})) {
5771                         addedAdj.data[prop] = adj.data[prop];
5772                       }
5773                     }
5774                   });
5775                   //Update data properties if the node existed
5776                   if(nodeExists) {
5777                     var addedNode = viz.graph.getNode(elem.id);
5778                     for(var prop in (elem.data || {})) {
5779                       addedNode.data[prop] = elem.data[prop];
5780                     }
5781                   }
5782                 });
5783                 viz.graph.eachNode(function(elem) {
5784                     elem.eachAdjacency(function(adj) {
5785                         if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5786                             viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5787                         }
5788                     });
5789                     if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5790                 });
5791                 
5792                 break;
5793             
5794             case 'replot':
5795                 viz.labels.clearLabels(true);
5796                 this.morph(json, { type: 'nothing' });
5797                 viz.refresh(true);
5798                 viz.refresh(true);
5799                 break;
5800                 
5801             case 'fade:seq': case 'fade': case 'fade:con':
5802                 that = this;
5803                 graph = viz.construct(json);
5804                 //preprocessing for nodes to delete.
5805                 //get node property modes to interpolate
5806                 var nodeModes = extraModes && ('node-property' in extraModes) 
5807                   && $.map($.splat(extraModes['node-property']), 
5808                       function(n) { return '$' + n; });
5809                 viz.graph.eachNode(function(elem) {
5810                   var graphNode = graph.getNode(elem.id);   
5811                   if(!graphNode) {
5812                       elem.setData('alpha', 1);
5813                       elem.setData('alpha', 1, 'start');
5814                       elem.setData('alpha', 0, 'end');
5815                       elem.ignore = true;
5816                     } else {
5817                       //Update node data information
5818                       var graphNodeData = graphNode.data;
5819                       for(var prop in graphNodeData) {
5820                         if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5821                           elem.endData[prop] = graphNodeData[prop];
5822                         } else {
5823                           elem.data[prop] = graphNodeData[prop];
5824                         }
5825                       }
5826                     }
5827                 }); 
5828                 viz.graph.eachNode(function(elem) {
5829                     if(elem.ignore) return;
5830                     elem.eachAdjacency(function(adj) {
5831                         if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5832                         var nodeFrom = graph.getNode(adj.nodeFrom.id);
5833                         var nodeTo = graph.getNode(adj.nodeTo.id);
5834                         if(!nodeFrom.adjacentTo(nodeTo)) {
5835                             var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5836                             fadeEdges = true;
5837                             adj.setData('alpha', 1);
5838                             adj.setData('alpha', 1, 'start');
5839                             adj.setData('alpha', 0, 'end');
5840                         }
5841                     });
5842                 }); 
5843                 //preprocessing for adding nodes.
5844                 var fadeEdges = this.preprocessSum(graph);
5845
5846                 var modes = !fadeEdges? ['node-property:alpha'] : 
5847                                         ['node-property:alpha', 
5848                                          'edge-property:alpha'];
5849                 //Append extra node-property animations (if any)
5850                 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))? 
5851                     (':' + $.splat(extraModes['node-property']).join(':')) : '');
5852                 //Append extra edge-property animations (if any)
5853                 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))? 
5854                     (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5855                 //Add label-property animations (if any)
5856                 if(extraModes && ('label-property' in extraModes)) {
5857                   modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5858                 }
5859                 viz.reposition();
5860                 viz.graph.eachNode(function(elem) {
5861                     if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5862                       elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5863                     }
5864                 });
5865                 viz.fx.animate($.merge(options, {
5866                     modes: ['polar'].concat(modes),
5867                     onComplete: function() {
5868                         viz.graph.eachNode(function(elem) {
5869                             if(elem.ignore) viz.graph.removeNode(elem.id);
5870                         });
5871                         viz.graph.eachNode(function(elem) {
5872                             elem.eachAdjacency(function(adj) {
5873                                 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5874                             });
5875                         });
5876                         options.onComplete();
5877                     }
5878                 }));
5879                 break;
5880
5881             default:;
5882         }
5883     },
5884
5885     
5886   /*
5887     Method: contract
5888  
5889     Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5890     
5891     Parameters:
5892  
5893     node - (object) A <Graph.Node>.
5894     opt - (object) An object containing options described below
5895     type - (string) Whether to 'replot' or 'animate' the contraction.
5896    
5897     There are also a number of Animation options. For more information see <Options.Fx>.
5898
5899     Example:
5900     (start code js)
5901      var viz = new $jit.Viz(options);
5902      viz.op.contract(node, {
5903        type: 'animate',
5904        duration: 1000,
5905        hideLabels: true,
5906        transition: $jit.Trans.Quart.easeOut
5907      });
5908    (end code)
5909  
5910    */
5911     contract: function(node, opt) {
5912       var viz = this.viz;
5913       if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5914       opt = $.merge(this.options, viz.config, opt || {}, {
5915         'modes': ['node-property:alpha:span', 'linear']
5916       });
5917       node.collapsed = true;
5918       (function subn(n) {
5919         n.eachSubnode(function(ch) {
5920           ch.ignore = true;
5921           ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5922           subn(ch);
5923         });
5924       })(node);
5925       if(opt.type == 'animate') {
5926         viz.compute('end');
5927         if(viz.rotated) {
5928           viz.rotate(viz.rotated, 'none', {
5929             'property':'end'
5930           });
5931         }
5932         (function subn(n) {
5933           n.eachSubnode(function(ch) {
5934             ch.setPos(node.getPos('end'), 'end');
5935             subn(ch);
5936           });
5937         })(node);
5938         viz.fx.animate(opt);
5939       } else if(opt.type == 'replot'){
5940         viz.refresh();
5941       }
5942     },
5943     
5944     /*
5945     Method: expand
5946  
5947     Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5948     
5949     Parameters:
5950  
5951     node - (object) A <Graph.Node>.
5952     opt - (object) An object containing options described below
5953     type - (string) Whether to 'replot' or 'animate'.
5954      
5955     There are also a number of Animation options. For more information see <Options.Fx>.
5956
5957     Example:
5958     (start code js)
5959       var viz = new $jit.Viz(options);
5960       viz.op.expand(node, {
5961         type: 'animate',
5962         duration: 1000,
5963         hideLabels: true,
5964         transition: $jit.Trans.Quart.easeOut
5965       });
5966     (end code)
5967  
5968    */
5969     expand: function(node, opt) {
5970       if(!('collapsed' in node)) return;
5971       var viz = this.viz;
5972       opt = $.merge(this.options, viz.config, opt || {}, {
5973         'modes': ['node-property:alpha:span', 'linear']
5974       });
5975       delete node.collapsed;
5976       (function subn(n) {
5977         n.eachSubnode(function(ch) {
5978           delete ch.ignore;
5979           ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5980           subn(ch);
5981         });
5982       })(node);
5983       if(opt.type == 'animate') {
5984         viz.compute('end');
5985         if(viz.rotated) {
5986           viz.rotate(viz.rotated, 'none', {
5987             'property':'end'
5988           });
5989         }
5990         viz.fx.animate(opt);
5991       } else if(opt.type == 'replot'){
5992         viz.refresh();
5993       }
5994     },
5995
5996     preprocessSum: function(graph) {
5997         var viz = this.viz;
5998         graph.eachNode(function(elem) {
5999             if(!viz.graph.hasNode(elem.id)) {
6000                 viz.graph.addNode(elem);
6001                 var n = viz.graph.getNode(elem.id);
6002                 n.setData('alpha', 0);
6003                 n.setData('alpha', 0, 'start');
6004                 n.setData('alpha', 1, 'end');
6005             }
6006         }); 
6007         var fadeEdges = false;
6008         graph.eachNode(function(elem) {
6009             elem.eachAdjacency(function(adj) {
6010                 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6011                 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6012                 if(!nodeFrom.adjacentTo(nodeTo)) {
6013                     var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6014                     if(nodeFrom.startAlpha == nodeFrom.endAlpha 
6015                     && nodeTo.startAlpha == nodeTo.endAlpha) {
6016                         fadeEdges = true;
6017                         adj.setData('alpha', 0);
6018                         adj.setData('alpha', 0, 'start');
6019                         adj.setData('alpha', 1, 'end');
6020                     } 
6021                 }
6022             });
6023         }); 
6024         return fadeEdges;
6025     }
6026 };
6027
6028
6029
6030 /*
6031    File: Helpers.js
6032  
6033    Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6034    Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6035    position is over the rendered shape.
6036    
6037    Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and 
6038    *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6039    
6040    Example:
6041    (start code js)
6042    //implement a new node type
6043    $jit.Viz.Plot.NodeTypes.implement({
6044      'customNodeType': {
6045        'render': function(node, canvas) {
6046          this.nodeHelper.circle.render ...
6047        },
6048        'contains': function(node, pos) {
6049          this.nodeHelper.circle.contains ...
6050        }
6051      }
6052    });
6053    //implement an edge type
6054    $jit.Viz.Plot.EdgeTypes.implement({
6055      'customNodeType': {
6056        'render': function(node, canvas) {
6057          this.edgeHelper.circle.render ...
6058        },
6059        //optional
6060        'contains': function(node, pos) {
6061          this.edgeHelper.circle.contains ...
6062        }
6063      }
6064    });
6065    (end code)
6066
6067 */
6068
6069 /*
6070    Object: NodeHelper
6071    
6072    Contains rendering and other type of primitives for simple shapes.
6073  */
6074 var NodeHelper = {
6075   'none': {
6076     'render': $.empty,
6077     'contains': $.lambda(false)
6078   },
6079   /*
6080    Object: NodeHelper.circle
6081    */
6082   'circle': {
6083     /*
6084      Method: render
6085      
6086      Renders a circle into the canvas.
6087      
6088      Parameters:
6089      
6090      type - (string) Possible options are 'fill' or 'stroke'.
6091      pos - (object) An *x*, *y* object with the position of the center of the circle.
6092      radius - (number) The radius of the circle to be rendered.
6093      canvas - (object) A <Canvas> instance.
6094      
6095      Example:
6096      (start code js)
6097      NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6098      (end code)
6099      */
6100     'render': function(type, pos, radius, canvas){
6101       var ctx = canvas.getCtx();
6102       ctx.beginPath();
6103       ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6104       ctx.closePath();
6105       ctx[type]();
6106     },
6107     /*
6108     Method: contains
6109     
6110     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6111     
6112     Parameters:
6113     
6114     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6115     pos - (object) An *x*, *y* object with the position to check.
6116     radius - (number) The radius of the rendered circle.
6117     
6118     Example:
6119     (start code js)
6120     NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6121     (end code)
6122     */
6123     'contains': function(npos, pos, radius){
6124       var diffx = npos.x - pos.x, 
6125           diffy = npos.y - pos.y, 
6126           diff = diffx * diffx + diffy * diffy;
6127       return diff <= radius * radius;
6128     }
6129   },
6130   /*
6131   Object: NodeHelper.ellipse
6132   */
6133   'ellipse': {
6134     /*
6135     Method: render
6136     
6137     Renders an ellipse into the canvas.
6138     
6139     Parameters:
6140     
6141     type - (string) Possible options are 'fill' or 'stroke'.
6142     pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6143     width - (number) The width of the ellipse.
6144     height - (number) The height of the ellipse.
6145     canvas - (object) A <Canvas> instance.
6146     
6147     Example:
6148     (start code js)
6149     NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6150     (end code)
6151     */
6152     'render': function(type, pos, width, height, canvas){
6153       var ctx = canvas.getCtx();
6154       height /= 2;
6155       width /= 2;
6156       ctx.save();
6157       ctx.scale(width / height, height / width);
6158       ctx.beginPath();
6159       ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6160           Math.PI * 2, true);
6161       ctx.closePath();
6162       ctx[type]();
6163       ctx.restore();
6164     },
6165     /*
6166     Method: contains
6167     
6168     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6169     
6170     Parameters:
6171     
6172     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6173     pos - (object) An *x*, *y* object with the position to check.
6174     width - (number) The width of the rendered ellipse.
6175     height - (number) The height of the rendered ellipse.
6176     
6177     Example:
6178     (start code js)
6179     NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6180     (end code)
6181     */
6182     'contains': function(npos, pos, width, height){
6183       // TODO(nico): be more precise...
6184       width /= 2; 
6185       height /= 2;
6186       var dist = (width + height) / 2, 
6187           diffx = npos.x - pos.x, 
6188           diffy = npos.y - pos.y, 
6189           diff = diffx * diffx + diffy * diffy;
6190       return diff <= dist * dist;
6191     }
6192   },
6193   /*
6194   Object: NodeHelper.square
6195   */
6196   'square': {
6197     /*
6198     Method: render
6199     
6200     Renders a square into the canvas.
6201     
6202     Parameters:
6203     
6204     type - (string) Possible options are 'fill' or 'stroke'.
6205     pos - (object) An *x*, *y* object with the position of the center of the square.
6206     dim - (number) The radius (or half-diameter) of the square.
6207     canvas - (object) A <Canvas> instance.
6208     
6209     Example:
6210     (start code js)
6211     NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6212     (end code)
6213     */
6214     'render': function(type, pos, dim, canvas){
6215       canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6216     },
6217     /*
6218     Method: contains
6219     
6220     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6221     
6222     Parameters:
6223     
6224     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6225     pos - (object) An *x*, *y* object with the position to check.
6226     dim - (number) The radius (or half-diameter) of the square.
6227     
6228     Example:
6229     (start code js)
6230     NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6231     (end code)
6232     */
6233     'contains': function(npos, pos, dim){
6234       return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6235     }
6236   },
6237   /*
6238   Object: NodeHelper.rectangle
6239   */
6240   'rectangle': {
6241     /*
6242     Method: render
6243     
6244     Renders a rectangle into the canvas.
6245     
6246     Parameters:
6247     
6248     type - (string) Possible options are 'fill' or 'stroke'.
6249     pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6250     width - (number) The width of the rectangle.
6251     height - (number) The height of the rectangle.
6252     canvas - (object) A <Canvas> instance.
6253     
6254     Example:
6255     (start code js)
6256     NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6257     (end code)
6258     */
6259     'render': function(type, pos, width, height, canvas){
6260       canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2, 
6261                                       width, height);
6262     },
6263     /*
6264     Method: contains
6265     
6266     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6267     
6268     Parameters:
6269     
6270     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6271     pos - (object) An *x*, *y* object with the position to check.
6272     width - (number) The width of the rendered rectangle.
6273     height - (number) The height of the rendered rectangle.
6274     
6275     Example:
6276     (start code js)
6277     NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6278     (end code)
6279     */
6280     'contains': function(npos, pos, width, height){
6281       return Math.abs(pos.x - npos.x) <= width / 2
6282           && Math.abs(pos.y - npos.y) <= height / 2;
6283     }
6284   },
6285   /*
6286   Object: NodeHelper.triangle
6287   */
6288   'triangle': {
6289     /*
6290     Method: render
6291     
6292     Renders a triangle into the canvas.
6293     
6294     Parameters:
6295     
6296     type - (string) Possible options are 'fill' or 'stroke'.
6297     pos - (object) An *x*, *y* object with the position of the center of the triangle.
6298     dim - (number) The dimension of the triangle.
6299     canvas - (object) A <Canvas> instance.
6300     
6301     Example:
6302     (start code js)
6303     NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6304     (end code)
6305     */
6306     'render': function(type, pos, dim, canvas){
6307       var ctx = canvas.getCtx(), 
6308           c1x = pos.x, 
6309           c1y = pos.y - dim, 
6310           c2x = c1x - dim, 
6311           c2y = pos.y + dim, 
6312           c3x = c1x + dim, 
6313           c3y = c2y;
6314       ctx.beginPath();
6315       ctx.moveTo(c1x, c1y);
6316       ctx.lineTo(c2x, c2y);
6317       ctx.lineTo(c3x, c3y);
6318       ctx.closePath();
6319       ctx[type]();
6320     },
6321     /*
6322     Method: contains
6323     
6324     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6325     
6326     Parameters:
6327     
6328     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6329     pos - (object) An *x*, *y* object with the position to check.
6330     dim - (number) The dimension of the shape.
6331     
6332     Example:
6333     (start code js)
6334     NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6335     (end code)
6336     */
6337     'contains': function(npos, pos, dim) {
6338       return NodeHelper.circle.contains(npos, pos, dim);
6339     }
6340   },
6341   /*
6342   Object: NodeHelper.star
6343   */
6344   'star': {
6345     /*
6346     Method: render
6347     
6348     Renders a star into the canvas.
6349     
6350     Parameters:
6351     
6352     type - (string) Possible options are 'fill' or 'stroke'.
6353     pos - (object) An *x*, *y* object with the position of the center of the star.
6354     dim - (number) The dimension of the star.
6355     canvas - (object) A <Canvas> instance.
6356     
6357     Example:
6358     (start code js)
6359     NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6360     (end code)
6361     */
6362     'render': function(type, pos, dim, canvas){
6363       var ctx = canvas.getCtx(), 
6364           pi5 = Math.PI / 5;
6365       ctx.save();
6366       ctx.translate(pos.x, pos.y);
6367       ctx.beginPath();
6368       ctx.moveTo(dim, 0);
6369       for (var i = 0; i < 9; i++) {
6370         ctx.rotate(pi5);
6371         if (i % 2 == 0) {
6372           ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6373         } else {
6374           ctx.lineTo(dim, 0);
6375         }
6376       }
6377       ctx.closePath();
6378       ctx[type]();
6379       ctx.restore();
6380     },
6381     /*
6382     Method: contains
6383     
6384     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6385     
6386     Parameters:
6387     
6388     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6389     pos - (object) An *x*, *y* object with the position to check.
6390     dim - (number) The dimension of the shape.
6391     
6392     Example:
6393     (start code js)
6394     NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6395     (end code)
6396     */
6397     'contains': function(npos, pos, dim) {
6398       return NodeHelper.circle.contains(npos, pos, dim);
6399     }
6400   }
6401 };
6402
6403 /*
6404   Object: EdgeHelper
6405   
6406   Contains rendering primitives for simple edge shapes.
6407 */
6408 var EdgeHelper = {
6409   /*
6410     Object: EdgeHelper.line
6411   */
6412   'line': {
6413       /*
6414       Method: render
6415       
6416       Renders a line into the canvas.
6417       
6418       Parameters:
6419       
6420       from - (object) An *x*, *y* object with the starting position of the line.
6421       to - (object) An *x*, *y* object with the ending position of the line.
6422       canvas - (object) A <Canvas> instance.
6423       
6424       Example:
6425       (start code js)
6426       EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6427       (end code)
6428       */
6429       'render': function(from, to, canvas){
6430         var ctx = canvas.getCtx();
6431         ctx.beginPath();
6432         ctx.moveTo(from.x, from.y);
6433         ctx.lineTo(to.x, to.y);
6434         ctx.stroke();
6435       },
6436       /*
6437       Method: contains
6438       
6439       Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6440       
6441       Parameters:
6442       
6443       posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6444       posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6445       pos - (object) An *x*, *y* object with the position to check.
6446       epsilon - (number) The dimension of the shape.
6447       
6448       Example:
6449       (start code js)
6450       EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6451       (end code)
6452       */
6453       'contains': function(posFrom, posTo, pos, epsilon) {
6454         var min = Math.min, 
6455             max = Math.max,
6456             minPosX = min(posFrom.x, posTo.x),
6457             maxPosX = max(posFrom.x, posTo.x),
6458             minPosY = min(posFrom.y, posTo.y),
6459             maxPosY = max(posFrom.y, posTo.y);
6460         
6461         if(pos.x >= minPosX && pos.x <= maxPosX 
6462             && pos.y >= minPosY && pos.y <= maxPosY) {
6463           if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6464             return true;
6465           }
6466           var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6467           return Math.abs(dist - pos.y) <= epsilon;
6468         }
6469         return false;
6470       }
6471     },
6472   /*
6473     Object: EdgeHelper.arrow
6474   */
6475   'arrow': {
6476       /*
6477       Method: render
6478       
6479       Renders an arrow into the canvas.
6480       
6481       Parameters:
6482       
6483       from - (object) An *x*, *y* object with the starting position of the arrow.
6484       to - (object) An *x*, *y* object with the ending position of the arrow.
6485       dim - (number) The dimension of the arrow.
6486       swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6487       canvas - (object) A <Canvas> instance.
6488       
6489       Example:
6490       (start code js)
6491       EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6492       (end code)
6493       */
6494     'render': function(from, to, dim, swap, canvas){
6495         var ctx = canvas.getCtx();
6496         // invert edge direction
6497         if (swap) {
6498           var tmp = from;
6499           from = to;
6500           to = tmp;
6501         }
6502         var vect = new Complex(to.x - from.x, to.y - from.y);
6503         vect.$scale(dim / vect.norm());
6504         var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6505             normal = new Complex(-vect.y / 2, vect.x / 2),
6506             v1 = intermediatePoint.add(normal), 
6507             v2 = intermediatePoint.$add(normal.$scale(-1));
6508         
6509         ctx.beginPath();
6510         ctx.moveTo(from.x, from.y);
6511         ctx.lineTo(to.x, to.y);
6512         ctx.stroke();
6513         ctx.beginPath();
6514         ctx.moveTo(v1.x, v1.y);
6515         ctx.lineTo(v2.x, v2.y);
6516         ctx.lineTo(to.x, to.y);
6517         ctx.closePath();
6518         ctx.fill();
6519     },
6520     /*
6521     Method: contains
6522     
6523     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6524     
6525     Parameters:
6526     
6527     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6528     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6529     pos - (object) An *x*, *y* object with the position to check.
6530     epsilon - (number) The dimension of the shape.
6531     
6532     Example:
6533     (start code js)
6534     EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6535     (end code)
6536     */
6537     'contains': function(posFrom, posTo, pos, epsilon) {
6538       return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6539     }
6540   },
6541   /*
6542     Object: EdgeHelper.hyperline
6543   */
6544   'hyperline': {
6545     /*
6546     Method: render
6547     
6548     Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6549     
6550     Parameters:
6551     
6552     from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6553     to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6554     r - (number) The scaling factor.
6555     canvas - (object) A <Canvas> instance.
6556     
6557     Example:
6558     (start code js)
6559     EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6560     (end code)
6561     */
6562     'render': function(from, to, r, canvas){
6563       var ctx = canvas.getCtx();  
6564       var centerOfCircle = computeArcThroughTwoPoints(from, to);
6565       if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6566           || centerOfCircle.ratio < 0) {
6567         ctx.beginPath();
6568         ctx.moveTo(from.x * r, from.y * r);
6569         ctx.lineTo(to.x * r, to.y * r);
6570         ctx.stroke();
6571       } else {
6572         var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6573             - centerOfCircle.x);
6574         var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6575             - centerOfCircle.x);
6576         var sense = sense(angleBegin, angleEnd);
6577         ctx.beginPath();
6578         ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6579             * r, angleBegin, angleEnd, sense);
6580         ctx.stroke();
6581       }
6582       /*      
6583         Calculates the arc parameters through two points.
6584         
6585         More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane> 
6586       
6587         Parameters:
6588       
6589         p1 - A <Complex> instance.
6590         p2 - A <Complex> instance.
6591         scale - The Disk's diameter.
6592       
6593         Returns:
6594       
6595         An object containing some arc properties.
6596       */
6597       function computeArcThroughTwoPoints(p1, p2){
6598         var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6599         var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6600         // Fall back to a straight line
6601         if (aDen == 0)
6602           return {
6603             x: 0,
6604             y: 0,
6605             ratio: -1
6606           };
6607     
6608         var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6609         var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6610         var x = -a / 2;
6611         var y = -b / 2;
6612         var squaredRatio = (a * a + b * b) / 4 - 1;
6613         // Fall back to a straight line
6614         if (squaredRatio < 0)
6615           return {
6616             x: 0,
6617             y: 0,
6618             ratio: -1
6619           };
6620         var ratio = Math.sqrt(squaredRatio);
6621         var out = {
6622           x: x,
6623           y: y,
6624           ratio: ratio > 1000? -1 : ratio,
6625           a: a,
6626           b: b
6627         };
6628     
6629         return out;
6630       }
6631       /*      
6632         Sets angle direction to clockwise (true) or counterclockwise (false). 
6633          
6634         Parameters: 
6635       
6636            angleBegin - Starting angle for drawing the arc. 
6637            angleEnd - The HyperLine will be drawn from angleBegin to angleEnd. 
6638       
6639         Returns: 
6640       
6641            A Boolean instance describing the sense for drawing the HyperLine. 
6642       */
6643       function sense(angleBegin, angleEnd){
6644         return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6645             : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6646       }
6647     },
6648     /*
6649     Method: contains
6650     
6651     Not Implemented
6652     
6653     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6654     
6655     Parameters:
6656     
6657     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6658     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6659     pos - (object) An *x*, *y* object with the position to check.
6660     epsilon - (number) The dimension of the shape.
6661     
6662     Example:
6663     (start code js)
6664     EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6665     (end code)
6666     */
6667     'contains': $.lambda(false)
6668   }
6669 };
6670
6671
6672 /*
6673  * File: Graph.Plot.js
6674  */
6675
6676 /*
6677    Object: Graph.Plot
6678
6679    <Graph> rendering and animation methods.
6680    
6681    Properties:
6682    
6683    nodeHelper - <NodeHelper> object.
6684    edgeHelper - <EdgeHelper> object.
6685 */
6686 Graph.Plot = {
6687     //Default intializer
6688     initialize: function(viz, klass){
6689       this.viz = viz;
6690       this.config = viz.config;
6691       this.node = viz.config.Node;
6692       this.edge = viz.config.Edge;
6693       this.animation = new Animation;
6694       this.nodeTypes = new klass.Plot.NodeTypes;
6695       this.edgeTypes = new klass.Plot.EdgeTypes;
6696       this.labels = viz.labels;
6697    },
6698
6699     //Add helpers
6700     nodeHelper: NodeHelper,
6701     edgeHelper: EdgeHelper,
6702     
6703     Interpolator: {
6704         //node/edge property parsers
6705         'map': {
6706           'border': 'color',
6707           'color': 'color',
6708           'width': 'number',
6709           'height': 'number',
6710           'dim': 'number',
6711           'alpha': 'number',
6712           'lineWidth': 'number',
6713           'angularWidth':'number',
6714           'span':'number',
6715           'valueArray':'array-number',
6716           'dimArray':'array-number'
6717           //'colorArray':'array-color'
6718         },
6719         
6720         //canvas specific parsers
6721         'canvas': {
6722           'globalAlpha': 'number',
6723           'fillStyle': 'color',
6724           'strokeStyle': 'color',
6725           'lineWidth': 'number',
6726           'shadowBlur': 'number',
6727           'shadowColor': 'color',
6728           'shadowOffsetX': 'number',
6729           'shadowOffsetY': 'number',
6730           'miterLimit': 'number'
6731         },
6732   
6733         //label parsers
6734         'label': {
6735           'size': 'number',
6736           'color': 'color'
6737         },
6738   
6739         //Number interpolator
6740         'compute': function(from, to, delta) {
6741           return from + (to - from) * delta;
6742         },
6743         
6744         //Position interpolators
6745         'moebius': function(elem, props, delta, vector) {
6746           var v = vector.scale(-delta);  
6747           if(v.norm() < 1) {
6748               var x = v.x, y = v.y;
6749               var ans = elem.startPos
6750                 .getc().moebiusTransformation(v);
6751               elem.pos.setc(ans.x, ans.y);
6752               v.x = x; v.y = y;
6753             }           
6754         },
6755
6756         'linear': function(elem, props, delta) {
6757             var from = elem.startPos.getc(true);
6758             var to = elem.endPos.getc(true);
6759             elem.pos.setc(this.compute(from.x, to.x, delta), 
6760                           this.compute(from.y, to.y, delta));
6761         },
6762
6763         'polar': function(elem, props, delta) {
6764           var from = elem.startPos.getp(true);
6765           var to = elem.endPos.getp();
6766           var ans = to.interpolate(from, delta);
6767           elem.pos.setp(ans.theta, ans.rho);
6768         },
6769         
6770         //Graph's Node/Edge interpolators
6771         'number': function(elem, prop, delta, getter, setter) {
6772           var from = elem[getter](prop, 'start');
6773           var to = elem[getter](prop, 'end');
6774           elem[setter](prop, this.compute(from, to, delta));
6775         },
6776
6777         'color': function(elem, prop, delta, getter, setter) {
6778           var from = $.hexToRgb(elem[getter](prop, 'start'));
6779           var to = $.hexToRgb(elem[getter](prop, 'end'));
6780           var comp = this.compute;
6781           var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6782                                 parseInt(comp(from[1], to[1], delta)),
6783                                 parseInt(comp(from[2], to[2], delta))]);
6784           
6785           elem[setter](prop, val);
6786         },
6787         
6788         'array-number': function(elem, prop, delta, getter, setter) {
6789           var from = elem[getter](prop, 'start'),
6790               to = elem[getter](prop, 'end'),
6791               cur = [];
6792           for(var i=0, l=from.length; i<l; i++) {
6793             var fromi = from[i], toi = to[i];
6794             if(fromi.length) {
6795               for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6796                 curi.push(this.compute(fromi[j], toi[j], delta));
6797               }
6798               cur.push(curi);
6799             } else {
6800               cur.push(this.compute(fromi, toi, delta));
6801             }
6802           }
6803           elem[setter](prop, cur);
6804         },
6805         
6806         'node': function(elem, props, delta, map, getter, setter) {
6807           map = this[map];
6808           if(props) {
6809             var len = props.length;
6810             for(var i=0; i<len; i++) {
6811               var pi = props[i];
6812               this[map[pi]](elem, pi, delta, getter, setter);
6813             }
6814           } else {
6815             for(var pi in map) {
6816               this[map[pi]](elem, pi, delta, getter, setter);
6817             }
6818           }
6819         },
6820         
6821         'edge': function(elem, props, delta, mapKey, getter, setter) {
6822             var adjs = elem.adjacencies;
6823             for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6824         },
6825         
6826         'node-property': function(elem, props, delta) {
6827           this['node'](elem, props, delta, 'map', 'getData', 'setData');
6828         },
6829         
6830         'edge-property': function(elem, props, delta) {
6831           this['edge'](elem, props, delta, 'map', 'getData', 'setData');  
6832         },
6833
6834         'label-property': function(elem, props, delta) {
6835           this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6836         },
6837         
6838         'node-style': function(elem, props, delta) {
6839           this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6840         },
6841         
6842         'edge-style': function(elem, props, delta) {
6843           this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');  
6844         }
6845     },
6846     
6847   
6848     /*
6849        sequence
6850     
6851        Iteratively performs an action while refreshing the state of the visualization.
6852
6853        Parameters:
6854
6855        options - (object) An object containing some sequence options described below
6856        condition - (function) A function returning a boolean instance in order to stop iterations.
6857        step - (function) A function to execute on each step of the iteration.
6858        onComplete - (function) A function to execute when the sequence finishes.
6859        duration - (number) Duration (in milliseconds) of each step.
6860
6861       Example:
6862        (start code js)
6863         var rg = new $jit.RGraph(options);
6864         var i = 0;
6865         rg.fx.sequence({
6866           condition: function() {
6867            return i == 10;
6868           },
6869           step: function() {
6870             alert(i++);
6871           },
6872           onComplete: function() {
6873            alert('done!');
6874           }
6875         });
6876        (end code)
6877
6878     */
6879     sequence: function(options) {
6880         var that = this;
6881         options = $.merge({
6882           condition: $.lambda(false),
6883           step: $.empty,
6884           onComplete: $.empty,
6885           duration: 200
6886         }, options || {});
6887
6888         var interval = setInterval(function() {
6889           if(options.condition()) {
6890             options.step();
6891           } else {
6892             clearInterval(interval);
6893             options.onComplete();
6894           }
6895           that.viz.refresh(true);
6896         }, options.duration);
6897     },
6898     
6899     /*
6900       prepare
6901  
6902       Prepare graph position and other attribute values before performing an Animation. 
6903       This method is used internally by the Toolkit.
6904       
6905       See also:
6906        
6907        <Animation>, <Graph.Plot.animate>
6908
6909     */
6910     prepare: function(modes) {
6911       var graph = this.viz.graph,
6912           accessors = {
6913             'node-property': {
6914               'getter': 'getData',
6915               'setter': 'setData'
6916             },
6917             'edge-property': {
6918               'getter': 'getData',
6919               'setter': 'setData'
6920             },
6921             'node-style': {
6922               'getter': 'getCanvasStyle',
6923               'setter': 'setCanvasStyle'
6924             },
6925             'edge-style': {
6926               'getter': 'getCanvasStyle',
6927               'setter': 'setCanvasStyle'
6928             }
6929           };
6930
6931       //parse modes
6932       var m = {};
6933       if($.type(modes) == 'array') {
6934         for(var i=0, len=modes.length; i < len; i++) {
6935           var elems = modes[i].split(':');
6936           m[elems.shift()] = elems;
6937         }
6938       } else {
6939         for(var p in modes) {
6940           if(p == 'position') {
6941             m[modes.position] = [];
6942           } else {
6943             m[p] = $.splat(modes[p]);
6944           }
6945         }
6946       }
6947       
6948       graph.eachNode(function(node) { 
6949         node.startPos.set(node.pos);
6950         $.each(['node-property', 'node-style'], function(p) {
6951           if(p in m) {
6952             var prop = m[p];
6953             for(var i=0, l=prop.length; i < l; i++) {
6954               node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6955             }
6956           }
6957         });
6958         $.each(['edge-property', 'edge-style'], function(p) {
6959           if(p in m) {
6960             var prop = m[p];
6961             node.eachAdjacency(function(adj) {
6962               for(var i=0, l=prop.length; i < l; i++) {
6963                 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6964               }
6965             });
6966           }
6967         });
6968       });
6969       return m;
6970     },
6971     
6972     /*
6973        Method: animate
6974     
6975        Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6976
6977        Parameters:
6978
6979        opt - (object) Animation options. The object properties are described below
6980        duration - (optional) Described in <Options.Fx>.
6981        fps - (optional) Described in <Options.Fx>.
6982        hideLabels - (optional|boolean) Whether to hide labels during the animation.
6983        modes - (required|object) An object with animation modes (described below).
6984
6985        Animation modes:
6986        
6987        Animation modes are strings representing different node/edge and graph properties that you'd like to animate. 
6988        They are represented by an object that has as keys main categories of properties to animate and as values a list 
6989        of these specific properties. The properties are described below
6990        
6991        position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6992        node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6993        edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6994        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.
6995        node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6996        edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6997
6998        Example:
6999        (start code js)
7000        var viz = new $jit.Viz(options);
7001        //...tweak some Data, CanvasStyles or LabelData properties...
7002        viz.fx.animate({
7003          modes: {
7004            'position': 'linear',
7005            'node-property': ['width', 'height'],
7006            'node-style': 'shadowColor',
7007            'label-property': 'size'
7008          },
7009          hideLabels: false
7010        });
7011        //...can also be written like this...
7012        viz.fx.animate({
7013          modes: ['linear',
7014                  'node-property:width:height',
7015                  'node-style:shadowColor',
7016                  'label-property:size'],
7017          hideLabels: false
7018        });
7019        (end code)
7020     */
7021     animate: function(opt, versor) {
7022       opt = $.merge(this.viz.config, opt || {});
7023       var that = this,
7024           viz = this.viz,
7025           graph  = viz.graph,
7026           interp = this.Interpolator,
7027           animation =  opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7028       //prepare graph values
7029       var m = this.prepare(opt.modes);
7030       
7031       //animate
7032       if(opt.hideLabels) this.labels.hideLabels(true);
7033       animation.setOptions($.merge(opt, {
7034         $animating: false,
7035         compute: function(delta) {
7036           graph.eachNode(function(node) { 
7037             for(var p in m) {
7038               interp[p](node, m[p], delta, versor);
7039             }
7040           });
7041           that.plot(opt, this.$animating, delta);
7042           this.$animating = true;
7043         },
7044         complete: function() {
7045           if(opt.hideLabels) that.labels.hideLabels(false);
7046           that.plot(opt);
7047           opt.onComplete();
7048           opt.onAfterCompute();
7049         }       
7050       })).start();
7051     },
7052     
7053     /*
7054       nodeFx
7055    
7056       Apply animation to node properties like color, width, height, dim, etc.
7057   
7058       Parameters:
7059   
7060       options - Animation options. This object properties is described below
7061       elements - The Elements to be transformed. This is an object that has a properties
7062       
7063       (start code js)
7064       'elements': {
7065         //can also be an array of ids
7066         'id': 'id-of-node-to-transform',
7067         //properties to be modified. All properties are optional.
7068         'properties': {
7069           'color': '#ccc', //some color
7070           'width': 10, //some width
7071           'height': 10, //some height
7072           'dim': 20, //some dim
7073           'lineWidth': 10 //some line width
7074         } 
7075       }
7076       (end code)
7077       
7078       - _reposition_ Whether to recalculate positions and add a motion animation. 
7079       This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7080       
7081       - _onComplete_ A method that is called when the animation completes.
7082       
7083       ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7084   
7085       Example:
7086       (start code js)
7087        var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7088        rg.fx.nodeFx({
7089          'elements': {
7090            'id':'mynodeid',
7091            'properties': {
7092              'color':'#ccf'
7093            },
7094            'transition': Trans.Quart.easeOut
7095          }
7096        });
7097       (end code)    
7098    */
7099    nodeFx: function(opt) {
7100      var viz = this.viz,
7101          graph  = viz.graph,
7102          animation = this.nodeFxAnimation,
7103          options = $.merge(this.viz.config, {
7104            'elements': {
7105              'id': false,
7106              'properties': {}
7107            },
7108            'reposition': false
7109          });
7110      opt = $.merge(options, opt || {}, {
7111        onBeforeCompute: $.empty,
7112        onAfterCompute: $.empty
7113      });
7114      //check if an animation is running
7115      animation.stopTimer();
7116      var props = opt.elements.properties;
7117      //set end values for nodes
7118      if(!opt.elements.id) {
7119        graph.eachNode(function(n) {
7120          for(var prop in props) {
7121            n.setData(prop, props[prop], 'end');
7122          }
7123        });
7124      } else {
7125        var ids = $.splat(opt.elements.id);
7126        $.each(ids, function(id) {
7127          var n = graph.getNode(id);
7128          if(n) {
7129            for(var prop in props) {
7130              n.setData(prop, props[prop], 'end');
7131            }
7132          }
7133        });
7134      }
7135      //get keys
7136      var propnames = [];
7137      for(var prop in props) propnames.push(prop);
7138      //add node properties modes
7139      var modes = ['node-property:' + propnames.join(':')];
7140      //set new node positions
7141      if(opt.reposition) {
7142        modes.push('linear');
7143        viz.compute('end');
7144      }
7145      //animate
7146      this.animate($.merge(opt, {
7147        modes: modes,
7148        type: 'nodefx'
7149      }));
7150    },
7151
7152     
7153     /*
7154        Method: plot
7155     
7156        Plots a <Graph>.
7157
7158        Parameters:
7159
7160        opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7161
7162        Example:
7163
7164        (start code js)
7165        var viz = new $jit.Viz(options);
7166        viz.fx.plot(); 
7167        (end code)
7168
7169     */
7170     plot: function(opt, animating) {
7171       var viz = this.viz, 
7172       aGraph = viz.graph, 
7173       canvas = viz.canvas, 
7174       id = viz.root, 
7175       that = this, 
7176       ctx = canvas.getCtx(), 
7177       min = Math.min,
7178       opt = opt || this.viz.controller;
7179       opt.clearCanvas && canvas.clear();
7180         
7181       var root = aGraph.getNode(id);
7182       if(!root) return;
7183       
7184       var T = !!root.visited;
7185       aGraph.eachNode(function(node) {
7186         var nodeAlpha = node.getData('alpha');
7187         node.eachAdjacency(function(adj) {
7188           var nodeTo = adj.nodeTo;
7189           if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7190             !animating && opt.onBeforePlotLine(adj);
7191             ctx.save();
7192             ctx.globalAlpha = min(nodeAlpha, 
7193                 nodeTo.getData('alpha'), 
7194                 adj.getData('alpha'));
7195             that.plotLine(adj, canvas, animating);
7196             ctx.restore();
7197             !animating && opt.onAfterPlotLine(adj);
7198           }
7199         });
7200         ctx.save();
7201         if(node.drawn) {
7202           !animating && opt.onBeforePlotNode(node);
7203           that.plotNode(node, canvas, animating);
7204           !animating && opt.onAfterPlotNode(node);
7205         }
7206         if(!that.labelsHidden && opt.withLabels) {
7207           if(node.drawn && nodeAlpha >= 0.95) {
7208             that.labels.plotLabel(canvas, node, opt);
7209           } else {
7210             that.labels.hideLabel(node, false);
7211           }
7212         }
7213         ctx.restore();
7214         node.visited = !T;
7215       });
7216     },
7217
7218   /*
7219       Plots a Subtree.
7220    */
7221    plotTree: function(node, opt, animating) {
7222        var that = this, 
7223        viz = this.viz, 
7224        canvas = viz.canvas,
7225        config = this.config,
7226        ctx = canvas.getCtx();
7227        var nodeAlpha = node.getData('alpha');
7228        node.eachSubnode(function(elem) {
7229          if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7230              var adj = node.getAdjacency(elem.id);
7231              !animating && opt.onBeforePlotLine(adj);
7232              ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7233              that.plotLine(adj, canvas, animating);
7234              !animating && opt.onAfterPlotLine(adj);
7235              that.plotTree(elem, opt, animating);
7236          }
7237        });
7238        if(node.drawn) {
7239            !animating && opt.onBeforePlotNode(node);
7240            this.plotNode(node, canvas, animating);
7241            !animating && opt.onAfterPlotNode(node);
7242            if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95) 
7243                this.labels.plotLabel(canvas, node, opt);
7244            else 
7245                this.labels.hideLabel(node, false);
7246        } else {
7247            this.labels.hideLabel(node, true);
7248        }
7249    },
7250
7251   /*
7252        Method: plotNode
7253     
7254        Plots a <Graph.Node>.
7255
7256        Parameters:
7257        
7258        node - (object) A <Graph.Node>.
7259        canvas - (object) A <Canvas> element.
7260
7261     */
7262     plotNode: function(node, canvas, animating) {
7263         var f = node.getData('type'), 
7264             ctxObj = this.node.CanvasStyles;
7265         if(f != 'none') {
7266           var width = node.getData('lineWidth'),
7267               color = node.getData('color'),
7268               alpha = node.getData('alpha'),
7269               ctx = canvas.getCtx();
7270           
7271           ctx.lineWidth = width;
7272           ctx.fillStyle = ctx.strokeStyle = color;
7273           ctx.globalAlpha = alpha;
7274           
7275           for(var s in ctxObj) {
7276             ctx[s] = node.getCanvasStyle(s);
7277           }
7278
7279           this.nodeTypes[f].render.call(this, node, canvas, animating);
7280         }
7281     },
7282     
7283     /*
7284        Method: plotLine
7285     
7286        Plots a <Graph.Adjacence>.
7287
7288        Parameters:
7289
7290        adj - (object) A <Graph.Adjacence>.
7291        canvas - (object) A <Canvas> instance.
7292
7293     */
7294     plotLine: function(adj, canvas, animating) {
7295       var f = adj.getData('type'),
7296           ctxObj = this.edge.CanvasStyles;
7297       if(f != 'none') {
7298         var width = adj.getData('lineWidth'),
7299             color = adj.getData('color'),
7300             ctx = canvas.getCtx();
7301         
7302         ctx.lineWidth = width;
7303         ctx.fillStyle = ctx.strokeStyle = color;
7304         
7305         for(var s in ctxObj) {
7306           ctx[s] = adj.getCanvasStyle(s);
7307         }
7308
7309         this.edgeTypes[f].render.call(this, adj, canvas, animating);
7310       }
7311     }    
7312   
7313 };
7314
7315
7316
7317 /*
7318  * File: Graph.Label.js
7319  *
7320 */
7321
7322 /*
7323    Object: Graph.Label
7324
7325    An interface for plotting/hiding/showing labels.
7326
7327    Description:
7328
7329    This is a generic interface for plotting/hiding/showing labels.
7330    The <Graph.Label> interface is implemented in multiple ways to provide
7331    different label types.
7332
7333    For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7334    HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels. 
7335    The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7336    
7337    All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7338 */
7339
7340 Graph.Label = {};
7341
7342 /*
7343    Class: Graph.Label.Native
7344
7345    Implements labels natively, using the Canvas text API.
7346 */
7347 Graph.Label.Native = new Class({
7348     /*
7349        Method: plotLabel
7350
7351        Plots a label for a given node.
7352
7353        Parameters:
7354
7355        canvas - (object) A <Canvas> instance.
7356        node - (object) A <Graph.Node>.
7357        controller - (object) A configuration object.
7358        
7359        Example:
7360        
7361        (start code js)
7362        var viz = new $jit.Viz(options);
7363        var node = viz.graph.getNode('nodeId');
7364        viz.labels.plotLabel(viz.canvas, node, viz.config);
7365        (end code)
7366     */
7367     plotLabel: function(canvas, node, controller) {
7368       var ctx = canvas.getCtx();
7369       var pos = node.pos.getc(true);
7370
7371       ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7372       ctx.textAlign = node.getLabelData('textAlign');
7373       ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7374       ctx.textBaseline = node.getLabelData('textBaseline');
7375
7376       this.renderLabel(canvas, node, controller);
7377     },
7378
7379     /*
7380        renderLabel
7381
7382        Does the actual rendering of the label in the canvas. The default
7383        implementation renders the label close to the position of the node, this
7384        method should be overriden to position the labels differently.
7385
7386        Parameters:
7387
7388        canvas - A <Canvas> instance.
7389        node - A <Graph.Node>.
7390        controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7391     */
7392     renderLabel: function(canvas, node, controller) {
7393       var ctx = canvas.getCtx();
7394       var pos = node.pos.getc(true);
7395       ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7396     },
7397
7398     hideLabel: $.empty,
7399     hideLabels: $.empty
7400 });
7401
7402 /*
7403    Class: Graph.Label.DOM
7404
7405    Abstract Class implementing some DOM label methods.
7406
7407    Implemented by:
7408
7409    <Graph.Label.HTML> and <Graph.Label.SVG>.
7410
7411 */
7412 Graph.Label.DOM = new Class({
7413     //A flag value indicating if node labels are being displayed or not.
7414     labelsHidden: false,
7415     //Label container
7416     labelContainer: false,
7417     //Label elements hash.
7418     labels: {},
7419
7420     /*
7421        Method: getLabelContainer
7422
7423        Lazy fetcher for the label container.
7424
7425        Returns:
7426
7427        The label container DOM element.
7428
7429        Example:
7430
7431       (start code js)
7432         var viz = new $jit.Viz(options);
7433         var labelContainer = viz.labels.getLabelContainer();
7434         alert(labelContainer.innerHTML);
7435       (end code)
7436     */
7437     getLabelContainer: function() {
7438       return this.labelContainer ?
7439         this.labelContainer :
7440         this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7441     },
7442
7443     /*
7444        Method: getLabel
7445
7446        Lazy fetcher for the label element.
7447
7448        Parameters:
7449
7450        id - (string) The label id (which is also a <Graph.Node> id).
7451
7452        Returns:
7453
7454        The label element.
7455
7456        Example:
7457
7458       (start code js)
7459         var viz = new $jit.Viz(options);
7460         var label = viz.labels.getLabel('someid');
7461         alert(label.innerHTML);
7462       (end code)
7463
7464     */
7465     getLabel: function(id) {
7466       return (id in this.labels && this.labels[id] != null) ?
7467         this.labels[id] :
7468         this.labels[id] = document.getElementById(id);
7469     },
7470
7471     /*
7472        Method: hideLabels
7473
7474        Hides all labels (by hiding the label container).
7475
7476        Parameters:
7477
7478        hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7479
7480        Example:
7481        (start code js)
7482         var viz = new $jit.Viz(options);
7483         rg.labels.hideLabels(true);
7484        (end code)
7485
7486     */
7487     hideLabels: function (hide) {
7488       var container = this.getLabelContainer();
7489       if(hide)
7490         container.style.display = 'none';
7491       else
7492         container.style.display = '';
7493       this.labelsHidden = hide;
7494     },
7495
7496     /*
7497        Method: clearLabels
7498
7499        Clears the label container.
7500
7501        Useful when using a new visualization with the same canvas element/widget.
7502
7503        Parameters:
7504
7505        force - (boolean) Forces deletion of all labels.
7506
7507        Example:
7508        (start code js)
7509         var viz = new $jit.Viz(options);
7510         viz.labels.clearLabels();
7511         (end code)
7512     */
7513     clearLabels: function(force) {
7514       for(var id in this.labels) {
7515         if (force || !this.viz.graph.hasNode(id)) {
7516           this.disposeLabel(id);
7517           delete this.labels[id];
7518         }
7519       }
7520     },
7521
7522     /*
7523        Method: disposeLabel
7524
7525        Removes a label.
7526
7527        Parameters:
7528
7529        id - (string) A label id (which generally is also a <Graph.Node> id).
7530
7531        Example:
7532        (start code js)
7533         var viz = new $jit.Viz(options);
7534         viz.labels.disposeLabel('labelid');
7535        (end code)
7536     */
7537     disposeLabel: function(id) {
7538       var elem = this.getLabel(id);
7539       if(elem && elem.parentNode) {
7540         elem.parentNode.removeChild(elem);
7541       }
7542     },
7543
7544     /*
7545        Method: hideLabel
7546
7547        Hides the corresponding <Graph.Node> label.
7548
7549        Parameters:
7550
7551        node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7552        show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7553
7554        Example:
7555        (start code js)
7556         var rg = new $jit.Viz(options);
7557         viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7558        (end code)
7559     */
7560     hideLabel: function(node, show) {
7561       node = $.splat(node);
7562       var st = show ? "" : "none", lab, that = this;
7563       $.each(node, function(n) {
7564         var lab = that.getLabel(n.id);
7565         if (lab) {
7566           lab.style.display = st;
7567         }
7568       });
7569     },
7570
7571     /*
7572        fitsInCanvas
7573
7574        Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7575
7576        Parameters:
7577
7578        pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7579        canvas - A <Canvas> instance.
7580
7581        Returns:
7582
7583        A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7584
7585     */
7586     fitsInCanvas: function(pos, canvas) {
7587       var size = canvas.getSize();
7588       if(pos.x >= size.width || pos.x < 0
7589          || pos.y >= size.height || pos.y < 0) return false;
7590        return true;
7591     }
7592 });
7593
7594 /*
7595    Class: Graph.Label.HTML
7596
7597    Implements HTML labels.
7598
7599    Extends:
7600
7601    All <Graph.Label.DOM> methods.
7602
7603 */
7604 Graph.Label.HTML = new Class({
7605     Implements: Graph.Label.DOM,
7606
7607     /*
7608        Method: plotLabel
7609
7610        Plots a label for a given node.
7611
7612        Parameters:
7613
7614        canvas - (object) A <Canvas> instance.
7615        node - (object) A <Graph.Node>.
7616        controller - (object) A configuration object.
7617        
7618       Example:
7619        
7620        (start code js)
7621        var viz = new $jit.Viz(options);
7622        var node = viz.graph.getNode('nodeId');
7623        viz.labels.plotLabel(viz.canvas, node, viz.config);
7624        (end code)
7625
7626
7627     */
7628     plotLabel: function(canvas, node, controller) {
7629       var id = node.id, tag = this.getLabel(id);
7630
7631       if(!tag && !(tag = document.getElementById(id))) {
7632         tag = document.createElement('div');
7633         var container = this.getLabelContainer();
7634         tag.id = id;
7635         tag.className = 'node';
7636         tag.style.position = 'absolute';
7637         controller.onCreateLabel(tag, node);
7638         container.appendChild(tag);
7639         this.labels[node.id] = tag;
7640       }
7641
7642       this.placeLabel(tag, node, controller);
7643     }
7644 });
7645
7646 /*
7647    Class: Graph.Label.SVG
7648
7649    Implements SVG labels.
7650
7651    Extends:
7652
7653    All <Graph.Label.DOM> methods.
7654 */
7655 Graph.Label.SVG = new Class({
7656     Implements: Graph.Label.DOM,
7657
7658     /*
7659        Method: plotLabel
7660
7661        Plots a label for a given node.
7662
7663        Parameters:
7664
7665        canvas - (object) A <Canvas> instance.
7666        node - (object) A <Graph.Node>.
7667        controller - (object) A configuration object.
7668        
7669        Example:
7670        
7671        (start code js)
7672        var viz = new $jit.Viz(options);
7673        var node = viz.graph.getNode('nodeId');
7674        viz.labels.plotLabel(viz.canvas, node, viz.config);
7675        (end code)
7676
7677
7678     */
7679     plotLabel: function(canvas, node, controller) {
7680       var id = node.id, tag = this.getLabel(id);
7681       if(!tag && !(tag = document.getElementById(id))) {
7682         var ns = 'http://www.w3.org/2000/svg';
7683           tag = document.createElementNS(ns, 'svg:text');
7684         var tspan = document.createElementNS(ns, 'svg:tspan');
7685         tag.appendChild(tspan);
7686         var container = this.getLabelContainer();
7687         tag.setAttribute('id', id);
7688         tag.setAttribute('class', 'node');
7689         container.appendChild(tag);
7690         controller.onCreateLabel(tag, node);
7691         this.labels[node.id] = tag;
7692       }
7693       this.placeLabel(tag, node, controller);
7694     }
7695 });
7696
7697
7698
7699 Graph.Geom = new Class({
7700
7701   initialize: function(viz) {
7702     this.viz = viz;
7703     this.config = viz.config;
7704     this.node = viz.config.Node;
7705     this.edge = viz.config.Edge;
7706   },
7707   /*
7708     Applies a translation to the tree.
7709   
7710     Parameters:
7711   
7712     pos - A <Complex> number specifying translation vector.
7713     prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7714   
7715     Example:
7716   
7717     (start code js)
7718       st.geom.translate(new Complex(300, 100), 'end');
7719     (end code)
7720   */  
7721   translate: function(pos, prop) {
7722      prop = $.splat(prop);
7723      this.viz.graph.eachNode(function(elem) {
7724          $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7725      });
7726   },
7727   /*
7728     Hides levels of the tree until it properly fits in canvas.
7729   */  
7730   setRightLevelToShow: function(node, canvas, callback) {
7731      var level = this.getRightLevelToShow(node, canvas), 
7732          fx = this.viz.labels,
7733          opt = $.merge({
7734            execShow:true,
7735            execHide:true,
7736            onHide: $.empty,
7737            onShow: $.empty
7738          }, callback || {});
7739      node.eachLevel(0, this.config.levelsToShow, function(n) {
7740          var d = n._depth - node._depth;
7741          if(d > level) {
7742              opt.onHide(n);
7743              if(opt.execHide) {
7744                n.drawn = false; 
7745                n.exist = false;
7746                fx.hideLabel(n, false);
7747              }
7748          } else {
7749              opt.onShow(n);
7750              if(opt.execShow) {
7751                n.exist = true;
7752              }
7753          }
7754      });
7755      node.drawn= true;
7756   },
7757   /*
7758     Returns the right level to show for the current tree in order to fit in canvas.
7759   */  
7760   getRightLevelToShow: function(node, canvas) {
7761      var config = this.config;
7762      var level = config.levelsToShow;
7763      var constrained = config.constrained;
7764      if(!constrained) return level;
7765      while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7766      return level;
7767   }
7768 });
7769
7770 /*
7771  * File: Loader.js
7772  * 
7773  */
7774
7775 /*
7776    Object: Loader
7777
7778    Provides methods for loading and serving JSON data.
7779 */
7780 var Loader = {
7781      construct: function(json) {
7782         var isGraph = ($.type(json) == 'array');
7783         var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7784         if(!isGraph) 
7785             //make tree
7786             (function (ans, json) {
7787                 ans.addNode(json);
7788                 if(json.children) {
7789                   for(var i=0, ch = json.children; i<ch.length; i++) {
7790                     ans.addAdjacence(json, ch[i]);
7791                     arguments.callee(ans, ch[i]);
7792                   }
7793                 }
7794             })(ans, json);
7795         else
7796             //make graph
7797             (function (ans, json) {
7798                 var getNode = function(id) {
7799                   for(var i=0, l=json.length; i<l; i++) {
7800                     if(json[i].id == id) {
7801                       return json[i];
7802                     }
7803                   }
7804                   // The node was not defined in the JSON
7805                   // Let's create it
7806                   var newNode = {
7807                                 "id" : id,
7808                                 "name" : id
7809                         };
7810                   return ans.addNode(newNode);
7811                 };
7812
7813                 for(var i=0, l=json.length; i<l; i++) {
7814                   ans.addNode(json[i]);
7815                   var adj = json[i].adjacencies;
7816                   if (adj) {
7817                     for(var j=0, lj=adj.length; j<lj; j++) {
7818                       var node = adj[j], data = {};
7819                       if(typeof adj[j] != 'string') {
7820                         data = $.merge(node.data, {});
7821                         node = node.nodeTo;
7822                       }
7823                       ans.addAdjacence(json[i], getNode(node), data);
7824                     }
7825                   }
7826                 }
7827             })(ans, json);
7828
7829         return ans;
7830     },
7831
7832     /*
7833      Method: loadJSON
7834     
7835      Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7836      
7837       A JSON tree or graph structure consists of nodes, each having as properties
7838        
7839        id - (string) A unique identifier for the node
7840        name - (string) A node's name
7841        data - (object) The data optional property contains a hash (i.e {}) 
7842        where you can store all the information you want about this node.
7843         
7844       For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7845       
7846       Example:
7847
7848       (start code js)
7849         var json = {  
7850           "id": "aUniqueIdentifier",  
7851           "name": "usually a nodes name",  
7852           "data": {
7853             "some key": "some value",
7854             "some other key": "some other value"
7855            },  
7856           "children": [ *other nodes or empty* ]  
7857         };  
7858       (end code)
7859         
7860         JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected. 
7861         For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7862         
7863         There are two types of *Graph* structures, *simple* and *extended* graph structures.
7864         
7865         For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the 
7866         id of the node connected to the main node.
7867         
7868         Example:
7869         
7870         (start code js)
7871         var json = [  
7872           {  
7873             "id": "aUniqueIdentifier",  
7874             "name": "usually a nodes name",  
7875             "data": {
7876               "some key": "some value",
7877               "some other key": "some other value"
7878              },  
7879             "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']  
7880           },
7881
7882           'other nodes go here...' 
7883         ];          
7884         (end code)
7885         
7886         For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7887         
7888         nodeTo - (string) The other node connected by this adjacency.
7889         data - (object) A data property, where we can store custom key/value information.
7890         
7891         Example:
7892         
7893         (start code js)
7894         var json = [  
7895           {  
7896             "id": "aUniqueIdentifier",  
7897             "name": "usually a nodes name",  
7898             "data": {
7899               "some key": "some value",
7900               "some other key": "some other value"
7901              },  
7902             "adjacencies": [  
7903             {  
7904               nodeTo:"aNodeId",  
7905               data: {} //put whatever you want here  
7906             },
7907             'other adjacencies go here...'  
7908           },
7909
7910           'other nodes go here...' 
7911         ];          
7912         (end code)
7913        
7914        About the data property:
7915        
7916        As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*. 
7917        You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and 
7918        have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7919        
7920        For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in 
7921        <Options.Node> will override the general value for that option with that particular value. For this to work 
7922        however, you do have to set *overridable = true* in <Options.Node>.
7923        
7924        The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge> 
7925        if <Options.Edge> has *overridable = true*.
7926        
7927        When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key, 
7928        since this is the value which will be taken into account when creating the layout. 
7929        The same thing goes for the *$color* parameter.
7930        
7931        In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example, 
7932        *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set 
7933        canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer 
7934        to the *shadowBlur* property.
7935        
7936        These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences> 
7937        by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7938        
7939        Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more 
7940        information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7941        
7942        loadJSON Parameters:
7943     
7944         json - A JSON Tree or Graph structure.
7945         i - For Graph structures only. Sets the indexed node as root for the visualization.
7946
7947     */
7948     loadJSON: function(json, i) {
7949       this.json = json;
7950       //if they're canvas labels erase them.
7951       if(this.labels && this.labels.clearLabels) {
7952         this.labels.clearLabels(true);
7953       }
7954       this.graph = this.construct(json);
7955       if($.type(json) != 'array'){
7956         this.root = json.id;
7957       } else {
7958         this.root = json[i? i : 0].id;
7959       }
7960     },
7961     
7962     /*
7963       Method: toJSON
7964    
7965       Returns a JSON tree/graph structure from the visualization's <Graph>. 
7966       See <Loader.loadJSON> for the graph formats available.
7967       
7968       See also:
7969       
7970       <Loader.loadJSON>
7971       
7972       Parameters:
7973       
7974       type - (string) Default's "tree". The type of the JSON structure to be returned. 
7975       Possible options are "tree" or "graph".
7976     */    
7977     toJSON: function(type) {
7978       type = type || "tree";
7979       if(type == 'tree') {
7980         var ans = {};
7981         var rootNode = this.graph.getNode(this.root);
7982         var ans = (function recTree(node) {
7983           var ans = {};
7984           ans.id = node.id;
7985           ans.name = node.name;
7986           ans.data = node.data;
7987           var ch =[];
7988           node.eachSubnode(function(n) {
7989             ch.push(recTree(n));
7990           });
7991           ans.children = ch;
7992           return ans;
7993         })(rootNode);
7994         return ans;
7995       } else {
7996         var ans = [];
7997         var T = !!this.graph.getNode(this.root).visited;
7998         this.graph.eachNode(function(node) {
7999           var ansNode = {};
8000           ansNode.id = node.id;
8001           ansNode.name = node.name;
8002           ansNode.data = node.data;
8003           var adjs = [];
8004           node.eachAdjacency(function(adj) {
8005             var nodeTo = adj.nodeTo;
8006             if(!!nodeTo.visited === T) {
8007               var ansAdj = {};
8008               ansAdj.nodeTo = nodeTo.id;
8009               ansAdj.data = adj.data;
8010               adjs.push(ansAdj);
8011             }
8012           });
8013           ansNode.adjacencies = adjs;
8014           ans.push(ansNode);
8015           node.visited = !T;
8016         });
8017         return ans;
8018       }
8019     }
8020 };
8021
8022
8023
8024 /*
8025  * File: Layouts.js
8026  * 
8027  * Implements base Tree and Graph layouts.
8028  *
8029  * Description:
8030  *
8031  * Implements base Tree and Graph layouts like Radial, Tree, etc.
8032  * 
8033  */
8034
8035 /*
8036  * Object: Layouts
8037  * 
8038  * Parent object for common layouts.
8039  *
8040  */
8041 var Layouts = $jit.Layouts = {};
8042
8043
8044 //Some util shared layout functions are defined here.
8045 var NodeDim = {
8046   label: null,
8047   
8048   compute: function(graph, prop, opt) {
8049     this.initializeLabel(opt);
8050     var label = this.label, style = label.style;
8051     graph.eachNode(function(n) {
8052       var autoWidth  = n.getData('autoWidth'),
8053           autoHeight = n.getData('autoHeight');
8054       if(autoWidth || autoHeight) {
8055         //delete dimensions since these are
8056         //going to be overridden now.
8057         delete n.data.$width;
8058         delete n.data.$height;
8059         delete n.data.$dim;
8060         
8061         var width  = n.getData('width'),
8062             height = n.getData('height');
8063         //reset label dimensions
8064         style.width  = autoWidth? 'auto' : width + 'px';
8065         style.height = autoHeight? 'auto' : height + 'px';
8066         
8067         //TODO(nico) should let the user choose what to insert here.
8068         label.innerHTML = n.name;
8069         
8070         var offsetWidth  = label.offsetWidth,
8071             offsetHeight = label.offsetHeight;
8072         var type = n.getData('type');
8073         if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8074           n.setData('width', offsetWidth);
8075           n.setData('height', offsetHeight);
8076         } else {
8077           var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8078           n.setData('width', dim);
8079           n.setData('height', dim);
8080           n.setData('dim', dim); 
8081         }
8082       }
8083     });
8084   },
8085   
8086   initializeLabel: function(opt) {
8087     if(!this.label) {
8088       this.label = document.createElement('div');
8089       document.body.appendChild(this.label);
8090     }
8091     this.setLabelStyles(opt);
8092   },
8093   
8094   setLabelStyles: function(opt) {
8095     $.extend(this.label.style, {
8096       'visibility': 'hidden',
8097       'position': 'absolute',
8098       'width': 'auto',
8099       'height': 'auto'
8100     });
8101     this.label.className = 'jit-autoadjust-label';
8102   }
8103 };
8104
8105
8106 /*
8107  * Class: Layouts.Tree
8108  * 
8109  * Implements a Tree Layout.
8110  * 
8111  * Implemented By:
8112  * 
8113  * <ST>
8114  * 
8115  * Inspired by:
8116  * 
8117  * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8118  * 
8119  */
8120 Layouts.Tree = (function() {
8121   //Layout functions
8122   var slice = Array.prototype.slice;
8123
8124   /*
8125      Calculates the max width and height nodes for a tree level
8126   */  
8127   function getBoundaries(graph, config, level, orn, prop) {
8128     var dim = config.Node;
8129     var multitree = config.multitree;
8130     if (dim.overridable) {
8131       var w = -1, h = -1;
8132       graph.eachNode(function(n) {
8133         if (n._depth == level
8134             && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8135           var dw = n.getData('width', prop);
8136           var dh = n.getData('height', prop);
8137           w = (w < dw) ? dw : w;
8138           h = (h < dh) ? dh : h;
8139         }
8140       });
8141       return {
8142         'width' : w < 0 ? dim.width : w,
8143         'height' : h < 0 ? dim.height : h
8144       };
8145     } else {
8146       return dim;
8147     }
8148   }
8149
8150
8151   function movetree(node, prop, val, orn) {
8152     var p = (orn == "left" || orn == "right") ? "y" : "x";
8153     node.getPos(prop)[p] += val;
8154   }
8155
8156
8157   function moveextent(extent, val) {
8158     var ans = [];
8159     $.each(extent, function(elem) {
8160       elem = slice.call(elem);
8161       elem[0] += val;
8162       elem[1] += val;
8163       ans.push(elem);
8164     });
8165     return ans;
8166   }
8167
8168
8169   function merge(ps, qs) {
8170     if (ps.length == 0)
8171       return qs;
8172     if (qs.length == 0)
8173       return ps;
8174     var p = ps.shift(), q = qs.shift();
8175     return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8176   }
8177
8178
8179   function mergelist(ls, def) {
8180     def = def || [];
8181     if (ls.length == 0)
8182       return def;
8183     var ps = ls.pop();
8184     return mergelist(ls, merge(ps, def));
8185   }
8186
8187
8188   function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8189     if (ext1.length <= i || ext2.length <= i)
8190       return 0;
8191
8192     var p = ext1[i][1], q = ext2[i][0];
8193     return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8194         + subtreeOffset, p - q + siblingOffset);
8195   }
8196
8197
8198   function fitlistl(es, subtreeOffset, siblingOffset) {
8199     function $fitlistl(acc, es, i) {
8200       if (es.length <= i)
8201         return [];
8202       var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8203       return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8204     }
8205     ;
8206     return $fitlistl( [], es, 0);
8207   }
8208
8209
8210   function fitlistr(es, subtreeOffset, siblingOffset) {
8211     function $fitlistr(acc, es, i) {
8212       if (es.length <= i)
8213         return [];
8214       var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8215       return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8216     }
8217     ;
8218     es = slice.call(es);
8219     var ans = $fitlistr( [], es.reverse(), 0);
8220     return ans.reverse();
8221   }
8222
8223
8224   function fitlist(es, subtreeOffset, siblingOffset, align) {
8225     var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8226         subtreeOffset, siblingOffset);
8227
8228     if (align == "left")
8229       esr = esl;
8230     else if (align == "right")
8231       esl = esr;
8232
8233     for ( var i = 0, ans = []; i < esl.length; i++) {
8234       ans[i] = (esl[i] + esr[i]) / 2;
8235     }
8236     return ans;
8237   }
8238
8239
8240   function design(graph, node, prop, config, orn) {
8241     var multitree = config.multitree;
8242     var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8243     var ind = +(orn == "left" || orn == "right");
8244     var p = auxp[ind], notp = auxp[1 - ind];
8245
8246     var cnode = config.Node;
8247     var s = auxs[ind], nots = auxs[1 - ind];
8248
8249     var siblingOffset = config.siblingOffset;
8250     var subtreeOffset = config.subtreeOffset;
8251     var align = config.align;
8252
8253     function $design(node, maxsize, acum) {
8254       var sval = node.getData(s, prop);
8255       var notsval = maxsize
8256           || (node.getData(nots, prop));
8257
8258       var trees = [], extents = [], chmaxsize = false;
8259       var chacum = notsval + config.levelDistance;
8260       node.eachSubnode(function(n) {
8261             if (n.exist
8262                 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8263
8264               if (!chmaxsize)
8265                 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8266
8267               var s = $design(n, chmaxsize[nots], acum + chacum);
8268               trees.push(s.tree);
8269               extents.push(s.extent);
8270             }
8271           });
8272       var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8273       for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8274         movetree(trees[i], prop, positions[i], orn);
8275         pextents.push(moveextent(extents[i], positions[i]));
8276       }
8277       var resultextent = [ [ -sval / 2, sval / 2 ] ]
8278           .concat(mergelist(pextents));
8279       node.getPos(prop)[p] = 0;
8280
8281       if (orn == "top" || orn == "left") {
8282         node.getPos(prop)[notp] = acum;
8283       } else {
8284         node.getPos(prop)[notp] = -acum;
8285       }
8286
8287       return {
8288         tree : node,
8289         extent : resultextent
8290       };
8291     }
8292
8293     $design(node, false, 0);
8294   }
8295
8296
8297   return new Class({
8298     /*
8299     Method: compute
8300     
8301     Computes nodes' positions.
8302
8303      */
8304     compute : function(property, computeLevels) {
8305       var prop = property || 'start';
8306       var node = this.graph.getNode(this.root);
8307       $.extend(node, {
8308         'drawn' : true,
8309         'exist' : true,
8310         'selected' : true
8311       });
8312       NodeDim.compute(this.graph, prop, this.config);
8313       if (!!computeLevels || !("_depth" in node)) {
8314         this.graph.computeLevels(this.root, 0, "ignore");
8315       }
8316       
8317       this.computePositions(node, prop);
8318     },
8319
8320     computePositions : function(node, prop) {
8321       var config = this.config;
8322       var multitree = config.multitree;
8323       var align = config.align;
8324       var indent = align !== 'center' && config.indent;
8325       var orn = config.orientation;
8326       var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8327       var that = this;
8328       $.each(orns, function(orn) {
8329         //calculate layout
8330           design(that.graph, node, prop, that.config, orn, prop);
8331           var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8332           //absolutize
8333           (function red(node) {
8334             node.eachSubnode(function(n) {
8335               if (n.exist
8336                   && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8337
8338                 n.getPos(prop)[i] += node.getPos(prop)[i];
8339                 if (indent) {
8340                   n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8341                 }
8342                 red(n);
8343               }
8344             });
8345           })(node);
8346         });
8347     }
8348   });
8349   
8350 })();
8351
8352 /*
8353  * File: Spacetree.js
8354  */
8355
8356 /*
8357    Class: ST
8358    
8359   A Tree layout with advanced contraction and expansion animations.
8360      
8361   Inspired by:
8362  
8363   SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson) 
8364   <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8365   
8366   Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8367   
8368   Note:
8369  
8370   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.
8371  
8372   Implements:
8373   
8374   All <Loader> methods
8375   
8376   Constructor Options:
8377   
8378   Inherits options from
8379   
8380   - <Options.Canvas>
8381   - <Options.Controller>
8382   - <Options.Tree>
8383   - <Options.Node>
8384   - <Options.Edge>
8385   - <Options.Label>
8386   - <Options.Events>
8387   - <Options.Tips>
8388   - <Options.NodeStyles>
8389   - <Options.Navigation>
8390   
8391   Additionally, there are other parameters and some default values changed
8392   
8393   constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8394   levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8395   levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8396   Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8397   offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8398   offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8399   duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8400   
8401   Instance Properties:
8402   
8403   canvas - Access a <Canvas> instance.
8404   graph - Access a <Graph> instance.
8405   op - Access a <ST.Op> instance.
8406   fx - Access a <ST.Plot> instance.
8407   labels - Access a <ST.Label> interface implementation.
8408
8409  */
8410
8411 $jit.ST= (function() {
8412     // Define some private methods first...
8413     // Nodes in path
8414     var nodesInPath = [];
8415     // Nodes to contract
8416     function getNodesToHide(node) {
8417       node = node || this.clickedNode;
8418       if(!this.config.constrained) {
8419         return [];
8420       }
8421       var Geom = this.geom;
8422       var graph = this.graph;
8423       var canvas = this.canvas;
8424       var level = node._depth, nodeArray = [];
8425           graph.eachNode(function(n) {
8426           if(n.exist && !n.selected) {
8427               if(n.isDescendantOf(node.id)) {
8428                 if(n._depth <= level) nodeArray.push(n);
8429               } else {
8430                 nodeArray.push(n);
8431               }
8432           }
8433           });
8434           var leafLevel = Geom.getRightLevelToShow(node, canvas);
8435           node.eachLevel(leafLevel, leafLevel, function(n) {
8436           if(n.exist && !n.selected) nodeArray.push(n);
8437           });
8438             
8439           for (var i = 0; i < nodesInPath.length; i++) {
8440             var n = this.graph.getNode(nodesInPath[i]);
8441             if(!n.isDescendantOf(node.id)) {
8442               nodeArray.push(n);
8443             }
8444           } 
8445           return nodeArray;       
8446     };
8447     // Nodes to expand
8448      function getNodesToShow(node) {
8449         var nodeArray = [], config = this.config;
8450         node = node || this.clickedNode;
8451         this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8452             if(config.multitree && !('$orn' in n.data) 
8453                         && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8454                 nodeArray.push(n);
8455             } else if(n.drawn && !n.anySubnode("drawn")) {
8456               nodeArray.push(n);
8457             }
8458         });
8459         return nodeArray;
8460      };
8461     // Now define the actual class.
8462     return new Class({
8463     
8464         Implements: [Loader, Extras, Layouts.Tree],
8465         
8466         initialize: function(controller) {            
8467           var $ST = $jit.ST;
8468           
8469           var config= {
8470                 levelsToShow: 2,
8471                 levelDistance: 30,
8472                 constrained: true,                
8473                 Node: {
8474                   type: 'rectangle'
8475                 },
8476                 duration: 700,
8477                 offsetX: 0,
8478                 offsetY: 0
8479             };
8480             
8481             this.controller = this.config = $.merge(
8482                 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller", 
8483                     "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8484
8485             var canvasConfig = this.config;
8486             if(canvasConfig.useCanvas) {
8487               this.canvas = canvasConfig.useCanvas;
8488               this.config.labelContainer = this.canvas.id + '-label';
8489             } else {
8490               if(canvasConfig.background) {
8491                 canvasConfig.background = $.merge({
8492                   type: 'Fade',
8493                   colorStop1: this.config.colorStop1,
8494                   colorStop2: this.config.colorStop2
8495                 }, canvasConfig.background);
8496               }
8497               this.canvas = new Canvas(this, canvasConfig);
8498               this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8499             }
8500
8501             this.graphOptions = {
8502                 'complex': true
8503             };
8504             this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8505             this.labels = new $ST.Label[canvasConfig.Label.type](this);
8506             this.fx = new $ST.Plot(this, $ST);
8507             this.op = new $ST.Op(this);
8508             this.group = new $ST.Group(this);
8509             this.geom = new $ST.Geom(this);
8510             this.clickedNode=  null;
8511             // initialize extras
8512             this.initializeExtras();
8513         },
8514     
8515         /*
8516          Method: plot
8517         
8518          Plots the <ST>. This is a shortcut to *fx.plot*.
8519
8520         */  
8521         plot: function() { this.fx.plot(this.controller); },
8522     
8523       
8524         /*
8525          Method: switchPosition
8526         
8527          Switches the tree orientation.
8528
8529          Parameters:
8530
8531         pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8532         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.
8533         onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8534
8535          Example:
8536
8537          (start code js)
8538            st.switchPosition("right", "animate", {
8539             onComplete: function() {
8540               alert('completed!');
8541             } 
8542            });
8543          (end code)
8544         */  
8545         switchPosition: function(pos, method, onComplete) {
8546           var Geom = this.geom, Plot = this.fx, that = this;
8547           if(!Plot.busy) {
8548               Plot.busy = true;
8549               this.contract({
8550                   onComplete: function() {
8551                       Geom.switchOrientation(pos);
8552                       that.compute('end', false);
8553                       Plot.busy = false;
8554                       if(method == 'animate') {
8555                           that.onClick(that.clickedNode.id, onComplete);  
8556                       } else if(method == 'replot') {
8557                           that.select(that.clickedNode.id, onComplete);
8558                       }
8559                   }
8560               }, pos);
8561           }
8562         },
8563
8564         /*
8565         Method: switchAlignment
8566        
8567         Switches the tree alignment.
8568
8569         Parameters:
8570
8571        align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8572        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.
8573        onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8574
8575         Example:
8576
8577         (start code js)
8578           st.switchAlignment("right", "animate", {
8579            onComplete: function() {
8580              alert('completed!');
8581            } 
8582           });
8583         (end code)
8584        */  
8585        switchAlignment: function(align, method, onComplete) {
8586         this.config.align = align;
8587         if(method == 'animate') {
8588                 this.select(this.clickedNode.id, onComplete);
8589         } else if(method == 'replot') {
8590                 this.onClick(this.clickedNode.id, onComplete);  
8591         }
8592        },
8593
8594        /*
8595         Method: addNodeInPath
8596        
8597         Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8598         
8599
8600         Parameters:
8601
8602        id - (string) A <Graph.Node> id.
8603
8604         Example:
8605
8606         (start code js)
8607           st.addNodeInPath("nodeId");
8608         (end code)
8609        */  
8610        addNodeInPath: function(id) {
8611            nodesInPath.push(id);
8612            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8613        },       
8614
8615        /*
8616        Method: clearNodesInPath
8617       
8618        Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8619        
8620        See also:
8621        
8622        <ST.addNodeInPath>
8623      
8624        Example:
8625
8626        (start code js)
8627          st.clearNodesInPath();
8628        (end code)
8629       */  
8630        clearNodesInPath: function(id) {
8631            nodesInPath.length = 0;
8632            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8633        },
8634         
8635        /*
8636          Method: refresh
8637         
8638          Computes positions and plots the tree.
8639          
8640        */
8641        refresh: function() {
8642            this.reposition();
8643            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8644        },    
8645
8646        reposition: function() {
8647             this.graph.computeLevels(this.root, 0, "ignore");
8648              this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8649             this.graph.eachNode(function(n) {
8650                 if(n.exist) n.drawn = true;
8651             });
8652             this.compute('end');
8653         },
8654         
8655         requestNodes: function(node, onComplete) {
8656           var handler = $.merge(this.controller, onComplete), 
8657           lev = this.config.levelsToShow;
8658           if(handler.request) {
8659               var leaves = [], d = node._depth;
8660               node.eachLevel(0, lev, function(n) {
8661                   if(n.drawn && 
8662                    !n.anySubnode()) {
8663                    leaves.push(n);
8664                    n._level = lev - (n._depth - d);
8665                   }
8666               });
8667               this.group.requestNodes(leaves, handler);
8668           }
8669             else
8670               handler.onComplete();
8671         },
8672      
8673         contract: function(onComplete, switched) {
8674           var orn  = this.config.orientation;
8675           var Geom = this.geom, Group = this.group;
8676           if(switched) Geom.switchOrientation(switched);
8677           var nodes = getNodesToHide.call(this);
8678           if(switched) Geom.switchOrientation(orn);
8679           Group.contract(nodes, $.merge(this.controller, onComplete));
8680         },
8681       
8682          move: function(node, onComplete) {
8683             this.compute('end', false);
8684             var move = onComplete.Move, offset = {
8685                 'x': move.offsetX,
8686                 'y': move.offsetY 
8687             };
8688             if(move.enable) {
8689                 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8690             }
8691             this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8692          },
8693       
8694         expand: function (node, onComplete) {
8695             var nodeArray = getNodesToShow.call(this, node);
8696             this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8697         },
8698     
8699         selectPath: function(node) {
8700           var that = this;
8701           this.graph.eachNode(function(n) { n.selected = false; }); 
8702           function path(node) {
8703               if(node == null || node.selected) return;
8704               node.selected = true;
8705               $.each(that.group.getSiblings([node])[node.id], 
8706               function(n) { 
8707                    n.exist = true; 
8708                    n.drawn = true; 
8709               });    
8710               var parents = node.getParents();
8711               parents = (parents.length > 0)? parents[0] : null;
8712               path(parents);
8713           };
8714           for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8715               path(this.graph.getNode(ns[i]));
8716           }
8717         },
8718       
8719         /*
8720         Method: setRoot
8721      
8722          Switches the current root node. Changes the topology of the Tree.
8723      
8724         Parameters:
8725            id - (string) The id of the node to be set as root.
8726            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.
8727            onComplete - (optional|object) An action to perform after the animation (if any).
8728  
8729         Example:
8730
8731         (start code js)
8732           st.setRoot('nodeId', 'animate', {
8733              onComplete: function() {
8734                alert('complete!');
8735              }
8736           });
8737         (end code)
8738      */
8739      setRoot: function(id, method, onComplete) {
8740                 if(this.busy) return;
8741                 this.busy = true;
8742           var that = this, canvas = this.canvas;
8743                 var rootNode = this.graph.getNode(this.root);
8744                 var clickedNode = this.graph.getNode(id);
8745                 function $setRoot() {
8746                 if(this.config.multitree && clickedNode.data.$orn) {
8747                         var orn = clickedNode.data.$orn;
8748                         var opp = {
8749                                         'left': 'right',
8750                                         'right': 'left',
8751                                         'top': 'bottom',
8752                                         'bottom': 'top'
8753                         }[orn];
8754                         rootNode.data.$orn = opp;
8755                         (function tag(rootNode) {
8756                                 rootNode.eachSubnode(function(n) {
8757                                         if(n.id != id) {
8758                                                 n.data.$orn = opp;
8759                                                 tag(n);
8760                                         }
8761                                 });
8762                         })(rootNode);
8763                         delete clickedNode.data.$orn;
8764                 }
8765                 this.root = id;
8766                 this.clickedNode = clickedNode;
8767                 this.graph.computeLevels(this.root, 0, "ignore");
8768                 this.geom.setRightLevelToShow(clickedNode, canvas, {
8769                   execHide: false,
8770                   onShow: function(node) {
8771                     if(!node.drawn) {
8772                     node.drawn = true;
8773                     node.setData('alpha', 1, 'end');
8774                     node.setData('alpha', 0);
8775                     node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8776                     }
8777                   }
8778                 });
8779               this.compute('end');
8780               this.busy = true;
8781               this.fx.animate({
8782                 modes: ['linear', 'node-property:alpha'],
8783                 onComplete: function() {
8784                   that.busy = false;
8785                   that.onClick(id, {
8786                     onComplete: function() {
8787                       onComplete && onComplete.onComplete();
8788                     }
8789                   });
8790                 }
8791               });
8792                 }
8793
8794                 // delete previous orientations (if any)
8795                 delete rootNode.data.$orns;
8796
8797                 if(method == 'animate') {
8798                   $setRoot.call(this);
8799                   that.selectPath(clickedNode);
8800                 } else if(method == 'replot') {
8801                         $setRoot.call(this);
8802                         this.select(this.root);
8803                 }
8804      },
8805
8806      /*
8807            Method: addSubtree
8808         
8809             Adds a subtree.
8810         
8811            Parameters:
8812               subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8813               method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8814               onComplete - (optional|object) An action to perform after the animation (if any).
8815     
8816            Example:
8817
8818            (start code js)
8819              st.addSubtree(json, 'animate', {
8820                 onComplete: function() {
8821                   alert('complete!');
8822                 }
8823              });
8824            (end code)
8825         */
8826         addSubtree: function(subtree, method, onComplete) {
8827             if(method == 'replot') {
8828                 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8829             } else if (method == 'animate') {
8830                 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8831             }
8832         },
8833     
8834         /*
8835            Method: removeSubtree
8836         
8837             Removes a subtree.
8838         
8839            Parameters:
8840               id - (string) The _id_ of the subtree to be removed.
8841               removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8842               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.
8843               onComplete - (optional|object) An action to perform after the animation (if any).
8844
8845           Example:
8846
8847           (start code js)
8848             st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8849               onComplete: function() {
8850                 alert('complete!');
8851               }
8852             });
8853           (end code)
8854     
8855         */
8856         removeSubtree: function(id, removeRoot, method, onComplete) {
8857             var node = this.graph.getNode(id), subids = [];
8858             node.eachLevel(+!removeRoot, false, function(n) {
8859                 subids.push(n.id);
8860             });
8861             if(method == 'replot') {
8862                 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8863             } else if (method == 'animate') {
8864                 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8865             }
8866         },
8867     
8868         /*
8869            Method: select
8870         
8871             Selects a node in the <ST> without performing an animation. Useful when selecting 
8872             nodes which are currently hidden or deep inside the tree.
8873
8874           Parameters:
8875             id - (string) The id of the node to select.
8876             onComplete - (optional|object) an onComplete callback.
8877
8878           Example:
8879           (start code js)
8880             st.select('mynodeid', {
8881               onComplete: function() {
8882                 alert('complete!');
8883               }
8884             });
8885           (end code)
8886         */
8887         select: function(id, onComplete) {
8888             var group = this.group, geom = this.geom;
8889             var node=  this.graph.getNode(id), canvas = this.canvas;
8890             var root  = this.graph.getNode(this.root);
8891             var complete = $.merge(this.controller, onComplete);
8892             var that = this;
8893     
8894             complete.onBeforeCompute(node);
8895             this.selectPath(node);
8896             this.clickedNode= node;
8897             this.requestNodes(node, {
8898                 onComplete: function(){
8899                     group.hide(group.prepare(getNodesToHide.call(that)), complete);
8900                     geom.setRightLevelToShow(node, canvas);
8901                     that.compute("current");
8902                     that.graph.eachNode(function(n) { 
8903                         var pos = n.pos.getc(true);
8904                         n.startPos.setc(pos.x, pos.y);
8905                         n.endPos.setc(pos.x, pos.y);
8906                         n.visited = false; 
8907                     });
8908                     var offset = { x: complete.offsetX, y: complete.offsetY };
8909                     that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8910                     group.show(getNodesToShow.call(that));              
8911                     that.plot();
8912                     complete.onAfterCompute(that.clickedNode);
8913                     complete.onComplete();
8914                 }
8915             });     
8916         },
8917     
8918       /*
8919          Method: onClick
8920     
8921         Animates the <ST> to center the node specified by *id*.
8922             
8923         Parameters:
8924         
8925         id - (string) A node id.
8926         options - (optional|object) A group of options and callbacks described below.
8927         onComplete - (object) An object callback called when the animation finishes.
8928         Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8929
8930         Example:
8931
8932         (start code js)
8933           st.onClick('mynodeid', {
8934                   Move: {
8935                         enable: true,
8936                     offsetX: 30,
8937                     offsetY: 5
8938                   },
8939                   onComplete: function() {
8940                       alert('yay!');
8941                   }
8942           });
8943         (end code)
8944     
8945         */    
8946       onClick: function (id, options) {
8947         var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8948         var innerController = {
8949             Move: {
8950                     enable: true,
8951               offsetX: config.offsetX || 0,
8952               offsetY: config.offsetY || 0  
8953             },
8954             setRightLevelToShowConfig: false,
8955             onBeforeRequest: $.empty,
8956             onBeforeContract: $.empty,
8957             onBeforeMove: $.empty,
8958             onBeforeExpand: $.empty
8959         };
8960         var complete = $.merge(this.controller, innerController, options);
8961         
8962         if(!this.busy) {
8963             this.busy = true;
8964             var node = this.graph.getNode(id);
8965             this.selectPath(node, this.clickedNode);
8966                 this.clickedNode = node;
8967             complete.onBeforeCompute(node);
8968             complete.onBeforeRequest(node);
8969             this.requestNodes(node, {
8970                 onComplete: function() {
8971                     complete.onBeforeContract(node);
8972                     that.contract({
8973                         onComplete: function() {
8974                             Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8975                             complete.onBeforeMove(node);
8976                             that.move(node, {
8977                                 Move: complete.Move,
8978                                 onComplete: function() {
8979                                     complete.onBeforeExpand(node);
8980                                     that.expand(node, {
8981                                         onComplete: function() {
8982                                             that.busy = false;
8983                                             complete.onAfterCompute(id);
8984                                             complete.onComplete();
8985                                         }
8986                                     }); // expand
8987                                 }
8988                             }); // move
8989                         }
8990                     });// contract
8991                 }
8992             });// request
8993         }
8994       }
8995     });
8996
8997 })();
8998
8999 $jit.ST.$extend = true;
9000
9001 /*
9002    Class: ST.Op
9003     
9004    Custom extension of <Graph.Op>.
9005
9006    Extends:
9007
9008    All <Graph.Op> methods
9009    
9010    See also:
9011    
9012    <Graph.Op>
9013
9014 */
9015 $jit.ST.Op = new Class({
9016
9017   Implements: Graph.Op
9018     
9019 });
9020
9021 /*
9022     
9023      Performs operations on group of nodes.
9024
9025 */
9026 $jit.ST.Group = new Class({
9027     
9028     initialize: function(viz) {
9029         this.viz = viz;
9030         this.canvas = viz.canvas;
9031         this.config = viz.config;
9032         this.animation = new Animation;
9033         this.nodes = null;
9034     },
9035     
9036     /*
9037     
9038        Calls the request method on the controller to request a subtree for each node. 
9039     */
9040     requestNodes: function(nodes, controller) {
9041         var counter = 0, len = nodes.length, nodeSelected = {};
9042         var complete = function() { controller.onComplete(); };
9043         var viz = this.viz;
9044         if(len == 0) complete();
9045         for(var i=0; i<len; i++) {
9046             nodeSelected[nodes[i].id] = nodes[i];
9047             controller.request(nodes[i].id, nodes[i]._level, {
9048                 onComplete: function(nodeId, data) {
9049                     if(data && data.children) {
9050                         data.id = nodeId;
9051                         viz.op.sum(data, { type: 'nothing' });
9052                     }
9053                     if(++counter == len) {
9054                         viz.graph.computeLevels(viz.root, 0);
9055                         complete();
9056                     }
9057                 }
9058             });
9059         }
9060     },
9061     
9062     /*
9063     
9064        Collapses group of nodes. 
9065     */
9066     contract: function(nodes, controller) {
9067         var viz = this.viz;
9068         var that = this;
9069
9070         nodes = this.prepare(nodes);
9071         this.animation.setOptions($.merge(controller, {
9072             $animating: false,
9073             compute: function(delta) {
9074               if(delta == 1) delta = 0.99;
9075               that.plotStep(1 - delta, controller, this.$animating);
9076               this.$animating = 'contract';
9077             },
9078             
9079             complete: function() {
9080                 that.hide(nodes, controller);
9081             }       
9082         })).start();
9083     },
9084     
9085     hide: function(nodes, controller) {
9086         var viz = this.viz;
9087         for(var i=0; i<nodes.length; i++) {
9088             // TODO nodes are requested on demand, but not
9089             // deleted when hidden. Would that be a good feature?
9090             // Currently that feature is buggy, so I'll turn it off
9091             // Actually this feature is buggy because trimming should take
9092             // place onAfterCompute and not right after collapsing nodes.
9093             if (true || !controller || !controller.request) {
9094                 nodes[i].eachLevel(1, false, function(elem){
9095                     if (elem.exist) {
9096                         $.extend(elem, {
9097                             'drawn': false,
9098                             'exist': false
9099                         });
9100                     }
9101                 });
9102             } else {
9103                 var ids = [];
9104                 nodes[i].eachLevel(1, false, function(n) {
9105                     ids.push(n.id);
9106                 });
9107                 viz.op.removeNode(ids, { 'type': 'nothing' });
9108                 viz.labels.clearLabels();
9109             }
9110         }
9111         controller.onComplete();
9112     },    
9113     
9114
9115     /*
9116        Expands group of nodes. 
9117     */
9118     expand: function(nodes, controller) {
9119         var that = this;
9120         this.show(nodes);
9121         this.animation.setOptions($.merge(controller, {
9122             $animating: false,
9123             compute: function(delta) {
9124                 that.plotStep(delta, controller, this.$animating);
9125                 this.$animating = 'expand';
9126             },
9127             
9128             complete: function() {
9129                 that.plotStep(undefined, controller, false);
9130                 controller.onComplete();
9131             }       
9132         })).start();
9133         
9134     },
9135     
9136     show: function(nodes) {
9137         var config = this.config;
9138         this.prepare(nodes);
9139         $.each(nodes, function(n) {
9140                 // check for root nodes if multitree
9141                 if(config.multitree && !('$orn' in n.data)) {
9142                         delete n.data.$orns;
9143                         var orns = ' ';
9144                         n.eachSubnode(function(ch) {
9145                                 if(('$orn' in ch.data) 
9146                                                 && orns.indexOf(ch.data.$orn) < 0 
9147                                                 && ch.exist && !ch.drawn) {
9148                                         orns += ch.data.$orn + ' ';
9149                                 }
9150                         });
9151                         n.data.$orns = orns;
9152                 }
9153             n.eachLevel(0, config.levelsToShow, function(n) {
9154                 if(n.exist) n.drawn = true;
9155             });     
9156         });
9157     },
9158     
9159     prepare: function(nodes) {
9160         this.nodes = this.getNodesWithChildren(nodes);
9161         return this.nodes;
9162     },
9163     
9164     /*
9165        Filters an array of nodes leaving only nodes with children.
9166     */
9167     getNodesWithChildren: function(nodes) {
9168         var ans = [], config = this.config, root = this.viz.root;
9169         nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9170         for(var i=0; i<nodes.length; i++) {
9171             if(nodes[i].anySubnode("exist")) {
9172                 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9173                     if(!config.multitree || '$orn' in nodes[j].data) {
9174                                 desc = desc || nodes[i].isDescendantOf(nodes[j].id);                            
9175                     }
9176                 }
9177                 if(!desc) ans.push(nodes[i]);
9178             }
9179         }
9180         return ans;
9181     },
9182     
9183     plotStep: function(delta, controller, animating) {
9184         var viz = this.viz,
9185         config = this.config,
9186         canvas = viz.canvas, 
9187         ctx = canvas.getCtx(),
9188         nodes = this.nodes;
9189         var i, node;
9190         // hide nodes that are meant to be collapsed/expanded
9191         var nds = {};
9192         for(i=0; i<nodes.length; i++) {
9193           node = nodes[i];
9194           nds[node.id] = [];
9195           var root = config.multitree && !('$orn' in node.data);
9196           var orns = root && node.data.$orns;
9197           node.eachSubgraph(function(n) { 
9198             // TODO(nico): Cleanup
9199                   // special check for root node subnodes when
9200                   // multitree is checked.
9201                   if(root && orns && orns.indexOf(n.data.$orn) > 0 
9202                                   && n.drawn) {
9203                           n.drawn = false;
9204                   nds[node.id].push(n);
9205               } else if((!root || !orns) && n.drawn) {
9206                 n.drawn = false;
9207                 nds[node.id].push(n);
9208               }
9209             }); 
9210             node.drawn = true;
9211         }
9212         // plot the whole (non-scaled) tree
9213         if(nodes.length > 0) viz.fx.plot();
9214         // show nodes that were previously hidden
9215         for(i in nds) {
9216           $.each(nds[i], function(n) { n.drawn = true; });
9217         }
9218         // plot each scaled subtree
9219         for(i=0; i<nodes.length; i++) {
9220           node = nodes[i];
9221           ctx.save();
9222           viz.fx.plotSubtree(node, controller, delta, animating);                
9223           ctx.restore();
9224         }
9225       },
9226
9227       getSiblings: function(nodes) {
9228         var siblings = {};
9229         $.each(nodes, function(n) {
9230             var par = n.getParents();
9231             if (par.length == 0) {
9232                 siblings[n.id] = [n];
9233             } else {
9234                 var ans = [];
9235                 par[0].eachSubnode(function(sn) {
9236                     ans.push(sn);
9237                 });
9238                 siblings[n.id] = ans;
9239             }
9240         });
9241         return siblings;
9242     }
9243 });
9244
9245 /*
9246    ST.Geom
9247
9248    Performs low level geometrical computations.
9249
9250    Access:
9251
9252    This instance can be accessed with the _geom_ parameter of the st instance created.
9253
9254    Example:
9255
9256    (start code js)
9257     var st = new ST(canvas, config);
9258     st.geom.translate //or can also call any other <ST.Geom> method
9259    (end code)
9260
9261 */
9262
9263 $jit.ST.Geom = new Class({
9264     Implements: Graph.Geom,
9265     /*
9266        Changes the tree current orientation to the one specified.
9267
9268        You should usually use <ST.switchPosition> instead.
9269     */  
9270     switchOrientation: function(orn) {
9271         this.config.orientation = orn;
9272     },
9273
9274     /*
9275        Makes a value dispatch according to the current layout
9276        Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9277      */
9278     dispatch: function() {
9279           // TODO(nico) should store Array.prototype.slice.call somewhere.
9280         var args = Array.prototype.slice.call(arguments);
9281         var s = args.shift(), len = args.length;
9282         var val = function(a) { return typeof a == 'function'? a() : a; };
9283         if(len == 2) {
9284             return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9285         } else if(len == 4) {
9286             switch(s) {
9287                 case "top": return val(args[0]);
9288                 case "right": return val(args[1]);
9289                 case "bottom": return val(args[2]);
9290                 case "left": return val(args[3]);
9291             }
9292         }
9293         return undefined;
9294     },
9295
9296     /*
9297        Returns label height or with, depending on the tree current orientation.
9298     */  
9299     getSize: function(n, invert) {
9300         var data = n.data, config = this.config;
9301         var siblingOffset = config.siblingOffset;
9302         var s = (config.multitree 
9303                         && ('$orn' in data) 
9304                         && data.$orn) || config.orientation;
9305         var w = n.getData('width') + siblingOffset;
9306         var h = n.getData('height') + siblingOffset;
9307         if(!invert)
9308             return this.dispatch(s, h, w);
9309         else
9310             return this.dispatch(s, w, h);
9311     },
9312     
9313     /*
9314        Calculates a subtree base size. This is an utility function used by _getBaseSize_
9315     */  
9316     getTreeBaseSize: function(node, level, leaf) {
9317         var size = this.getSize(node, true), baseHeight = 0, that = this;
9318         if(leaf(level, node)) return size;
9319         if(level === 0) return 0;
9320         node.eachSubnode(function(elem) {
9321             baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9322         });
9323         return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9324     },
9325
9326
9327     /*
9328        getEdge
9329        
9330        Returns a Complex instance with the begin or end position of the edge to be plotted.
9331
9332        Parameters:
9333
9334        node - A <Graph.Node> that is connected to this edge.
9335        type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9336
9337        Returns:
9338
9339        A <Complex> number specifying the begin or end position.
9340     */  
9341     getEdge: function(node, type, s) {
9342         var $C = function(a, b) { 
9343           return function(){
9344             return node.pos.add(new Complex(a, b));
9345           }; 
9346         };
9347         var dim = this.node;
9348         var w = node.getData('width');
9349         var h = node.getData('height');
9350
9351         if(type == 'begin') {
9352             if(dim.align == "center") {
9353                 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9354                                      $C(0, -h/2),$C(w/2, 0));
9355             } else if(dim.align == "left") {
9356                 return this.dispatch(s, $C(0, h), $C(0, 0),
9357                                      $C(0, 0), $C(w, 0));
9358             } else if(dim.align == "right") {
9359                 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9360                                      $C(0, -h),$C(0, 0));
9361             } else throw "align: not implemented";
9362             
9363             
9364         } else if(type == 'end') {
9365             if(dim.align == "center") {
9366                 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9367                                      $C(0, h/2),  $C(-w/2, 0));
9368             } else if(dim.align == "left") {
9369                 return this.dispatch(s, $C(0, 0), $C(w, 0),
9370                                      $C(0, h), $C(0, 0));
9371             } else if(dim.align == "right") {
9372                 return this.dispatch(s, $C(0, -h),$C(0, 0),
9373                                      $C(0, 0), $C(-w, 0));
9374             } else throw "align: not implemented";
9375         }
9376     },
9377
9378     /*
9379        Adjusts the tree position due to canvas scaling or translation.
9380     */  
9381     getScaledTreePosition: function(node, scale) {
9382         var dim = this.node;
9383         var w = node.getData('width');
9384         var h = node.getData('height');
9385         var s = (this.config.multitree 
9386                         && ('$orn' in node.data) 
9387                         && node.data.$orn) || this.config.orientation;
9388
9389         var $C = function(a, b) { 
9390           return function(){
9391             return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9392           }; 
9393         };
9394         if(dim.align == "left") {
9395             return this.dispatch(s, $C(0, h), $C(0, 0),
9396                                  $C(0, 0), $C(w, 0));
9397         } else if(dim.align == "center") {
9398             return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9399                                  $C(0, -h / 2),$C(w / 2, 0));
9400         } else if(dim.align == "right") {
9401             return this.dispatch(s, $C(0, 0), $C(-w, 0),
9402                                  $C(0, -h),$C(0, 0));
9403         } else throw "align: not implemented";
9404     },
9405
9406     /*
9407        treeFitsInCanvas
9408        
9409        Returns a Boolean if the current subtree fits in canvas.
9410
9411        Parameters:
9412
9413        node - A <Graph.Node> which is the current root of the subtree.
9414        canvas - The <Canvas> object.
9415        level - The depth of the subtree to be considered.
9416     */  
9417     treeFitsInCanvas: function(node, canvas, level) {
9418         var csize = canvas.getSize();
9419         var s = (this.config.multitree 
9420                         && ('$orn' in node.data) 
9421                         && node.data.$orn) || this.config.orientation;
9422
9423         var size = this.dispatch(s, csize.width, csize.height);
9424         var baseSize = this.getTreeBaseSize(node, level, function(level, node) { 
9425           return level === 0 || !node.anySubnode();
9426         });
9427         return (baseSize < size);
9428     }
9429 });
9430
9431 /*
9432   Class: ST.Plot
9433   
9434   Custom extension of <Graph.Plot>.
9435
9436   Extends:
9437
9438   All <Graph.Plot> methods
9439   
9440   See also:
9441   
9442   <Graph.Plot>
9443
9444 */
9445 $jit.ST.Plot = new Class({
9446     
9447     Implements: Graph.Plot,
9448     
9449     /*
9450        Plots a subtree from the spacetree.
9451     */
9452     plotSubtree: function(node, opt, scale, animating) {
9453         var viz = this.viz, canvas = viz.canvas, config = viz.config;
9454         scale = Math.min(Math.max(0.001, scale), 1);
9455         if(scale >= 0) {
9456             node.drawn = false;     
9457             var ctx = canvas.getCtx();
9458             var diff = viz.geom.getScaledTreePosition(node, scale);
9459             ctx.translate(diff.x, diff.y);
9460             ctx.scale(scale, scale);
9461         }
9462         this.plotTree(node, $.merge(opt, {
9463           'withLabels': true,
9464           'hideLabels': !!scale,
9465           'plotSubtree': function(n, ch) {
9466             var root = config.multitree && !('$orn' in node.data);
9467             var orns = root && node.getData('orns');
9468             return !root || orns.indexOf(elem.getData('orn')) > -1;
9469           }
9470         }), animating);
9471         if(scale >= 0) node.drawn = true;
9472     },   
9473    
9474     /*
9475         Method: getAlignedPos
9476         
9477         Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9478         
9479         Parameters:
9480         
9481         pos - (object) A <Graph.Node> position.
9482         width - (number) The width of the node.
9483         height - (number) The height of the node.
9484         
9485      */
9486     getAlignedPos: function(pos, width, height) {
9487         var nconfig = this.node;
9488         var square, orn;
9489         if(nconfig.align == "center") {
9490             square = {
9491                 x: pos.x - width / 2,
9492                 y: pos.y - height / 2
9493             };
9494         } else if (nconfig.align == "left") {
9495             orn = this.config.orientation;
9496             if(orn == "bottom" || orn == "top") {
9497                 square = {
9498                     x: pos.x - width / 2,
9499                     y: pos.y
9500                 };
9501             } else {
9502                 square = {
9503                     x: pos.x,
9504                     y: pos.y - height / 2
9505                 };
9506             }
9507         } else if(nconfig.align == "right") {
9508             orn = this.config.orientation;
9509             if(orn == "bottom" || orn == "top") {
9510                 square = {
9511                     x: pos.x - width / 2,
9512                     y: pos.y - height
9513                 };
9514             } else {
9515                 square = {
9516                     x: pos.x - width,
9517                     y: pos.y - height / 2
9518                 };
9519             }
9520         } else throw "align: not implemented";
9521         
9522         return square;
9523     },
9524     
9525     getOrientation: function(adj) {
9526         var config = this.config;
9527         var orn = config.orientation;
9528
9529         if(config.multitree) {
9530                 var nodeFrom = adj.nodeFrom;
9531                 var nodeTo = adj.nodeTo;
9532                 orn = (('$orn' in nodeFrom.data) 
9533                         && nodeFrom.data.$orn) 
9534                         || (('$orn' in nodeTo.data) 
9535                         && nodeTo.data.$orn);
9536         }
9537
9538         return orn; 
9539     }
9540 });
9541
9542 /*
9543   Class: ST.Label
9544
9545   Custom extension of <Graph.Label>. 
9546   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9547
9548   Extends:
9549
9550   All <Graph.Label> methods and subclasses.
9551
9552   See also:
9553
9554   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9555  */ 
9556 $jit.ST.Label = {};
9557
9558 /*
9559    ST.Label.Native
9560
9561    Custom extension of <Graph.Label.Native>.
9562
9563    Extends:
9564
9565    All <Graph.Label.Native> methods
9566
9567    See also:
9568
9569    <Graph.Label.Native>
9570 */
9571 $jit.ST.Label.Native = new Class({
9572   Implements: Graph.Label.Native,
9573
9574   renderLabel: function(canvas, node, controller) {
9575     var ctx = canvas.getCtx();
9576     var coord = node.pos.getc(true);
9577     ctx.fillText(node.name, coord.x, coord.y);
9578   }
9579 });
9580
9581 $jit.ST.Label.DOM = new Class({
9582   Implements: Graph.Label.DOM,
9583
9584   /* 
9585       placeLabel
9586
9587       Overrides abstract method placeLabel in <Graph.Plot>.
9588
9589       Parameters:
9590
9591       tag - A DOM label element.
9592       node - A <Graph.Node>.
9593       controller - A configuration/controller object passed to the visualization.
9594      
9595     */
9596     placeLabel: function(tag, node, controller) {
9597         var pos = node.pos.getc(true), 
9598             config = this.viz.config, 
9599             dim = config.Node, 
9600             canvas = this.viz.canvas,
9601             w = node.getData('width'),
9602             h = node.getData('height'),
9603             radius = canvas.getSize(),
9604             labelPos, orn;
9605         
9606         var ox = canvas.translateOffsetX,
9607             oy = canvas.translateOffsetY,
9608             sx = canvas.scaleOffsetX,
9609             sy = canvas.scaleOffsetY,
9610             posx = pos.x * sx + ox,
9611             posy = pos.y * sy + oy;
9612
9613         if(dim.align == "center") {
9614             labelPos= {
9615                 x: Math.round(posx - w / 2 + radius.width/2),
9616                 y: Math.round(posy - h / 2 + radius.height/2)
9617             };
9618         } else if (dim.align == "left") {
9619             orn = config.orientation;
9620             if(orn == "bottom" || orn == "top") {
9621                 labelPos= {
9622                     x: Math.round(posx - w / 2 + radius.width/2),
9623                     y: Math.round(posy + radius.height/2)
9624                 };
9625             } else {
9626                 labelPos= {
9627                     x: Math.round(posx + radius.width/2),
9628                     y: Math.round(posy - h / 2 + radius.height/2)
9629                 };
9630             }
9631         } else if(dim.align == "right") {
9632             orn = config.orientation;
9633             if(orn == "bottom" || orn == "top") {
9634                 labelPos= {
9635                     x: Math.round(posx - w / 2 + radius.width/2),
9636                     y: Math.round(posy - h + radius.height/2)
9637                 };
9638             } else {
9639                 labelPos= {
9640                     x: Math.round(posx - w + radius.width/2),
9641                     y: Math.round(posy - h / 2 + radius.height/2)
9642                 };
9643             }
9644         } else throw "align: not implemented";
9645
9646         var style = tag.style;
9647         style.left = labelPos.x + 'px';
9648         style.top  = labelPos.y + 'px';
9649         style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9650         controller.onPlaceLabel(tag, node);
9651     }
9652 });
9653
9654 /*
9655   ST.Label.SVG
9656
9657   Custom extension of <Graph.Label.SVG>.
9658
9659   Extends:
9660
9661   All <Graph.Label.SVG> methods
9662
9663   See also:
9664
9665   <Graph.Label.SVG>
9666 */
9667 $jit.ST.Label.SVG = new Class({
9668   Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9669
9670   initialize: function(viz) {
9671     this.viz = viz;
9672   }
9673 });
9674
9675 /*
9676    ST.Label.HTML
9677
9678    Custom extension of <Graph.Label.HTML>.
9679
9680    Extends:
9681
9682    All <Graph.Label.HTML> methods.
9683
9684    See also:
9685
9686    <Graph.Label.HTML>
9687
9688 */
9689 $jit.ST.Label.HTML = new Class({
9690   Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9691
9692   initialize: function(viz) {
9693     this.viz = viz;
9694   }
9695 });
9696
9697
9698 /*
9699   Class: ST.Plot.NodeTypes
9700
9701   This class contains a list of <Graph.Node> built-in types. 
9702   Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9703
9704   You can add your custom node types, customizing your visualization to the extreme.
9705
9706   Example:
9707
9708   (start code js)
9709     ST.Plot.NodeTypes.implement({
9710       'mySpecialType': {
9711         'render': function(node, canvas) {
9712           //print your custom node to canvas
9713         },
9714         //optional
9715         'contains': function(node, pos) {
9716           //return true if pos is inside the node or false otherwise
9717         }
9718       }
9719     });
9720   (end code)
9721
9722 */
9723 $jit.ST.Plot.NodeTypes = new Class({
9724   'none': {
9725     'render': $.empty,
9726     'contains': $.lambda(false)
9727   },
9728   'circle': {
9729     'render': function(node, canvas) {
9730       var dim  = node.getData('dim'),
9731           pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9732           dim2 = dim/2;
9733       this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9734     },
9735     'contains': function(node, pos) {
9736       var dim  = node.getData('dim'),
9737           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9738           dim2 = dim/2;
9739       this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9740     }
9741   },
9742   'square': {
9743     'render': function(node, canvas) {
9744       var dim  = node.getData('dim'),
9745           dim2 = dim/2,
9746           pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9747       this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9748     },
9749     'contains': function(node, pos) {
9750       var dim  = node.getData('dim'),
9751           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9752           dim2 = dim/2;
9753       this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9754     }
9755   },
9756   'ellipse': {
9757     'render': function(node, canvas) {
9758       var width = node.getData('width'),
9759           height = node.getData('height'),
9760           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9761       this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9762     },
9763     'contains': function(node, pos) {
9764       var width = node.getData('width'),
9765           height = node.getData('height'),
9766           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9767       this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9768     }
9769   },
9770   'rectangle': {
9771     'render': function(node, canvas) {
9772       var width = node.getData('width'),
9773           height = node.getData('height'),
9774           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9775       this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9776     },
9777     'contains': function(node, pos) {
9778       var width = node.getData('width'),
9779           height = node.getData('height'),
9780           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9781       this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9782     }
9783   }
9784 });
9785
9786 /*
9787   Class: ST.Plot.EdgeTypes
9788
9789   This class contains a list of <Graph.Adjacence> built-in types. 
9790   Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9791
9792   You can add your custom edge types, customizing your visualization to the extreme.
9793
9794   Example:
9795
9796   (start code js)
9797     ST.Plot.EdgeTypes.implement({
9798       'mySpecialType': {
9799         'render': function(adj, canvas) {
9800           //print your custom edge to canvas
9801         },
9802         //optional
9803         'contains': function(adj, pos) {
9804           //return true if pos is inside the arc or false otherwise
9805         }
9806       }
9807     });
9808   (end code)
9809
9810 */
9811 $jit.ST.Plot.EdgeTypes = new Class({
9812     'none': $.empty,
9813     'line': {
9814       'render': function(adj, canvas) {
9815         var orn = this.getOrientation(adj),
9816             nodeFrom = adj.nodeFrom, 
9817             nodeTo = adj.nodeTo,
9818             rel = nodeFrom._depth < nodeTo._depth,
9819             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9820             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9821         this.edgeHelper.line.render(from, to, canvas);
9822       },
9823       'contains': function(adj, pos) {
9824         var orn = this.getOrientation(adj),
9825             nodeFrom = adj.nodeFrom, 
9826             nodeTo = adj.nodeTo,
9827             rel = nodeFrom._depth < nodeTo._depth,
9828             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9829             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9830         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9831       }
9832     },
9833      'arrow': {
9834        'render': function(adj, canvas) {
9835          var orn = this.getOrientation(adj),
9836              node = adj.nodeFrom, 
9837              child = adj.nodeTo,
9838              dim = adj.getData('dim'),
9839              from = this.viz.geom.getEdge(node, 'begin', orn),
9840              to = this.viz.geom.getEdge(child, 'end', orn),
9841              direction = adj.data.$direction,
9842              inv = (direction && direction.length>1 && direction[0] != node.id);
9843          this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9844        },
9845        'contains': function(adj, pos) {
9846          var orn = this.getOrientation(adj),
9847              nodeFrom = adj.nodeFrom, 
9848              nodeTo = adj.nodeTo,
9849              rel = nodeFrom._depth < nodeTo._depth,
9850              from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9851              to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9852          return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9853        }
9854      },
9855     'quadratic:begin': {
9856        'render': function(adj, canvas) {
9857           var orn = this.getOrientation(adj);
9858           var nodeFrom = adj.nodeFrom, 
9859               nodeTo = adj.nodeTo,
9860               rel = nodeFrom._depth < nodeTo._depth,
9861               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9862               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9863               dim = adj.getData('dim'),
9864               ctx = canvas.getCtx();
9865           ctx.beginPath();
9866           ctx.moveTo(begin.x, begin.y);
9867           switch(orn) {
9868             case "left":
9869               ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9870               break;
9871             case "right":
9872               ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9873               break;
9874             case "top":
9875               ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9876               break;
9877             case "bottom":
9878               ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9879               break;
9880           }
9881           ctx.stroke();
9882         }
9883      },
9884     'quadratic:end': {
9885        'render': function(adj, canvas) {
9886           var orn = this.getOrientation(adj);
9887           var nodeFrom = adj.nodeFrom, 
9888               nodeTo = adj.nodeTo,
9889               rel = nodeFrom._depth < nodeTo._depth,
9890               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9891               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9892               dim = adj.getData('dim'),
9893               ctx = canvas.getCtx();
9894           ctx.beginPath();
9895           ctx.moveTo(begin.x, begin.y);
9896           switch(orn) {
9897             case "left":
9898               ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9899               break;
9900             case "right":
9901               ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9902               break;
9903             case "top":
9904               ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9905               break;
9906             case "bottom":
9907               ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9908               break;
9909           }
9910           ctx.stroke();
9911        }
9912      },
9913     'bezier': {
9914        'render': function(adj, canvas) {
9915          var orn = this.getOrientation(adj),
9916              nodeFrom = adj.nodeFrom, 
9917              nodeTo = adj.nodeTo,
9918              rel = nodeFrom._depth < nodeTo._depth,
9919              begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9920              end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9921              dim = adj.getData('dim'),
9922              ctx = canvas.getCtx();
9923          ctx.beginPath();
9924          ctx.moveTo(begin.x, begin.y);
9925          switch(orn) {
9926            case "left":
9927              ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9928              break;
9929            case "right":
9930              ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9931              break;
9932            case "top":
9933              ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9934              break;
9935            case "bottom":
9936              ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9937              break;
9938          }
9939          ctx.stroke();
9940        }
9941     }
9942 });
9943
9944
9945 Options.LineChart = {
9946   $extend: true,
9947
9948   animate: false,
9949   labelOffset: 3, // label offset
9950   type: 'basic', // gradient
9951   dataPointSize: 10,
9952   Tips: {
9953     enable: false,
9954     onShow: $.empty,
9955     onHide: $.empty
9956   },
9957   Ticks: {
9958         enable: false,
9959         segments: 4,
9960         color: '#000000'
9961   },
9962   Events: {
9963     enable: false,
9964     onClick: $.empty
9965   },
9966   selectOnHover: true,
9967   showAggregates: true,
9968   showLabels: true,
9969   filterOnClick: false,
9970   restoreOnRightClick: false
9971 };
9972
9973
9974 /*
9975  * File: LineChart.js
9976  *
9977 */
9978
9979 $jit.ST.Plot.NodeTypes.implement({
9980   'linechart-basic' : {
9981     'render' : function(node, canvas) {
9982       var pos = node.pos.getc(true), 
9983           width = node.getData('width'),
9984           height = node.getData('height'),
9985           algnPos = this.getAlignedPos(pos, width, height),
9986           x = algnPos.x + width/2 , y = algnPos.y,
9987           stringArray = node.getData('stringArray'),
9988           lastNode = node.getData('lastNode'),
9989           dimArray = node.getData('dimArray'),
9990           valArray = node.getData('valueArray'),
9991           colorArray = node.getData('colorArray'),
9992           colorLength = colorArray.length,
9993           config = node.getData('config'),
9994           gradient = node.getData('gradient'),
9995           showLabels = config.showLabels,
9996           aggregates = config.showAggregates,
9997           label = config.Label,
9998           prev = node.getData('prev'),
9999           dataPointSize = config.dataPointSize;
10000
10001       var ctx = canvas.getCtx(), border = node.getData('border');
10002       if (colorArray && dimArray && stringArray) {
10003         
10004                for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10005                 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10006                         ctx.lineWidth = 4;
10007                         ctx.lineCap = "round";
10008                   if(!lastNode) {
10009
10010                           ctx.save();
10011                                   //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
10012                           ctx.beginPath();
10013                           ctx.moveTo(x, y  - dimArray[i][0]); 
10014                           ctx.lineTo(x + width, y - dimArray[i][1]);
10015                           ctx.stroke();
10016                           ctx.restore();
10017                   }
10018                   //render data point
10019                   ctx.fillRect(x - (dataPointSize/2), y  - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10020                 }
10021         
10022
10023           if(label.type == 'Native' && showLabels) {
10024           //bottom labels
10025           ctx.fillStyle = ctx.strokeStyle = label.color;
10026           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10027           ctx.textAlign = 'center';
10028           ctx.textBaseline = 'middle';
10029           ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10030           }
10031           
10032
10033       }
10034     },
10035     'contains': function(node, mpos) {
10036       var pos = node.pos.getc(true), 
10037           width = node.getData('width'),
10038           height = node.getData('height'),
10039           config = node.getData('config'),
10040           dataPointSize = config.dataPointSize,
10041           dataPointMidPoint = dataPointSize/2,
10042           algnPos = this.getAlignedPos(pos, width, height),
10043           x = algnPos.x + width/2, y = algnPos.y,
10044           dimArray = node.getData('dimArray');
10045       //bounding box check
10046       if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10047         return false;
10048       }
10049       //deep check
10050       for(var i=0, l=dimArray.length; i<l; i++) {
10051         var dimi = dimArray[i];
10052                 var url = Url.decode(node.getData('linkArray')[i]);
10053           if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10054                 var valArrayCur = node.getData('valArrayCur');
10055           var results = array_match(valArrayCur[i],valArrayCur);
10056           var matches = results[0];
10057           var indexValues = results[1];
10058           if(matches > 1) {
10059                         var names = new Array(),
10060                                 values = new Array(),
10061                                 percentages = new Array(),
10062                                 linksArr = new Array();
10063                                 for(var j=0, il=indexValues.length; j<il; j++) {
10064                                         names[j] = node.getData('stringArray')[indexValues[j]];
10065                                         values[j] = valArrayCur[indexValues[j]];
10066                                         percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10067                                         linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10068                                         
10069                                 }       
10070                         return {
10071                             'name': names,
10072                             'color': node.getData('colorArray')[i],
10073                             'value': values,
10074                             'percentage': percentages,
10075                             'link': false,
10076                             'collision': true
10077                         };
10078                 }
10079           else {
10080                   return {
10081                     'name': node.getData('stringArray')[i],
10082                     'color': node.getData('colorArray')[i],
10083                     'value': node.getData('valueArray')[i][0],
10084         //            'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10085                     'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10086                     'link': url,
10087                     'collision': false
10088                   };
10089           }
10090         }
10091       }
10092       return false;
10093     }
10094   }
10095 });
10096
10097 /*
10098   Class: Line
10099   
10100   A visualization that displays line charts.
10101   
10102   Constructor Options:
10103   
10104   See <Options.Line>.
10105
10106 */
10107 $jit.LineChart = new Class({
10108   st: null,
10109   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10110   selected: {},
10111   busy: false,
10112   
10113   initialize: function(opt) {
10114     this.controller = this.config = 
10115       $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10116         Label: { type: 'Native' }
10117       }, opt);
10118     //set functions for showLabels and showAggregates
10119     var showLabels = this.config.showLabels,
10120         typeLabels = $.type(showLabels),
10121         showAggregates = this.config.showAggregates,
10122         typeAggregates = $.type(showAggregates);
10123     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10124     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10125     Options.Fx.clearCanvas = false;
10126     this.initializeViz();
10127   },
10128   
10129   initializeViz: function() {
10130     var config = this.config,
10131         that = this,
10132         nodeType = config.type.split(":")[0],
10133         nodeLabels = {};
10134
10135     var st = new $jit.ST({
10136       injectInto: config.injectInto,
10137       orientation: "bottom",
10138       backgroundColor: config.backgroundColor,
10139       renderBackground: config.renderBackground,
10140       levelDistance: 0,
10141       siblingOffset: 0,
10142       subtreeOffset: 0,
10143       withLabels: config.Label.type != 'Native',
10144       useCanvas: config.useCanvas,
10145       Label: {
10146         type: config.Label.type
10147       },
10148       Node: {
10149         overridable: true,
10150         type: 'linechart-' + nodeType,
10151         align: 'left',
10152         width: 1,
10153         height: 1
10154       },
10155       Edge: {
10156         type: 'none'
10157       },
10158       Tips: {
10159         enable: config.Tips.enable,
10160         type: 'Native',
10161         force: true,
10162         onShow: function(tip, node, contains) {
10163           var elem = contains;
10164           config.Tips.onShow(tip, elem, node);
10165         }
10166       },
10167       Events: {
10168         enable: true,
10169         type: 'Native',
10170         onClick: function(node, eventInfo, evt) {
10171           if(!config.filterOnClick && !config.Events.enable) return;
10172           var elem = eventInfo.getContains();
10173           if(elem) config.filterOnClick && that.filter(elem.name);
10174           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10175         },
10176         onRightClick: function(node, eventInfo, evt) {
10177           if(!config.restoreOnRightClick) return;
10178           that.restore();
10179         },
10180         onMouseMove: function(node, eventInfo, evt) {
10181           if(!config.selectOnHover) return;
10182           if(node) {
10183             var elem = eventInfo.getContains();
10184             that.select(node.id, elem.name, elem.index);
10185           } else {
10186             that.select(false, false, false);
10187           }
10188         }
10189       },
10190       onCreateLabel: function(domElement, node) {
10191         var labelConf = config.Label,
10192             valueArray = node.getData('valueArray'),
10193             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10194             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10195         if(node.getData('prev')) {
10196           var nlbs = {
10197             wrapper: document.createElement('div'),
10198             aggregate: document.createElement('div'),
10199             label: document.createElement('div')
10200           };
10201           var wrapper = nlbs.wrapper,
10202               label = nlbs.label,
10203               aggregate = nlbs.aggregate,
10204               wrapperStyle = wrapper.style,
10205               labelStyle = label.style,
10206               aggregateStyle = aggregate.style;
10207           //store node labels
10208           nodeLabels[node.id] = nlbs;
10209           //append labels
10210           wrapper.appendChild(label);
10211           wrapper.appendChild(aggregate);
10212           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10213             label.style.display = 'none';
10214           }
10215           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10216             aggregate.style.display = 'none';
10217           }
10218           wrapperStyle.position = 'relative';
10219           wrapperStyle.overflow = 'visible';
10220           wrapperStyle.fontSize = labelConf.size + 'px';
10221           wrapperStyle.fontFamily = labelConf.family;
10222           wrapperStyle.color = labelConf.color;
10223           wrapperStyle.textAlign = 'center';
10224           aggregateStyle.position = labelStyle.position = 'absolute';
10225           
10226           domElement.style.width = node.getData('width') + 'px';
10227           domElement.style.height = node.getData('height') + 'px';
10228           label.innerHTML = node.name;
10229           
10230           domElement.appendChild(wrapper);
10231         }
10232       },
10233       onPlaceLabel: function(domElement, node) {
10234         if(!node.getData('prev')) return;
10235         var labels = nodeLabels[node.id],
10236             wrapperStyle = labels.wrapper.style,
10237             labelStyle = labels.label.style,
10238             aggregateStyle = labels.aggregate.style,
10239             width = node.getData('width'),
10240             height = node.getData('height'),
10241             dimArray = node.getData('dimArray'),
10242             valArray = node.getData('valueArray'),
10243             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10244             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10245             font = parseInt(wrapperStyle.fontSize, 10),
10246             domStyle = domElement.style;
10247         
10248         if(dimArray && valArray) {
10249           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10250             labelStyle.display = '';
10251           } else {
10252             labelStyle.display = 'none';
10253           }
10254           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10255             aggregateStyle.display = '';
10256           } else {
10257             aggregateStyle.display = 'none';
10258           }
10259           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10260           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10261           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10262             if(dimArray[i][0] > 0) {
10263               acum+= valArray[i][0];
10264               leftAcum+= dimArray[i][0];
10265             }
10266           }
10267           aggregateStyle.top = (-font - config.labelOffset) + 'px';
10268           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10269           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10270           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10271           labels.aggregate.innerHTML = acum;
10272         }
10273       }
10274     });
10275     
10276     var size = st.canvas.getSize(),
10277         margin = config.Margin;
10278     st.config.offsetY = -size.height/2 + margin.bottom 
10279       + (config.showLabels && (config.labelOffset + config.Label.size));
10280     st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10281     this.st = st;
10282     this.canvas = this.st.canvas;
10283   },
10284   
10285     renderTitle: function() {
10286         var canvas = this.canvas,
10287         size = canvas.getSize(),
10288         config = this.config,
10289         margin = config.Margin,
10290         label = config.Label,
10291         title = config.Title;
10292         ctx = canvas.getCtx();
10293         ctx.fillStyle = title.color;
10294         ctx.textAlign = 'left';
10295         ctx.textBaseline = 'top';
10296         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10297         if(label.type == 'Native') {
10298                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10299         }
10300   },  
10301   
10302     renderTicks: function() {
10303
10304         var canvas = this.canvas,
10305         size = canvas.getSize(),
10306         config = this.config,
10307         margin = config.Margin,
10308         ticks = config.Ticks,
10309         title = config.Title,
10310         subtitle = config.Subtitle,
10311         label = config.Label,
10312         maxValue = this.maxValue,
10313         maxTickValue = Math.ceil(maxValue*.1)*10;
10314         if(maxTickValue == maxValue) {
10315                 var length = maxTickValue.toString().length;
10316                 maxTickValue = maxTickValue + parseInt(pad(1,length));
10317         }
10318
10319
10320         labelValue = 0,
10321         labelIncrement = maxTickValue/ticks.segments,
10322         ctx = canvas.getCtx();
10323         ctx.strokeStyle = ticks.color;
10324     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10325         ctx.textAlign = 'center';
10326         ctx.textBaseline = 'middle';
10327         
10328         idLabel = canvas.id + "-label";
10329         labelDim = 100;
10330         container = document.getElementById(idLabel);
10331                   
10332                   
10333                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10334                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10335                 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)),
10336                 segmentLength = grid/ticks.segments;
10337                 ctx.fillStyle = ticks.color;
10338                 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));
10339
10340                 while(axis>=grid) {
10341                         ctx.save();
10342                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10343                         ctx.rotate(Math.PI / 2);
10344                         ctx.fillStyle = label.color;
10345                         if(config.showLabels) {
10346                                 if(label.type == 'Native') { 
10347                                         ctx.fillText(labelValue, 0, 0);
10348                                 } else {
10349                                         //html labels on y axis
10350                                         labelDiv = document.createElement('div');
10351                                         labelDiv.innerHTML = labelValue;
10352                                         labelDiv.className = "rotatedLabel";
10353 //                                      labelDiv.class = "rotatedLabel";
10354                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10355                                         labelDiv.style.left = margin.left + "px";
10356                                         labelDiv.style.width = labelDim + "px";
10357                                         labelDiv.style.height = labelDim + "px";
10358                                         labelDiv.style.textAlign = "center";
10359                                         labelDiv.style.verticalAlign = "middle";
10360                                         labelDiv.style.position = "absolute";
10361                                         container.appendChild(labelDiv);
10362                                 }
10363                         }
10364                         ctx.restore();
10365                         ctx.fillStyle = ticks.color;
10366                         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 );
10367                         htmlOrigin += segmentLength;
10368                         axis += segmentLength;
10369                         labelValue += labelIncrement;
10370                 }
10371         
10372
10373         
10374         
10375         
10376
10377   },
10378   
10379   renderBackground: function() {
10380                 var canvas = this.canvas,
10381                 config = this.config,
10382                 backgroundColor = config.backgroundColor,
10383                 size = canvas.getSize(),
10384                 ctx = canvas.getCtx();
10385                 //ctx.globalCompositeOperation = "destination-over";
10386             ctx.fillStyle = backgroundColor;
10387             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
10388   },
10389   clear: function() {
10390         var canvas = this.canvas;
10391         var ctx = canvas.getCtx(),
10392         size = canvas.getSize();
10393         ctx.fillStyle = "rgba(255,255,255,0)";
10394         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10395         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
10396  },
10397   resizeGraph: function(json,width) {
10398         var canvas = this.canvas,
10399         size = canvas.getSize(),
10400             orgHeight = size.height;
10401
10402         canvas.resize(width,orgHeight);
10403         if(typeof FlashCanvas == "undefined") {
10404                 canvas.clear();
10405         } else {
10406                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
10407         }
10408         this.loadJSON(json);
10409
10410         },
10411  /*
10412   Method: loadJSON
10413  
10414   Loads JSON data into the visualization. 
10415   
10416   Parameters:
10417   
10418   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>.
10419   
10420   Example:
10421   (start code js)
10422   var areaChart = new $jit.AreaChart(options);
10423   areaChart.loadJSON(json);
10424   (end code)
10425  */  
10426   loadJSON: function(json) {
10427     var prefix = $.time(), 
10428         ch = [], 
10429         st = this.st,
10430         name = $.splat(json.label), 
10431         color = $.splat(json.color || this.colors),
10432         config = this.config,
10433         ticks = config.Ticks,
10434         renderBackground = config.renderBackground,
10435         gradient = !!config.type.split(":")[1],
10436         animate = config.animate,
10437         title = config.Title,
10438         groupTotalValue = 0;
10439     
10440     var valArrayAll = new Array();
10441
10442     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10443         var val = values[i];
10444         var valArray = $.splat(val.values);
10445         for (var j=0, len=valArray.length; j<len; j++) {
10446                 valArrayAll.push(parseInt(valArray[j]));
10447         }
10448         groupTotalValue += parseInt(valArray.sum());
10449     }
10450     
10451     this.maxValue =  Math.max.apply(null, valArrayAll);
10452     
10453     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10454       var val = values[i], prev = values[i-1];
10455
10456       var next = (i+1 < l) ? values[i+1] : 0;
10457       var valLeft = $.splat(values[i].values);
10458       var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10459       var valArray = $.zip(valLeft, valRight);
10460       var valArrayCur = $.splat(values[i].values);
10461       var linkArray = $.splat(values[i].links);
10462       var acumLeft = 0, acumRight = 0;
10463       var lastNode = (l-1 == i) ? true : false; 
10464       ch.push({
10465         'id': prefix + val.label,
10466         'name': val.label,
10467         'data': {
10468           'value': valArray,
10469           '$valueArray': valArray,
10470           '$valArrayCur': valArrayCur,
10471           '$colorArray': color,
10472           '$linkArray': linkArray,
10473           '$stringArray': name,
10474           '$next': next? next.label:false,
10475           '$prev': prev? prev.label:false,
10476           '$config': config,
10477           '$lastNode': lastNode,
10478           '$groupTotalValue': groupTotalValue,
10479           '$gradient': gradient
10480         },
10481         'children': []
10482       });
10483     }
10484     var root = {
10485       'id': prefix + '$root',
10486       'name': '',
10487       'data': {
10488         '$type': 'none',
10489         '$width': 1,
10490         '$height': 1
10491       },
10492       'children': ch
10493     };
10494     st.loadJSON(root);
10495     
10496     this.normalizeDims();
10497     
10498     if(renderBackground) {
10499         this.renderBackground();        
10500     }
10501     
10502     if(!animate && ticks.enable) {
10503                 this.renderTicks();
10504         }
10505         
10506         
10507         if(title.text) {
10508                 this.renderTitle();     
10509         }
10510         
10511     st.compute();
10512     st.select(st.root);
10513     if(animate) {
10514       st.fx.animate({
10515         modes: ['node-property:height:dimArray'],
10516         duration:1500
10517       });
10518     }
10519   },
10520   
10521  /*
10522   Method: updateJSON
10523  
10524   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.
10525   
10526   Parameters:
10527   
10528   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10529   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10530   
10531   Example:
10532   
10533   (start code js)
10534   areaChart.updateJSON(json, {
10535     onComplete: function() {
10536       alert('update complete!');
10537     }
10538   });
10539   (end code)
10540  */  
10541   updateJSON: function(json, onComplete) {
10542     if(this.busy) return;
10543     this.busy = true;
10544     
10545     var st = this.st,
10546         graph = st.graph,
10547         labels = json.label && $.splat(json.label),
10548         values = json.values,
10549         animate = this.config.animate,
10550         that = this;
10551     $.each(values, function(v) {
10552       var n = graph.getByName(v.label);
10553       if(n) {
10554         v.values = $.splat(v.values);
10555         var stringArray = n.getData('stringArray'),
10556             valArray = n.getData('valueArray');
10557         $.each(valArray, function(a, i) {
10558           a[0] = v.values[i];
10559           if(labels) stringArray[i] = labels[i];
10560         });
10561         n.setData('valueArray', valArray);
10562         var prev = n.getData('prev'),
10563             next = n.getData('next'),
10564             nextNode = graph.getByName(next);
10565         if(prev) {
10566           var p = graph.getByName(prev);
10567           if(p) {
10568             var valArray = p.getData('valueArray');
10569             $.each(valArray, function(a, i) {
10570               a[1] = v.values[i];
10571             });
10572           }
10573         }
10574         if(!nextNode) {
10575           var valArray = n.getData('valueArray');
10576           $.each(valArray, function(a, i) {
10577             a[1] = v.values[i];
10578           });
10579         }
10580       }
10581     });
10582     this.normalizeDims();
10583     st.compute();
10584     
10585     st.select(st.root);
10586     if(animate) {
10587       st.fx.animate({
10588         modes: ['node-property:height:dimArray'],
10589         duration:1500,
10590         onComplete: function() {
10591           that.busy = false;
10592           onComplete && onComplete.onComplete();
10593         }
10594       });
10595     }
10596   },
10597   
10598 /*
10599   Method: filter
10600  
10601   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10602   
10603   Parameters:
10604   
10605   Variable strings arguments with the name of the stacks.
10606   
10607   Example:
10608   
10609   (start code js)
10610   areaChart.filter('label A', 'label C');
10611   (end code)
10612   
10613   See also:
10614   
10615   <AreaChart.restore>.
10616  */  
10617   filter: function() {
10618     if(this.busy) return;
10619     this.busy = true;
10620     if(this.config.Tips.enable) this.st.tips.hide();
10621     this.select(false, false, false);
10622     var args = Array.prototype.slice.call(arguments);
10623     var rt = this.st.graph.getNode(this.st.root);
10624     var that = this;
10625     rt.eachAdjacency(function(adj) {
10626       var n = adj.nodeTo, 
10627           dimArray = n.getData('dimArray'),
10628           stringArray = n.getData('stringArray');
10629       n.setData('dimArray', $.map(dimArray, function(d, i) {
10630         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10631       }), 'end');
10632     });
10633     this.st.fx.animate({
10634       modes: ['node-property:dimArray'],
10635       duration:1500,
10636       onComplete: function() {
10637         that.busy = false;
10638       }
10639     });
10640   },
10641   
10642   /*
10643   Method: restore
10644  
10645   Sets all stacks that could have been filtered visible.
10646   
10647   Example:
10648   
10649   (start code js)
10650   areaChart.restore();
10651   (end code)
10652   
10653   See also:
10654   
10655   <AreaChart.filter>.
10656  */  
10657   restore: function() {
10658     if(this.busy) return;
10659     this.busy = true;
10660     if(this.config.Tips.enable) this.st.tips.hide();
10661     this.select(false, false, false);
10662     this.normalizeDims();
10663     var that = this;
10664     this.st.fx.animate({
10665       modes: ['node-property:height:dimArray'],
10666       duration:1500,
10667       onComplete: function() {
10668         that.busy = false;
10669       }
10670     });
10671   },
10672   //adds the little brown bar when hovering the node
10673   select: function(id, name, index) {
10674     if(!this.config.selectOnHover) return;
10675     var s = this.selected;
10676     if(s.id != id || s.name != name 
10677         || s.index != index) {
10678       s.id = id;
10679       s.name = name;
10680       s.index = index;
10681       this.st.graph.eachNode(function(n) {
10682         n.setData('border', false);
10683       });
10684       if(id) {
10685         var n = this.st.graph.getNode(id);
10686         n.setData('border', s);
10687         var link = index === 0? 'prev':'next';
10688         link = n.getData(link);
10689         if(link) {
10690           n = this.st.graph.getByName(link);
10691           if(n) {
10692             n.setData('border', {
10693               name: name,
10694               index: 1-index
10695             });
10696           }
10697         }
10698       }
10699       this.st.plot();
10700     }
10701   },
10702   
10703   /*
10704     Method: getLegend
10705    
10706     Returns an object containing as keys the legend names and as values hex strings with color values.
10707     
10708     Example:
10709     
10710     (start code js)
10711     var legend = areaChart.getLegend();
10712     (end code)
10713  */  
10714   getLegend: function() {
10715     var legend = new Array();
10716     var name = new Array();
10717     var color = new Array();
10718     var n;
10719     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10720       n = adj.nodeTo;
10721     });
10722     var colors = n.getData('colorArray'),
10723         len = colors.length;
10724     $.each(n.getData('stringArray'), function(s, i) {
10725       color[i] = colors[i % len];
10726       name[i] = s;
10727     });
10728         legend['name'] = name;
10729         legend['color'] = color;
10730     return legend;
10731   },
10732   
10733   /*
10734     Method: getMaxValue
10735    
10736     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10737     
10738     Example:
10739     
10740     (start code js)
10741     var ans = areaChart.getMaxValue();
10742     (end code)
10743     
10744     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10745     
10746     Example:
10747     
10748     (start code js)
10749     //will return 100 for all AreaChart instances,
10750     //displaying all of them with the same scale
10751     $jit.AreaChart.implement({
10752       'getMaxValue': function() {
10753         return 100;
10754       }
10755     });
10756     (end code)
10757     
10758 */  
10759
10760   normalizeDims: function() {
10761     //number of elements
10762     var root = this.st.graph.getNode(this.st.root), l=0;
10763     root.eachAdjacency(function() {
10764       l++;
10765     });
10766     
10767
10768     var maxValue = this.maxValue || 1,
10769         size = this.st.canvas.getSize(),
10770         config = this.config,
10771         margin = config.Margin,
10772         labelOffset = config.labelOffset + config.Label.size,
10773         fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10774         animate = config.animate,
10775         ticks = config.Ticks,
10776         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
10777           - (config.showLabels && labelOffset);
10778           
10779           
10780         var maxTickValue = Math.ceil(maxValue*.1)*10;
10781                 if(maxTickValue == maxValue) {
10782                         var length = maxTickValue.toString().length;
10783                         maxTickValue = maxTickValue + parseInt(pad(1,length));
10784                 }
10785                 
10786                 
10787                 
10788     this.st.graph.eachNode(function(n) {
10789       var acumLeft = 0, acumRight = 0, animateValue = [];
10790       $.each(n.getData('valueArray'), function(v) {
10791         acumLeft += +v[0];
10792         acumRight += +v[1];
10793         animateValue.push([0, 0]);
10794       });
10795       var acum = acumRight>acumLeft? acumRight:acumLeft;
10796       
10797       n.setData('width', fixedDim);
10798       if(animate) {
10799         n.setData('height', acum * height / maxValue, 'end');
10800         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10801           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10802         }), 'end');
10803         var dimArray = n.getData('dimArray');
10804         if(!dimArray) {
10805           n.setData('dimArray', animateValue);
10806         }
10807       } else {
10808         
10809         if(ticks.enable) {
10810                 n.setData('height', acum * height / maxValue);
10811                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10812                   return [n[0] * height / maxTickValue, n[1] * height / maxTickValue]; 
10813                 }));
10814         } else {
10815                 n.setData('height', acum * height / maxValue);
10816                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10817                   return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10818                 }));
10819         }
10820         
10821         
10822       }
10823     });
10824   }
10825 });
10826
10827
10828
10829
10830
10831 /*
10832  * File: AreaChart.js
10833  *
10834 */
10835
10836 $jit.ST.Plot.NodeTypes.implement({
10837   'areachart-stacked' : {
10838     'render' : function(node, canvas) {
10839       var pos = node.pos.getc(true), 
10840           width = node.getData('width'),
10841           height = node.getData('height'),
10842           algnPos = this.getAlignedPos(pos, width, height),
10843           x = algnPos.x, y = algnPos.y,
10844           stringArray = node.getData('stringArray'),
10845           dimArray = node.getData('dimArray'),
10846           valArray = node.getData('valueArray'),
10847           valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10848           valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10849           colorArray = node.getData('colorArray'),
10850           colorLength = colorArray.length,
10851           config = node.getData('config'),
10852           gradient = node.getData('gradient'),
10853           showLabels = config.showLabels,
10854           aggregates = config.showAggregates,
10855           label = config.Label,
10856           prev = node.getData('prev');
10857
10858       var ctx = canvas.getCtx(), border = node.getData('border');
10859       if (colorArray && dimArray && stringArray) {
10860         for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10861           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10862           ctx.save();
10863           if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10864             var h1 = acumLeft + dimArray[i][0],
10865                 h2 = acumRight + dimArray[i][1],
10866                 alpha = Math.atan((h2 - h1) / width),
10867                 delta = 55;
10868             var linear = ctx.createLinearGradient(x + width/2, 
10869                 y - (h1 + h2)/2,
10870                 x + width/2 + delta * Math.sin(alpha),
10871                 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10872             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10873                 function(v) { return (v * 0.85) >> 0; }));
10874             linear.addColorStop(0, colorArray[i % colorLength]);
10875             linear.addColorStop(1, color);
10876             ctx.fillStyle = linear;
10877           }
10878           ctx.beginPath();
10879           ctx.moveTo(x, y - acumLeft);
10880           ctx.lineTo(x + width, y - acumRight);
10881           ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10882           ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10883           ctx.lineTo(x, y - acumLeft);
10884           ctx.fill();
10885           ctx.restore();
10886           if(border) {
10887             var strong = border.name == stringArray[i];
10888             var perc = strong? 0.7 : 0.8;
10889             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10890                 function(v) { return (v * perc) >> 0; }));
10891             ctx.strokeStyle = color;
10892             ctx.lineWidth = strong? 4 : 1;
10893             ctx.save();
10894             ctx.beginPath();
10895             if(border.index === 0) {
10896               ctx.moveTo(x, y - acumLeft);
10897               ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10898             } else {
10899               ctx.moveTo(x + width, y - acumRight);
10900               ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10901             }
10902             ctx.stroke();
10903             ctx.restore();
10904           }
10905           acumLeft += (dimArray[i][0] || 0);
10906           acumRight += (dimArray[i][1] || 0);
10907           
10908           if(dimArray[i][0] > 0)
10909             valAcum += (valArray[i][0] || 0);
10910         }
10911         if(prev && label.type == 'Native') {
10912           ctx.save();
10913           ctx.beginPath();
10914           ctx.fillStyle = ctx.strokeStyle = label.color;
10915           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10916           ctx.textAlign = 'center';
10917           ctx.textBaseline = 'middle';
10918           if(aggregates(node.name, valLeft, valRight, node)) {
10919             ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10920           }
10921           if(showLabels(node.name, valLeft, valRight, node)) {
10922             ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10923           }
10924           ctx.restore();
10925         }
10926       }
10927     },
10928     'contains': function(node, mpos) {
10929       var pos = node.pos.getc(true), 
10930           width = node.getData('width'),
10931           height = node.getData('height'),
10932           algnPos = this.getAlignedPos(pos, width, height),
10933           x = algnPos.x, y = algnPos.y,
10934           dimArray = node.getData('dimArray'),
10935           rx = mpos.x - x;
10936       //bounding box check
10937       if(mpos.x < x || mpos.x > x + width
10938         || mpos.y > y || mpos.y < y - height) {
10939         return false;
10940       }
10941       //deep check
10942       for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10943         var dimi = dimArray[i];
10944         lAcum -= dimi[0];
10945         rAcum -= dimi[1];
10946         var intersec = lAcum + (rAcum - lAcum) * rx / width;
10947         if(mpos.y >= intersec) {
10948           var index = +(rx > width/2);
10949           return {
10950             'name': node.getData('stringArray')[i],
10951             'color': node.getData('colorArray')[i],
10952             'value': node.getData('valueArray')[i][index],
10953             'index': index
10954           };
10955         }
10956       }
10957       return false;
10958     }
10959   }
10960 });
10961
10962 /*
10963   Class: AreaChart
10964   
10965   A visualization that displays stacked area charts.
10966   
10967   Constructor Options:
10968   
10969   See <Options.AreaChart>.
10970
10971 */
10972 $jit.AreaChart = new Class({
10973   st: null,
10974   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10975   selected: {},
10976   busy: false,
10977   
10978   initialize: function(opt) {
10979     this.controller = this.config = 
10980       $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10981         Label: { type: 'Native' }
10982       }, opt);
10983     //set functions for showLabels and showAggregates
10984     var showLabels = this.config.showLabels,
10985         typeLabels = $.type(showLabels),
10986         showAggregates = this.config.showAggregates,
10987         typeAggregates = $.type(showAggregates);
10988     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10989     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10990     
10991     this.initializeViz();
10992   },
10993   
10994   initializeViz: function() {
10995     var config = this.config,
10996         that = this,
10997         nodeType = config.type.split(":")[0],
10998         nodeLabels = {};
10999
11000     var st = new $jit.ST({
11001       injectInto: config.injectInto,
11002       orientation: "bottom",
11003       levelDistance: 0,
11004       siblingOffset: 0,
11005       subtreeOffset: 0,
11006       withLabels: config.Label.type != 'Native',
11007       useCanvas: config.useCanvas,
11008       Label: {
11009         type: config.Label.type
11010       },
11011       Node: {
11012         overridable: true,
11013         type: 'areachart-' + nodeType,
11014         align: 'left',
11015         width: 1,
11016         height: 1
11017       },
11018       Edge: {
11019         type: 'none'
11020       },
11021       Tips: {
11022         enable: config.Tips.enable,
11023         type: 'Native',
11024         force: true,
11025         onShow: function(tip, node, contains) {
11026           var elem = contains;
11027           config.Tips.onShow(tip, elem, node);
11028         }
11029       },
11030       Events: {
11031         enable: true,
11032         type: 'Native',
11033         onClick: function(node, eventInfo, evt) {
11034           if(!config.filterOnClick && !config.Events.enable) return;
11035           var elem = eventInfo.getContains();
11036           if(elem) config.filterOnClick && that.filter(elem.name);
11037           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11038         },
11039         onRightClick: function(node, eventInfo, evt) {
11040           if(!config.restoreOnRightClick) return;
11041           that.restore();
11042         },
11043         onMouseMove: function(node, eventInfo, evt) {
11044           if(!config.selectOnHover) return;
11045           if(node) {
11046             var elem = eventInfo.getContains();
11047             that.select(node.id, elem.name, elem.index);
11048           } else {
11049             that.select(false, false, false);
11050           }
11051         }
11052       },
11053       onCreateLabel: function(domElement, node) {
11054         var labelConf = config.Label,
11055             valueArray = node.getData('valueArray'),
11056             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11057             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11058         if(node.getData('prev')) {
11059           var nlbs = {
11060             wrapper: document.createElement('div'),
11061             aggregate: document.createElement('div'),
11062             label: document.createElement('div')
11063           };
11064           var wrapper = nlbs.wrapper,
11065               label = nlbs.label,
11066               aggregate = nlbs.aggregate,
11067               wrapperStyle = wrapper.style,
11068               labelStyle = label.style,
11069               aggregateStyle = aggregate.style;
11070           //store node labels
11071           nodeLabels[node.id] = nlbs;
11072           //append labels
11073           wrapper.appendChild(label);
11074           wrapper.appendChild(aggregate);
11075           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11076             label.style.display = 'none';
11077           }
11078           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11079             aggregate.style.display = 'none';
11080           }
11081           wrapperStyle.position = 'relative';
11082           wrapperStyle.overflow = 'visible';
11083           wrapperStyle.fontSize = labelConf.size + 'px';
11084           wrapperStyle.fontFamily = labelConf.family;
11085           wrapperStyle.color = labelConf.color;
11086           wrapperStyle.textAlign = 'center';
11087           aggregateStyle.position = labelStyle.position = 'absolute';
11088           
11089           domElement.style.width = node.getData('width') + 'px';
11090           domElement.style.height = node.getData('height') + 'px';
11091           label.innerHTML = node.name;
11092           
11093           domElement.appendChild(wrapper);
11094         }
11095       },
11096       onPlaceLabel: function(domElement, node) {
11097         if(!node.getData('prev')) return;
11098         var labels = nodeLabels[node.id],
11099             wrapperStyle = labels.wrapper.style,
11100             labelStyle = labels.label.style,
11101             aggregateStyle = labels.aggregate.style,
11102             width = node.getData('width'),
11103             height = node.getData('height'),
11104             dimArray = node.getData('dimArray'),
11105             valArray = node.getData('valueArray'),
11106             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11107             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11108             font = parseInt(wrapperStyle.fontSize, 10),
11109             domStyle = domElement.style;
11110         
11111         if(dimArray && valArray) {
11112           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11113             labelStyle.display = '';
11114           } else {
11115             labelStyle.display = 'none';
11116           }
11117           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11118             aggregateStyle.display = '';
11119           } else {
11120             aggregateStyle.display = 'none';
11121           }
11122           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11123           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11124           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11125             if(dimArray[i][0] > 0) {
11126               acum+= valArray[i][0];
11127               leftAcum+= dimArray[i][0];
11128             }
11129           }
11130           aggregateStyle.top = (-font - config.labelOffset) + 'px';
11131           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11132           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11133           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11134           labels.aggregate.innerHTML = acum;
11135         }
11136       }
11137     });
11138     
11139     var size = st.canvas.getSize(),
11140         margin = config.Margin;
11141     st.config.offsetY = -size.height/2 + margin.bottom 
11142       + (config.showLabels && (config.labelOffset + config.Label.size));
11143     st.config.offsetX = (margin.right - margin.left)/2;
11144     this.st = st;
11145     this.canvas = this.st.canvas;
11146   },
11147   
11148  /*
11149   Method: loadJSON
11150  
11151   Loads JSON data into the visualization. 
11152   
11153   Parameters:
11154   
11155   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>.
11156   
11157   Example:
11158   (start code js)
11159   var areaChart = new $jit.AreaChart(options);
11160   areaChart.loadJSON(json);
11161   (end code)
11162  */  
11163   loadJSON: function(json) {
11164     var prefix = $.time(), 
11165         ch = [], 
11166         st = this.st,
11167         name = $.splat(json.label), 
11168         color = $.splat(json.color || this.colors),
11169         config = this.config,
11170         gradient = !!config.type.split(":")[1],
11171         animate = config.animate;
11172     
11173     for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11174       var val = values[i], prev = values[i-1], next = values[i+1];
11175       var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11176       var valArray = $.zip(valLeft, valRight);
11177       var acumLeft = 0, acumRight = 0;
11178       ch.push({
11179         'id': prefix + val.label,
11180         'name': val.label,
11181         'data': {
11182           'value': valArray,
11183           '$valueArray': valArray,
11184           '$colorArray': color,
11185           '$stringArray': name,
11186           '$next': next.label,
11187           '$prev': prev? prev.label:false,
11188           '$config': config,
11189           '$gradient': gradient
11190         },
11191         'children': []
11192       });
11193     }
11194     var root = {
11195       'id': prefix + '$root',
11196       'name': '',
11197       'data': {
11198         '$type': 'none',
11199         '$width': 1,
11200         '$height': 1
11201       },
11202       'children': ch
11203     };
11204     st.loadJSON(root);
11205     
11206     this.normalizeDims();
11207     st.compute();
11208     st.select(st.root);
11209     if(animate) {
11210       st.fx.animate({
11211         modes: ['node-property:height:dimArray'],
11212         duration:1500
11213       });
11214     }
11215   },
11216   
11217  /*
11218   Method: updateJSON
11219  
11220   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.
11221   
11222   Parameters:
11223   
11224   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11225   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11226   
11227   Example:
11228   
11229   (start code js)
11230   areaChart.updateJSON(json, {
11231     onComplete: function() {
11232       alert('update complete!');
11233     }
11234   });
11235   (end code)
11236  */  
11237   updateJSON: function(json, onComplete) {
11238     if(this.busy) return;
11239     this.busy = true;
11240     
11241     var st = this.st,
11242         graph = st.graph,
11243         labels = json.label && $.splat(json.label),
11244         values = json.values,
11245         animate = this.config.animate,
11246         that = this;
11247     $.each(values, function(v) {
11248       var n = graph.getByName(v.label);
11249       if(n) {
11250         v.values = $.splat(v.values);
11251         var stringArray = n.getData('stringArray'),
11252             valArray = n.getData('valueArray');
11253         $.each(valArray, function(a, i) {
11254           a[0] = v.values[i];
11255           if(labels) stringArray[i] = labels[i];
11256         });
11257         n.setData('valueArray', valArray);
11258         var prev = n.getData('prev'),
11259             next = n.getData('next'),
11260             nextNode = graph.getByName(next);
11261         if(prev) {
11262           var p = graph.getByName(prev);
11263           if(p) {
11264             var valArray = p.getData('valueArray');
11265             $.each(valArray, function(a, i) {
11266               a[1] = v.values[i];
11267             });
11268           }
11269         }
11270         if(!nextNode) {
11271           var valArray = n.getData('valueArray');
11272           $.each(valArray, function(a, i) {
11273             a[1] = v.values[i];
11274           });
11275         }
11276       }
11277     });
11278     this.normalizeDims();
11279     st.compute();
11280     st.select(st.root);
11281     if(animate) {
11282       st.fx.animate({
11283         modes: ['node-property:height:dimArray'],
11284         duration:1500,
11285         onComplete: function() {
11286           that.busy = false;
11287           onComplete && onComplete.onComplete();
11288         }
11289       });
11290     }
11291   },
11292   
11293 /*
11294   Method: filter
11295  
11296   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11297   
11298   Parameters:
11299   
11300   Variable strings arguments with the name of the stacks.
11301   
11302   Example:
11303   
11304   (start code js)
11305   areaChart.filter('label A', 'label C');
11306   (end code)
11307   
11308   See also:
11309   
11310   <AreaChart.restore>.
11311  */  
11312   filter: function() {
11313     if(this.busy) return;
11314     this.busy = true;
11315     if(this.config.Tips.enable) this.st.tips.hide();
11316     this.select(false, false, false);
11317     var args = Array.prototype.slice.call(arguments);
11318     var rt = this.st.graph.getNode(this.st.root);
11319     var that = this;
11320     rt.eachAdjacency(function(adj) {
11321       var n = adj.nodeTo, 
11322           dimArray = n.getData('dimArray'),
11323           stringArray = n.getData('stringArray');
11324       n.setData('dimArray', $.map(dimArray, function(d, i) {
11325         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11326       }), 'end');
11327     });
11328     this.st.fx.animate({
11329       modes: ['node-property:dimArray'],
11330       duration:1500,
11331       onComplete: function() {
11332         that.busy = false;
11333       }
11334     });
11335   },
11336   
11337   /*
11338   Method: restore
11339  
11340   Sets all stacks that could have been filtered visible.
11341   
11342   Example:
11343   
11344   (start code js)
11345   areaChart.restore();
11346   (end code)
11347   
11348   See also:
11349   
11350   <AreaChart.filter>.
11351  */  
11352   restore: function() {
11353     if(this.busy) return;
11354     this.busy = true;
11355     if(this.config.Tips.enable) this.st.tips.hide();
11356     this.select(false, false, false);
11357     this.normalizeDims();
11358     var that = this;
11359     this.st.fx.animate({
11360       modes: ['node-property:height:dimArray'],
11361       duration:1500,
11362       onComplete: function() {
11363         that.busy = false;
11364       }
11365     });
11366   },
11367   //adds the little brown bar when hovering the node
11368   select: function(id, name, index) {
11369     if(!this.config.selectOnHover) return;
11370     var s = this.selected;
11371     if(s.id != id || s.name != name 
11372         || s.index != index) {
11373       s.id = id;
11374       s.name = name;
11375       s.index = index;
11376       this.st.graph.eachNode(function(n) {
11377         n.setData('border', false);
11378       });
11379       if(id) {
11380         var n = this.st.graph.getNode(id);
11381         n.setData('border', s);
11382         var link = index === 0? 'prev':'next';
11383         link = n.getData(link);
11384         if(link) {
11385           n = this.st.graph.getByName(link);
11386           if(n) {
11387             n.setData('border', {
11388               name: name,
11389               index: 1-index
11390             });
11391           }
11392         }
11393       }
11394       this.st.plot();
11395     }
11396   },
11397   
11398   /*
11399     Method: getLegend
11400    
11401     Returns an object containing as keys the legend names and as values hex strings with color values.
11402     
11403     Example:
11404     
11405     (start code js)
11406     var legend = areaChart.getLegend();
11407     (end code)
11408  */  
11409   getLegend: function() {
11410     var legend = {};
11411     var n;
11412     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11413       n = adj.nodeTo;
11414     });
11415     var colors = n.getData('colorArray'),
11416         len = colors.length;
11417     $.each(n.getData('stringArray'), function(s, i) {
11418       legend[s] = colors[i % len];
11419     });
11420     return legend;
11421   },
11422   
11423   /*
11424     Method: getMaxValue
11425    
11426     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11427     
11428     Example:
11429     
11430     (start code js)
11431     var ans = areaChart.getMaxValue();
11432     (end code)
11433     
11434     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11435     
11436     Example:
11437     
11438     (start code js)
11439     //will return 100 for all AreaChart instances,
11440     //displaying all of them with the same scale
11441     $jit.AreaChart.implement({
11442       'getMaxValue': function() {
11443         return 100;
11444       }
11445     });
11446     (end code)
11447     
11448 */  
11449   getMaxValue: function() {
11450     var maxValue = 0;
11451     this.st.graph.eachNode(function(n) {
11452       var valArray = n.getData('valueArray'),
11453           acumLeft = 0, acumRight = 0;
11454       $.each(valArray, function(v) { 
11455         acumLeft += +v[0];
11456         acumRight += +v[1];
11457       });
11458       var acum = acumRight>acumLeft? acumRight:acumLeft;
11459       maxValue = maxValue>acum? maxValue:acum;
11460     });
11461     return maxValue;
11462   },
11463   
11464   normalizeDims: function() {
11465     //number of elements
11466     var root = this.st.graph.getNode(this.st.root), l=0;
11467     root.eachAdjacency(function() {
11468       l++;
11469     });
11470     var maxValue = this.getMaxValue() || 1,
11471         size = this.st.canvas.getSize(),
11472         config = this.config,
11473         margin = config.Margin,
11474         labelOffset = config.labelOffset + config.Label.size,
11475         fixedDim = (size.width - (margin.left + margin.right)) / l,
11476         animate = config.animate,
11477         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
11478           - (config.showLabels && labelOffset);
11479     this.st.graph.eachNode(function(n) {
11480       var acumLeft = 0, acumRight = 0, animateValue = [];
11481       $.each(n.getData('valueArray'), function(v) {
11482         acumLeft += +v[0];
11483         acumRight += +v[1];
11484         animateValue.push([0, 0]);
11485       });
11486       var acum = acumRight>acumLeft? acumRight:acumLeft;
11487       n.setData('width', fixedDim);
11488       if(animate) {
11489         n.setData('height', acum * height / maxValue, 'end');
11490         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11491           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11492         }), 'end');
11493         var dimArray = n.getData('dimArray');
11494         if(!dimArray) {
11495           n.setData('dimArray', animateValue);
11496         }
11497       } else {
11498         n.setData('height', acum * height / maxValue);
11499         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11500           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11501         }));
11502       }
11503     });
11504   }
11505 });
11506
11507 /*
11508  * File: Options.BarChart.js
11509  *
11510 */
11511
11512 /*
11513   Object: Options.BarChart
11514   
11515   <BarChart> options. 
11516   Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11517   
11518   Syntax:
11519   
11520   (start code js)
11521
11522   Options.BarChart = {
11523     animate: true,
11524     labelOffset: 3,
11525     barsOffset: 0,
11526     type: 'stacked',
11527     hoveredColor: '#9fd4ff',
11528     orientation: 'horizontal',
11529     showAggregates: true,
11530     showLabels: true
11531   };
11532   
11533   (end code)
11534   
11535   Example:
11536   
11537   (start code js)
11538
11539   var barChart = new $jit.BarChart({
11540     animate: true,
11541     barsOffset: 10,
11542     type: 'stacked:gradient'
11543   });
11544   
11545   (end code)
11546
11547   Parameters:
11548   
11549   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11550   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11551   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11552   barsOffset - (number) Default's *0*. Separation between bars.
11553   type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11554   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11555   orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11556   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11557   showLabels - (boolean) Default's *true*. Display the name of the slots.
11558   
11559 */
11560
11561 Options.BarChart = {
11562   $extend: true,
11563   
11564   animate: true,
11565   type: 'stacked', //stacked, grouped, : gradient
11566   labelOffset: 3, //label offset
11567   barsOffset: 0, //distance between bars
11568   nodeCount: 0, //number of bars
11569   hoveredColor: '#9fd4ff',
11570   background: false,
11571   renderBackground: false,
11572   orientation: 'horizontal',
11573   showAggregates: true,
11574   showLabels: true,
11575   Ticks: {
11576         enable: false,
11577         segments: 4,
11578         color: '#000000'
11579   },
11580   Tips: {
11581     enable: false,
11582     onShow: $.empty,
11583     onHide: $.empty
11584   },
11585   Events: {
11586     enable: false,
11587     onClick: $.empty
11588   }
11589 };
11590
11591 /*
11592  * File: BarChart.js
11593  *
11594 */
11595
11596 $jit.ST.Plot.NodeTypes.implement({
11597   'barchart-stacked' : {
11598     'render' : function(node, canvas) {
11599       var pos = node.pos.getc(true), 
11600           width = node.getData('width'),
11601           height = node.getData('height'),
11602           algnPos = this.getAlignedPos(pos, width, height),
11603           x = algnPos.x, y = algnPos.y,
11604           dimArray = node.getData('dimArray'),
11605           valueArray = node.getData('valueArray'),
11606           stringArray = node.getData('stringArray'),
11607           linkArray = node.getData('linkArray'),
11608           gvl = node.getData('gvl'),
11609           colorArray = node.getData('colorArray'),
11610           colorLength = colorArray.length,
11611           nodeCount = node.getData('nodeCount');
11612       var ctx = canvas.getCtx(),
11613           canvasSize = canvas.getSize(),
11614           opt = {},
11615           border = node.getData('border'),
11616           gradient = node.getData('gradient'),
11617           config = node.getData('config'),
11618           horz = config.orientation == 'horizontal',
11619           aggregates = config.showAggregates,
11620           showLabels = config.showLabels,
11621           label = config.Label,
11622           margin = config.Margin;
11623           
11624           
11625       if (colorArray && dimArray && stringArray) {
11626         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11627                 acum += (dimArray[i] || 0);
11628         }
11629       }
11630       
11631        //drop shadow 
11632        if(config.shadow.enable) {
11633        shadowThickness = config.shadow.size;
11634        ctx.fillStyle = "rgba(0,0,0,.2)";
11635           if(horz) {
11636             ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11637           } else {
11638             ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11639           }
11640        }
11641        
11642       if (colorArray && dimArray && stringArray) {
11643         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11644           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11645           if(gradient) {
11646             var linear;
11647             
11648
11649           
11650             if(horz) {
11651               linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y, 
11652                   x + acum + dimArray[i]/2, y + height);
11653             } else {
11654               linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2, 
11655                   x + width, y - acum- dimArray[i]/2);
11656             }
11657             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11658                 function(v) { return (v * 0.8) >> 0; }));
11659             linear.addColorStop(0, color);
11660             linear.addColorStop(0.3, colorArray[i % colorLength]);
11661             linear.addColorStop(0.7, colorArray[i % colorLength]);
11662             linear.addColorStop(1, color);
11663             ctx.fillStyle = linear;
11664           }
11665
11666           if (horz)
11667           {
11668               yCoord = y;
11669               xCoord = x + acum;
11670               chartBarWidth = dimArray[i];
11671               chartBarHeight = height;
11672           }
11673           else
11674           {
11675               xCoord = x;
11676               yCoord = y - acum - dimArray[i];
11677               chartBarWidth = width;
11678               chartBarHeight = dimArray[i];
11679           }
11680           ctx.fillRect(xCoord, yCoord, chartBarWidth, chartBarHeight);
11681
11682           // add label
11683           if (chartBarHeight > 0)
11684           {
11685               ctx.font = label.style + ' ' + (label.size - 2) + 'px ' + label.family;
11686               labelText = valueArray[i].toString();
11687               mtxt = ctx.measureText(labelText);
11688
11689               labelTextPaddingX = 10;
11690               labelTextPaddingY = 6;
11691
11692               labelBoxWidth = mtxt.width + labelTextPaddingX;
11693               labelBoxHeight = label.size + labelTextPaddingY;
11694
11695               // do NOT draw label if label box is smaller than chartBarHeight
11696               if ((horz && (labelBoxWidth < chartBarWidth)) || (!horz && (labelBoxHeight < chartBarHeight)))
11697               {
11698                   labelBoxX = xCoord + chartBarWidth/2 - mtxt.width/2 - labelTextPaddingX/2;
11699                   labelBoxY = yCoord + chartBarHeight/2 - labelBoxHeight/2;
11700
11701                   ctx.fillStyle = "rgba(255,255,255,.2)";
11702                   $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "fill");
11703                   ctx.fillStyle = "rgba(0,0,0,.8)";
11704                   $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "stroke");
11705                   ctx.textAlign = 'center';
11706                   ctx.fillStyle = "rgba(255,255,255,.6)";
11707                   ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2, labelBoxY + labelBoxHeight/2);
11708                   ctx.fillStyle = "rgba(0,0,0,.6)";
11709                   ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2 + 1, labelBoxY + labelBoxHeight/2 + 1);
11710               }
11711           }
11712
11713           if(border && border.name == stringArray[i]) {
11714             opt.acum = acum;
11715             opt.dimValue = dimArray[i];
11716           }
11717           acum += (dimArray[i] || 0);
11718           valAcum += (valueArray[i] || 0);
11719         }
11720         if(border) {
11721           ctx.save();
11722           ctx.lineWidth = 2;
11723           ctx.strokeStyle = border.color;
11724           if(horz) {
11725             ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11726           } else {
11727             ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11728           }
11729           ctx.restore();
11730         }
11731         if(label.type == 'Native') {
11732           ctx.save();
11733           ctx.fillStyle = ctx.strokeStyle = label.color;
11734           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11735           ctx.textBaseline = 'middle';
11736                         if(gvl) {
11737                                 acumValueLabel = gvl;
11738                         } else {
11739                                 acumValueLabel = valAcum;
11740                         }
11741           if(aggregates(node.name, valAcum)) {
11742             if(!horz) {
11743                           ctx.textAlign = 'center';
11744                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11745                           //background box
11746                           ctx.save();
11747                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11748                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11749                                  (label ? label.size + config.labelOffset : 0));
11750                           mtxt = ctx.measureText(acumValueLabel);
11751                           boxWidth = mtxt.width+10;
11752                           inset = 10;
11753                           boxHeight = label.size+6;
11754                           
11755                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11756                                 bottomPadding = acum - config.labelOffset - boxHeight;
11757                           } else {
11758                                 bottomPadding = acum + config.labelOffset + inset;
11759                           }
11760                         
11761                         
11762                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11763                           cornerRadius = 4;     
11764                           boxX = -inset/2;
11765                           boxY = -boxHeight/2;
11766                           
11767                           ctx.rotate(0 * Math.PI / 180);
11768                           ctx.fillStyle = "rgba(255,255,255,.8)";
11769                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11770                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11771                           }
11772                           //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11773                           ctx.fillStyle = ctx.strokeStyle = label.color;
11774                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11775                           ctx.restore();
11776
11777             }
11778           }
11779           if(showLabels(node.name, valAcum, node)) {
11780             if(horz) {
11781
11782
11783                 //background box
11784                 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11785                                 inset = 10;
11786                                 
11787                                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11788                 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11789                 boxWidth = mtxt.width+10;
11790                 inset = 10;
11791                 
11792                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11793                         leftPadding = acum - config.labelOffset - boxWidth - inset;
11794                 } else {
11795                         leftPadding = acum + config.labelOffset;
11796                 }
11797                 
11798                 
11799                                 ctx.textAlign = 'left';
11800                                 ctx.translate(x + inset + leftPadding, y + height/2);
11801                                 boxHeight = label.size+6;
11802                                 boxX = -inset/2;
11803                                 boxY = -boxHeight/2;
11804                                 ctx.fillStyle = "rgba(255,255,255,.8)";
11805                                 cornerRadius = 4;
11806                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {  
11807                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11808                                 }
11809                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11810                                 
11811                           ctx.fillStyle = label.color;
11812               ctx.rotate(0 * Math.PI / 180);
11813               ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11814
11815
11816             } else {
11817               //if the number of nodes greater than 8 rotate labels 45 degrees
11818               if(nodeCount > 8) {
11819                                 ctx.textAlign = 'left';
11820                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11821                                 ctx.rotate(45* Math.PI / 180);
11822                                 ctx.fillText(node.name, 0, 0);
11823                           } else {
11824                                 ctx.textAlign = 'center';
11825                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11826                           }
11827             }
11828           }
11829           ctx.restore();
11830         }
11831       }
11832     },
11833     'contains': function(node, mpos) {
11834       var pos = node.pos.getc(true), 
11835           width = node.getData('width'),
11836           height = node.getData('height'),
11837           algnPos = this.getAlignedPos(pos, width, height),
11838           x = algnPos.x, y = algnPos.y,
11839           dimArray = node.getData('dimArray'),
11840           config = node.getData('config'),
11841           rx = mpos.x - x,
11842           horz = config.orientation == 'horizontal';
11843       //bounding box check
11844       if(horz) {
11845         if(mpos.x < x || mpos.x > x + width
11846             || mpos.y > y + height || mpos.y < y) {
11847             return false;
11848           }
11849       } else {
11850         if(mpos.x < x || mpos.x > x + width
11851             || mpos.y > y || mpos.y < y - height) {
11852             return false;
11853           }
11854       }
11855       //deep check
11856       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11857         var dimi = dimArray[i];
11858                 var url = Url.decode(node.getData('linkArray')[i]);
11859         if(horz) {
11860           acum += dimi;
11861           var intersec = acum;
11862           if(mpos.x <= intersec) {
11863             return {
11864               'name': node.getData('stringArray')[i],
11865               'color': node.getData('colorArray')[i],
11866               'value': node.getData('valueArray')[i],
11867               'valuelabel': node.getData('valuelabelArray')[i],
11868                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11869                           'link': url,
11870               'label': node.name
11871             };
11872           }
11873         } else {
11874           acum -= dimi;
11875           var intersec = acum;
11876           if(mpos.y >= intersec) {
11877             return {
11878               'name': node.getData('stringArray')[i],
11879               'color': node.getData('colorArray')[i],
11880               'value': node.getData('valueArray')[i],
11881                           'valuelabel': node.getData('valuelabelArray')[i],
11882                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11883               'link': url,
11884               'label': node.name
11885             };
11886           }
11887         }
11888       }
11889       return false;
11890     }
11891   },
11892   'barchart-grouped' : {
11893     'render' : function(node, canvas) {
11894       var pos = node.pos.getc(true), 
11895           width = node.getData('width'),
11896           height = node.getData('height'),
11897           algnPos = this.getAlignedPos(pos, width, height),
11898           x = algnPos.x, y = algnPos.y,
11899           dimArray = node.getData('dimArray'),
11900           valueArray = node.getData('valueArray'),
11901           valuelabelArray = node.getData('valuelabelArray'),
11902           linkArray = node.getData('linkArray'),
11903           valueLength = valueArray.length,
11904           colorArray = node.getData('colorArray'),
11905           colorLength = colorArray.length,
11906           stringArray = node.getData('stringArray'); 
11907
11908       var ctx = canvas.getCtx(),
11909           canvasSize = canvas.getSize(),
11910           opt = {},
11911           border = node.getData('border'),
11912           gradient = node.getData('gradient'),
11913           config = node.getData('config'),
11914           horz = config.orientation == 'horizontal',
11915           aggregates = config.showAggregates,
11916           showLabels = config.showLabels,
11917           label = config.Label,
11918           shadow = config.shadow,
11919           margin = config.Margin,
11920           fixedDim = (horz? height : width) / valueLength;
11921       
11922       //drop shadow
11923       
11924        maxValue = Math.max.apply(null, dimArray);
11925        
11926        
11927           
11928            ctx.fillStyle = "rgba(0,0,0,.2)";
11929       if (colorArray && dimArray && stringArray && shadow.enable) {
11930                  shadowThickness = shadow.size;
11931
11932         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11933                 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11934                 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11935                 if(horz) {
11936                                     
11937                         ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11938                                         
11939                 } else {
11940                         
11941                         if(i == 0) {
11942                                 if(nextBar && nextBar > dimArray[i]) {
11943                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11944                                 } else if (nextBar && nextBar < dimArray[i]){
11945                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11946                                 } else {
11947                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11948                                 }
11949                         } else if (i> 0 && i<l-1) {
11950                                 if(nextBar && nextBar > dimArray[i]) {
11951                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11952                                 } else if (nextBar && nextBar < dimArray[i]){
11953                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11954                                 } else {
11955                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11956                                 }
11957                         } else if (i == l-1) {
11958                                 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11959                         }
11960                         
11961                         
11962                 }
11963         }
11964
11965       } 
11966                         
11967       
11968       if (colorArray && dimArray && stringArray) {
11969         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11970           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11971           if(gradient) {
11972             var linear;
11973             if(horz) {
11974               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
11975                   x + dimArray[i]/2, y + fixedDim * (i + 1));
11976             } else {
11977               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
11978                   x + fixedDim * (i + 1), y - dimArray[i]/2);
11979             }
11980             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11981                 function(v) { return (v * 0.8) >> 0; }));
11982             linear.addColorStop(0, color);
11983             linear.addColorStop(0.3, colorArray[i % colorLength]);
11984             linear.addColorStop(0.7, colorArray[i % colorLength]);
11985             linear.addColorStop(1, color);
11986             ctx.fillStyle = linear;
11987           }
11988           if(horz) {
11989             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11990           } else {
11991             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11992           }
11993           if(border && border.name == stringArray[i]) {
11994             opt.acum = fixedDim * i;
11995             opt.dimValue = dimArray[i];
11996           }
11997           acum += (dimArray[i] || 0);
11998           valAcum += (valueArray[i] || 0);
11999                   ctx.fillStyle = ctx.strokeStyle = label.color;
12000           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12001           
12002           inset = 10;
12003                   if(aggregates(node.name, valAcum) && label.type == 'Native') {
12004                                 if(valuelabelArray[i]) {
12005                                         acumValueLabel = valuelabelArray[i];
12006                                 } else {
12007                                         acumValueLabel = valueArray[i];
12008                                 }
12009                            if(horz) {
12010                                   ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12011                                   ctx.textAlign = 'left';
12012                                   ctx.textBaseline = 'top';
12013                                   ctx.fillStyle = "rgba(255,255,255,.8)";
12014                                   //background box
12015                                   gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
12016                                   mtxt = ctx.measureText(acumValueLabel);
12017                                   boxWidth = mtxt.width+10;
12018                                   
12019                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12020                                         leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
12021                                   } else {
12022                                         leftPadding = dimArray[i] + config.labelOffset + inset;
12023                                   }
12024                               boxHeight = label.size+6;
12025                                   boxX = x + leftPadding;
12026                                   boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
12027                                   cornerRadius = 4;     
12028                                   
12029                                   
12030                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12031                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12032                                   }
12033                                 //  $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12034                                   
12035                                   ctx.fillStyle = ctx.strokeStyle = label.color;
12036                                   ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12037                                   
12038
12039                                         
12040                                         
12041                                 } else {
12042                                   
12043                                         ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12044                                         ctx.save();
12045                                         ctx.textAlign = 'center';
12046                                         
12047                                         //background box
12048                                         gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12049                                          (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12050                                          (label ? label.size + config.labelOffset : 0));
12051                                         
12052                                         mtxt = ctx.measureText(acumValueLabel);
12053                                         boxWidth = mtxt.width+10;
12054                                         boxHeight = label.size+6;
12055                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12056                                                 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12057                                         } else {
12058                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12059                                         }
12060                                                                                                 
12061                                         
12062                                         ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12063                                         
12064                                         boxX = -boxWidth/2;
12065                                         boxY = -boxHeight/2;
12066                                         ctx.fillStyle = "rgba(255,255,255,.8)";
12067                                         
12068                                         cornerRadius = 4;       
12069
12070                                         //ctx.rotate(270* Math.PI / 180);
12071                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12072                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12073                                         }
12074                                         //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12075                                         
12076                                         ctx.fillStyle = ctx.strokeStyle = label.color;
12077                                         ctx.fillText(acumValueLabel, 0,0);
12078                                         ctx.restore();
12079
12080                                 }
12081                         }
12082         }
12083         if(border) {
12084           ctx.save();
12085           ctx.lineWidth = 2;
12086           ctx.strokeStyle = border.color;
12087           if(horz) {
12088             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12089           } else {
12090             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12091           }
12092           ctx.restore();
12093         }
12094         if(label.type == 'Native') {
12095           ctx.save();
12096           ctx.fillStyle = ctx.strokeStyle = label.color;
12097           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12098           ctx.textBaseline = 'middle';
12099
12100           if(showLabels(node.name, valAcum, node)) {
12101             if(horz) {
12102               ctx.textAlign = 'center';
12103               ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12104               ctx.rotate(Math.PI / 2);
12105               ctx.fillText(node.name, 0, 0);
12106             } else {
12107               ctx.textAlign = 'center';
12108               ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12109             }
12110           }
12111           ctx.restore();
12112         }
12113       }
12114     },
12115     'contains': function(node, mpos) {
12116       var pos = node.pos.getc(true), 
12117           width = node.getData('width'),
12118           height = node.getData('height'),
12119           algnPos = this.getAlignedPos(pos, width, height),
12120           x = algnPos.x, y = algnPos.y,
12121           dimArray = node.getData('dimArray'),
12122           len = dimArray.length,
12123           config = node.getData('config'),
12124           rx = mpos.x - x,
12125           horz = config.orientation == 'horizontal',
12126           fixedDim = (horz? height : width) / len;
12127       //bounding box check
12128       if(horz) {
12129         if(mpos.x < x || mpos.x > x + width
12130             || mpos.y > y + height || mpos.y < y) {
12131             return false;
12132           }
12133       } else {
12134         if(mpos.x < x || mpos.x > x + width
12135             || mpos.y > y || mpos.y < y - height) {
12136             return false;
12137           }
12138       }
12139       //deep check
12140       for(var i=0, l=dimArray.length; i<l; i++) {
12141         var dimi = dimArray[i];
12142                 var url = Url.decode(node.getData('linkArray')[i]);
12143         if(horz) {
12144           var limit = y + fixedDim * i;
12145           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12146             return {
12147               'name': node.getData('stringArray')[i],
12148               'color': node.getData('colorArray')[i],
12149               'value': node.getData('valueArray')[i],
12150                           'valuelabel': node.getData('valuelabelArray')[i],
12151               'title': node.getData('titleArray')[i],
12152                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12153               'link': url,
12154               'label': node.name
12155             };
12156           }
12157         } else {
12158           var limit = x + fixedDim * i;
12159           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12160             return {
12161               'name': node.getData('stringArray')[i],
12162               'color': node.getData('colorArray')[i],
12163               'value': node.getData('valueArray')[i],
12164                           'valuelabel': node.getData('valuelabelArray')[i],
12165               'title': node.getData('titleArray')[i],
12166                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12167               'link': url,
12168               'label': node.name
12169             };
12170           }
12171         }
12172       }
12173       return false;
12174     }
12175   },
12176   'barchart-basic' : {
12177     'render' : function(node, canvas) {
12178       var pos = node.pos.getc(true), 
12179           width = node.getData('width'),
12180           height = node.getData('height'),
12181           algnPos = this.getAlignedPos(pos, width, height),
12182           x = algnPos.x, y = algnPos.y,
12183           dimArray = node.getData('dimArray'),
12184           valueArray = node.getData('valueArray'),
12185                   valuelabelArray = node.getData('valuelabelArray'),
12186           linkArray = node.getData('linkArray'),
12187           valueLength = valueArray.length,
12188           colorArray = node.getData('colorMono'),
12189           colorLength = colorArray.length,
12190           stringArray = node.getData('stringArray'); 
12191
12192       var ctx = canvas.getCtx(),
12193           canvasSize = canvas.getSize(),
12194           opt = {},
12195           border = node.getData('border'),
12196           gradient = node.getData('gradient'),
12197           config = node.getData('config'),
12198           horz = config.orientation == 'horizontal',
12199           aggregates = config.showAggregates,
12200           showLabels = config.showLabels,
12201           label = config.Label,
12202           fixedDim = (horz? height : width) / valueLength,
12203           margin = config.Margin;
12204       
12205       if (colorArray && dimArray && stringArray) {
12206         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12207           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12208
12209           if(gradient) {
12210             var linear;
12211             if(horz) {
12212               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
12213                   x + dimArray[i]/2, y + fixedDim * (i + 1));
12214             } else {
12215               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
12216                   x + fixedDim * (i + 1), y - dimArray[i]/2);
12217             }
12218             //drop shadow 
12219            if(config.shadow.size) {
12220                   shadowThickness = config.shadow.size;
12221                   ctx.fillStyle = "rgba(0,0,0,.2)";
12222                   if(horz) {
12223                     ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12224                   } else {
12225                     ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12226                   }
12227           }
12228           
12229             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
12230                 function(v) { return (v * 0.8) >> 0; }));
12231             linear.addColorStop(0, color);
12232             linear.addColorStop(0.3, colorArray[i % colorLength]);
12233             linear.addColorStop(0.7, colorArray[i % colorLength]);
12234             linear.addColorStop(1, color);
12235             ctx.fillStyle = linear;
12236           }
12237           if(horz) {
12238             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12239           } else {
12240             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12241           }
12242           if(border && border.name == stringArray[i]) {
12243             opt.acum = fixedDim * i;
12244             opt.dimValue = dimArray[i];
12245           }
12246           acum += (dimArray[i] || 0);
12247           valAcum += (valueArray[i] || 0);
12248                   
12249               if(label.type == 'Native') {
12250                                  ctx.fillStyle = ctx.strokeStyle = label.color;
12251                                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12252                                  if(aggregates(node.name, valAcum)) {
12253                                         if(valuelabelArray[i]) {
12254                                                 acumValueLabel = valuelabelArray[i];
12255                                           } else {
12256                                                 acumValueLabel = valueArray[i];
12257                                           }
12258                                          if(!horz) {
12259                                           ctx.textAlign = 'center';
12260                                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12261                                           //background box
12262                                           ctx.save();
12263                                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12264                                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12265                                                  (label ? label.size + config.labelOffset : 0));
12266                           mtxt = ctx.measureText(acumValueLabel);
12267                                           boxWidth = mtxt.width+10;
12268                                           inset = 10;
12269                                           boxHeight = label.size+6;
12270                                           
12271                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12272                                                 bottomPadding = dimArray[i] - config.labelOffset  - inset;
12273                                           } else {
12274                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12275                                           }
12276                                         
12277                                         
12278                                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12279                                           cornerRadius = 4;     
12280                                           boxX = -inset/2;
12281                                           boxY = -boxHeight/2;
12282                                           
12283                                           //ctx.rotate(270* Math.PI / 180);
12284                                           ctx.fillStyle = "rgba(255,255,255,.6)";
12285                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12286                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12287                                           }
12288                                          // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12289                                           ctx.fillStyle = ctx.strokeStyle = label.color;
12290                                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12291                                           ctx.restore();
12292                                         }
12293                                 }
12294                 }
12295         }
12296         if(border) {
12297           ctx.save();
12298           ctx.lineWidth = 2;
12299           ctx.strokeStyle = border.color;
12300           if(horz) {
12301             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12302           } else {
12303             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12304           }
12305           ctx.restore();
12306         }
12307         if(label.type == 'Native') {
12308           ctx.save();
12309           ctx.fillStyle = ctx.strokeStyle = label.color;
12310           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12311           ctx.textBaseline = 'middle';
12312           if(showLabels(node.name, valAcum, node)) {
12313             if(horz) {
12314                 
12315                 //background box
12316                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12317                 mtxt = ctx.measureText(node.name + ": " + valAcum);
12318                 boxWidth = mtxt.width+10;
12319                 inset = 10;
12320                 
12321                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12322                         leftPadding = acum - config.labelOffset - boxWidth - inset;
12323                 } else {
12324                         leftPadding = acum + config.labelOffset;
12325                 }
12326                 
12327                                 
12328                                 ctx.textAlign = 'left';
12329                                 ctx.translate(x + inset + leftPadding, y + height/2);
12330                                 boxHeight = label.size+6;
12331                                 boxX = -inset/2;
12332                                 boxY = -boxHeight/2;
12333                                 ctx.fillStyle = "rgba(255,255,255,.8)";
12334                                 
12335                                 cornerRadius = 4;       
12336                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12337                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12338                                 }
12339                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12340                 
12341                                 
12342                                 ctx.fillStyle = label.color;
12343                                 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12344
12345             } else {
12346               
12347                           if(stringArray.length > 8) {
12348                                 ctx.textAlign = 'left';
12349                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12350                                 ctx.rotate(45* Math.PI / 180);
12351                                 ctx.fillText(node.name, 0, 0);
12352                           } else {
12353                                 ctx.textAlign = 'center';
12354                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12355                           }
12356               
12357             }
12358           }
12359           ctx.restore();
12360         }
12361       }
12362     },
12363     'contains': function(node, mpos) {
12364       var pos = node.pos.getc(true), 
12365           width = node.getData('width'),
12366           height = node.getData('height'),
12367           config = node.getData('config'),
12368           algnPos = this.getAlignedPos(pos, width, height),
12369           x = algnPos.x, y = algnPos.y ,
12370           dimArray = node.getData('dimArray'),
12371           len = dimArray.length,
12372           rx = mpos.x - x,
12373           horz = config.orientation == 'horizontal',
12374           fixedDim = (horz? height : width) / len;
12375
12376       //bounding box check
12377       if(horz) {
12378         if(mpos.x < x || mpos.x > x + width
12379             || mpos.y > y + height || mpos.y < y) {
12380             return false;
12381           }
12382       } else {
12383         if(mpos.x < x || mpos.x > x + width
12384             || mpos.y > y || mpos.y < y - height) {
12385             return false;
12386           }
12387       }
12388       //deep check
12389       for(var i=0, l=dimArray.length; i<l; i++) {
12390         var dimi = dimArray[i];
12391                 var url = Url.decode(node.getData('linkArray')[i]);
12392         if(horz) {
12393           var limit = y + fixedDim * i;
12394           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12395             return {
12396               'name': node.getData('stringArray')[i],
12397               'color': node.getData('colorArray')[i],
12398               'value': node.getData('valueArray')[i],
12399                           'valuelabel': node.getData('valuelabelArray')[i],
12400                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12401               'link': url,
12402               'label': node.name
12403             };
12404           }
12405         } else {
12406           var limit = x + fixedDim * i;
12407           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12408             return {
12409               'name': node.getData('stringArray')[i],
12410               'color': node.getData('colorArray')[i],
12411               'value': node.getData('valueArray')[i],
12412                           'valuelabel': node.getData('valuelabelArray')[i],
12413                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12414               'link': url,
12415               'label': node.name
12416             };
12417           }
12418         }
12419       }
12420       return false;
12421     }
12422   }
12423 });
12424
12425 /*
12426   Class: BarChart
12427   
12428   A visualization that displays stacked bar charts.
12429   
12430   Constructor Options:
12431   
12432   See <Options.BarChart>.
12433
12434 */
12435 $jit.BarChart = new Class({
12436   st: null,
12437   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12438   selected: {},
12439   busy: false,
12440   
12441   initialize: function(opt) {
12442     this.controller = this.config = 
12443       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12444         Label: { type: 'Native' }
12445       }, opt);
12446     //set functions for showLabels and showAggregates
12447     var showLabels = this.config.showLabels,
12448         typeLabels = $.type(showLabels),
12449         showAggregates = this.config.showAggregates,
12450         typeAggregates = $.type(showAggregates);
12451     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12452     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12453     Options.Fx.clearCanvas = false;
12454     this.initializeViz();
12455   },
12456   
12457   initializeViz: function() {
12458     var config = this.config, that = this;
12459     var nodeType = config.type.split(":")[0],
12460         horz = config.orientation == 'horizontal',
12461         nodeLabels = {};
12462     var st = new $jit.ST({
12463       injectInto: config.injectInto,
12464       orientation: horz? 'left' : 'bottom',
12465       background: config.background,
12466       renderBackground: config.renderBackground,
12467       backgroundColor: config.backgroundColor,
12468       colorStop1: config.colorStop1,
12469       colorStop2: config.colorStop2,
12470       levelDistance: 0,
12471       nodeCount: config.nodeCount,
12472       siblingOffset: config.barsOffset,
12473       subtreeOffset: 0,
12474       withLabels: config.Label.type != 'Native',      
12475       useCanvas: config.useCanvas,
12476       Label: {
12477         type: config.Label.type
12478       },
12479       Node: {
12480         overridable: true,
12481         type: 'barchart-' + nodeType,
12482         align: 'left',
12483         width: 1,
12484         height: 1
12485       },
12486       Edge: {
12487         type: 'none'
12488       },
12489       Tips: {
12490         enable: config.Tips.enable,
12491         type: 'Native',
12492         force: true,
12493         onShow: function(tip, node, contains) {
12494           var elem = contains;
12495           config.Tips.onShow(tip, elem, node);
12496                           if(elem.link != 'undefined' && elem.link != '') {
12497                                 document.body.style.cursor = 'pointer';
12498                           }
12499         },
12500                 onHide: function(call) {
12501                         document.body.style.cursor = 'default';
12502
12503         }
12504       },
12505       Events: {
12506         enable: true,
12507         type: 'Native',
12508         onClick: function(node, eventInfo, evt) {
12509           if(!config.Events.enable) return;
12510           var elem = eventInfo.getContains();
12511           config.Events.onClick(elem, eventInfo, evt);
12512         },
12513         onMouseMove: function(node, eventInfo, evt) {
12514           if(!config.hoveredColor) return;
12515           if(node) {
12516             var elem = eventInfo.getContains();
12517             that.select(node.id, elem.name, elem.index);
12518           } else {
12519             that.select(false, false, false);
12520           }
12521         }
12522       },
12523       onCreateLabel: function(domElement, node) {
12524         var labelConf = config.Label,
12525             valueArray = node.getData('valueArray'),
12526             acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12527             grouped = config.type.split(':')[0] == 'grouped',
12528             horz = config.orientation == 'horizontal';
12529         var nlbs = {
12530           wrapper: document.createElement('div'),
12531           aggregate: document.createElement('div'),
12532           label: document.createElement('div')
12533         };
12534         
12535         var wrapper = nlbs.wrapper,
12536             label = nlbs.label,
12537             aggregate = nlbs.aggregate,
12538             wrapperStyle = wrapper.style,
12539             labelStyle = label.style,
12540             aggregateStyle = aggregate.style;
12541         //store node labels
12542         nodeLabels[node.id] = nlbs;
12543         //append labels
12544         wrapper.appendChild(label);
12545         wrapper.appendChild(aggregate);
12546         if(!config.showLabels(node.name, acum, node)) {
12547           labelStyle.display = 'none';
12548         }
12549         if(!config.showAggregates(node.name, acum, node)) {
12550           aggregateStyle.display = 'none';
12551         }
12552         wrapperStyle.position = 'relative';
12553         wrapperStyle.overflow = 'visible';
12554         wrapperStyle.fontSize = labelConf.size + 'px';
12555         wrapperStyle.fontFamily = labelConf.family;
12556         wrapperStyle.color = labelConf.color;
12557         wrapperStyle.textAlign = 'center';
12558         aggregateStyle.position = labelStyle.position = 'absolute';
12559         
12560         domElement.style.width = node.getData('width') + 'px';
12561         domElement.style.height = node.getData('height') + 'px';
12562         aggregateStyle.left = "0px";
12563         labelStyle.left =  config.labelOffset + 'px';
12564         labelStyle.whiteSpace =  "nowrap";
12565                 label.innerHTML = node.name;       
12566         
12567         domElement.appendChild(wrapper);
12568       },
12569       onPlaceLabel: function(domElement, node) {
12570         if(!nodeLabels[node.id]) return;
12571         var labels = nodeLabels[node.id],
12572             wrapperStyle = labels.wrapper.style,
12573             labelStyle = labels.label.style,
12574             aggregateStyle = labels.aggregate.style,
12575             grouped = config.type.split(':')[0] == 'grouped',
12576             horz = config.orientation == 'horizontal',
12577             dimArray = node.getData('dimArray'),
12578             valArray = node.getData('valueArray'),
12579             nodeCount = node.getData('nodeCount'),
12580             valueLength = valArray.length;
12581             valuelabelArray = node.getData('valuelabelArray'),
12582             stringArray = node.getData('stringArray'),
12583             width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12584             height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12585             font = parseInt(wrapperStyle.fontSize, 10),
12586             domStyle = domElement.style,
12587             fixedDim = (horz? height : width) / valueLength;
12588             
12589         
12590         if(dimArray && valArray) {
12591           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12592           
12593           aggregateStyle.width = width  - config.labelOffset + "px";
12594           for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12595             if(dimArray[i] > 0) {
12596               acum+= valArray[i];
12597             }
12598           }
12599           if(config.showLabels(node.name, acum, node)) {
12600             labelStyle.display = '';
12601           } else {
12602             labelStyle.display = 'none';
12603           }
12604           if(config.showAggregates(node.name, acum, node)) {
12605             aggregateStyle.display = '';
12606           } else {
12607             aggregateStyle.display = 'none';
12608           }
12609           if(config.orientation == 'horizontal') {
12610             aggregateStyle.textAlign = 'right';
12611             labelStyle.textAlign = 'left';
12612             labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12613             aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12614             domElement.style.height = wrapperStyle.height = height + 'px';
12615           } else {
12616             aggregateStyle.top = (-font - config.labelOffset) + 'px';
12617             labelStyle.top = (config.labelOffset + height) + 'px';
12618             domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12619             domElement.style.height = wrapperStyle.height = height + 'px';
12620             if(stringArray.length > 8) {
12621                 labels.label.className = "rotatedLabelReverse";
12622                 labelStyle.textAlign = "left";
12623                 labelStyle.top = config.labelOffset + height + width/2 + "px";
12624             }
12625           }
12626           
12627           if(horz) {
12628
12629                         labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12630                         labels.aggregate.innerHTML = "";
12631
12632           } else {
12633                 
12634                         if(grouped) {
12635                                 maxValue = Math.max.apply(null,dimArray);
12636                                 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12637                                         valueLabelDim = 50;
12638                                         valueLabel = document.createElement('div');
12639                                         valueLabel.innerHTML =  valuelabelArray[i];
12640 //                                      valueLabel.class = "rotatedLabel";
12641                                         valueLabel.className = "rotatedLabel";
12642                                         valueLabel.style.position = "absolute";
12643                                                 valueLabel.style.textAlign = "left";
12644                                                 valueLabel.style.verticalAlign = "middle";
12645                                         valueLabel.style.height = valueLabelDim + "px";
12646                                         valueLabel.style.width = valueLabelDim + "px";
12647                                         valueLabel.style.top =  (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12648                                         valueLabel.style.left = (fixedDim * i) + "px";
12649                                         labels.wrapper.appendChild(valueLabel);
12650                                 }
12651                         } else {
12652                                 labels.aggregate.innerHTML = acum;
12653                         }
12654           }
12655         }
12656       }
12657     });
12658
12659     var size = st.canvas.getSize(),
12660         l = config.nodeCount,
12661         margin = config.Margin;
12662         title = config.Title;
12663         subtitle = config.Subtitle,
12664         grouped = config.type.split(':')[0] == 'grouped',
12665         margin = config.Margin,
12666         ticks = config.Ticks,
12667         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12668         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12669         horz = config.orientation == 'horizontal',
12670         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12671         fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12672         whiteSpace = size.width - (marginWidth + (fixedDim * l));
12673         //bug in IE7 when vertical bar charts load in dashlets where number of bars exceed a certain width, canvas renders with an incorrect width, a hard refresh fixes the problem
12674         if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12675         location.reload();
12676         //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12677         if(!grouped && !horz) {
12678                 st.config.siblingOffset = whiteSpace/(l+1);
12679         }
12680         
12681         
12682         
12683         //Bars offset
12684     if(horz) {
12685       st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);    
12686           if(config.Ticks.enable)       {
12687                 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;
12688           } else {
12689                 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12690           }
12691     } else {
12692       st.config.offsetY = -size.height/2 + margin.bottom 
12693         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12694           if(config.Ticks.enable)       {
12695                 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12696           } else {
12697                 st.config.offsetX = (margin.right - margin.left)/2;
12698           }
12699     }
12700     this.st = st;
12701     this.canvas = this.st.canvas;
12702   },
12703   
12704  
12705   
12706   renderTitle: function() {
12707         var canvas = this.canvas,
12708         size = canvas.getSize(),
12709         config = this.config,
12710         margin = config.Margin,
12711         label = config.Label,
12712         title = config.Title;
12713         ctx = canvas.getCtx();
12714         ctx.fillStyle = title.color;
12715         ctx.textAlign = 'left';
12716         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12717         if(label.type == 'Native') {
12718                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12719         }
12720   },  
12721   
12722   renderSubtitle: function() {
12723         var canvas = this.canvas,
12724         size = canvas.getSize(),
12725         config = this.config,
12726         margin = config.Margin,
12727         label = config.Label,
12728         subtitle = config.Subtitle,
12729         nodeCount = config.nodeCount,
12730         horz = config.orientation == 'horizontal' ? true : false,
12731         ctx = canvas.getCtx();
12732         ctx.fillStyle = title.color;
12733         ctx.textAlign = 'left';
12734         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12735         if(label.type == 'Native') {
12736                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12737         }
12738   },
12739   
12740   renderScrollNote: function() {
12741         var canvas = this.canvas,
12742         size = canvas.getSize(),
12743         config = this.config,
12744         margin = config.Margin,
12745         label = config.Label,
12746         note = config.ScrollNote;
12747         ctx = canvas.getCtx();
12748         ctx.fillStyle = title.color;
12749         title = config.Title;
12750         ctx.textAlign = 'center';
12751         ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12752         if(label.type == 'Native') {
12753                 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12754         }
12755   },  
12756   
12757   renderTicks: function() {
12758
12759         var canvas = this.canvas,
12760         size = canvas.getSize(),
12761         config = this.config,
12762         margin = config.Margin,
12763         ticks = config.Ticks,
12764         title = config.Title,
12765         subtitle = config.Subtitle,
12766         label = config.Label,
12767         shadow = config.shadow;
12768         horz = config.orientation == 'horizontal',
12769         grouped = config.type.split(':')[0] == 'grouped',
12770         ctx = canvas.getCtx();
12771         ctx.strokeStyle = ticks.color;
12772     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12773
12774         ctx.textAlign = 'center';
12775         ctx.textBaseline = 'middle';
12776         
12777         idLabel = canvas.id + "-label";
12778         labelDim = 100;
12779         container = document.getElementById(idLabel);
12780                   
12781                   
12782         if(horz) {
12783                 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12784                 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12785                 segmentLength = grid/ticks.segments;
12786                 ctx.fillStyle = ticks.color;
12787
12788         // Main horizontal line
12789         var xTop = axis;
12790         var yTop = size.height / 2 - margin.bottom - config.labelOffset - label.size - (subtitle.text ? subtitle.size + subtitle.offset : 0) + (shadow.enable ? shadow.size : 0);
12791         var xLength = size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0);
12792         var yLength = 1;
12793                 ctx.fillRect(xTop, yTop, xLength, yLength);
12794
12795         maxTickValue = config.Ticks.maxValue;
12796         var humanNumber = config.Ticks.humanNumber;
12797         var segments = config.Ticks.segments;
12798         var tempHumanNumber = humanNumber;
12799         var humanNumberPow = 0;
12800         // Tries to find pow of humanNumber if it is less than 1. For 0.001 humanNumberPos will be 3. it means 0.001*10^3 = 1.
12801         // humanNumberPow is required for work with number less than 1.
12802         while (tempHumanNumber % 1 != 0)
12803         {
12804             tempHumanNumber = tempHumanNumber * 10;
12805             humanNumberPow ++;
12806         }
12807
12808         // Tries convert length of line to steps in pixels. 4 steps, 160 length - 40 pixels per step
12809         var pixelsPerStep = xLength / maxTickValue;
12810         var 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);
12811         for (var i = 0; i <= segments; i++)
12812         {
12813             var iX = Math.round(xTop + i * pixelsPerStep * humanNumber);
12814             ctx.save();
12815             ctx.translate(iX, yTop + yLength + margin.top);
12816             ctx.rotate(0 * Math.PI / 2 * 3);
12817             ctx.fillStyle = label.color;
12818             // Float numbers fix (0.45 can be displayed as 0.44466666)
12819             var labelText = humanNumber * Math.pow(10, humanNumberPow) * i;
12820             labelText = labelText * Math.pow(10, -humanNumberPow);
12821             if (config.showLabels)
12822             {
12823                 // Filling Text through canvas or html elements
12824                 if (label.type == 'Native')
12825                 {
12826                     ctx.fillText(labelText, 0, 0); // For coords see ctx.translate above
12827                 }
12828                 else
12829                 {
12830                     //html labels on y axis
12831                     labelDiv = document.createElement('div');
12832                     labelDiv.innerHTML = labelText;
12833                     labelDiv.style.top = Math.round(size.height - margin.bottom - config.labelOffset) + "px";
12834                     labelDiv.style.left = Math.round(margin.left - labelDim / 2 + i * pixelsPerStep * humanNumber) + "px";
12835                     labelDiv.style.width = labelDim + "px";
12836                     labelDiv.style.height = labelDim + "px";
12837                     labelDiv.style.textAlign = "center";
12838                     labelDiv.style.verticalAlign = "middle";
12839                     labelDiv.style.position = "absolute";
12840                     labelDiv.style.background = '1px solid red';
12841                     container.appendChild(labelDiv);
12842                 }
12843             }
12844             ctx.restore();
12845             ctx.fillStyle = ticks.color;
12846             // Drawing line
12847             ctx.fillRect(Math.round(axis) + i * pixelsPerStep * humanNumber, -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));
12848         }
12849         } else {
12850         
12851                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12852                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12853                 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)),
12854                 segmentLength = grid/ticks.segments;
12855                 ctx.fillStyle = ticks.color;
12856
12857         // Main horizontal line
12858         var xTop = -size.width / 2 + margin.left + config.labelOffset + label.size - 1;
12859         var yTop = -size.height / 2 + margin.top + (title.text ? title.size + title.offset : 0);
12860         var xLength = 1;
12861         var yLength = size.height - margin.top - margin.bottom - label.size - config.labelOffset - (title.text ? title.size + title.offset : 0) - (subtitle.text ? subtitle.size + subtitle.offset : 0);
12862                 ctx.fillRect(xTop, yTop, xLength, yLength);
12863
12864         maxTickValue = config.Ticks.maxValue;
12865         var humanNumber = config.Ticks.humanNumber;
12866         var segments = config.Ticks.segments;
12867         var tempHumanNumber = humanNumber;
12868         var humanNumberPow = 0;
12869         // Tries to find pow of humanNumber if it is less than 1. For 0.001 humanNumberPos will be 3. it means 0.001*10^3 = 1.
12870         // humanNumberPow is required for work with number less than 1.
12871         while (tempHumanNumber % 1 != 0)
12872         {
12873             tempHumanNumber = tempHumanNumber * 10;
12874             humanNumberPow ++;
12875         }
12876
12877         // Tries convert length of line to steps in pixels. 4 steps, 160 length - 40 pixels per step
12878         var pixelsPerStep = yLength / maxTickValue;
12879         for (var i = 0; i <= segments; i++)
12880         {
12881             var iY = Math.round(yTop + yLength - i * pixelsPerStep * humanNumber);
12882             ctx.save();
12883                         ctx.translate(-size.width / 2 + margin.left, iY);
12884             ctx.rotate(0 * Math.PI / 2 * 3);
12885             ctx.fillStyle = label.color;
12886             // Float numbers fix (0.45 can be displayed as 0.44466666)
12887             var labelText = humanNumber * Math.pow(10, humanNumberPow) * i;
12888             labelText = labelText * Math.pow(10, -humanNumberPow);
12889             if (config.showLabels)
12890             {
12891                 // Filling Text through canvas or html elements
12892                 if (label.type == 'Native')
12893                 {
12894                     ctx.fillText(labelText, 0, 0); // For coords see ctx.translate above
12895                 }
12896                 else
12897                 {
12898                     //html labels on y axis
12899                     labelDiv = document.createElement('div');
12900                     labelDiv.innerHTML = labelText;
12901                     labelDiv.className = "rotatedLabel";
12902 //                                      labelDiv.class = "rotatedLabel";
12903                     labelDiv.style.top = Math.round(htmlOrigin - labelDim / 2 - i * pixelsPerStep * humanNumber) + "px";
12904                     labelDiv.style.left = margin.left + "px";
12905                     labelDiv.style.width = labelDim + "px";
12906                     labelDiv.style.height = labelDim + "px";
12907                     labelDiv.style.textAlign = "center";
12908                     labelDiv.style.verticalAlign = "middle";
12909                     labelDiv.style.position = "absolute";
12910                     container.appendChild(labelDiv);
12911                 }
12912             }
12913             ctx.restore();
12914                         ctx.fillStyle = ticks.color;
12915                         ctx.fillRect(-size.width / 2 + margin.left + config.labelOffset + label.size, iY, size.width - margin.right - margin.left - config.labelOffset - label.size, 1);
12916         }
12917         }
12918         
12919         
12920         
12921
12922   },
12923   
12924   renderBackground: function() {
12925                 var canvas = this.canvas,
12926                 config = this.config,
12927                 backgroundColor = config.backgroundColor,
12928                 size = canvas.getSize(),
12929                 ctx = canvas.getCtx();
12930             ctx.fillStyle = backgroundColor;
12931             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
12932   },
12933   
12934   clear: function() {
12935         var canvas = this.canvas;
12936         var ctx = canvas.getCtx(),
12937         size = canvas.getSize();
12938         ctx.fillStyle = "rgba(255,255,255,0)";
12939         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12940         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
12941  },
12942   resizeGraph: function(json,width) {
12943         var canvas = this.canvas,
12944         size = canvas.getSize(),
12945         config = this.config,
12946         orgHeight = size.height,
12947         margin = config.Margin,
12948         st = this.st,
12949         grouped = config.type.split(':')[0] == 'grouped',
12950         horz = config.orientation == 'horizontal';
12951         
12952         canvas.resize(width,orgHeight);
12953         if(typeof FlashCanvas == "undefined") {
12954                 canvas.clear();
12955         } else {
12956                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
12957         }
12958         if(horz) {
12959                 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12960         }
12961         
12962         this.loadJSON(json);
12963
12964         
12965         },
12966   /*
12967     Method: loadJSON
12968    
12969     Loads JSON data into the visualization. 
12970     
12971     Parameters:
12972     
12973     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>.
12974     
12975     Example:
12976     (start code js)
12977     var barChart = new $jit.BarChart(options);
12978     barChart.loadJSON(json);
12979     (end code)
12980  */  
12981   loadJSON: function(json) {
12982     if(this.busy) return;
12983     this.busy = true;
12984     
12985     var prefix = $.time(), 
12986         ch = [], 
12987         st = this.st,
12988         name = $.splat(json.label), 
12989         color = $.splat(json.color || this.colors),
12990         config = this.config,
12991         gradient = !!config.type.split(":")[1],
12992         renderBackground = config.renderBackground,
12993         animate = config.animate,
12994         ticks = config.Ticks,
12995         title = config.Title,
12996         note = config.ScrollNote,
12997         subtitle = config.Subtitle,
12998         horz = config.orientation == 'horizontal',
12999         that = this,
13000                 colorLength = color.length,
13001                 nameLength = name.length;
13002         groupTotalValue = 0;
13003     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13004         var val = values[i];
13005         var valArray = $.splat(val.values);
13006         groupTotalValue += parseFloat(valArray.sum());
13007     }
13008
13009     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13010       var val = values[i];
13011       var valArray = $.splat(values[i].values);
13012       var valuelabelArray = $.splat(values[i].valuelabels);
13013       var linkArray = $.splat(values[i].links);
13014       var titleArray = $.splat(values[i].titles);
13015       var barTotalValue = valArray.sum();
13016       var acum = 0;
13017       ch.push({
13018         'id': prefix + val.label,
13019         'name': val.label,
13020         
13021         'data': {
13022           'value': valArray,
13023           '$linkArray': linkArray,
13024                   '$gvl': val.gvaluelabel,
13025           '$titleArray': titleArray,
13026           '$valueArray': valArray,
13027           '$valuelabelArray': valuelabelArray,
13028           '$colorArray': color,
13029           '$colorMono': $.splat(color[i % colorLength]),
13030           '$stringArray': name,
13031           '$barTotalValue': barTotalValue,
13032           '$groupTotalValue': groupTotalValue,
13033           '$nodeCount': values.length,
13034           '$gradient': gradient,
13035           '$config': config
13036         },
13037         'children': []
13038       });
13039     }
13040     var root = {
13041       'id': prefix + '$root',
13042       'name': '',
13043       'data': {
13044         '$type': 'none',
13045         '$width': 1,
13046         '$height': 1
13047       },
13048       'children': ch
13049     };
13050     st.loadJSON(root);
13051     
13052     this.normalizeDims();
13053     
13054     if(renderBackground) {
13055                 this.renderBackground();
13056     }
13057         
13058         if(!animate && ticks.enable) {
13059                 this.renderTicks();
13060         }
13061         if(!animate && note.text) {
13062                 this.renderScrollNote();
13063         }
13064         if(!animate && title.text) {
13065                 this.renderTitle();
13066         }
13067         if(!animate && subtitle.text) {
13068                 this.renderSubtitle();
13069         }
13070
13071     st.compute();
13072     st.select(st.root);
13073     if(animate) {
13074       if(horz) {
13075         st.fx.animate({
13076           modes: ['node-property:width:dimArray'],
13077           duration:1500,
13078           onComplete: function() {
13079             that.busy = false;
13080           }
13081         });
13082       } else {
13083         st.fx.animate({
13084           modes: ['node-property:height:dimArray'],
13085           duration:1500,
13086           onComplete: function() {
13087             that.busy = false;
13088           }
13089         });
13090       }
13091     } else {
13092       this.busy = false;
13093     }
13094   },
13095   
13096   /*
13097     Method: updateJSON
13098    
13099     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.
13100     
13101     Parameters:
13102     
13103     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13104     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13105     
13106     Example:
13107     
13108     (start code js)
13109     barChart.updateJSON(json, {
13110       onComplete: function() {
13111         alert('update complete!');
13112       }
13113     });
13114     (end code)
13115  */  
13116   updateJSON: function(json, onComplete) {
13117     if(this.busy) return;
13118     this.busy = true;
13119     
13120     var st = this.st;
13121     var graph = st.graph;
13122     var values = json.values;
13123     var animate = this.config.animate;
13124     var that = this;
13125     var horz = this.config.orientation == 'horizontal';
13126     $.each(values, function(v) {
13127       var n = graph.getByName(v.label);
13128       if(n) {
13129         n.setData('valueArray', $.splat(v.values));
13130         if(json.label) {
13131           n.setData('stringArray', $.splat(json.label));
13132         }
13133       }
13134     });
13135     this.normalizeDims();
13136     st.compute();
13137     st.select(st.root);
13138     if(animate) {
13139       if(horz) {
13140         st.fx.animate({
13141           modes: ['node-property:width:dimArray'],
13142           duration:1500,
13143           onComplete: function() {
13144             that.busy = false;
13145             onComplete && onComplete.onComplete();
13146           }
13147         });
13148       } else {
13149         st.fx.animate({
13150           modes: ['node-property:height:dimArray'],
13151           duration:1500,
13152           onComplete: function() {
13153             that.busy = false;
13154             onComplete && onComplete.onComplete();
13155           }
13156         });
13157       }
13158     }
13159   },
13160   
13161   //adds the little brown bar when hovering the node
13162   select: function(id, name) {
13163
13164     if(!this.config.hoveredColor) return;
13165     var s = this.selected;
13166     if(s.id != id || s.name != name) {
13167       s.id = id;
13168       s.name = name;
13169       s.color = this.config.hoveredColor;
13170       this.st.graph.eachNode(function(n) {
13171         if(id == n.id) {
13172           n.setData('border', s);
13173         } else {
13174           n.setData('border', false);
13175         }
13176       });
13177       this.st.plot();
13178     }
13179   },
13180   
13181   /*
13182     Method: getLegend
13183    
13184     Returns an object containing as keys the legend names and as values hex strings with color values.
13185     
13186     Example:
13187     
13188     (start code js)
13189     var legend = barChart.getLegend();
13190     (end code)
13191   */  
13192   getLegend: function() {
13193     var legend = new Array();
13194     var name = new Array();
13195     var color = new Array();
13196     var n;
13197     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13198       n = adj.nodeTo;
13199     });
13200     var colors = n.getData('colorArray'),
13201         len = colors.length;
13202     $.each(n.getData('stringArray'), function(s, i) {
13203       color[i] = colors[i % len];
13204       name[i] = s;
13205     });
13206         legend['name'] = name;
13207         legend['color'] = color;
13208     return legend;
13209   },
13210   
13211   /*
13212     Method: getMaxValue
13213    
13214     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13215     
13216     Example:
13217     
13218     (start code js)
13219     var ans = barChart.getMaxValue();
13220     (end code)
13221     
13222     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13223     
13224     Example:
13225     
13226     (start code js)
13227     //will return 100 for all BarChart instances,
13228     //displaying all of them with the same scale
13229     $jit.BarChart.implement({
13230       'getMaxValue': function() {
13231         return 100;
13232       }
13233     });
13234     (end code)
13235     
13236   */  
13237   getMaxValue: function() {
13238     var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13239     this.st.graph.eachNode(function(n) {
13240       var valArray = n.getData('valueArray'),
13241           acum = 0;
13242       if(!valArray) return;
13243       if(stacked) {
13244         $.each(valArray, function(v) { 
13245           acum += +v;
13246         });
13247       } else {
13248         acum = Math.max.apply(null, valArray);
13249       }
13250       maxValue = maxValue>acum? maxValue:acum;
13251     });
13252     return maxValue;
13253   },
13254   
13255   setBarType: function(type) {
13256     this.config.type = type;
13257     this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13258   },
13259   
13260   normalizeDims: function() {
13261     //number of elements
13262     var root = this.st.graph.getNode(this.st.root), l=0;
13263     root.eachAdjacency(function() {
13264       l++;
13265     });
13266     var maxValue = this.getMaxValue() || 1,
13267         size = this.st.canvas.getSize(),
13268         config = this.config,
13269         margin = config.Margin,
13270         ticks = config.Ticks,
13271         title = config.Title,
13272         subtitle = config.Subtitle,
13273         grouped = config.type.split(':')[0] == 'grouped',
13274         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13275         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13276         horz = config.orientation == 'horizontal',
13277         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13278         animate = config.animate,
13279         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13280
13281           - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13282         dim1 = horz? 'height':'width',
13283         dim2 = horz? 'width':'height',
13284         basic = config.type.split(':')[0] == 'basic';
13285
13286         // Bug #47147 Correct detection of maxTickValue and step size for asix labels
13287         var iDirection = 10; // We need this var for convert value. 10^2 = 100
13288         var zeroCount = 0; // Pow for iDirection for detection of size of human step.
13289         var iNumber = maxValue; // This var will store two first digits from maxValue. For 1265848 it will be 12. For 0.0453 it will be 45.
13290         // Tries to get two first digits from maxValue
13291         if (iNumber >= 0)
13292         {
13293             // Tries to calculate zeroCount
13294             // if iNumber = 100 we will get zeroCount = 2, iNumber = 0.1
13295             while (iNumber >= 1)
13296             {
13297                 zeroCount ++;
13298                 iNumber = iNumber / 10;
13299             }
13300             iNumber = Math.floor(iNumber * 100); // We need to increase iNumber by 100 to get two first digits. for 0.1 it will be 0.1*100 = 10
13301         }
13302         else
13303         {
13304             iDirection = 0.1; // if iNumber is less than 1 we should change iDirection. 0.1^2 = 0.01
13305             // Tries to calculate zeroCount
13306             // if iNumber = 0.01 we will get zeroCount = 2, iNumber = 1
13307             while (iNumber < 1)
13308             {
13309                 zeroCount ++;
13310                 iNumber = iNumber * 10;
13311             }
13312             iNumber = Math.floor(iNumber * 10); // We need to increase iNumber by 10 to get two first digits. for 1 it will be 1*10 = 10
13313         }
13314         var humanNumber = 0;
13315         var iNumberTemp = iNumber + 1; // We need to add 1 for correct tick size detection. It means that tick always will be great than max value of chart.
13316         // 5 is human step. And we try to detect max value of tick. if maxValue is 1234567 it means iNumber = 12, iNumberTemp = 13 and as result we will get iNumberTemp = 15.
13317         while (iNumberTemp % 5 != 0)
13318         {
13319             iNumberTemp ++;
13320         }
13321         var isFound = false;
13322         zeroCount --; // We need to reduce zeroCount because of increase by 10 or 100 in steps above. It means iNumber = 10, zeroCount = 2 - 1 = 1. 10 * 10^1 = 100. 100 is original value of iNumber
13323         // Tries to find humanNumber. Our step is 5. ticks.segments is number of lines = 4 (for example)
13324         // iNumberTemp = 15 (for example). 15 % 4 = 3. It means that we should add 5 to iNumberTemp till division will equal 0. 20 % 4 = 0. Our result is iNumberTemp = 20
13325         while (isFound == false)
13326         {
13327             if (iNumberTemp % ticks.segments == 0)
13328             {
13329                 humanNumber = iNumberTemp / ticks.segments;
13330                 isFound = true;
13331                 break;
13332             }
13333             iNumberTemp = iNumberTemp + 5;
13334         }
13335         // Getting real values
13336         var maxTickValue = config.Ticks.maxValue = maxTickValue = iNumberTemp * Math.pow(iDirection, zeroCount - 1);
13337         config.Ticks.humanNumber = humanNumber = humanNumber * Math.pow(iDirection, zeroCount - 1);
13338         config.Ticks.segments = Math.floor(maxTickValue / humanNumber);
13339
13340                 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13341
13342                 
13343     this.st.graph.eachNode(function(n) {
13344       var acum = 0, animateValue = [];
13345       $.each(n.getData('valueArray'), function(v) {
13346         acum += +v;
13347         animateValue.push(0);
13348       });
13349       
13350       if(grouped) {
13351         fixedDim = animateValue.length * 40;
13352       }
13353       n.setData(dim1, fixedDim);
13354       
13355       
13356       if(animate) {
13357         n.setData(dim2, acum * height / maxValue, 'end');
13358         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13359           return n * height / maxValue; 
13360         }), 'end');
13361         var dimArray = n.getData('dimArray');
13362         if(!dimArray) {
13363           n.setData('dimArray', animateValue);
13364         }
13365       } else {
13366         
13367
13368                 if(ticks.enable) {
13369                         n.setData(dim2, acum * height / maxTickValue);
13370                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13371                           return n * height / maxTickValue; 
13372                         }));
13373                 } else {
13374                         n.setData(dim2, acum * height / maxValue);
13375                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13376                           return n * height / maxValue; 
13377                         }));
13378                 }
13379       }
13380     });
13381   }
13382 });
13383
13384 //funnel chart options
13385
13386
13387 Options.FunnelChart = {
13388   $extend: true,
13389   
13390   animate: true,
13391   type: 'stacked', //stacked, grouped, : gradient
13392   labelOffset: 3, //label offset
13393   barsOffset: 0, //distance between bars
13394   hoveredColor: '#9fd4ff',
13395   orientation: 'vertical',
13396   showAggregates: true,
13397   showLabels: true,
13398   Tips: {
13399     enable: false,
13400     onShow: $.empty,
13401     onHide: $.empty
13402   },
13403   Events: {
13404     enable: false,
13405     onClick: $.empty
13406   }
13407 };
13408
13409 $jit.ST.Plot.NodeTypes.implement({
13410   'funnelchart-basic' : {
13411     'render' : function(node, canvas) {
13412       var pos = node.pos.getc(true), 
13413           width  = node.getData('width'),
13414           height = node.getData('height'),
13415           algnPos = this.getAlignedPos(pos, width, height),
13416           x = algnPos.x, y = algnPos.y,
13417           dimArray = node.getData('dimArray'),
13418           valueArray = node.getData('valueArray'),
13419           valuelabelArray = node.getData('valuelabelArray'),
13420           linkArray = node.getData('linkArray'),
13421           colorArray = node.getData('colorArray'),
13422           colorLength = colorArray.length,
13423           stringArray = node.getData('stringArray');
13424       var ctx = canvas.getCtx(),
13425           opt = {},
13426           border = node.getData('border'),
13427           gradient = node.getData('gradient'),
13428           config = node.getData('config'),
13429           horz = config.orientation == 'horizontal',
13430           aggregates = config.showAggregates,
13431           showLabels = config.showLabels,
13432           label = config.Label,
13433           size = canvas.getSize(),
13434           labelOffset = config.labelOffset + 10;
13435           minWidth =  width * .25;
13436           ratio = .65;
13437
13438       if (colorArray && dimArray && stringArray) {
13439         
13440         
13441         // horizontal lines
13442         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13443         ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13444
13445         if(label.type == 'Native') {      
13446        if(showLabels(node.name, valAcum, node)) {
13447                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13448                  var stringValue = stringArray[i];
13449                  var valueLabel = String(valuelabelArray[i]);
13450              var mV = ctx.measureText(stringValue);
13451              var mVL = ctx.measureText(valueLabel);
13452                  var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13453                          var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13454                          var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13455                  var bottomWidth = minWidth + ((acum) * ratio);  
13456                  var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);  
13457                          var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13458                          var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13459 //             ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13460
13461                         //right lines
13462                         ctx.beginPath();
13463                         ctx.moveTo(bottomWidth/2,y - acum); //
13464                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13465                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight);  // bottom right
13466                         ctx.stroke();
13467                         //left lines
13468                         ctx.beginPath();
13469                         ctx.moveTo(-bottomWidth/2,y - acum); //
13470                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13471                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight);  // bottom right
13472                         ctx.stroke();
13473        }
13474         }
13475
13476                 acum += (dimArray[i] || 0);
13477           valAcum += (valueArray[i] || 0);
13478           
13479           
13480                 }
13481                 
13482  
13483   
13484         //funnel segments and labels
13485         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13486           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13487                           var colori = colorArray[i % colorLength];
13488                           if(label.type == 'Native') { 
13489                                   var stringValue = stringArray[i];
13490                           var valueLabel = String(valuelabelArray[i]);
13491                               var mV = ctx.measureText(stringValue);
13492                       var mVL = ctx.measureText(valueLabel);
13493                           } else {
13494                                   var mV = 10;
13495                       var mVL = 10;     
13496                           }
13497                       var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13498                       var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13499                       var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13500                       var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13501                       
13502           var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13503           var bottomWidth = minWidth + ((acum) * ratio);
13504           var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13505           
13506
13507           if(gradient) {
13508             var linear;
13509               linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13510                         var colorRgb = $.hexToRgb(colori);
13511             var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
13512                 function(v) { return (v * .5) >> 0; });
13513             linear.addColorStop(0, 'rgba('+color+',1)');
13514             linear.addColorStop(0.5,  'rgba('+colorRgb+',1)');
13515             linear.addColorStop(1, 'rgba('+color+',1)');
13516             ctx.fillStyle = linear;
13517           }
13518           
13519                         ctx.beginPath();
13520                         ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13521                         ctx.lineTo(topWidth/2,y - acum - dimArray[i]);  // top right
13522                         ctx.lineTo(bottomWidth/2,y - acum);  // bottom right
13523                         ctx.lineTo(-bottomWidth/2,y - acum);  // bottom left
13524                         ctx.closePath(); 
13525                         ctx.fill();
13526                 
13527           
13528           if(border && border.name == stringArray[i]) {
13529             opt.acum = acum;
13530             opt.dimValue = dimArray[i];
13531           }
13532           
13533           
13534         if(border) {
13535           ctx.save();
13536           ctx.lineWidth = 2;
13537           ctx.strokeStyle = border.color;
13538
13539             //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13540          
13541           ctx.restore();
13542         }
13543         if(label.type == 'Native') {
13544           ctx.save();
13545           ctx.fillStyle = ctx.strokeStyle = label.color;
13546           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13547           ctx.textBaseline = 'middle';
13548
13549                                 acumValueLabel = valAcum;
13550
13551           if(showLabels(node.name, valAcum, node)) {
13552
13553                       
13554               ctx.textAlign = 'left';
13555               ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13556               ctx.textAlign = 'right';
13557               ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13558               }
13559           ctx.restore();
13560         }
13561
13562           acum += (dimArray[i] || 0);
13563           valAcum += (valueArray[i] || 0);
13564           
13565         }
13566
13567       }
13568     },
13569     'contains': function(node, mpos) {
13570       var pos = node.pos.getc(true), 
13571           width = node.getData('width'),
13572           height = node.getData('height'),
13573           algnPos = this.getAlignedPos(pos, width, height),
13574           x = algnPos.x, y = algnPos.y,
13575           dimArray = node.getData('dimArray'),
13576           config = node.getData('config'),
13577           st = node.getData('st'),
13578           rx = mpos.x - x,
13579           horz = config.orientation == 'horizontal',
13580            minWidth =  width * .25;
13581           ratio = .65,
13582           canvas = node.getData('canvas'),
13583           size = canvas.getSize(),
13584           offsetY = st.config.offsetY;
13585       //bounding box check
13586
13587         if(mpos.y > y || mpos.y < y - height) {
13588             return false;
13589           }
13590           
13591          var newY = Math.abs(mpos.y + offsetY);
13592         var bound = minWidth + (newY * ratio);
13593         var boundLeft = -bound/2;
13594         var boundRight = bound/2;
13595          if(mpos.x < boundLeft || mpos.x > boundRight ) {
13596             return false;
13597           }
13598
13599       
13600       //deep check
13601       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13602         var dimi = dimArray[i];
13603
13604           
13605           
13606                 var url = Url.decode(node.getData('linkArray')[i]);
13607           acum -= dimi;  
13608           var intersec = acum;
13609           if(mpos.y >= intersec) {
13610             return {
13611               'name': node.getData('stringArray')[i],
13612               'color': node.getData('colorArray')[i],
13613               'value': node.getData('valueArray')[i],
13614               'percentage': node.getData('percentageArray')[i],
13615                           'valuelabel': node.getData('valuelabelArray')[i],
13616               'link': url,
13617               'label': node.name
13618             };
13619           }
13620         
13621       }
13622       return false;
13623     }
13624   }
13625 });
13626
13627 /*
13628   Class: FunnelChart
13629   
13630   A visualization that displays funnel charts.
13631   
13632   Constructor Options:
13633   
13634   See <Options.FunnelChart>.
13635
13636 */
13637 $jit.FunnelChart = new Class({
13638   st: null,
13639   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13640   selected: {},
13641   busy: false,
13642   
13643   initialize: function(opt) {
13644     this.controller = this.config = 
13645       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13646         Label: { type: 'Native' }
13647       }, opt);
13648     //set functions for showLabels and showAggregates
13649     var showLabels = this.config.showLabels,
13650         typeLabels = $.type(showLabels),
13651         showAggregates = this.config.showAggregates,
13652         typeAggregates = $.type(showAggregates);
13653     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13654     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13655     Options.Fx.clearCanvas = false;
13656     this.initializeViz();
13657   },
13658   
13659   initializeViz: function() {
13660     var config = this.config, that = this;
13661     var nodeType = config.type.split(":")[0],
13662         horz = config.orientation == 'horizontal',
13663         nodeLabels = {};
13664     var st = new $jit.ST({
13665       injectInto: config.injectInto,
13666       orientation: horz? 'left' : 'bottom',
13667       levelDistance: 0,
13668       background: config.background,
13669       renderBackground: config.renderBackground,
13670       backgroundColor: config.backgroundColor,
13671       colorStop1: config.colorStop1,
13672       colorStop2: config.colorStop2,
13673       siblingOffset: config.segmentOffset,
13674       subtreeOffset: 0,
13675       withLabels: config.Label.type != 'Native',      
13676       useCanvas: config.useCanvas,
13677       Label: {
13678         type: config.Label.type
13679       },
13680       Node: {
13681         overridable: true,
13682         type: 'funnelchart-' + nodeType,
13683         align: 'left',
13684         width: 1,
13685         height: 1
13686       },
13687       Edge: {
13688         type: 'none'
13689       },
13690       Tips: {
13691         enable: config.Tips.enable,
13692         type: 'Native',
13693         force: true,
13694         onShow: function(tip, node, contains) {
13695           var elem = contains;
13696           config.Tips.onShow(tip, elem, node);
13697                           if(elem.link != 'undefined' && elem.link != '') {
13698                                 document.body.style.cursor = 'pointer';
13699                           }
13700         },
13701                 onHide: function(call) {
13702                         document.body.style.cursor = 'default';
13703
13704         }
13705       },
13706       Events: {
13707         enable: true,
13708         type: 'Native',
13709         onClick: function(node, eventInfo, evt) {
13710           if(!config.Events.enable) return;
13711           var elem = eventInfo.getContains();
13712           config.Events.onClick(elem, eventInfo, evt);
13713         },
13714         onMouseMove: function(node, eventInfo, evt) {
13715           if(!config.hoveredColor) return;
13716           if(node) {
13717             var elem = eventInfo.getContains();
13718             that.select(node.id, elem.name, elem.index);
13719           } else {
13720             that.select(false, false, false);
13721           }
13722         }
13723       },
13724       onCreateLabel: function(domElement, node) {
13725         var labelConf = config.Label,
13726             valueArray = node.getData('valueArray'),
13727             idArray = node.getData('idArray'),
13728             valuelabelArray = node.getData('valuelabelArray'),
13729             stringArray = node.getData('stringArray');
13730             size = st.canvas.getSize()
13731             prefix = $.time();
13732                 
13733                 for(var i=0, l=valueArray.length; i<l; i++) {
13734         var nlbs = {
13735           wrapper: document.createElement('div'),
13736           valueLabel: document.createElement('div'),
13737           label: document.createElement('div')
13738         };
13739         var wrapper = nlbs.wrapper,
13740             label = nlbs.label,
13741             valueLabel = nlbs.valueLabel,
13742             wrapperStyle = wrapper.style,
13743             labelStyle = label.style,
13744             valueLabelStyle = valueLabel.style;
13745         //store node labels
13746         nodeLabels[idArray[i]] = nlbs;
13747         //append labels
13748         wrapper.appendChild(label);
13749         wrapper.appendChild(valueLabel);
13750
13751         wrapperStyle.position = 'relative';
13752         wrapperStyle.overflow = 'visible';
13753         wrapperStyle.fontSize = labelConf.size + 'px';
13754         wrapperStyle.fontFamily = labelConf.family;
13755         wrapperStyle.color = labelConf.color;
13756         wrapperStyle.textAlign = 'center';
13757         wrapperStyle.width = size.width + 'px';
13758         valueLabelStyle.position = labelStyle.position = 'absolute';
13759         valueLabelStyle.left = labelStyle.left =  '0px';
13760                 valueLabelStyle.width = (size.width/3) + 'px';
13761                 valueLabelStyle.textAlign = 'right';
13762         label.innerHTML = stringArray[i];
13763         valueLabel.innerHTML = valuelabelArray[i];
13764         domElement.id = prefix+'funnel';
13765         domElement.style.width = size.width + 'px';
13766         
13767                 domElement.appendChild(wrapper);
13768                 }
13769
13770       },
13771       onPlaceLabel: function(domElement, node) {
13772
13773             var dimArray = node.getData('dimArray'),
13774             idArray = node.getData('idArray'),
13775             valueArray = node.getData('valueArray'),
13776             valuelabelArray = node.getData('valuelabelArray'),
13777             stringArray = node.getData('stringArray');
13778             size = st.canvas.getSize(),
13779             pos = node.pos.getc(true),
13780              domElement.style.left = "0px",
13781              domElement.style.top = "0px",
13782              minWidth = node.getData('width') * .25,
13783              ratio = .65,
13784              pos = node.pos.getc(true),
13785              labelConf = config.Label;
13786              
13787              
13788                 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13789
13790         var labels = nodeLabels[idArray[i]],
13791             wrapperStyle = labels.wrapper.style,
13792             labelStyle = labels.label.style,
13793             valueLabelStyle = labels.valueLabel.style;
13794                 var bottomWidth = minWidth + (acum * ratio); 
13795                 
13796             font = parseInt(wrapperStyle.fontSize, 10),
13797             domStyle = domElement.style;
13798            
13799                 
13800        
13801                         wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13802             valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13803             labelStyle.left =  (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13804
13805                         acum += (dimArray[i] || 0);
13806
13807                 }
13808
13809       }
13810
13811     });
13812
13813     var size = st.canvas.getSize(),
13814         margin = config.Margin;
13815         title = config.Title;
13816         subtitle = config.Subtitle;
13817         //y offset
13818
13819       st.config.offsetY = -size.height/2 + margin.bottom 
13820         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13821
13822                 st.config.offsetX = (margin.right - margin.left)/2;
13823           
13824     
13825     this.st = st;
13826     this.canvas = this.st.canvas;
13827   },
13828   
13829   renderTitle: function() {
13830         var canvas = this.canvas,
13831         size = canvas.getSize(),
13832         config = this.config,
13833         margin = config.Margin,
13834         label = config.Label,
13835         title = config.Title;
13836         ctx = canvas.getCtx();
13837         ctx.fillStyle = title.color;
13838         ctx.textAlign = 'left';
13839         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13840         if(label.type == 'Native') {
13841                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13842         }
13843   },  
13844   
13845   renderSubtitle: function() {
13846         var canvas = this.canvas,
13847         size = canvas.getSize(),
13848         config = this.config,
13849         margin = config.Margin,
13850         label = config.Label,
13851         subtitle = config.Subtitle;
13852         ctx = canvas.getCtx();
13853         ctx.fillStyle = title.color;
13854         ctx.textAlign = 'left';
13855         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13856         if(label.type == 'Native') {
13857                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13858         }
13859   },
13860   
13861   
13862   renderDropShadow: function() {
13863         var canvas = this.canvas,
13864         size = canvas.getSize(),
13865         config = this.config,
13866         margin = config.Margin,
13867         horz = config.orientation == 'horizontal',
13868         label = config.Label,
13869         title = config.Title,
13870         shadowThickness = 4,
13871         subtitle = config.Subtitle,
13872         ctx = canvas.getCtx(),
13873         minwidth = (size.width/8) * .25,
13874         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13875         topMargin = (title.text? title.size + title.offset : 0)  + margin.top,
13876     height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13877           - (config.showLabels && (config.Label.size + config.labelOffset)),
13878     ratio = .65,
13879         topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13880         topY = (-size.height/2) + topMargin - shadowThickness;
13881         bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13882         bottomWidth = minwidth + shadowThickness;
13883         ctx.beginPath();
13884         ctx.fillStyle = "rgba(0,0,0,.2)";
13885         ctx.moveTo(0,topY);
13886         ctx.lineTo(-topWidth/2,topY); //top left
13887         ctx.lineTo(-bottomWidth/2,bottomY);  // bottom left
13888         ctx.lineTo(bottomWidth/2,bottomY);  // bottom right
13889         ctx.lineTo(topWidth/2,topY);  // top right
13890         ctx.closePath(); 
13891         ctx.fill();
13892                         
13893                         
13894   },
13895
13896    renderBackground: function() {
13897                 var canvas = this.canvas,
13898                 config = this.config,
13899                 backgroundColor = config.backgroundColor,
13900                 size = canvas.getSize(),
13901                 ctx = canvas.getCtx();
13902                 //ctx.globalCompositeOperation = "destination-over";
13903             ctx.fillStyle = backgroundColor;
13904             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
13905   },
13906   clear: function() {
13907         var canvas = this.canvas;
13908         var ctx = canvas.getCtx(),
13909         size = canvas.getSize();
13910         ctx.fillStyle = "rgba(255,255,255,0)";
13911         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13912         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13913   },
13914    resizeGraph: function(json,width) {
13915         var canvas = this.canvas,
13916         size = canvas.getSize(),
13917         config = this.config,
13918         orgHeight = size.height;
13919         
13920
13921         canvas.resize(width,orgHeight);
13922
13923         if(typeof FlashCanvas == "undefined") {
13924                 canvas.clear();
13925         } else {
13926                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13927         }
13928         this.loadJSON(json);
13929
13930         },
13931         
13932   loadJSON: function(json) {
13933     if(this.busy) return;
13934     this.busy = true;
13935     var prefix = $.time(), 
13936         ch = [], 
13937         st = this.st,
13938         name = $.splat(json.label), 
13939         color = $.splat(json.color || this.colors),
13940         config = this.config,
13941         canvas = this.canvas,
13942         gradient = !!config.type.split(":")[1],
13943         animate = config.animate,
13944         title = config.Title,
13945         subtitle = config.Subtitle,
13946         renderBackground = config.renderBackground,
13947         horz = config.orientation == 'horizontal',
13948         that = this,
13949                 colorLength = color.length,
13950                 nameLength = name.length,
13951                 totalValue = 0;
13952     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13953         var val = values[i];
13954         var valArray = $.splat(val.values);
13955         totalValue += parseFloat(valArray.sum());
13956     }
13957     
13958     
13959     var nameArray = new Array();
13960     var idArray = new Array();
13961     var valArray = new Array();
13962     var valuelabelArray = new Array();
13963     var linkArray = new Array();
13964     var titleArray = new Array();
13965     var percentageArray = new Array();
13966     
13967     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13968       var val = values[i];
13969       nameArray[i] = $.splat(val.label);
13970       idArray[i] = $.splat(prefix + val.label);
13971       valArray[i] = $.splat(val.values);
13972       valuelabelArray[i] = $.splat(val.valuelabels);
13973       linkArray[i] = $.splat(val.links);
13974       titleArray[i] = $.splat(val.titles);
13975       percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13976       var acum = 0;
13977     }
13978     
13979
13980     nameArray.reverse();
13981     valArray.reverse();
13982     valuelabelArray.reverse();
13983     linkArray.reverse();
13984     titleArray.reverse();
13985     percentageArray.reverse();
13986     
13987       ch.push({
13988         'id': prefix + val.label,
13989         'name': val.label,
13990         
13991         'data': {
13992           'value': valArray,
13993           '$idArray': idArray,
13994           '$linkArray': linkArray,
13995           '$titleArray': titleArray,
13996           '$valueArray': valArray,
13997           '$valuelabelArray': valuelabelArray,
13998           '$colorArray': color,
13999           '$colorMono': $.splat(color[i % colorLength]),
14000           '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
14001           '$gradient': gradient,
14002           '$config': config,
14003           '$percentageArray' : percentageArray,
14004           '$canvas': canvas,
14005           '$st': st
14006         },
14007         'children': []
14008       });
14009     
14010     var root = {
14011       'id': prefix + '$root',
14012       'name': '',
14013       'data': {
14014         '$type': 'none',
14015         '$width': 1,
14016         '$height': 1
14017       },
14018       'children': ch
14019     };
14020     st.loadJSON(root);
14021     
14022     this.normalizeDims();
14023         
14024         if(renderBackground) {
14025                 this.renderBackground();        
14026         }
14027         if(!animate && title.text) {
14028                 this.renderTitle();
14029         }
14030         if(!animate && subtitle.text) {
14031                 this.renderSubtitle();
14032         }
14033         if(typeof FlashCanvas == "undefined") {
14034                 this.renderDropShadow();
14035         }
14036     st.compute();
14037     st.select(st.root);
14038     if(animate) {
14039       if(horz) {
14040         st.fx.animate({
14041           modes: ['node-property:width:dimArray'],
14042           duration:1500,
14043           onComplete: function() {
14044             that.busy = false;
14045           }
14046         });
14047       } else {
14048         st.fx.animate({
14049           modes: ['node-property:height:dimArray'],
14050           duration:1500,
14051           onComplete: function() {
14052             that.busy = false;
14053           }
14054         });
14055       }
14056     } else {
14057       this.busy = false;
14058     }
14059   },
14060   
14061   /*
14062     Method: updateJSON
14063    
14064     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.
14065     
14066     Parameters:
14067     
14068     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
14069     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
14070     
14071     Example:
14072     
14073     (start code js)
14074     barChart.updateJSON(json, {
14075       onComplete: function() {
14076         alert('update complete!');
14077       }
14078     });
14079     (end code)
14080  */  
14081   updateJSON: function(json, onComplete) {
14082     if(this.busy) return;
14083     this.busy = true;
14084     
14085     var st = this.st;
14086     var graph = st.graph;
14087     var values = json.values;
14088     var animate = this.config.animate;
14089     var that = this;
14090     var horz = this.config.orientation == 'horizontal';
14091     $.each(values, function(v) {
14092       var n = graph.getByName(v.label);
14093       if(n) {
14094         n.setData('valueArray', $.splat(v.values));
14095         if(json.label) {
14096           n.setData('stringArray', $.splat(json.label));
14097         }
14098       }
14099     });
14100     this.normalizeDims();
14101     st.compute();
14102     st.select(st.root);
14103     if(animate) {
14104       if(horz) {
14105         st.fx.animate({
14106           modes: ['node-property:width:dimArray'],
14107           duration:1500,
14108           onComplete: function() {
14109             that.busy = false;
14110             onComplete && onComplete.onComplete();
14111           }
14112         });
14113       } else {
14114         st.fx.animate({
14115           modes: ['node-property:height:dimArray'],
14116           duration:1500,
14117           onComplete: function() {
14118             that.busy = false;
14119             onComplete && onComplete.onComplete();
14120           }
14121         });
14122       }
14123     }
14124   },
14125   
14126   //adds the little brown bar when hovering the node
14127   select: function(id, name) {
14128
14129     if(!this.config.hoveredColor) return;
14130     var s = this.selected;
14131     if(s.id != id || s.name != name) {
14132       s.id = id;
14133       s.name = name;
14134       s.color = this.config.hoveredColor;
14135       this.st.graph.eachNode(function(n) {
14136         if(id == n.id) {
14137           n.setData('border', s);
14138         } else {
14139           n.setData('border', false);
14140         }
14141       });
14142       this.st.plot();
14143     }
14144   },
14145   
14146   /*
14147     Method: getLegend
14148    
14149     Returns an object containing as keys the legend names and as values hex strings with color values.
14150     
14151     Example:
14152     
14153     (start code js)
14154     var legend = barChart.getLegend();
14155     (end code)
14156   */  
14157   getLegend: function() {
14158     var legend = new Array();
14159     var name = new Array();
14160     var color = new Array();
14161     var n;
14162     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14163       n = adj.nodeTo;
14164     });
14165     var colors = n.getData('colorArray'),
14166         len = colors.length;
14167     $.each(n.getData('stringArray'), function(s, i) {
14168       color[i] = colors[i % len];
14169       name[i] = s;
14170     });
14171         legend['name'] = name;
14172         legend['color'] = color;
14173     return legend;
14174   },
14175   
14176   /*
14177     Method: getMaxValue
14178    
14179     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14180     
14181     Example:
14182     
14183     (start code js)
14184     var ans = barChart.getMaxValue();
14185     (end code)
14186     
14187     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14188     
14189     Example:
14190     
14191     (start code js)
14192     //will return 100 for all BarChart instances,
14193     //displaying all of them with the same scale
14194     $jit.BarChart.implement({
14195       'getMaxValue': function() {
14196         return 100;
14197       }
14198     });
14199     (end code)
14200     
14201   */  
14202   getMaxValue: function() {
14203     var maxValue = 0, stacked = true;
14204     this.st.graph.eachNode(function(n) {
14205       var valArray = n.getData('valueArray'),
14206           acum = 0;
14207       if(!valArray) return;
14208       if(stacked) {
14209         $.each(valArray, function(v) { 
14210           acum += +v;
14211         });
14212       } else {
14213         acum = Math.max.apply(null, valArray);
14214       }
14215       maxValue = maxValue>acum? maxValue:acum;
14216     });
14217     return maxValue;
14218   },
14219   
14220   setBarType: function(type) {
14221     this.config.type = type;
14222     this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14223   },
14224   
14225   normalizeDims: function() {
14226     //number of elements
14227     var root = this.st.graph.getNode(this.st.root), l=0;
14228     root.eachAdjacency(function() {
14229       l++;
14230     });
14231     var maxValue = this.getMaxValue() || 1,
14232         size = this.st.canvas.getSize(),
14233         config = this.config,
14234         margin = config.Margin,
14235         title = config.Title,
14236         subtitle = config.Subtitle,
14237         marginWidth = margin.left + margin.right,
14238         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14239         horz = config.orientation == 'horizontal',
14240         animate = config.animate,
14241         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14242
14243           - (config.showLabels && (config.Label.size + config.labelOffset)),
14244         dim1 = horz? 'height':'width',
14245         dim2 = horz? 'width':'height';
14246         
14247
14248         minWidth = size.width/8;
14249         
14250
14251
14252     this.st.graph.eachNode(function(n) {
14253       var acum = 0, animateValue = [];
14254       $.each(n.getData('valueArray'), function(v) {
14255         acum += +v;
14256         animateValue.push(0);
14257       });
14258       n.setData(dim1, minWidth);
14259             
14260       if(animate) {
14261         n.setData(dim2, acum * height / maxValue, 'end');
14262         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14263           return n * height / maxValue; 
14264         }), 'end');
14265         var dimArray = n.getData('dimArray');
14266         if(!dimArray) {
14267           n.setData('dimArray', animateValue);
14268         }
14269       } else {
14270                         n.setData(dim2, acum * height / maxValue);
14271                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14272                           return n * height / maxValue; 
14273                         }));
14274       }
14275
14276     });
14277   }
14278 });
14279
14280
14281
14282 /*
14283  * File: Options.PieChart.js
14284  *
14285 */
14286 /*
14287   Object: Options.PieChart
14288   
14289   <PieChart> options. 
14290   Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14291   
14292   Syntax:
14293   
14294   (start code js)
14295
14296   Options.PieChart = {
14297     animate: true,
14298     offset: 25,
14299     sliceOffset:0,
14300     labelOffset: 3,
14301     type: 'stacked',
14302     hoveredColor: '#9fd4ff',
14303     showLabels: true,
14304     resizeLabels: false,
14305     updateHeights: false
14306   };  
14307
14308   (end code)
14309   
14310   Example:
14311   
14312   (start code js)
14313
14314   var pie = new $jit.PieChart({
14315     animate: true,
14316     sliceOffset: 5,
14317     type: 'stacked:gradient'
14318   });  
14319
14320   (end code)
14321   
14322   Parameters:
14323   
14324   animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14325   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14326   sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14327   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14328   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14329   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14330   showLabels - (boolean) Default's *true*. Display the name of the slots.
14331   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.
14332   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.
14333
14334 */
14335 Options.PieChart = {
14336   $extend: true,
14337
14338   animate: true,
14339   offset: 25, // page offset
14340   sliceOffset:0,
14341   labelOffset: 3, // label offset
14342   type: 'stacked', // gradient
14343   labelType: 'name',
14344   hoveredColor: '#9fd4ff',
14345   Events: {
14346     enable: false,
14347     onClick: $.empty
14348   },
14349   Tips: {
14350     enable: false,
14351     onShow: $.empty,
14352     onHide: $.empty
14353   },
14354   showLabels: true,
14355   resizeLabels: false,
14356   
14357   //only valid for mono-valued datasets
14358   updateHeights: false
14359 };
14360
14361 /*
14362  * Class: Layouts.Radial
14363  * 
14364  * Implements a Radial Layout.
14365  * 
14366  * Implemented By:
14367  * 
14368  * <RGraph>, <Hypertree>
14369  * 
14370  */
14371 Layouts.Radial = new Class({
14372
14373   /*
14374    * Method: compute
14375    * 
14376    * Computes nodes' positions.
14377    * 
14378    * Parameters:
14379    * 
14380    * property - _optional_ A <Graph.Node> position property to store the new
14381    * positions. Possible values are 'pos', 'end' or 'start'.
14382    * 
14383    */
14384   compute : function(property) {
14385     var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14386     NodeDim.compute(this.graph, prop, this.config);
14387     this.graph.computeLevels(this.root, 0, "ignore");
14388     var lengthFunc = this.createLevelDistanceFunc(); 
14389     this.computeAngularWidths(prop);
14390     this.computePositions(prop, lengthFunc);
14391   },
14392
14393   /*
14394    * computePositions
14395    * 
14396    * Performs the main algorithm for computing node positions.
14397    */
14398   computePositions : function(property, getLength) {
14399     var propArray = property;
14400     var graph = this.graph;
14401     var root = graph.getNode(this.root);
14402     var parent = this.parent;
14403     var config = this.config;
14404
14405     for ( var i=0, l=propArray.length; i < l; i++) {
14406       var pi = propArray[i];
14407       root.setPos($P(0, 0), pi);
14408       root.setData('span', Math.PI * 2, pi);
14409     }
14410
14411     root.angleSpan = {
14412       begin : 0,
14413       end : 2 * Math.PI
14414     };
14415
14416     graph.eachBFS(this.root, function(elem) {
14417       var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14418       var angleInit = elem.angleSpan.begin;
14419       var len = getLength(elem);
14420       //Calculate the sum of all angular widths
14421       var totalAngularWidths = 0, subnodes = [], maxDim = {};
14422       elem.eachSubnode(function(sib) {
14423         totalAngularWidths += sib._treeAngularWidth;
14424         //get max dim
14425         for ( var i=0, l=propArray.length; i < l; i++) {
14426           var pi = propArray[i], dim = sib.getData('dim', pi);
14427           maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14428         }
14429         subnodes.push(sib);
14430       }, "ignore");
14431       //Maintain children order
14432       //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14433       if (parent && parent.id == elem.id && subnodes.length > 0
14434           && subnodes[0].dist) {
14435         subnodes.sort(function(a, b) {
14436           return (a.dist >= b.dist) - (a.dist <= b.dist);
14437         });
14438       }
14439       //Calculate nodes positions.
14440       for (var k = 0, ls=subnodes.length; k < ls; k++) {
14441         var child = subnodes[k];
14442         if (!child._flag) {
14443           var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14444           var theta = angleInit + angleProportion / 2;
14445
14446           for ( var i=0, l=propArray.length; i < l; i++) {
14447             var pi = propArray[i];
14448             child.setPos($P(theta, len), pi);
14449             child.setData('span', angleProportion, pi);
14450             child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14451           }
14452
14453           child.angleSpan = {
14454             begin : angleInit,
14455             end : angleInit + angleProportion
14456           };
14457           angleInit += angleProportion;
14458         }
14459       }
14460     }, "ignore");
14461   },
14462
14463   /*
14464    * Method: setAngularWidthForNodes
14465    * 
14466    * Sets nodes angular widths.
14467    */
14468   setAngularWidthForNodes : function(prop) {
14469     this.graph.eachBFS(this.root, function(elem, i) {
14470       var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14471       elem._angularWidth = diamValue / i;
14472     }, "ignore");
14473   },
14474
14475   /*
14476    * Method: setSubtreesAngularWidth
14477    * 
14478    * Sets subtrees angular widths.
14479    */
14480   setSubtreesAngularWidth : function() {
14481     var that = this;
14482     this.graph.eachNode(function(elem) {
14483       that.setSubtreeAngularWidth(elem);
14484     }, "ignore");
14485   },
14486
14487   /*
14488    * Method: setSubtreeAngularWidth
14489    * 
14490    * Sets the angular width for a subtree.
14491    */
14492   setSubtreeAngularWidth : function(elem) {
14493     var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14494     elem.eachSubnode(function(child) {
14495       that.setSubtreeAngularWidth(child);
14496       sumAW += child._treeAngularWidth;
14497     }, "ignore");
14498     elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14499   },
14500
14501   /*
14502    * Method: computeAngularWidths
14503    * 
14504    * Computes nodes and subtrees angular widths.
14505    */
14506   computeAngularWidths : function(prop) {
14507     this.setAngularWidthForNodes(prop);
14508     this.setSubtreesAngularWidth();
14509   }
14510
14511 });
14512
14513
14514 /*
14515  * File: Sunburst.js
14516  */
14517
14518 /*
14519    Class: Sunburst
14520       
14521    A radial space filling tree visualization.
14522    
14523    Inspired by:
14524  
14525    Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14526    
14527    Note:
14528    
14529    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.
14530    
14531   Implements:
14532   
14533   All <Loader> methods
14534   
14535    Constructor Options:
14536    
14537    Inherits options from
14538    
14539    - <Options.Canvas>
14540    - <Options.Controller>
14541    - <Options.Node>
14542    - <Options.Edge>
14543    - <Options.Label>
14544    - <Options.Events>
14545    - <Options.Tips>
14546    - <Options.NodeStyles>
14547    - <Options.Navigation>
14548    
14549    Additionally, there are other parameters and some default values changed
14550    
14551    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14552    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
14553    Node.type - Described in <Options.Node>. Default's to *multipie*.
14554    Node.height - Described in <Options.Node>. Default's *0*.
14555    Edge.type - Described in <Options.Edge>. Default's *none*.
14556    Label.textAlign - Described in <Options.Label>. Default's *start*.
14557    Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14558      
14559    Instance Properties:
14560
14561    canvas - Access a <Canvas> instance.
14562    graph - Access a <Graph> instance.
14563    op - Access a <Sunburst.Op> instance.
14564    fx - Access a <Sunburst.Plot> instance.
14565    labels - Access a <Sunburst.Label> interface implementation.   
14566
14567 */
14568
14569 $jit.Sunburst = new Class({
14570
14571   Implements: [ Loader, Extras, Layouts.Radial ],
14572
14573   initialize: function(controller) {
14574     var $Sunburst = $jit.Sunburst;
14575
14576     var config = {
14577       interpolation: 'linear',
14578       levelDistance: 100,
14579       Node: {
14580         'type': 'multipie',
14581         'height':0
14582       },
14583       Edge: {
14584         'type': 'none'
14585       },
14586       Label: {
14587         textAlign: 'start',
14588         textBaseline: 'middle'
14589       }
14590     };
14591
14592     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14593         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14594
14595     var canvasConfig = this.config;
14596     if(canvasConfig.useCanvas) {
14597       this.canvas = canvasConfig.useCanvas;
14598       this.config.labelContainer = this.canvas.id + '-label';
14599     } else {
14600       if(canvasConfig.background) {
14601         canvasConfig.background = $.merge({
14602           type: 'Fade',
14603           colorStop1: this.config.colorStop1,
14604           colorStop2: this.config.colorStop2
14605         }, canvasConfig.background);
14606       }
14607       this.canvas = new Canvas(this, canvasConfig);
14608       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14609     }
14610
14611     this.graphOptions = {
14612       'complex': false,
14613       'Node': {
14614         'selected': false,
14615         'exist': true,
14616         'drawn': true
14617       }
14618     };
14619     this.graph = new Graph(this.graphOptions, this.config.Node,
14620         this.config.Edge);
14621     this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14622     this.fx = new $Sunburst.Plot(this, $Sunburst);
14623     this.op = new $Sunburst.Op(this);
14624     this.json = null;
14625     this.root = null;
14626     this.rotated = null;
14627     this.busy = false;
14628     // initialize extras
14629     this.initializeExtras();
14630   },
14631
14632   /* 
14633   
14634     createLevelDistanceFunc 
14635   
14636     Returns the levelDistance function used for calculating a node distance 
14637     to its origin. This function returns a function that is computed 
14638     per level and not per node, such that all nodes with the same depth will have the 
14639     same distance to the origin. The resulting function gets the 
14640     parent node as parameter and returns a float.
14641
14642    */
14643   createLevelDistanceFunc: function() {
14644     var ld = this.config.levelDistance;
14645     return function(elem) {
14646       return (elem._depth + 1) * ld;
14647     };
14648   },
14649
14650   /* 
14651      Method: refresh 
14652      
14653      Computes positions and plots the tree.
14654
14655    */
14656   refresh: function() {
14657     this.compute();
14658     this.plot();
14659   },
14660
14661   /*
14662    reposition
14663   
14664    An alias for computing new positions to _endPos_
14665
14666    See also:
14667
14668    <Sunburst.compute>
14669    
14670   */
14671   reposition: function() {
14672     this.compute('end');
14673   },
14674
14675   /*
14676   Method: rotate
14677   
14678   Rotates the graph so that the selected node is horizontal on the right.
14679
14680   Parameters:
14681   
14682   node - (object) A <Graph.Node>.
14683   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14684   opt - (object) Configuration options merged with this visualization configuration options.
14685   
14686   See also:
14687
14688   <Sunburst.rotateAngle>
14689   
14690   */
14691   rotate: function(node, method, opt) {
14692     var theta = node.getPos(opt.property || 'current').getp(true).theta;
14693     this.rotated = node;
14694     this.rotateAngle(-theta, method, opt);
14695   },
14696
14697   /*
14698   Method: rotateAngle
14699   
14700   Rotates the graph of an angle theta.
14701   
14702    Parameters:
14703    
14704    node - (object) A <Graph.Node>.
14705    method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14706    opt - (object) Configuration options merged with this visualization configuration options.
14707    
14708    See also:
14709
14710    <Sunburst.rotate>
14711   
14712   */
14713   rotateAngle: function(theta, method, opt) {
14714     var that = this;
14715     var options = $.merge(this.config, opt || {}, {
14716       modes: [ 'polar' ]
14717     });
14718     var prop = opt.property || (method === "animate" ? 'end' : 'current');
14719     if(method === 'animate') {
14720       this.fx.animation.pause();
14721     }
14722     this.graph.eachNode(function(n) {
14723       var p = n.getPos(prop);
14724       p.theta += theta;
14725       if (p.theta < 0) {
14726         p.theta += Math.PI * 2;
14727       }
14728     });
14729     if (method == 'animate') {
14730       this.fx.animate(options);
14731     } else if (method == 'replot') {
14732       this.fx.plot();
14733       this.busy = false;
14734     }
14735   },
14736
14737   /*
14738    Method: plot
14739   
14740    Plots the Sunburst. This is a shortcut to *fx.plot*.
14741   */
14742   plot: function() {
14743     this.fx.plot();
14744   }
14745 });
14746
14747 $jit.Sunburst.$extend = true;
14748
14749 (function(Sunburst) {
14750
14751   /*
14752      Class: Sunburst.Op
14753
14754      Custom extension of <Graph.Op>.
14755
14756      Extends:
14757
14758      All <Graph.Op> methods
14759      
14760      See also:
14761      
14762      <Graph.Op>
14763
14764   */
14765   Sunburst.Op = new Class( {
14766
14767     Implements: Graph.Op
14768
14769   });
14770
14771   /*
14772      Class: Sunburst.Plot
14773
14774     Custom extension of <Graph.Plot>.
14775   
14776     Extends:
14777   
14778     All <Graph.Plot> methods
14779     
14780     See also:
14781     
14782     <Graph.Plot>
14783   
14784   */
14785   Sunburst.Plot = new Class( {
14786
14787     Implements: Graph.Plot
14788
14789   });
14790
14791   /*
14792     Class: Sunburst.Label
14793
14794     Custom extension of <Graph.Label>. 
14795     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14796   
14797     Extends:
14798   
14799     All <Graph.Label> methods and subclasses.
14800   
14801     See also:
14802   
14803     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14804   
14805    */
14806   Sunburst.Label = {};
14807
14808   /*
14809      Sunburst.Label.Native
14810
14811      Custom extension of <Graph.Label.Native>.
14812
14813      Extends:
14814
14815      All <Graph.Label.Native> methods
14816
14817      See also:
14818
14819      <Graph.Label.Native>
14820   */
14821   Sunburst.Label.Native = new Class( {
14822     Implements: Graph.Label.Native,
14823
14824     initialize: function(viz) {
14825       this.viz = viz;
14826       this.label = viz.config.Label;
14827       this.config = viz.config;
14828     },
14829
14830     renderLabel: function(canvas, node, controller) {
14831       var span = node.getData('span');
14832       if(span < Math.PI /2 && Math.tan(span) * 
14833           this.config.levelDistance * node._depth < 10) {
14834         return;
14835       }
14836       var ctx = canvas.getCtx();
14837       var measure = ctx.measureText(node.name);
14838       if (node.id == this.viz.root) {
14839         var x = -measure.width / 2, y = 0, thetap = 0;
14840         var ld = 0;
14841       } else {
14842         var indent = 5;
14843         var ld = controller.levelDistance - indent;
14844         var clone = node.pos.clone();
14845         clone.rho += indent;
14846         var p = clone.getp(true);
14847         var ct = clone.getc(true);
14848         var x = ct.x, y = ct.y;
14849         // get angle in degrees
14850         var pi = Math.PI;
14851         var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14852         var thetap = cond ? p.theta + pi : p.theta;
14853         if (cond) {
14854           x -= Math.abs(Math.cos(p.theta) * measure.width);
14855           y += Math.sin(p.theta) * measure.width;
14856         } else if (node.id == this.viz.root) {
14857           x -= measure.width / 2;
14858         }
14859       }
14860       ctx.save();
14861       ctx.translate(x, y);
14862       ctx.rotate(thetap);
14863       ctx.fillText(node.name, 0, 0);
14864       ctx.restore();
14865     }
14866   });
14867
14868   /*
14869      Sunburst.Label.SVG
14870
14871     Custom extension of <Graph.Label.SVG>.
14872   
14873     Extends:
14874   
14875     All <Graph.Label.SVG> methods
14876   
14877     See also:
14878   
14879     <Graph.Label.SVG>
14880   
14881   */
14882   Sunburst.Label.SVG = new Class( {
14883     Implements: Graph.Label.SVG,
14884
14885     initialize: function(viz) {
14886       this.viz = viz;
14887     },
14888
14889     /* 
14890        placeLabel
14891
14892        Overrides abstract method placeLabel in <Graph.Plot>.
14893
14894        Parameters:
14895
14896        tag - A DOM label element.
14897        node - A <Graph.Node>.
14898        controller - A configuration/controller object passed to the visualization.
14899       
14900      */
14901     placeLabel: function(tag, node, controller) {
14902       var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14903       var radius = canvas.getSize();
14904       var labelPos = {
14905         x: Math.round(pos.x + radius.width / 2),
14906         y: Math.round(pos.y + radius.height / 2)
14907       };
14908       tag.setAttribute('x', labelPos.x);
14909       tag.setAttribute('y', labelPos.y);
14910
14911       var bb = tag.getBBox();
14912       if (bb) {
14913         // center the label
14914     var x = tag.getAttribute('x');
14915     var y = tag.getAttribute('y');
14916     // get polar coordinates
14917     var p = node.pos.getp(true);
14918     // get angle in degrees
14919     var pi = Math.PI;
14920     var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14921     if (cond) {
14922       tag.setAttribute('x', x - bb.width);
14923       tag.setAttribute('y', y - bb.height);
14924     } else if (node.id == viz.root) {
14925       tag.setAttribute('x', x - bb.width / 2);
14926     }
14927
14928     var thetap = cond ? p.theta + pi : p.theta;
14929     if(node._depth)
14930       tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14931           + ' ' + y + ')');
14932   }
14933
14934   controller.onPlaceLabel(tag, node);
14935 }
14936   });
14937
14938   /*
14939      Sunburst.Label.HTML
14940
14941      Custom extension of <Graph.Label.HTML>.
14942
14943      Extends:
14944
14945      All <Graph.Label.HTML> methods.
14946
14947      See also:
14948
14949      <Graph.Label.HTML>
14950
14951   */
14952   Sunburst.Label.HTML = new Class( {
14953     Implements: Graph.Label.HTML,
14954
14955     initialize: function(viz) {
14956       this.viz = viz;
14957     },
14958     /* 
14959        placeLabel
14960
14961        Overrides abstract method placeLabel in <Graph.Plot>.
14962
14963        Parameters:
14964
14965        tag - A DOM label element.
14966        node - A <Graph.Node>.
14967        controller - A configuration/controller object passed to the visualization.
14968       
14969      */
14970     placeLabel: function(tag, node, controller) {
14971       var pos = node.pos.clone(), 
14972           canvas = this.viz.canvas,
14973           height = node.getData('height'),
14974           ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14975           radius = canvas.getSize();
14976       pos.rho += ldist;
14977       pos = pos.getc(true);
14978       
14979       var labelPos = {
14980         x: Math.round(pos.x + radius.width / 2),
14981         y: Math.round(pos.y + radius.height / 2)
14982       };
14983
14984       var style = tag.style;
14985       style.left = labelPos.x + 'px';
14986       style.top = labelPos.y + 'px';
14987       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14988
14989       controller.onPlaceLabel(tag, node);
14990     }
14991   });
14992
14993   /*
14994     Class: Sunburst.Plot.NodeTypes
14995
14996     This class contains a list of <Graph.Node> built-in types. 
14997     Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14998
14999     You can add your custom node types, customizing your visualization to the extreme.
15000
15001     Example:
15002
15003     (start code js)
15004       Sunburst.Plot.NodeTypes.implement({
15005         'mySpecialType': {
15006           'render': function(node, canvas) {
15007             //print your custom node to canvas
15008           },
15009           //optional
15010           'contains': function(node, pos) {
15011             //return true if pos is inside the node or false otherwise
15012           }
15013         }
15014       });
15015     (end code)
15016
15017   */
15018   Sunburst.Plot.NodeTypes = new Class( {
15019     'none': {
15020       'render': $.empty,
15021       'contains': $.lambda(false),
15022       'anglecontains': function(node, pos) {
15023         var span = node.getData('span') / 2, theta = node.pos.theta;
15024         var begin = theta - span, end = theta + span;
15025         if (begin < 0)
15026           begin += Math.PI * 2;
15027         var atan = Math.atan2(pos.y, pos.x);
15028         if (atan < 0)
15029           atan += Math.PI * 2;
15030         if (begin > end) {
15031           return (atan > begin && atan <= Math.PI * 2) || atan < end;
15032         } else {
15033           return atan > begin && atan < end;
15034         }
15035       },
15036           'anglecontainsgauge': function(node, pos) {
15037         var span = node.getData('span') / 2, theta = node.pos.theta;
15038                 var config = node.getData('config');
15039         var ld = this.config.levelDistance;
15040                 var yOffset = pos.y-(ld/2);
15041                 var begin = ((theta - span)/2)+Math.PI,
15042         end = ((theta + span)/2)+Math.PI;
15043                 
15044         if (begin < 0)
15045           begin += Math.PI * 2;
15046         var atan = Math.atan2(yOffset, pos.x);
15047
15048                 
15049         if (atan < 0)
15050           atan += Math.PI * 2;
15051                   
15052                   
15053         if (begin > end) {
15054           return (atan > begin && atan <= Math.PI * 2) || atan < end;
15055         } else {
15056           return atan > begin && atan < end;
15057         }
15058       }
15059     },
15060
15061     'pie': {
15062       'render': function(node, canvas) {
15063         var span = node.getData('span') / 2, theta = node.pos.theta;
15064         var begin = theta - span, end = theta + span;
15065         var polarNode = node.pos.getp(true);
15066         var polar = new Polar(polarNode.rho, begin);
15067         var p1coord = polar.getc(true);
15068         polar.theta = end;
15069         var p2coord = polar.getc(true);
15070
15071         var ctx = canvas.getCtx();
15072         ctx.beginPath();
15073         ctx.moveTo(0, 0);
15074         ctx.lineTo(p1coord.x, p1coord.y);
15075         ctx.moveTo(0, 0);
15076         ctx.lineTo(p2coord.x, p2coord.y);
15077         ctx.moveTo(0, 0);
15078         ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
15079             false);
15080         ctx.fill();
15081       },
15082       'contains': function(node, pos) {
15083         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15084           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15085           var ld = this.config.levelDistance, d = node._depth;
15086           return (rho <= ld * d);
15087         }
15088         return false;
15089       }
15090     },
15091     'multipie': {
15092       'render': function(node, canvas) {
15093         var height = node.getData('height');
15094         var ldist = height? height : this.config.levelDistance;
15095         var span = node.getData('span') / 2, theta = node.pos.theta;
15096         var begin = theta - span, end = theta + span;
15097         var polarNode = node.pos.getp(true);
15098
15099         var polar = new Polar(polarNode.rho, begin);
15100         var p1coord = polar.getc(true);
15101
15102         polar.theta = end;
15103         var p2coord = polar.getc(true);
15104
15105         polar.rho += ldist;
15106         var p3coord = polar.getc(true);
15107
15108         polar.theta = begin;
15109         var p4coord = polar.getc(true);
15110
15111         var ctx = canvas.getCtx();
15112         ctx.moveTo(0, 0);
15113         ctx.beginPath();
15114         ctx.arc(0, 0, polarNode.rho, begin, end, false);
15115         ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15116         ctx.moveTo(p1coord.x, p1coord.y);
15117         ctx.lineTo(p4coord.x, p4coord.y);
15118         ctx.moveTo(p2coord.x, p2coord.y);
15119         ctx.lineTo(p3coord.x, p3coord.y);
15120         ctx.fill();
15121
15122         if (node.collapsed) {
15123           ctx.save();
15124           ctx.lineWidth = 2;
15125           ctx.moveTo(0, 0);
15126           ctx.beginPath();
15127           ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15128               true);
15129           ctx.stroke();
15130           ctx.restore();
15131         }
15132       },
15133       'contains': function(node, pos) {
15134         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15135           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15136           var height = node.getData('height');
15137           var ldist = height? height : this.config.levelDistance;
15138           var ld = this.config.levelDistance, d = node._depth;
15139           return (rho >= ld * d) && (rho <= (ld * d + ldist));
15140         }
15141         return false;
15142       }
15143     },
15144
15145     'gradient-multipie': {
15146       'render': function(node, canvas) {
15147         var ctx = canvas.getCtx();
15148         var height = node.getData('height');
15149         var ldist = height? height : this.config.levelDistance;
15150         var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15151             0, 0, node.getPos().rho + ldist);
15152
15153         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15154         $.each(colorArray, function(i) {
15155           ans.push(parseInt(i * 0.5, 10));
15156         });
15157         var endColor = $.rgbToHex(ans);
15158         radialGradient.addColorStop(0, endColor);
15159         radialGradient.addColorStop(1, node.getData('color'));
15160         ctx.fillStyle = radialGradient;
15161         this.nodeTypes['multipie'].render.call(this, node, canvas);
15162       },
15163       'contains': function(node, pos) {
15164         return this.nodeTypes['multipie'].contains.call(this, node, pos);
15165       }
15166     },
15167
15168     'gradient-pie': {
15169       'render': function(node, canvas) {
15170         var ctx = canvas.getCtx();
15171         var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15172             .getPos().rho);
15173
15174         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15175         $.each(colorArray, function(i) {
15176           ans.push(parseInt(i * 0.5, 10));
15177         });
15178         var endColor = $.rgbToHex(ans);
15179         radialGradient.addColorStop(1, endColor);
15180         radialGradient.addColorStop(0, node.getData('color'));
15181         ctx.fillStyle = radialGradient;
15182         this.nodeTypes['pie'].render.call(this, node, canvas);
15183       },
15184       'contains': function(node, pos) {
15185         return this.nodeTypes['pie'].contains.call(this, node, pos);
15186       }
15187     }
15188   });
15189
15190   /*
15191     Class: Sunburst.Plot.EdgeTypes
15192
15193     This class contains a list of <Graph.Adjacence> built-in types. 
15194     Edge types implemented are 'none', 'line' and 'arrow'.
15195   
15196     You can add your custom edge types, customizing your visualization to the extreme.
15197   
15198     Example:
15199   
15200     (start code js)
15201       Sunburst.Plot.EdgeTypes.implement({
15202         'mySpecialType': {
15203           'render': function(adj, canvas) {
15204             //print your custom edge to canvas
15205           },
15206           //optional
15207           'contains': function(adj, pos) {
15208             //return true if pos is inside the arc or false otherwise
15209           }
15210         }
15211       });
15212     (end code)
15213   
15214   */
15215   Sunburst.Plot.EdgeTypes = new Class({
15216     'none': $.empty,
15217     'line': {
15218       'render': function(adj, canvas) {
15219         var from = adj.nodeFrom.pos.getc(true),
15220             to = adj.nodeTo.pos.getc(true);
15221         this.edgeHelper.line.render(from, to, canvas);
15222       },
15223       'contains': function(adj, pos) {
15224         var from = adj.nodeFrom.pos.getc(true),
15225             to = adj.nodeTo.pos.getc(true);
15226         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15227       }
15228     },
15229     'arrow': {
15230       'render': function(adj, canvas) {
15231         var from = adj.nodeFrom.pos.getc(true),
15232             to = adj.nodeTo.pos.getc(true),
15233             dim = adj.getData('dim'),
15234             direction = adj.data.$direction,
15235             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15236         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15237       },
15238       'contains': function(adj, pos) {
15239         var from = adj.nodeFrom.pos.getc(true),
15240             to = adj.nodeTo.pos.getc(true);
15241         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15242       }
15243     },
15244     'hyperline': {
15245       'render': function(adj, canvas) {
15246         var from = adj.nodeFrom.pos.getc(),
15247             to = adj.nodeTo.pos.getc(),
15248             dim = Math.max(from.norm(), to.norm());
15249         this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15250       },
15251       'contains': $.lambda(false) //TODO(nico): Implement this!
15252     }
15253   });
15254
15255 })($jit.Sunburst);
15256
15257
15258 /*
15259  * File: PieChart.js
15260  *
15261 */
15262
15263 $jit.Sunburst.Plot.NodeTypes.implement({
15264   'piechart-stacked' : {
15265     'render' : function(node, canvas) {
15266       var pos = node.pos.getp(true),
15267           dimArray = node.getData('dimArray'),
15268           valueArray = node.getData('valueArray'),
15269           colorArray = node.getData('colorArray'),
15270           colorLength = colorArray.length,
15271           stringArray = node.getData('stringArray'),
15272           span = node.getData('span') / 2,
15273           theta = node.pos.theta,
15274           begin = theta - span,
15275           end = theta + span,
15276           polar = new Polar;
15277     
15278       var ctx = canvas.getCtx(), 
15279           opt = {},
15280           gradient = node.getData('gradient'),
15281           border = node.getData('border'),
15282           config = node.getData('config'),
15283           showLabels = config.showLabels,
15284           resizeLabels = config.resizeLabels,
15285           label = config.Label;
15286
15287       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15288       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15289
15290       if (colorArray && dimArray && stringArray) {
15291         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15292           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15293           if(dimi <= 0) continue;
15294           ctx.fillStyle = ctx.strokeStyle = colori;
15295           if(gradient && dimi) {
15296             var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15297                 xpos, ypos, acum + dimi + config.sliceOffset);
15298             var colorRgb = $.hexToRgb(colori), 
15299                 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15300                 endColor = $.rgbToHex(ans);
15301
15302             radialGradient.addColorStop(0, colori);
15303             radialGradient.addColorStop(0.5, colori);
15304             radialGradient.addColorStop(1, endColor);
15305             ctx.fillStyle = radialGradient;
15306           }
15307           
15308           polar.rho = acum + config.sliceOffset;
15309           polar.theta = begin;
15310           var p1coord = polar.getc(true);
15311           polar.theta = end;
15312           var p2coord = polar.getc(true);
15313           polar.rho += dimi;
15314           var p3coord = polar.getc(true);
15315           polar.theta = begin;
15316           var p4coord = polar.getc(true);
15317
15318           ctx.beginPath();
15319           //fixing FF arc method + fill
15320           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15321           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15322           ctx.fill();
15323           if(border && border.name == stringArray[i]) {
15324             opt.acum = acum;
15325             opt.dimValue = dimArray[i];
15326             opt.begin = begin;
15327             opt.end = end;
15328           }
15329           acum += (dimi || 0);
15330           valAcum += (valueArray[i] || 0);
15331         }
15332         if(border) {
15333           ctx.save();
15334           ctx.globalCompositeOperation = "source-over";
15335           ctx.lineWidth = 2;
15336           ctx.strokeStyle = border.color;
15337           var s = begin < end? 1 : -1;
15338           ctx.beginPath();
15339           //fixing FF arc method + fill
15340           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15341           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15342           ctx.closePath();
15343           ctx.stroke();
15344           ctx.restore();
15345         }
15346         if(showLabels && label.type == 'Native') {
15347           ctx.save();
15348           ctx.fillStyle = ctx.strokeStyle = label.color;
15349           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15350               fontSize = (label.size * scale) >> 0;
15351           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15352           
15353           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15354           ctx.textBaseline = 'middle';
15355           ctx.textAlign = 'center';
15356           
15357           polar.rho = acum + config.labelOffset + config.sliceOffset;
15358           polar.theta = node.pos.theta;
15359           var cart = polar.getc(true);
15360           
15361           ctx.fillText(node.name, cart.x, cart.y);
15362           ctx.restore();
15363         }
15364       }
15365     },
15366     'contains': function(node, pos) {
15367       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15368         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15369         var ld = this.config.levelDistance, d = node._depth;
15370         var config = node.getData('config');
15371         if(rho <=ld * d + config.sliceOffset) {
15372           var dimArray = node.getData('dimArray');
15373           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15374             var dimi = dimArray[i];
15375             if(rho >= acum && rho <= acum + dimi) {
15376               return {
15377                 name: node.getData('stringArray')[i],
15378                 color: node.getData('colorArray')[i],
15379                 value: node.getData('valueArray')[i],
15380                 label: node.name
15381               };
15382             }
15383             acum += dimi;
15384           }
15385         }
15386         return false;
15387         
15388       }
15389       return false;
15390     }
15391   },
15392     'piechart-basic' : {
15393     'render' : function(node, canvas) {
15394       var pos = node.pos.getp(true),
15395           dimArray = node.getData('dimArray'),
15396           valueArray = node.getData('valueArray'),
15397           colorArray = node.getData('colorMono'),
15398           colorLength = colorArray.length,
15399           stringArray = node.getData('stringArray'),
15400                   percentage = node.getData('percentage'),
15401                   iteration = node.getData('iteration'),
15402           span = node.getData('span') / 2,
15403           theta = node.pos.theta,
15404           begin = theta - span,
15405           end = theta + span,
15406           polar = new Polar;
15407     
15408       var ctx = canvas.getCtx(), 
15409           opt = {},
15410           gradient = node.getData('gradient'),
15411           border = node.getData('border'),
15412           config = node.getData('config'),
15413           renderSubtitle = node.getData('renderSubtitle'),
15414           renderBackground = config.renderBackground,
15415           showLabels = config.showLabels,
15416           resizeLabels = config.resizeLabels,
15417           label = config.Label;
15418
15419       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15420       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15421       //background rendering for IE
15422                 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15423                         backgroundColor = config.backgroundColor,
15424                         size = canvas.getSize();
15425                         ctx.save();
15426                     ctx.fillStyle = backgroundColor;
15427                     ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15428                     
15429                     //subtitle
15430
15431                         var margin = config.Margin,
15432                         title = config.Title,
15433                         subtitle = config.Subtitle;
15434                         ctx.fillStyle = title.color;
15435                         ctx.textAlign = 'left';
15436                         
15437                         if(title.text != "") {
15438                                 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15439                                 ctx.moveTo(0,0);
15440                                 if(label.type == 'Native') {
15441                                         ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15442                                 }
15443                         }       
15444         
15445                         if(subtitle.text != "") {
15446                                 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15447                                 if(label.type == 'Native') {
15448                                         ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15449                                 } 
15450                         }
15451                         ctx.restore();          
15452                 }
15453       if (colorArray && dimArray && stringArray) {
15454         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15455           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15456           if(dimi <= 0) continue;
15457           ctx.fillStyle = ctx.strokeStyle = colori;
15458           
15459           polar.rho = acum + config.sliceOffset;
15460           polar.theta = begin;
15461           var p1coord = polar.getc(true);
15462           polar.theta = end;
15463           var p2coord = polar.getc(true);
15464           polar.rho += dimi;
15465           var p3coord = polar.getc(true);
15466           polar.theta = begin;
15467           var p4coord = polar.getc(true);
15468           
15469           if(typeof FlashCanvas == "undefined") {
15470                   //drop shadow
15471                   ctx.beginPath();
15472                   ctx.fillStyle = "rgba(0,0,0,.2)";
15473                   ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15474                   ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);    
15475                   ctx.fill();
15476                   
15477                   if(gradient && dimi) {
15478                     var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15479                         xpos, ypos, acum + dimi + config.sliceOffset);
15480                     var colorRgb = $.hexToRgb(colori), 
15481                         endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15482                         endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15483         
15484                     radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15485                     radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15486                                 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15487                     radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15488                     ctx.fillStyle = radialGradient;
15489                   }
15490           }
15491
15492           
15493           //fixing FF arc method + fill
15494           ctx.beginPath();
15495           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15496           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15497           ctx.fill();
15498           if(border && border.name == stringArray[i]) {
15499             opt.acum = acum;
15500             opt.dimValue = dimArray[i];
15501             opt.begin = begin;
15502             opt.end = end;
15503             opt.sliceValue = valueArray[i];
15504           }
15505           acum += (dimi || 0);
15506           valAcum += (valueArray[i] || 0);
15507         }
15508         if(border) {
15509           ctx.save();
15510           ctx.globalCompositeOperation = "source-over";
15511           ctx.lineWidth = 2;
15512           ctx.strokeStyle = border.color;
15513           var s = begin < end? 1 : -1;
15514           ctx.beginPath();
15515           //fixing FF arc method + fill
15516           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15517           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15518           ctx.closePath();
15519           ctx.stroke();
15520           ctx.restore();
15521         }
15522         if(showLabels && label.type == 'Native') {
15523           ctx.save();
15524           ctx.fillStyle = ctx.strokeStyle = label.color;
15525           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15526               fontSize = (label.size * scale) >> 0;
15527           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15528           
15529           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15530           ctx.textBaseline = 'middle';
15531           ctx.textAlign = 'center';
15532           pi = Math.PI;
15533           angle = theta * 360 / (2 * pi);
15534           polar.rho = acum + config.labelOffset + config.sliceOffset;
15535           polar.theta = node.pos.theta;
15536           var cart = polar.getc(true);
15537           if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15538                 
15539                 } else {
15540                   if(config.labelType == 'name') {
15541                                 ctx.fillText(node.name, cart.x, cart.y);
15542                           } else {
15543                                 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15544                           }
15545           }
15546           ctx.restore();
15547         }
15548       }
15549     },
15550     'contains': function(node, pos) {
15551       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15552         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15553         var ld = this.config.levelDistance, d = node._depth;
15554         var config = node.getData('config');
15555
15556         if(rho <=ld * d + config.sliceOffset) {
15557           var dimArray = node.getData('dimArray');
15558           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15559             var dimi = dimArray[i];
15560             if(rho >= acum && rho <= acum + dimi) {
15561                           var url = Url.decode(node.getData('linkArray')[i]);
15562               return {
15563                 name: node.getData('stringArray')[i],
15564                 link: url,
15565                 color: node.getData('colorArray')[i],
15566                 value: node.getData('valueArray')[i],
15567                 percentage: node.getData('percentage'),
15568                 valuelabel: node.getData('valuelabelsArray')[i],
15569                 label: node.name
15570               };
15571             }
15572             acum += dimi;
15573           }
15574         }
15575         return false;
15576         
15577       }
15578       return false;
15579     }
15580   }
15581 });
15582
15583 /*
15584   Class: PieChart
15585   
15586   A visualization that displays stacked bar charts.
15587   
15588   Constructor Options:
15589   
15590   See <Options.PieChart>.
15591
15592 */
15593 $jit.PieChart = new Class({
15594   sb: null,
15595   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15596   selected: {},
15597   busy: false,
15598   
15599   initialize: function(opt) {
15600     this.controller = this.config = 
15601       $.merge(Options("Canvas", "PieChart", "Label"), {
15602         Label: { type: 'Native' }
15603       }, opt);
15604     this.initializeViz();
15605   },
15606   
15607   initializeViz: function() {
15608     var config = this.config, that = this;
15609     var nodeType = config.type.split(":")[0];
15610     var sb = new $jit.Sunburst({
15611       injectInto: config.injectInto,
15612       useCanvas: config.useCanvas,
15613       withLabels: config.Label.type != 'Native',
15614       background: config.background,
15615       renderBackground: config.renderBackground,
15616       backgroundColor: config.backgroundColor,
15617       colorStop1: config.colorStop1,
15618       colorStop2: config.colorStop2,
15619       Label: {
15620         type: config.Label.type
15621       },
15622       Node: {
15623         overridable: true,
15624         type: 'piechart-' + nodeType,
15625         width: 1,
15626         height: 1
15627       },
15628       Edge: {
15629         type: 'none'
15630       },
15631       Tips: {
15632         enable: config.Tips.enable,
15633         type: 'Native',
15634         force: true,
15635         onShow: function(tip, node, contains) {
15636           var elem = contains;
15637           config.Tips.onShow(tip, elem, node);
15638                           if(elem.link != 'undefined' && elem.link != '') {
15639                                 document.body.style.cursor = 'pointer';
15640                           }
15641         },
15642                 onHide: function() {
15643                                 document.body.style.cursor = 'default';
15644         }
15645       },
15646       Events: {
15647         enable: true,
15648         type: 'Native',
15649         onClick: function(node, eventInfo, evt) {
15650           if(!config.Events.enable) return;
15651           var elem = eventInfo.getContains();
15652           config.Events.onClick(elem, eventInfo, evt);
15653         },
15654         onMouseMove: function(node, eventInfo, evt) {
15655           if(!config.hoveredColor) return;
15656           if(node) {
15657             var elem = eventInfo.getContains();
15658             that.select(node.id, elem.name, elem.index);
15659           } else {
15660             that.select(false, false, false);
15661           }
15662         }
15663       },
15664       onCreateLabel: function(domElement, node) {
15665         var labelConf = config.Label;
15666         if(config.showLabels) {
15667           var style = domElement.style;
15668           style.fontSize = labelConf.size + 'px';
15669           style.fontFamily = labelConf.family;
15670           style.color = labelConf.color;
15671           style.textAlign = 'center';
15672           if(config.labelType == 'name') {
15673                 domElement.innerHTML = node.name;
15674           } else {
15675                 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15676           }
15677           domElement.style.width = '400px';
15678         }
15679       },
15680       onPlaceLabel: function(domElement, node) {
15681         if(!config.showLabels) return;
15682         var pos = node.pos.getp(true),
15683             dimArray = node.getData('dimArray'),
15684             span = node.getData('span') / 2,
15685             theta = node.pos.theta,
15686             begin = theta - span,
15687             end = theta + span,
15688             polar = new Polar;
15689
15690         var showLabels = config.showLabels,
15691             resizeLabels = config.resizeLabels,
15692             label = config.Label;
15693         
15694         if (dimArray) {
15695           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15696             acum += dimArray[i];
15697           }
15698           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15699               fontSize = (label.size * scale) >> 0;
15700           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15701           domElement.style.fontSize = fontSize + 'px';
15702           polar.rho = acum + config.labelOffset + config.sliceOffset;
15703           polar.theta = (begin + end) / 2;
15704           var pos = polar.getc(true);
15705           var radius = that.canvas.getSize();
15706           var labelPos = {
15707             x: Math.round(pos.x + radius.width / 2),
15708             y: Math.round(pos.y + radius.height / 2)
15709           };
15710           domElement.style.left = (labelPos.x - 200) + 'px';
15711           domElement.style.top = labelPos.y + 'px';
15712         }
15713       }
15714     });
15715     
15716     var size = sb.canvas.getSize(),
15717         min = Math.min;
15718     sb.config.levelDistance = min(size.width, size.height)/2 
15719       - config.offset - config.sliceOffset;
15720     this.sb = sb;
15721     this.canvas = this.sb.canvas;
15722     this.canvas.getCtx().globalCompositeOperation = 'lighter';
15723   },
15724     renderBackground: function() {
15725                 var canvas = this.canvas,
15726                 config = this.config,
15727                 backgroundColor = config.backgroundColor,
15728                 size = canvas.getSize(),
15729                 ctx = canvas.getCtx();
15730                 ctx.globalCompositeOperation = "destination-over";
15731             ctx.fillStyle = backgroundColor;
15732             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
15733   },
15734    renderTitle: function() {
15735         var canvas = this.canvas,
15736         size = canvas.getSize(),
15737         config = this.config,
15738         margin = config.Margin,
15739         radius = this.sb.config.levelDistance,
15740         title = config.Title,
15741         label = config.Label,
15742         subtitle = config.Subtitle;
15743         ctx = canvas.getCtx();
15744         ctx.fillStyle = title.color;
15745         ctx.textAlign = 'left';
15746         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15747         ctx.moveTo(0,0);
15748         if(label.type == 'Native') {
15749                 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15750         }       
15751   },
15752   renderSubtitle: function() {
15753         var canvas = this.canvas,
15754         size = canvas.getSize(),
15755         config = this.config,
15756         margin = config.Margin,
15757         radius = this.sb.config.levelDistance,
15758         title = config.Title,
15759         label = config.Label,
15760         subtitle = config.Subtitle;
15761         ctx = canvas.getCtx();
15762         ctx.fillStyle = title.color;
15763         ctx.textAlign = 'left';
15764         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15765         ctx.moveTo(0,0);
15766         if(label.type == 'Native') {
15767                 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15768         }       
15769   },
15770   clear: function() {
15771         var canvas = this.canvas;
15772         var ctx = canvas.getCtx(),
15773         size = canvas.getSize();
15774         ctx.fillStyle = "rgba(255,255,255,0)";
15775         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15776         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15777   },
15778   resizeGraph: function(json,width) {
15779         var canvas = this.canvas,
15780         size = canvas.getSize(),
15781         config = this.config,
15782         orgHeight = size.height;
15783         
15784         canvas.resize(width,orgHeight);
15785         if(typeof FlashCanvas == "undefined") {
15786                 canvas.clear();
15787         } else {
15788                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15789         }
15790         this.loadJSON(json);
15791
15792         },
15793   /*
15794     Method: loadJSON
15795    
15796     Loads JSON data into the visualization. 
15797     
15798     Parameters:
15799     
15800     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>.
15801     
15802     Example:
15803     (start code js)
15804     var pieChart = new $jit.PieChart(options);
15805     pieChart.loadJSON(json);
15806     (end code)
15807   */  
15808   loadJSON: function(json) {
15809     var prefix = $.time(), 
15810         ch = [], 
15811         sb = this.sb,
15812         name = $.splat(json.label),
15813         nameLength = name.length,
15814         color = $.splat(json.color || this.colors),
15815         colorLength = color.length,
15816         config = this.config,
15817         renderBackground = config.renderBackground,
15818         title = config.Title,
15819                 subtitle = config.Subtitle,
15820         gradient = !!config.type.split(":")[1],
15821         animate = config.animate,
15822         mono = nameLength == 1;
15823         totalValue = 0;
15824     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15825         var val = values[i];
15826         var valArray = $.splat(val.values);
15827         totalValue += parseFloat(valArray.sum());
15828     }
15829
15830     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15831       var val = values[i];
15832       var valArray = $.splat(val.values);
15833           var percentage = (valArray.sum()/totalValue) * 100;
15834
15835       var linkArray = $.splat(val.links);
15836       var valuelabelsArray = $.splat(val.valuelabels);
15837       
15838  
15839       ch.push({
15840         'id': prefix + val.label,
15841         'name': val.label,
15842         'data': {
15843           'value': valArray,
15844           'valuelabel': valuelabelsArray,
15845           '$linkArray': linkArray,
15846           '$valuelabelsArray': valuelabelsArray,
15847           '$valueArray': valArray,
15848           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15849           '$colorMono': $.splat(color[i % colorLength]),
15850           '$stringArray': name,
15851           '$gradient': gradient,
15852           '$config': config,
15853           '$iteration': i,
15854           '$percentage': percentage.toFixed(1),
15855           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15856         },
15857         'children': []
15858       });
15859     }
15860     var root = {
15861       'id': prefix + '$root',
15862       'name': '',
15863       'data': {
15864         '$type': 'none',
15865         '$width': 1,
15866         '$height': 1
15867       },
15868       'children': ch
15869     };
15870     sb.loadJSON(root);
15871     
15872     
15873     this.normalizeDims();
15874
15875     
15876     sb.refresh();
15877     if(title.text != "") {
15878         this.renderTitle();
15879     }
15880        
15881     if(subtitle.text != "") {
15882         this.renderSubtitle();
15883     }
15884      if(renderBackground && typeof FlashCanvas == "undefined") {
15885         this.renderBackground();        
15886     }
15887     
15888     if(animate) {
15889       sb.fx.animate({
15890         modes: ['node-property:dimArray'],
15891         duration:1500
15892       });
15893     }
15894   },
15895   
15896   /*
15897     Method: updateJSON
15898    
15899     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.
15900     
15901     Parameters:
15902     
15903     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15904     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15905     
15906     Example:
15907     
15908     (start code js)
15909     pieChart.updateJSON(json, {
15910       onComplete: function() {
15911         alert('update complete!');
15912       }
15913     });
15914     (end code)
15915   */  
15916   updateJSON: function(json, onComplete) {
15917     if(this.busy) return;
15918     this.busy = true;
15919     
15920     var sb = this.sb;
15921     var graph = sb.graph;
15922     var values = json.values;
15923     var animate = this.config.animate;
15924     var that = this;
15925     $.each(values, function(v) {
15926       var n = graph.getByName(v.label),
15927           vals = $.splat(v.values);
15928       if(n) {
15929         n.setData('valueArray', vals);
15930         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15931         if(json.label) {
15932           n.setData('stringArray', $.splat(json.label));
15933         }
15934       }
15935     });
15936     this.normalizeDims();
15937     if(animate) {
15938       sb.compute('end');
15939       sb.fx.animate({
15940         modes: ['node-property:dimArray:span', 'linear'],
15941         duration:1500,
15942         onComplete: function() {
15943           that.busy = false;
15944           onComplete && onComplete.onComplete();
15945         }
15946       });
15947     } else {
15948       sb.refresh();
15949     }
15950   },
15951     
15952   //adds the little brown bar when hovering the node
15953   select: function(id, name) {
15954     if(!this.config.hoveredColor) return;
15955     var s = this.selected;
15956     if(s.id != id || s.name != name) {
15957       s.id = id;
15958       s.name = name;
15959       s.color = this.config.hoveredColor;
15960       this.sb.graph.eachNode(function(n) {
15961         if(id == n.id) {
15962           n.setData('border', s);
15963         } else {
15964           n.setData('border', false);
15965         }
15966       });
15967       this.sb.plot();
15968     }
15969   },
15970   
15971   /*
15972     Method: getLegend
15973    
15974     Returns an object containing as keys the legend names and as values hex strings with color values.
15975     
15976     Example:
15977     
15978     (start code js)
15979     var legend = pieChart.getLegend();
15980     (end code)
15981   */  
15982   getLegend: function() {
15983     var legend = new Array();
15984     var name = new Array();
15985     var color = new Array();
15986     var n;
15987     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15988       n = adj.nodeTo;
15989     });
15990     var colors = n.getData('colorArray'),
15991         len = colors.length;
15992     $.each(n.getData('stringArray'), function(s, i) {
15993       color[i] = colors[i % len];
15994       name[i] = s;
15995     });
15996         legend['name'] = name;
15997         legend['color'] = color;
15998     return legend;
15999   },
16000   
16001   /*
16002     Method: getMaxValue
16003    
16004     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16005     
16006     Example:
16007     
16008     (start code js)
16009     var ans = pieChart.getMaxValue();
16010     (end code)
16011     
16012     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16013     
16014     Example:
16015     
16016     (start code js)
16017     //will return 100 for all PieChart instances,
16018     //displaying all of them with the same scale
16019     $jit.PieChart.implement({
16020       'getMaxValue': function() {
16021         return 100;
16022       }
16023     });
16024     (end code)
16025     
16026   */  
16027   getMaxValue: function() {
16028     var maxValue = 0;
16029     this.sb.graph.eachNode(function(n) {
16030       var valArray = n.getData('valueArray'),
16031           acum = 0;
16032       $.each(valArray, function(v) { 
16033         acum += +v;
16034       });
16035       maxValue = maxValue>acum? maxValue:acum;
16036     });
16037     return maxValue;
16038   },
16039   
16040   normalizeDims: function() {
16041     //number of elements
16042     var root = this.sb.graph.getNode(this.sb.root), l=0;
16043     root.eachAdjacency(function() {
16044       l++;
16045     });
16046     var maxValue = this.getMaxValue() || 1,
16047         config = this.config,
16048         animate = config.animate,
16049         rho = this.sb.config.levelDistance;
16050     this.sb.graph.eachNode(function(n) {
16051       var acum = 0, animateValue = [];
16052       $.each(n.getData('valueArray'), function(v) {
16053         acum += +v;
16054         animateValue.push(1);
16055       });
16056       var stat = (animateValue.length == 1) && !config.updateHeights;
16057       if(animate) {
16058         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16059           return stat? rho: (n * rho / maxValue); 
16060         }), 'end');
16061         var dimArray = n.getData('dimArray');
16062         if(!dimArray) {
16063           n.setData('dimArray', animateValue);
16064         }
16065       } else {
16066         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16067           return stat? rho : (n * rho / maxValue); 
16068         }));
16069       }
16070       n.setData('normalizedDim', acum / maxValue);
16071     });
16072   }
16073 });
16074
16075
16076 //Gauge Chart
16077
16078 Options.GaugeChart = {
16079   $extend: true,
16080
16081   animate: true,
16082   offset: 25, // page offset
16083   sliceOffset:0,
16084   labelOffset: 3, // label offset
16085   type: 'stacked', // gradient
16086   labelType: 'name',
16087   hoveredColor: '#9fd4ff',
16088   Events: {
16089     enable: false,
16090     onClick: $.empty
16091   },
16092   Tips: {
16093     enable: false,
16094     onShow: $.empty,
16095     onHide: $.empty
16096   },
16097   showLabels: true,
16098   resizeLabels: false,
16099   
16100   //only valid for mono-valued datasets
16101   updateHeights: false
16102 };
16103
16104
16105
16106 $jit.Sunburst.Plot.NodeTypes.implement({
16107     'gaugechart-basic' : {
16108     'render' : function(node, canvas) {
16109       var pos = node.pos.getp(true),
16110           dimArray = node.getData('dimArray'),
16111           valueArray = node.getData('valueArray'),
16112           valuelabelsArray = node.getData('valuelabelsArray'),
16113           gaugeTarget = node.getData('gaugeTarget'),
16114           nodeIteration = node.getData('nodeIteration'),
16115           nodeLength = node.getData('nodeLength'),
16116           colorArray = node.getData('colorMono'),
16117           colorLength = colorArray.length,
16118           stringArray = node.getData('stringArray'),
16119           span = node.getData('span') / 2,
16120           theta = node.pos.theta,
16121           begin = ((theta - span)/2)+Math.PI,
16122           end = ((theta + span)/2)+Math.PI,
16123           polar = new Polar;
16124
16125   
16126       var ctx = canvas.getCtx(), 
16127           opt = {},
16128           gradient = node.getData('gradient'),
16129           border = node.getData('border'),
16130           config = node.getData('config'),
16131           showLabels = config.showLabels,
16132           resizeLabels = config.resizeLabels,
16133           label = config.Label;
16134
16135       var xpos = Math.cos((begin + end) /2);
16136       var ypos = Math.sin((begin + end) /2);
16137
16138       if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16139         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16140           var dimi = dimArray[i], colori = colorArray[i % colorLength];
16141           if(dimi <= 0) continue;
16142           ctx.fillStyle = ctx.strokeStyle = colori;
16143           if(gradient && dimi) {
16144             var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16145                 xpos, (ypos + dimi/2), acum + dimi);
16146             var colorRgb = $.hexToRgb(colori), 
16147                 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16148                 endColor = $.rgbToHex(ans);
16149
16150             radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16151             radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16152             radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16153             radialGradient.addColorStop(1,  'rgba('+ans+',1)');
16154             ctx.fillStyle = radialGradient;
16155           }
16156           
16157           polar.rho = acum;
16158           polar.theta = begin;
16159           var p1coord = polar.getc(true);
16160           polar.theta = end;
16161           var p2coord = polar.getc(true);
16162           polar.rho += dimi;
16163           var p3coord = polar.getc(true);
16164           polar.theta = begin;
16165           var p4coord = polar.getc(true);
16166
16167                   
16168           ctx.beginPath();
16169           //fixing FF arc method + fill
16170           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16171           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16172           ctx.fill();
16173                   
16174
16175           acum += (dimi || 0);
16176           valAcum += (valueArray[i] || 0);                
16177         }
16178                 
16179                 if(showLabels && label.type == 'Native') {
16180                           ctx.save();
16181                           ctx.fillStyle = ctx.strokeStyle = label.color;
16182
16183                           
16184                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16185                           ctx.textBaseline = 'bottom';
16186                           ctx.textAlign = 'center';
16187
16188                           polar.rho = acum * .65;
16189                           polar.theta = begin;
16190                           var cart = polar.getc(true);
16191                           
16192                           //changes y pos of first label
16193                           if(nodeIteration == 1) {
16194                                 textY = cart.y - (label.size/2) + acum /2;
16195                           } else {
16196                                 textY = cart.y + acum/2;
16197                           }
16198                           
16199                           if(config.labelType == 'name') {
16200                                 ctx.fillText(node.name, cart.x, textY);
16201                           } else {
16202                                 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16203                           }
16204                           
16205                           //adds final label
16206                           if(nodeIteration == nodeLength) {
16207                                 polar.theta = end;
16208                                 var cart = polar.getc(true);
16209                                 if(config.labelType == 'name') {
16210                                         ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16211                                 } else {
16212                                         ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16213                                 }
16214                                 
16215                           }
16216                           ctx.restore();
16217                 }
16218
16219       }
16220       },
16221     'contains': function(node, pos) {
16222                 
16223                 
16224                 
16225       if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16226                 var config = node.getData('config');
16227         var ld = this.config.levelDistance , d = node._depth;
16228                 var yOffset = pos.y - (ld/2);
16229                 var xOffset = pos.x;
16230         var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16231         if(rho <=parseInt(ld * d)) {
16232           var dimArray = node.getData('dimArray');
16233           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16234                         var dimi = dimArray[i];
16235             if(rho >= ld * .8 && rho <= acum + dimi) {
16236                 
16237                           var url = Url.decode(node.getData('linkArray')[i]);
16238               return {
16239                 name: node.getData('stringArray')[i],
16240                 link: url,
16241                 color: node.getData('colorArray')[i],
16242                 value: node.getData('valueArray')[i],
16243                 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16244                 label: node.name
16245               };
16246             }
16247             acum += dimi;
16248                         
16249                         
16250           }
16251         }
16252         return false;
16253         
16254       }
16255       return false;
16256     }
16257   }
16258 });
16259
16260 /*
16261   Class: GaugeChart
16262   
16263   A visualization that displays gauge charts
16264   
16265   Constructor Options:
16266   
16267   See <Options.Gauge>.
16268
16269 */
16270 $jit.GaugeChart = new Class({
16271   sb: null,
16272   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16273   selected: {},
16274   busy: false,
16275   
16276   initialize: function(opt) {
16277     this.controller = this.config = 
16278       $.merge(Options("Canvas", "GaugeChart", "Label"), {
16279         Label: { type: 'Native' }
16280       }, opt);
16281     this.initializeViz();
16282   },
16283   
16284   initializeViz: function() {
16285     var config = this.config, that = this;
16286     var nodeType = config.type.split(":")[0];
16287     var sb = new $jit.Sunburst({
16288       injectInto: config.injectInto,
16289       useCanvas: config.useCanvas,
16290       withLabels: config.Label.type != 'Native',
16291       background: config.background,
16292       renderBackground: config.renderBackground,
16293       backgroundColor: config.backgroundColor,
16294       colorStop1: config.colorStop1,
16295       colorStop2: config.colorStop2,
16296       Label: {
16297         type: config.Label.type
16298       },
16299       Node: {
16300         overridable: true,
16301         type: 'gaugechart-' + nodeType,
16302         width: 1,
16303         height: 1
16304       },
16305       Edge: {
16306         type: 'none'
16307       },
16308       Tips: {
16309         enable: config.Tips.enable,
16310         type: 'Native',
16311         force: true,
16312         onShow: function(tip, node, contains) {
16313           var elem = contains;
16314           config.Tips.onShow(tip, elem, node);
16315                           if(elem.link != 'undefined' && elem.link != '') {
16316                                 document.body.style.cursor = 'pointer';
16317                           }
16318         },
16319                 onHide: function() {
16320                                 document.body.style.cursor = 'default';
16321         }
16322       },
16323       Events: {
16324         enable: true,
16325         type: 'Native',
16326         onClick: function(node, eventInfo, evt) {
16327           if(!config.Events.enable) return;
16328           var elem = eventInfo.getContains();
16329           config.Events.onClick(elem, eventInfo, evt);
16330         }
16331         },
16332       onCreateLabel: function(domElement, node) {
16333         var labelConf = config.Label;
16334         if(config.showLabels) {
16335           var style = domElement.style;
16336           style.fontSize = labelConf.size + 'px';
16337           style.fontFamily = labelConf.family;
16338           style.color = labelConf.color;
16339           style.textAlign = 'center';
16340           valuelabelsArray = node.getData('valuelabelsArray'),
16341           nodeIteration = node.getData('nodeIteration'),
16342           nodeLength = node.getData('nodeLength'),
16343           canvas = sb.canvas,
16344           prefix = $.time();
16345           
16346           if(config.labelType == 'name') {
16347                 domElement.innerHTML = node.name;
16348           } else {
16349                 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16350           }
16351           
16352           domElement.style.width = '400px';
16353           
16354           //adds final label
16355                   if(nodeIteration == nodeLength && nodeLength != 0) {
16356                   idLabel = canvas.id + "-label";
16357                   container = document.getElementById(idLabel);
16358                   finalLabel = document.createElement('div');
16359                   finalLabelStyle = finalLabel.style;
16360                   finalLabel.id = prefix + "finalLabel";
16361                   finalLabelStyle.position = "absolute";
16362                   finalLabelStyle.width = "400px";
16363                   finalLabelStyle.left = "0px";
16364                   container.appendChild(finalLabel);
16365                         if(config.labelType == 'name') {
16366                                 finalLabel.innerHTML = node.name;
16367                         } else {
16368                                 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16369                         }
16370                         
16371                   }
16372         }
16373       },
16374       onPlaceLabel: function(domElement, node) {
16375         if(!config.showLabels) return;
16376         var pos = node.pos.getp(true),
16377             dimArray = node.getData('dimArray'),
16378             nodeIteration = node.getData('nodeIteration'),
16379             nodeLength = node.getData('nodeLength'),
16380             span = node.getData('span') / 2,
16381             theta = node.pos.theta,
16382             begin = ((theta - span)/2)+Math.PI,
16383             end = ((theta + span)/2)+Math.PI,
16384             polar = new Polar;
16385
16386         var showLabels = config.showLabels,
16387             resizeLabels = config.resizeLabels,
16388             label = config.Label,
16389             radiusOffset = sb.config.levelDistance;
16390
16391         if (dimArray) {
16392           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16393             acum += dimArray[i];
16394           }
16395           var scale = resizeLabels? node.getData('normalizedDim') : 1,
16396               fontSize = (label.size * scale) >> 0;
16397           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16398           domElement.style.fontSize = fontSize + 'px';
16399           polar.rho = acum * .65;
16400           polar.theta = begin;
16401           var pos = polar.getc(true);
16402           var radius = that.canvas.getSize();
16403           var labelPos = {
16404             x: Math.round(pos.x + radius.width / 2),
16405             y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16406           };
16407           
16408
16409           
16410           domElement.style.left = (labelPos.x - 200) + 'px';
16411           domElement.style.top = labelPos.y + 'px';
16412           
16413           //reposition first label
16414           if(nodeIteration == 1) {
16415                  domElement.style.top = labelPos.y - label.size + 'px';
16416           }
16417           
16418           
16419           //position final label
16420                 if(nodeIteration == nodeLength && nodeLength != 0) {
16421                         polar.theta = end;
16422                         var final = polar.getc(true);
16423                         var finalPos = {
16424                                         x: Math.round(final.x + radius.width / 2),
16425                                 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16426                                 };
16427                         finalLabel.style.left = (finalPos.x - 200) + "px";
16428                         finalLabel.style.top = finalPos.y - label.size + "px";
16429                     }
16430           
16431         }
16432       }
16433    
16434     });
16435     this.sb = sb;
16436     this.canvas = this.sb.canvas;
16437     var size = sb.canvas.getSize(),
16438         min = Math.min;
16439         sb.config.levelDistance = min(size.width, size.height)/2 
16440       - config.offset - config.sliceOffset;
16441
16442
16443   },
16444         
16445   renderBackground: function() {
16446         var canvas = this.sb.canvas,
16447         config = this.config,
16448         style = config.gaugeStyle,
16449         ctx = canvas.getCtx(),
16450         size = canvas.getSize(),
16451         radius = this.sb.config.levelDistance,
16452         startAngle = (Math.PI/180)*1,
16453         endAngle = (Math.PI/180)*179;
16454         
16455
16456         //background border
16457         ctx.fillStyle = style.borderColor;      
16458         ctx.beginPath();
16459         ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true); 
16460         ctx.fill(); 
16461         
16462         
16463         var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16464         radialGradient.addColorStop(0, '#ffffff');  
16465         radialGradient.addColorStop(0.3, style.backgroundColor);  
16466         radialGradient.addColorStop(0.6, style.backgroundColor);  
16467         radialGradient.addColorStop(1, '#FFFFFF'); 
16468         ctx.fillStyle = radialGradient;
16469         
16470         //background
16471         startAngle = (Math.PI/180)*0;
16472         endAngle = (Math.PI/180)*180;
16473         ctx.beginPath();
16474         ctx.arc(0,radius/2,radius,startAngle,endAngle, true); 
16475         ctx.fill();     
16476         
16477         
16478  
16479   },
16480   
16481   
16482   renderNeedle: function(gaugePosition,target) {
16483         var canvas = this.sb.canvas,
16484         config = this.config,
16485         style = config.gaugeStyle,
16486         ctx = canvas.getCtx(),
16487         size = canvas.getSize(),
16488         radius = this.sb.config.levelDistance;
16489         gaugeCenter = (radius/2);
16490         startAngle = 0;
16491         endAngle = (Math.PI/180)*180;
16492         
16493         
16494         // needle
16495         ctx.fillStyle = style.needleColor;
16496         var segments = 180/target;
16497         needleAngle = gaugePosition * segments;
16498         ctx.translate(0, gaugeCenter);
16499         ctx.save();
16500         ctx.rotate(needleAngle * Math.PI / 180);  
16501         ctx.beginPath();
16502         ctx.moveTo(0,0); 
16503         ctx.lineTo(0,-4);  
16504         ctx.lineTo(-radius*.9,-1);  
16505         ctx.lineTo(-radius*.9,1);  
16506         ctx.lineTo(0,4);  
16507         ctx.lineTo(0,0);  
16508         ctx.closePath(); 
16509         ctx.fill();
16510         ctx.restore(); 
16511         
16512         
16513         // stroke needle
16514         ctx.lineWidth = 1;
16515         ctx.strokeStyle = '#aa0000';
16516         ctx.save();
16517         ctx.rotate(needleAngle * Math.PI / 180);  
16518         ctx.beginPath();
16519         ctx.moveTo(0,0); 
16520         ctx.lineTo(0,-4);  
16521         ctx.lineTo(-radius*.8,-1);  
16522         ctx.lineTo(-radius*.8,1);  
16523         ctx.lineTo(0,4);  
16524         ctx.lineTo(0,0);  
16525         ctx.closePath(); 
16526         ctx.stroke();
16527         ctx.restore(); 
16528
16529         //needle cap
16530         ctx.fillStyle = "#000000";
16531         ctx.lineWidth = style.borderSize;
16532     ctx.strokeStyle = style.borderColor;
16533         var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16534         radialGradient.addColorStop(0, '#666666');  
16535         radialGradient.addColorStop(0.8, '#444444');  
16536         radialGradient.addColorStop(1, 'rgba(0,0,0,0)'); 
16537         ctx.fillStyle = radialGradient;
16538         ctx.translate(0,5);
16539         ctx.save();
16540         ctx.beginPath();
16541         ctx.arc(0,0,radius*.2,startAngle,endAngle, true); 
16542         ctx.fill();     
16543         ctx.restore();
16544
16545         
16546   },
16547   
16548   renderTicks: function(values) {
16549         var canvas = this.sb.canvas,
16550         config = this.config,
16551         style = config.gaugeStyle,
16552         ctx = canvas.getCtx(),
16553         size = canvas.getSize(),
16554         radius = this.sb.config.levelDistance,
16555         gaugeCenter = (radius/2);
16556         
16557         
16558         ctx.strokeStyle = style.borderColor;
16559         ctx.lineWidth = 5;
16560         ctx.lineCap = "round";
16561                 for(var i=0, total = 0, l=values.length; i<l; i++) {
16562                         var val = values[i];
16563                         if(val.label != 'GaugePosition') {
16564                         total += (parseInt(val.values) || 0);
16565                         }
16566                 }
16567         
16568                 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16569                         var val = values[i];
16570                         if(val.label != 'GaugePosition') {
16571                         acum += (parseInt(val.values) || 0);
16572
16573                            var segments = 180/total;
16574                         angle = acum * segments;
16575
16576                           //alert(acum);
16577                                  ctx.save();
16578                                  ctx.translate(0, gaugeCenter);
16579                                  ctx.beginPath();
16580                                 ctx.rotate(angle * (Math.PI/180));
16581                                 ctx.moveTo(-radius,0);
16582                                 ctx.lineTo(-radius*.75,0);
16583                                 ctx.stroke();
16584                                  ctx.restore();
16585                         
16586                         }
16587                 }
16588         },
16589         
16590         renderPositionLabel: function(position) {
16591                 var canvas = this.sb.canvas,
16592                 config = this.config,
16593                 label = config.Label,
16594                 style = config.gaugeStyle,
16595                 ctx = canvas.getCtx(),
16596                 size = canvas.getSize(),
16597                 radius = this.sb.config.levelDistance,
16598                 gaugeCenter = (radius/2);
16599                 ctx.textBaseline = 'middle';
16600                 ctx.textAlign = 'center';
16601                 ctx.font = style.positionFontSize + 'px ' + label.family;
16602                 ctx.fillStyle = "#ffffff";
16603                 ctx.lineWidth = 2;
16604                 height = style.positionFontSize + 10,
16605                 cornerRadius = 8,
16606                 idLabel = canvas.id + "-label";
16607                 container = document.getElementById(idLabel);
16608                 if(label.type == 'Native') {
16609                         var m = ctx.measureText(position),
16610                         width = m.width + 40;
16611                 } else {
16612                         var width = 70;
16613                 }
16614                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16615                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16616                 if(label.type == 'Native') {
16617                         ctx.fillStyle = label.color;
16618                         ctx.fillText(position, 0, (height/2) + style.positionOffset);
16619                 } else {
16620                         var labelDiv =  document.createElement('div');
16621                         labelDivStyle = labelDiv.style;
16622                         labelDivStyle.color =  label.color;
16623                         labelDivStyle.fontSize =  style.positionFontSize + "px";
16624                         labelDivStyle.position = "absolute";
16625                         labelDivStyle.width = width + "px";
16626                         labelDivStyle.left = (size.width/2) - (width/2) + "px";
16627                         labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16628                         labelDiv.innerHTML = position;
16629                         container.appendChild(labelDiv);
16630                 }
16631         
16632     },
16633     
16634    renderSubtitle: function() {
16635         var canvas = this.canvas,
16636         size = canvas.getSize(),
16637         config = this.config,
16638         margin = config.Margin,
16639         radius = this.sb.config.levelDistance,
16640         title = config.Title,
16641         label = config.Label,
16642         subtitle = config.Subtitle;
16643         ctx = canvas.getCtx();
16644         ctx.fillStyle = title.color;
16645         ctx.textAlign = 'left';
16646         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16647         ctx.moveTo(0,0);
16648         if(label.type == 'Native') {
16649                 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2)); 
16650         }
16651   },
16652   
16653   renderChartBackground: function() {
16654                 var canvas = this.canvas,
16655                 config = this.config,
16656                 backgroundColor = config.backgroundColor,
16657                 size = canvas.getSize(),
16658                 ctx = canvas.getCtx();
16659                 //ctx.globalCompositeOperation = "destination-over";
16660             ctx.fillStyle = backgroundColor;
16661             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
16662   },
16663   clear: function() {
16664         var canvas = this.canvas;
16665         var ctx = canvas.getCtx(),
16666         size = canvas.getSize();
16667         ctx.fillStyle = "rgba(255,255,255,0)";
16668         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16669         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16670  },
16671   resizeGraph: function(json,width) {
16672         var canvas = this.canvas,
16673         size = canvas.getSize(),
16674             orgHeight = size.height;
16675         
16676         canvas.resize(width,orgHeight);
16677         if(typeof FlashCanvas == "undefined") {
16678                 canvas.clear();
16679         } else {
16680                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16681         }
16682         this.loadJSON(json);
16683
16684         },
16685   loadJSON: function(json) {
16686   
16687      var prefix = $.time(), 
16688         ch = [], 
16689         sb = this.sb,
16690         name = $.splat(json.label),
16691         nameLength = name.length,
16692         color = $.splat(json.color || this.colors),
16693         colorLength = color.length,
16694         config = this.config,
16695         renderBackground = config.renderBackground,
16696         gradient = !!config.type.split(":")[1],
16697         animate = config.animate,
16698         mono = nameLength == 1;
16699                 var props = $.splat(json.properties)[0];
16700
16701     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16702         
16703       var val = values[i];
16704           if(val.label != 'GaugePosition') {
16705                   var valArray = $.splat(val.values);
16706                   var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16707                   var valuelabelsArray = $.splat(val.valuelabels);
16708
16709                   ch.push({
16710                         'id': prefix + val.label,
16711                         'name': val.label,
16712                         'data': {
16713                           'value': valArray,
16714                           'valuelabel': valuelabelsArray,
16715                           '$linkArray': linkArray,
16716                           '$valuelabelsArray': valuelabelsArray,
16717                           '$valueArray': valArray,
16718                           '$nodeIteration': i,
16719                           '$nodeLength': l-1,
16720                           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16721                           '$colorMono': $.splat(color[i % colorLength]),
16722                           '$stringArray': name,
16723                           '$gradient': gradient,
16724                           '$config': config,
16725                           '$gaugeTarget': props['gaugeTarget'],
16726                           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16727                         },
16728                         'children': []
16729                   });
16730           } else {
16731                 var gaugePosition = val.gvalue;
16732                 var gaugePositionLabel = val.gvaluelabel;
16733           }
16734     }
16735     var root = {
16736       'id': prefix + '$root',
16737       'name': '',
16738       'data': {
16739         '$type': 'none',
16740         '$width': 1,
16741         '$height': 1
16742       },
16743       'children': ch
16744     };
16745         
16746         
16747     sb.loadJSON(root);
16748     
16749     if(renderBackground) {
16750         this.renderChartBackground();   
16751     }
16752     
16753     this.renderBackground();
16754     this.renderSubtitle();
16755     
16756     this.normalizeDims();
16757         
16758     sb.refresh();
16759     if(animate) {
16760       sb.fx.animate({
16761         modes: ['node-property:dimArray'],
16762         duration:1500
16763       });
16764     }
16765         
16766
16767         this.renderPositionLabel(gaugePositionLabel);
16768         if (props['gaugeTarget'] != 0) {
16769                 this.renderTicks(json.values);
16770                 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16771         }
16772         
16773         
16774
16775   },
16776   
16777   updateJSON: function(json, onComplete) {
16778     if(this.busy) return;
16779     this.busy = true;
16780     
16781     var sb = this.sb;
16782     var graph = sb.graph;
16783     var values = json.values;
16784     var animate = this.config.animate;
16785     var that = this;
16786     $.each(values, function(v) {
16787       var n = graph.getByName(v.label),
16788           vals = $.splat(v.values);
16789       if(n) {
16790         n.setData('valueArray', vals);
16791         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16792         if(json.label) {
16793           n.setData('stringArray', $.splat(json.label));
16794         }
16795       }
16796     });
16797     this.normalizeDims();
16798     if(animate) {
16799       sb.compute('end');
16800       sb.fx.animate({
16801         modes: ['node-property:dimArray:span', 'linear'],
16802         duration:1500,
16803         onComplete: function() {
16804           that.busy = false;
16805           onComplete && onComplete.onComplete();
16806         }
16807       });
16808     } else {
16809       sb.refresh();
16810     }
16811   },
16812     
16813   //adds the little brown bar when hovering the node
16814   select: function(id, name) {
16815     if(!this.config.hoveredColor) return;
16816     var s = this.selected;
16817     if(s.id != id || s.name != name) {
16818       s.id = id;
16819       s.name = name;
16820       s.color = this.config.hoveredColor;
16821       this.sb.graph.eachNode(function(n) {
16822         if(id == n.id) {
16823           n.setData('border', s);
16824         } else {
16825           n.setData('border', false);
16826         }
16827       });
16828       this.sb.plot();
16829     }
16830   },
16831   
16832   /*
16833     Method: getLegend
16834    
16835     Returns an object containing as keys the legend names and as values hex strings with color values.
16836     
16837     Example:
16838     
16839     (start code js)
16840     var legend = pieChart.getLegend();
16841     (end code)
16842   */  
16843   getLegend: function() {
16844     var legend = new Array();
16845     var name = new Array();
16846     var color = new Array();
16847     var n;
16848     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16849       n = adj.nodeTo;
16850     });
16851     var colors = n.getData('colorArray'),
16852         len = colors.length;
16853     $.each(n.getData('stringArray'), function(s, i) {
16854       color[i] = colors[i % len];
16855       name[i] = s;
16856     });
16857         legend['name'] = name;
16858         legend['color'] = color;
16859     return legend;
16860   },
16861   
16862   /*
16863     Method: getMaxValue
16864    
16865     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16866     
16867     Example:
16868     
16869     (start code js)
16870     var ans = pieChart.getMaxValue();
16871     (end code)
16872     
16873     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16874     
16875     Example:
16876     
16877     (start code js)
16878     //will return 100 for all PieChart instances,
16879     //displaying all of them with the same scale
16880     $jit.PieChart.implement({
16881       'getMaxValue': function() {
16882         return 100;
16883       }
16884     });
16885     (end code)
16886     
16887   */  
16888   getMaxValue: function() {
16889     var maxValue = 0;
16890     this.sb.graph.eachNode(function(n) {
16891       var valArray = n.getData('valueArray'),
16892           acum = 0;
16893       $.each(valArray, function(v) { 
16894         acum += +v;
16895       });
16896       maxValue = maxValue>acum? maxValue:acum;
16897     });
16898     return maxValue;
16899   },
16900   
16901   normalizeDims: function() {
16902     //number of elements
16903     var root = this.sb.graph.getNode(this.sb.root), l=0;
16904     root.eachAdjacency(function() {
16905       l++;
16906     });
16907     var maxValue = this.getMaxValue() || 1,
16908         config = this.config,
16909         animate = config.animate,
16910         rho = this.sb.config.levelDistance;
16911     this.sb.graph.eachNode(function(n) {
16912       var acum = 0, animateValue = [];
16913       $.each(n.getData('valueArray'), function(v) {
16914         acum += +v;
16915         animateValue.push(1);
16916       });
16917       var stat = (animateValue.length == 1) && !config.updateHeights;
16918       if(animate) {
16919         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16920           return stat? rho: (n * rho / maxValue); 
16921         }), 'end');
16922         var dimArray = n.getData('dimArray');
16923         if(!dimArray) {
16924           n.setData('dimArray', animateValue);
16925         }
16926       } else {
16927         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16928           return stat? rho : (n * rho / maxValue); 
16929         }));
16930       }
16931       n.setData('normalizedDim', acum / maxValue);
16932     });
16933   }
16934 });
16935
16936
16937 /*
16938  * Class: Layouts.TM
16939  * 
16940  * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16941  * 
16942  * Implemented By:
16943  * 
16944  * <TM>
16945  * 
16946  */
16947 Layouts.TM = {};
16948
16949 Layouts.TM.SliceAndDice = new Class({
16950   compute: function(prop) {
16951     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16952     this.controller.onBeforeCompute(root);
16953     var size = this.canvas.getSize(),
16954         config = this.config,
16955         width = size.width,
16956         height = size.height;
16957     this.graph.computeLevels(this.root, 0, "ignore");
16958     //set root position and dimensions
16959     root.getPos(prop).setc(-width/2, -height/2);
16960     root.setData('width', width, prop);
16961     root.setData('height', height + config.titleHeight, prop);
16962     this.computePositions(root, root, this.layout.orientation, prop);
16963     this.controller.onAfterCompute(root);
16964   },
16965   
16966   computePositions: function(par, ch, orn, prop) {
16967     //compute children areas
16968     var totalArea = 0;
16969     par.eachSubnode(function(n) {
16970       totalArea += n.getData('area', prop);
16971     });
16972     
16973     var config = this.config,
16974         offst = config.offset,
16975         width  = par.getData('width', prop),
16976         height = par.getData('height', prop) - config.titleHeight,
16977         fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16978     
16979     var otherSize, size, dim, pos, pos2, posth, pos2th;
16980     var horizontal = (orn == "h");
16981     if(horizontal) {
16982       orn = 'v';    
16983       otherSize = height;
16984       size = width * fact;
16985       dim = 'height';
16986       pos = 'y';
16987       pos2 = 'x';
16988       posth = config.titleHeight;
16989       pos2th = 0;
16990     } else {
16991       orn = 'h';    
16992       otherSize = height * fact;
16993       size = width;
16994       dim = 'width';
16995       pos = 'x';
16996       pos2 = 'y';
16997       posth = 0;
16998       pos2th = config.titleHeight;
16999     }
17000     var cpos = ch.getPos(prop);
17001     ch.setData('width', size, prop);
17002     ch.setData('height', otherSize, prop);
17003     var offsetSize = 0, tm = this;
17004     ch.eachSubnode(function(n) {
17005       var p = n.getPos(prop);
17006       p[pos] = offsetSize + cpos[pos] + posth;
17007       p[pos2] = cpos[pos2] + pos2th;
17008       tm.computePositions(ch, n, orn, prop);
17009       offsetSize += n.getData(dim, prop);
17010     });
17011   }
17012
17013 });
17014
17015 Layouts.TM.Area = {
17016  /*
17017     Method: compute
17018  
17019    Called by loadJSON to calculate recursively all node positions and lay out the tree.
17020  
17021     Parameters:
17022
17023        json - A JSON tree. See also <Loader.loadJSON>.
17024        coord - A coordinates object specifying width, height, left and top style properties.
17025  */
17026  compute: function(prop) {
17027     prop = prop || "current";
17028     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17029     this.controller.onBeforeCompute(root);
17030     var config = this.config,
17031         size = this.canvas.getSize(),
17032         width = size.width,
17033         height = size.height,
17034         offst = config.offset,
17035         offwdth = width - offst,
17036         offhght = height - offst;
17037     this.graph.computeLevels(this.root, 0, "ignore");
17038     //set root position and dimensions
17039     root.getPos(prop).setc(-width/2, -height/2);
17040     root.setData('width', width, prop);
17041     root.setData('height', height, prop);
17042     //create a coordinates object
17043     var coord = {
17044         'top': -height/2 + config.titleHeight,
17045         'left': -width/2,
17046         'width': offwdth,
17047         'height': offhght - config.titleHeight
17048     };
17049     this.computePositions(root, coord, prop);
17050     this.controller.onAfterCompute(root);
17051  }, 
17052  
17053  /*
17054     Method: computeDim
17055  
17056    Computes dimensions and positions of a group of nodes
17057    according to a custom layout row condition. 
17058  
17059     Parameters:
17060
17061        tail - An array of nodes.  
17062        initElem - An array of nodes (containing the initial node to be laid).
17063        w - A fixed dimension where nodes will be layed out.
17064        coord - A coordinates object specifying width, height, left and top style properties.
17065        comp - A custom comparison function
17066  */
17067  computeDim: function(tail, initElem, w, coord, comp, prop) {
17068    if(tail.length + initElem.length == 1) {
17069      var l = (tail.length == 1)? tail : initElem;
17070      this.layoutLast(l, w, coord, prop);
17071      return;
17072    }
17073    if(tail.length >= 2 && initElem.length == 0) {
17074      initElem = [tail.shift()];
17075    }
17076    if(tail.length == 0) {
17077      if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17078      return;
17079    }
17080    var c = tail[0];
17081    if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17082      this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17083    } else {
17084      var newCoords = this.layoutRow(initElem, w, coord, prop);
17085      this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17086    }
17087  },
17088
17089  
17090  /*
17091     Method: worstAspectRatio
17092  
17093    Calculates the worst aspect ratio of a group of rectangles. 
17094        
17095     See also:
17096        
17097        <http://en.wikipedia.org/wiki/Aspect_ratio>
17098    
17099     Parameters:
17100
17101      ch - An array of nodes.  
17102      w  - The fixed dimension where rectangles are being laid out.
17103
17104     Returns:
17105  
17106         The worst aspect ratio.
17107
17108
17109  */
17110  worstAspectRatio: function(ch, w) {
17111    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17112    var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17113    for(var i=0, l=ch.length; i<l; i++) {
17114      var area = ch[i]._area;
17115      areaSum += area; 
17116      minArea = minArea < area? minArea : area;
17117      maxArea = maxArea > area? maxArea : area; 
17118    }
17119    var sqw = w * w, sqAreaSum = areaSum * areaSum;
17120    return Math.max(sqw * maxArea / sqAreaSum,
17121            sqAreaSum / (sqw * minArea));
17122  },
17123  
17124  /*
17125     Method: avgAspectRatio
17126  
17127    Calculates the average aspect ratio of a group of rectangles. 
17128        
17129        See also:
17130        
17131        <http://en.wikipedia.org/wiki/Aspect_ratio>
17132    
17133     Parameters:
17134
17135      ch - An array of nodes.  
17136        w - The fixed dimension where rectangles are being laid out.
17137
17138     Returns:
17139  
17140         The average aspect ratio.
17141
17142
17143  */
17144  avgAspectRatio: function(ch, w) {
17145    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17146    var arSum = 0;
17147    for(var i=0, l=ch.length; i<l; i++) {
17148      var area = ch[i]._area;
17149      var h = area / w;
17150      arSum += w > h? w / h : h / w;
17151    }
17152    return arSum / l;
17153  },
17154
17155  /*
17156     layoutLast
17157  
17158    Performs the layout of the last computed sibling.
17159  
17160     Parameters:
17161
17162        ch - An array of nodes.  
17163        w - A fixed dimension where nodes will be layed out.
17164      coord - A coordinates object specifying width, height, left and top style properties.
17165  */
17166  layoutLast: function(ch, w, coord, prop) {
17167    var child = ch[0];
17168    child.getPos(prop).setc(coord.left, coord.top);
17169    child.setData('width', coord.width, prop);
17170    child.setData('height', coord.height, prop);
17171  }
17172 };
17173
17174
17175 Layouts.TM.Squarified = new Class({
17176  Implements: Layouts.TM.Area,
17177  
17178  computePositions: function(node, coord, prop) {
17179    var config = this.config;
17180    
17181    if (coord.width >= coord.height) 
17182      this.layout.orientation = 'h';
17183    else
17184      this.layout.orientation = 'v';
17185    
17186    var ch = node.getSubnodes([1, 1], "ignore");
17187    if(ch.length > 0) {
17188      this.processChildrenLayout(node, ch, coord, prop);
17189      for(var i=0, l=ch.length; i<l; i++) {
17190        var chi = ch[i]; 
17191        var offst = config.offset,
17192            height = chi.getData('height', prop) - offst - config.titleHeight,
17193            width = chi.getData('width', prop) - offst;
17194        var chipos = chi.getPos(prop);
17195        coord = {
17196          'width': width,
17197          'height': height,
17198          'top': chipos.y + config.titleHeight,
17199          'left': chipos.x
17200        };
17201        this.computePositions(chi, coord, prop);
17202      }
17203    }
17204  },
17205
17206  /*
17207     Method: processChildrenLayout
17208  
17209    Computes children real areas and other useful parameters for performing the Squarified algorithm.
17210  
17211     Parameters:
17212
17213        par - The parent node of the json subtree.  
17214        ch - An Array of nodes
17215      coord - A coordinates object specifying width, height, left and top style properties.
17216  */
17217  processChildrenLayout: function(par, ch, coord, prop) {
17218    //compute children real areas
17219    var parentArea = coord.width * coord.height;
17220    var i, l=ch.length, totalChArea=0, chArea = [];
17221    for(i=0; i<l; i++) {
17222      chArea[i] = parseFloat(ch[i].getData('area', prop));
17223      totalChArea += chArea[i];
17224    }
17225    for(i=0; i<l; i++) {
17226      ch[i]._area = parentArea * chArea[i] / totalChArea;
17227    }
17228    var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17229    ch.sort(function(a, b) { 
17230      var diff = b._area - a._area; 
17231      return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1)); 
17232    });
17233    var initElem = [ch[0]];
17234    var tail = ch.slice(1);
17235    this.squarify(tail, initElem, minimumSideValue, coord, prop);
17236  },
17237
17238  /*
17239    Method: squarify
17240  
17241    Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17242  
17243     Parameters:
17244
17245        tail - An array of nodes.  
17246        initElem - An array of nodes, containing the initial node to be laid out.
17247        w - A fixed dimension where nodes will be laid out.
17248        coord - A coordinates object specifying width, height, left and top style properties.
17249  */
17250  squarify: function(tail, initElem, w, coord, prop) {
17251    this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17252  },
17253  
17254  /*
17255     Method: layoutRow
17256  
17257    Performs the layout of an array of nodes.
17258  
17259     Parameters:
17260
17261        ch - An array of nodes.  
17262        w - A fixed dimension where nodes will be laid out.
17263        coord - A coordinates object specifying width, height, left and top style properties.
17264  */
17265  layoutRow: function(ch, w, coord, prop) {
17266    if(this.layout.horizontal()) {
17267      return this.layoutV(ch, w, coord, prop);
17268    } else {
17269      return this.layoutH(ch, w, coord, prop);
17270    }
17271  },
17272  
17273  layoutV: function(ch, w, coord, prop) {
17274    var totalArea = 0, rnd = function(x) { return x; }; 
17275    $.each(ch, function(elem) { totalArea += elem._area; });
17276    var width = rnd(totalArea / w), top =  0; 
17277    for(var i=0, l=ch.length; i<l; i++) {
17278      var h = rnd(ch[i]._area / width);
17279      var chi = ch[i];
17280      chi.getPos(prop).setc(coord.left, coord.top + top);
17281      chi.setData('width', width, prop);
17282      chi.setData('height', h, prop);
17283      top += h;
17284    }
17285    var ans = {
17286      'height': coord.height,
17287      'width': coord.width - width,
17288      'top': coord.top,
17289      'left': coord.left + width
17290    };
17291    //take minimum side value.
17292    ans.dim = Math.min(ans.width, ans.height);
17293    if(ans.dim != ans.height) this.layout.change();
17294    return ans;
17295  },
17296  
17297  layoutH: function(ch, w, coord, prop) {
17298    var totalArea = 0; 
17299    $.each(ch, function(elem) { totalArea += elem._area; });
17300    var height = totalArea / w,
17301        top = coord.top, 
17302        left = 0;
17303    
17304    for(var i=0, l=ch.length; i<l; i++) {
17305      var chi = ch[i];
17306      var w = chi._area / height;
17307      chi.getPos(prop).setc(coord.left + left, top);
17308      chi.setData('width', w, prop);
17309      chi.setData('height', height, prop);
17310      left += w;
17311    }
17312    var ans = {
17313      'height': coord.height - height,
17314      'width': coord.width,
17315      'top': coord.top + height,
17316      'left': coord.left
17317    };
17318    ans.dim = Math.min(ans.width, ans.height);
17319    if(ans.dim != ans.width) this.layout.change();
17320    return ans;
17321  }
17322 });
17323
17324 Layouts.TM.Strip = new Class({
17325   Implements: Layouts.TM.Area,
17326
17327     /*
17328       Method: compute
17329     
17330      Called by loadJSON to calculate recursively all node positions and lay out the tree.
17331     
17332       Parameters:
17333     
17334          json - A JSON subtree. See also <Loader.loadJSON>. 
17335        coord - A coordinates object specifying width, height, left and top style properties.
17336     */
17337     computePositions: function(node, coord, prop) {
17338      var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17339      if(ch.length > 0) {
17340        this.processChildrenLayout(node, ch, coord, prop);
17341        for(var i=0, l=ch.length; i<l; i++) {
17342          var chi = ch[i];
17343          var offst = config.offset,
17344              height = chi.getData('height', prop) - offst - config.titleHeight,
17345              width  = chi.getData('width', prop)  - offst;
17346          var chipos = chi.getPos(prop);
17347          coord = {
17348            'width': width,
17349            'height': height,
17350            'top': chipos.y + config.titleHeight,
17351            'left': chipos.x
17352          };
17353          this.computePositions(chi, coord, prop);
17354        }
17355      }
17356     },
17357     
17358     /*
17359       Method: processChildrenLayout
17360     
17361      Computes children real areas and other useful parameters for performing the Strip algorithm.
17362     
17363       Parameters:
17364     
17365          par - The parent node of the json subtree.  
17366          ch - An Array of nodes
17367          coord - A coordinates object specifying width, height, left and top style properties.
17368     */
17369     processChildrenLayout: function(par, ch, coord, prop) {
17370      //compute children real areas
17371       var parentArea = coord.width * coord.height;
17372       var i, l=ch.length, totalChArea=0, chArea = [];
17373       for(i=0; i<l; i++) {
17374         chArea[i] = +ch[i].getData('area', prop);
17375         totalChArea += chArea[i];
17376       }
17377       for(i=0; i<l; i++) {
17378         ch[i]._area = parentArea * chArea[i] / totalChArea;
17379       }
17380      var side = this.layout.horizontal()? coord.width : coord.height;
17381      var initElem = [ch[0]];
17382      var tail = ch.slice(1);
17383      this.stripify(tail, initElem, side, coord, prop);
17384     },
17385     
17386     /*
17387       Method: stripify
17388     
17389      Performs an heuristic method to calculate div elements sizes in order to have 
17390      a good compromise between aspect ratio and order.
17391     
17392       Parameters:
17393     
17394          tail - An array of nodes.  
17395          initElem - An array of nodes.
17396          w - A fixed dimension where nodes will be layed out.
17397        coord - A coordinates object specifying width, height, left and top style properties.
17398     */
17399     stripify: function(tail, initElem, w, coord, prop) {
17400      this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17401     },
17402     
17403     /*
17404       Method: layoutRow
17405     
17406      Performs the layout of an array of nodes.
17407     
17408       Parameters:
17409     
17410          ch - An array of nodes.  
17411          w - A fixed dimension where nodes will be laid out.
17412          coord - A coordinates object specifying width, height, left and top style properties.
17413     */
17414     layoutRow: function(ch, w, coord, prop) {
17415      if(this.layout.horizontal()) {
17416        return this.layoutH(ch, w, coord, prop);
17417      } else {
17418        return this.layoutV(ch, w, coord, prop);
17419      }
17420     },
17421     
17422     layoutV: function(ch, w, coord, prop) {
17423      var totalArea = 0; 
17424      $.each(ch, function(elem) { totalArea += elem._area; });
17425      var width = totalArea / w, top =  0; 
17426      for(var i=0, l=ch.length; i<l; i++) {
17427        var chi = ch[i];
17428        var h = chi._area / width;
17429        chi.getPos(prop).setc(coord.left, 
17430            coord.top + (w - h - top));
17431        chi.setData('width', width, prop);
17432        chi.setData('height', h, prop);
17433        top += h;
17434      }
17435     
17436      return {
17437        'height': coord.height,
17438        'width': coord.width - width,
17439        'top': coord.top,
17440        'left': coord.left + width,
17441        'dim': w
17442      };
17443     },
17444     
17445     layoutH: function(ch, w, coord, prop) {
17446      var totalArea = 0; 
17447      $.each(ch, function(elem) { totalArea += elem._area; });
17448      var height = totalArea / w,
17449          top = coord.height - height, 
17450          left = 0;
17451      
17452      for(var i=0, l=ch.length; i<l; i++) {
17453        var chi = ch[i];
17454        var s = chi._area / height;
17455        chi.getPos(prop).setc(coord.left + left, coord.top + top);
17456        chi.setData('width', s, prop);
17457        chi.setData('height', height, prop);
17458        left += s;
17459      }
17460      return {
17461        'height': coord.height - height,
17462        'width': coord.width,
17463        'top': coord.top,
17464        'left': coord.left,
17465        'dim': w
17466      };
17467     }
17468  });
17469
17470 /*
17471  * Class: Layouts.Icicle
17472  *
17473  * Implements the icicle tree layout.
17474  *
17475  * Implemented By:
17476  *
17477  * <Icicle>
17478  *
17479  */
17480
17481 Layouts.Icicle = new Class({
17482  /*
17483   * Method: compute
17484   *
17485   * Called by loadJSON to calculate all node positions.
17486   *
17487   * Parameters:
17488   *
17489   * posType - The nodes' position to compute. Either "start", "end" or
17490   *            "current". Defaults to "current".
17491   */
17492   compute: function(posType) {
17493     posType = posType || "current";
17494     var root = this.graph.getNode(this.root),
17495         config = this.config,
17496         size = this.canvas.getSize(),
17497         width = size.width,
17498         height = size.height,
17499         offset = config.offset,
17500         levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17501
17502     this.controller.onBeforeCompute(root);
17503
17504     Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17505
17506     var treeDepth = 0;
17507
17508     Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17509
17510     var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17511     var maxDepth = Math.min(treeDepth, levelsToShow-1);
17512     var initialDepth = startNode._depth;
17513     if(this.layout.horizontal()) {
17514       this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17515     } else {
17516       this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17517     }
17518   },
17519
17520   computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17521     root.getPos(posType).setc(x, y);
17522     root.setData('width', width, posType);
17523     root.setData('height', height, posType);
17524
17525     var nodeLength, prevNodeLength = 0, totalDim = 0;
17526     var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17527
17528     if(!children.length)
17529       return;
17530
17531     $.each(children, function(e) { totalDim += e.getData('dim'); });
17532
17533     for(var i=0, l=children.length; i < l; i++) {
17534       if(this.layout.horizontal()) {
17535         nodeLength = height * children[i].getData('dim') / totalDim;
17536         this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17537         y += nodeLength;
17538       } else {
17539         nodeLength = width * children[i].getData('dim') / totalDim;
17540         this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17541         x += nodeLength;
17542       }
17543     }
17544   }
17545 });
17546
17547
17548
17549 /*
17550  * File: Icicle.js
17551  *
17552 */
17553
17554 /*
17555   Class: Icicle
17556   
17557   Icicle space filling visualization.
17558   
17559   Implements:
17560   
17561   All <Loader> methods
17562   
17563   Constructor Options:
17564   
17565   Inherits options from
17566   
17567   - <Options.Canvas>
17568   - <Options.Controller>
17569   - <Options.Node>
17570   - <Options.Edge>
17571   - <Options.Label>
17572   - <Options.Events>
17573   - <Options.Tips>
17574   - <Options.NodeStyles>
17575   - <Options.Navigation>
17576   
17577   Additionally, there are other parameters and some default values changed
17578
17579   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17580   offset - (number) Default's *2*. Boxes offset.
17581   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17582   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17583   animate - (boolean) Default's *false*. Whether to animate transitions.
17584   Node.type - Described in <Options.Node>. Default's *rectangle*.
17585   Label.type - Described in <Options.Label>. Default's *Native*.
17586   duration - Described in <Options.Fx>. Default's *700*.
17587   fps - Described in <Options.Fx>. Default's *45*.
17588   
17589   Instance Properties:
17590   
17591   canvas - Access a <Canvas> instance.
17592   graph - Access a <Graph> instance.
17593   op - Access a <Icicle.Op> instance.
17594   fx - Access a <Icicle.Plot> instance.
17595   labels - Access a <Icicle.Label> interface implementation.
17596
17597 */
17598
17599 $jit.Icicle = new Class({
17600   Implements: [ Loader, Extras, Layouts.Icicle ],
17601
17602   layout: {
17603     orientation: "h",
17604     vertical: function(){
17605       return this.orientation == "v";
17606     },
17607     horizontal: function(){
17608       return this.orientation == "h";
17609     },
17610     change: function(){
17611       this.orientation = this.vertical()? "h" : "v";
17612     }
17613   },
17614
17615   initialize: function(controller) {
17616     var config = {
17617       animate: false,
17618       orientation: "h",
17619       offset: 2,
17620       levelsToShow: Number.MAX_VALUE,
17621       constrained: false,
17622       Node: {
17623         type: 'rectangle',
17624         overridable: true
17625       },
17626       Edge: {
17627         type: 'none'
17628       },
17629       Label: {
17630         type: 'Native'
17631       },
17632       duration: 700,
17633       fps: 45
17634     };
17635
17636     var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17637                        "Events", "Navigation", "Controller", "Label");
17638     this.controller = this.config = $.merge(opts, config, controller);
17639     this.layout.orientation = this.config.orientation;
17640
17641     var canvasConfig = this.config;
17642     if (canvasConfig.useCanvas) {
17643       this.canvas = canvasConfig.useCanvas;
17644       this.config.labelContainer = this.canvas.id + '-label';
17645     } else {
17646       this.canvas = new Canvas(this, canvasConfig);
17647       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17648     }
17649
17650     this.graphOptions = {
17651       'complex': true,
17652       'Node': {
17653         'selected': false,
17654         'exist': true,
17655         'drawn': true
17656       }
17657     };
17658
17659     this.graph = new Graph(
17660       this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17661
17662     this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17663     this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17664     this.op = new $jit.Icicle.Op(this);
17665     this.group = new $jit.Icicle.Group(this);
17666     this.clickedNode = null;
17667
17668     this.initializeExtras();
17669   },
17670
17671   /* 
17672     Method: refresh 
17673     
17674     Computes positions and plots the tree.
17675   */
17676   refresh: function(){
17677     var labelType = this.config.Label.type;
17678     if(labelType != 'Native') {
17679       var that = this;
17680       this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17681     }
17682     this.compute();
17683     this.plot();
17684   },
17685
17686   /* 
17687     Method: plot 
17688     
17689     Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
17690   
17691    */
17692   plot: function(){
17693     this.fx.plot(this.config);
17694   },
17695
17696   /* 
17697     Method: enter 
17698     
17699     Sets the node as root.
17700     
17701      Parameters:
17702      
17703      node - (object) A <Graph.Node>.
17704   
17705    */
17706   enter: function (node) {
17707     if (this.busy)
17708       return;
17709     this.busy = true;
17710
17711     var that = this,
17712         config = this.config;
17713
17714     var callback = {
17715       onComplete: function() {
17716         //compute positions of newly inserted nodes
17717         if(config.request)
17718           that.compute();
17719
17720         if(config.animate) {
17721           that.graph.nodeList.setDataset(['current', 'end'], {
17722             'alpha': [1, 0] //fade nodes
17723           });
17724
17725           Graph.Util.eachSubgraph(node, function(n) {
17726             n.setData('alpha', 1, 'end');
17727           }, "ignore");
17728
17729           that.fx.animate({
17730             duration: 500,
17731             modes:['node-property:alpha'],
17732             onComplete: function() {
17733               that.clickedNode = node;
17734               that.compute('end');
17735
17736               that.fx.animate({
17737                 modes:['linear', 'node-property:width:height'],
17738                 duration: 1000,
17739                 onComplete: function() {
17740                   that.busy = false;
17741                   that.clickedNode = node;
17742                 }
17743               });
17744             }
17745           });
17746         } else {
17747           that.clickedNode = node;
17748           that.busy = false;
17749           that.refresh();
17750         }
17751       }
17752     };
17753
17754     if(config.request) {
17755       this.requestNodes(clickedNode, callback);
17756     } else {
17757       callback.onComplete();
17758     }
17759   },
17760
17761   /* 
17762     Method: out 
17763     
17764     Sets the parent node of the current selected node as root.
17765   
17766    */
17767   out: function(){
17768     if(this.busy)
17769       return;
17770
17771     var that = this,
17772         GUtil = Graph.Util,
17773         config = this.config,
17774         graph = this.graph,
17775         parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17776         parent = parents[0],
17777         clickedNode = parent,
17778         previousClickedNode = this.clickedNode;
17779
17780     this.busy = true;
17781     this.events.hoveredNode = false;
17782
17783     if(!parent) {
17784       this.busy = false;
17785       return;
17786     }
17787
17788     //final plot callback
17789     callback = {
17790       onComplete: function() {
17791         that.clickedNode = parent;
17792         if(config.request) {
17793           that.requestNodes(parent, {
17794             onComplete: function() {
17795               that.compute();
17796               that.plot();
17797               that.busy = false;
17798             }
17799           });
17800         } else {
17801           that.compute();
17802           that.plot();
17803           that.busy = false;
17804         }
17805       }
17806     };
17807
17808     //animate node positions
17809     if(config.animate) {
17810       this.clickedNode = clickedNode;
17811       this.compute('end');
17812       //animate the visible subtree only
17813       this.clickedNode = previousClickedNode;
17814       this.fx.animate({
17815         modes:['linear', 'node-property:width:height'],
17816         duration: 1000,
17817         onComplete: function() {
17818           //animate the parent subtree
17819           that.clickedNode = clickedNode;
17820           //change nodes alpha
17821           graph.nodeList.setDataset(['current', 'end'], {
17822             'alpha': [0, 1]
17823           });
17824           GUtil.eachSubgraph(previousClickedNode, function(node) {
17825             node.setData('alpha', 1);
17826           }, "ignore");
17827           that.fx.animate({
17828             duration: 500,
17829             modes:['node-property:alpha'],
17830             onComplete: function() {
17831               callback.onComplete();
17832             }
17833           });
17834         }
17835       });
17836     } else {
17837       callback.onComplete();
17838     }
17839   },
17840   requestNodes: function(node, onComplete){
17841     var handler = $.merge(this.controller, onComplete),
17842         levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17843
17844     if (handler.request) {
17845       var leaves = [], d = node._depth;
17846       Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17847         if (n.drawn && !Graph.Util.anySubnode(n)) {
17848           leaves.push(n);
17849           n._level = n._depth - d;
17850           if (this.config.constrained)
17851             n._level = levelsToShow - n._level;
17852
17853         }
17854       });
17855       this.group.requestNodes(leaves, handler);
17856     } else {
17857       handler.onComplete();
17858     }
17859   }
17860 });
17861
17862 /*
17863   Class: Icicle.Op
17864   
17865   Custom extension of <Graph.Op>.
17866   
17867   Extends:
17868   
17869   All <Graph.Op> methods
17870   
17871   See also:
17872   
17873   <Graph.Op>
17874   
17875   */
17876 $jit.Icicle.Op = new Class({
17877
17878   Implements: Graph.Op
17879
17880 });
17881
17882 /*
17883  * Performs operations on group of nodes.
17884  */
17885 $jit.Icicle.Group = new Class({
17886
17887   initialize: function(viz){
17888     this.viz = viz;
17889     this.canvas = viz.canvas;
17890     this.config = viz.config;
17891   },
17892
17893   /*
17894    * Calls the request method on the controller to request a subtree for each node.
17895    */
17896   requestNodes: function(nodes, controller){
17897     var counter = 0, len = nodes.length, nodeSelected = {};
17898     var complete = function(){
17899       controller.onComplete();
17900     };
17901     var viz = this.viz;
17902     if (len == 0)
17903       complete();
17904     for(var i = 0; i < len; i++) {
17905       nodeSelected[nodes[i].id] = nodes[i];
17906       controller.request(nodes[i].id, nodes[i]._level, {
17907         onComplete: function(nodeId, data){
17908           if (data && data.children) {
17909             data.id = nodeId;
17910             viz.op.sum(data, {
17911               type: 'nothing'
17912             });
17913           }
17914           if (++counter == len) {
17915             Graph.Util.computeLevels(viz.graph, viz.root, 0);
17916             complete();
17917           }
17918         }
17919       });
17920     }
17921   }
17922 });
17923
17924 /*
17925   Class: Icicle.Plot
17926   
17927   Custom extension of <Graph.Plot>.
17928   
17929   Extends:
17930   
17931   All <Graph.Plot> methods
17932   
17933   See also:
17934   
17935   <Graph.Plot>
17936   
17937   */
17938 $jit.Icicle.Plot = new Class({
17939   Implements: Graph.Plot,
17940
17941   plot: function(opt, animating){
17942     opt = opt || this.viz.controller;
17943     var viz = this.viz,
17944         graph = viz.graph,
17945         root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17946         initialDepth = root._depth;
17947
17948     viz.canvas.clear();
17949     this.plotTree(root, $.merge(opt, {
17950       'withLabels': true,
17951       'hideLabels': false,
17952       'plotSubtree': function(root, node) {
17953         return !viz.config.constrained ||
17954                (node._depth - initialDepth < viz.config.levelsToShow);
17955       }
17956     }), animating);
17957   }
17958 });
17959
17960 /*
17961   Class: Icicle.Label
17962   
17963   Custom extension of <Graph.Label>. 
17964   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17965   
17966   Extends:
17967   
17968   All <Graph.Label> methods and subclasses.
17969   
17970   See also:
17971   
17972   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17973   
17974   */
17975 $jit.Icicle.Label = {};
17976
17977 /*
17978   Icicle.Label.Native
17979   
17980   Custom extension of <Graph.Label.Native>.
17981   
17982   Extends:
17983   
17984   All <Graph.Label.Native> methods
17985   
17986   See also:
17987   
17988   <Graph.Label.Native>
17989
17990   */
17991 $jit.Icicle.Label.Native = new Class({
17992   Implements: Graph.Label.Native,
17993
17994   renderLabel: function(canvas, node, controller) {
17995     var ctx = canvas.getCtx(),
17996         width = node.getData('width'),
17997         height = node.getData('height'),
17998         size = node.getLabelData('size'),
17999         m = ctx.measureText(node.name);
18000
18001     // Guess as much as possible if the label will fit in the node
18002     if(height < (size * 1.5) || width < m.width)
18003       return;
18004
18005     var pos = node.pos.getc(true);
18006     ctx.fillText(node.name,
18007                  pos.x + width / 2,
18008                  pos.y + height / 2);
18009   }
18010 });
18011
18012 /*
18013   Icicle.Label.SVG
18014   
18015   Custom extension of <Graph.Label.SVG>.
18016   
18017   Extends:
18018   
18019   All <Graph.Label.SVG> methods
18020   
18021   See also:
18022   
18023   <Graph.Label.SVG>
18024 */
18025 $jit.Icicle.Label.SVG = new Class( {
18026   Implements: Graph.Label.SVG,
18027
18028   initialize: function(viz){
18029     this.viz = viz;
18030   },
18031
18032   /*
18033     placeLabel
18034    
18035     Overrides abstract method placeLabel in <Graph.Plot>.
18036    
18037     Parameters:
18038    
18039     tag - A DOM label element.
18040     node - A <Graph.Node>.
18041     controller - A configuration/controller object passed to the visualization.
18042    */
18043   placeLabel: function(tag, node, controller){
18044     var pos = node.pos.getc(true), canvas = this.viz.canvas;
18045     var radius = canvas.getSize();
18046     var labelPos = {
18047       x: Math.round(pos.x + radius.width / 2),
18048       y: Math.round(pos.y + radius.height / 2)
18049     };
18050     tag.setAttribute('x', labelPos.x);
18051     tag.setAttribute('y', labelPos.y);
18052
18053     controller.onPlaceLabel(tag, node);
18054   }
18055 });
18056
18057 /*
18058   Icicle.Label.HTML
18059   
18060   Custom extension of <Graph.Label.HTML>.
18061   
18062   Extends:
18063   
18064   All <Graph.Label.HTML> methods.
18065   
18066   See also:
18067   
18068   <Graph.Label.HTML>
18069   
18070   */
18071 $jit.Icicle.Label.HTML = new Class( {
18072   Implements: Graph.Label.HTML,
18073
18074   initialize: function(viz){
18075     this.viz = viz;
18076   },
18077
18078   /*
18079     placeLabel
18080    
18081     Overrides abstract method placeLabel in <Graph.Plot>.
18082    
18083     Parameters:
18084    
18085     tag - A DOM label element.
18086     node - A <Graph.Node>.
18087     controller - A configuration/controller object passed to the visualization.
18088    */
18089   placeLabel: function(tag, node, controller){
18090     var pos = node.pos.getc(true), canvas = this.viz.canvas;
18091     var radius = canvas.getSize();
18092     var labelPos = {
18093       x: Math.round(pos.x + radius.width / 2),
18094       y: Math.round(pos.y + radius.height / 2)
18095     };
18096
18097     var style = tag.style;
18098     style.left = labelPos.x + 'px';
18099     style.top = labelPos.y + 'px';
18100     style.display = '';
18101
18102     controller.onPlaceLabel(tag, node);
18103   }
18104 });
18105
18106 /*
18107   Class: Icicle.Plot.NodeTypes
18108   
18109   This class contains a list of <Graph.Node> built-in types. 
18110   Node types implemented are 'none', 'rectangle'.
18111   
18112   You can add your custom node types, customizing your visualization to the extreme.
18113   
18114   Example:
18115   
18116   (start code js)
18117     Icicle.Plot.NodeTypes.implement({
18118       'mySpecialType': {
18119         'render': function(node, canvas) {
18120           //print your custom node to canvas
18121         },
18122         //optional
18123         'contains': function(node, pos) {
18124           //return true if pos is inside the node or false otherwise
18125         }
18126       }
18127     });
18128   (end code)
18129   
18130   */
18131 $jit.Icicle.Plot.NodeTypes = new Class( {
18132   'none': {
18133     'render': $.empty
18134   },
18135
18136   'rectangle': {
18137     'render': function(node, canvas, animating) {
18138       var config = this.viz.config;
18139       var offset = config.offset;
18140       var width = node.getData('width');
18141       var height = node.getData('height');
18142       var border = node.getData('border');
18143       var pos = node.pos.getc(true);
18144       var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18145       var ctx = canvas.getCtx();
18146       
18147       if(width - offset < 2 || height - offset < 2) return;
18148       
18149       if(config.cushion) {
18150         var color = node.getData('color');
18151         var lg = ctx.createRadialGradient(posx + (width - offset)/2, 
18152                                           posy + (height - offset)/2, 1, 
18153                                           posx + (width-offset)/2, posy + (height-offset)/2, 
18154                                           width < height? height : width);
18155         var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
18156             function(r) { return r * 0.3 >> 0; }));
18157         lg.addColorStop(0, color);
18158         lg.addColorStop(1, colorGrad);
18159         ctx.fillStyle = lg;
18160       }
18161
18162       if (border) {
18163         ctx.strokeStyle = border;
18164         ctx.lineWidth = 3;
18165       }
18166
18167       ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18168       border && ctx.strokeRect(pos.x, pos.y, width, height);
18169     },
18170
18171     'contains': function(node, pos) {
18172       if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18173       var npos = node.pos.getc(true),
18174           width = node.getData('width'),
18175           height = node.getData('height');
18176       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18177     }
18178   }
18179 });
18180
18181 $jit.Icicle.Plot.EdgeTypes = new Class( {
18182   'none': $.empty
18183 });
18184
18185
18186
18187 /*
18188  * File: Layouts.ForceDirected.js
18189  *
18190 */
18191
18192 /*
18193  * Class: Layouts.ForceDirected
18194  * 
18195  * Implements a Force Directed Layout.
18196  * 
18197  * Implemented By:
18198  * 
18199  * <ForceDirected>
18200  * 
18201  * Credits:
18202  * 
18203  * Marcus Cobden <http://marcuscobden.co.uk>
18204  * 
18205  */
18206 Layouts.ForceDirected = new Class({
18207
18208   getOptions: function(random) {
18209     var s = this.canvas.getSize();
18210     var w = s.width, h = s.height;
18211     //count nodes
18212     var count = 0;
18213     this.graph.eachNode(function(n) { 
18214       count++;
18215     });
18216     var k2 = w * h / count, k = Math.sqrt(k2);
18217     var l = this.config.levelDistance;
18218     
18219     return {
18220       width: w,
18221       height: h,
18222       tstart: w * 0.1,
18223       nodef: function(x) { return k2 / (x || 1); },
18224       edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18225     };
18226   },
18227   
18228   compute: function(property, incremental) {
18229     var prop = $.splat(property || ['current', 'start', 'end']);
18230     var opt = this.getOptions();
18231     NodeDim.compute(this.graph, prop, this.config);
18232     this.graph.computeLevels(this.root, 0, "ignore");
18233     this.graph.eachNode(function(n) {
18234       $.each(prop, function(p) {
18235         var pos = n.getPos(p);
18236         if(pos.equals(Complex.KER)) {
18237           pos.x = opt.width/5 * (Math.random() - 0.5);
18238           pos.y = opt.height/5 * (Math.random() - 0.5);
18239         }
18240         //initialize disp vector
18241         n.disp = {};
18242         $.each(prop, function(p) {
18243           n.disp[p] = $C(0, 0);
18244         });
18245       });
18246     });
18247     this.computePositions(prop, opt, incremental);
18248   },
18249   
18250   computePositions: function(property, opt, incremental) {
18251     var times = this.config.iterations, i = 0, that = this;
18252     if(incremental) {
18253       (function iter() {
18254         for(var total=incremental.iter, j=0; j<total; j++) {
18255           opt.t = opt.tstart * (1 - i++/(times -1));
18256           that.computePositionStep(property, opt);
18257           if(i >= times) {
18258             incremental.onComplete();
18259             return;
18260           }
18261         }
18262         incremental.onStep(Math.round(i / (times -1) * 100));
18263         setTimeout(iter, 1);
18264       })();
18265     } else {
18266       for(; i < times; i++) {
18267         opt.t = opt.tstart * (1 - i/(times -1));
18268         this.computePositionStep(property, opt);
18269       }
18270     }
18271   },
18272   
18273   computePositionStep: function(property, opt) {
18274     var graph = this.graph;
18275     var min = Math.min, max = Math.max;
18276     var dpos = $C(0, 0);
18277     //calculate repulsive forces
18278     graph.eachNode(function(v) {
18279       //initialize disp
18280       $.each(property, function(p) {
18281         v.disp[p].x = 0; v.disp[p].y = 0;
18282       });
18283       graph.eachNode(function(u) {
18284         if(u.id != v.id) {
18285           $.each(property, function(p) {
18286             var vp = v.getPos(p), up = u.getPos(p);
18287             dpos.x = vp.x - up.x;
18288             dpos.y = vp.y - up.y;
18289             var norm = dpos.norm() || 1;
18290             v.disp[p].$add(dpos
18291                 .$scale(opt.nodef(norm) / norm));
18292           });
18293         }
18294       });
18295     });
18296     //calculate attractive forces
18297     var T = !!graph.getNode(this.root).visited;
18298     graph.eachNode(function(node) {
18299       node.eachAdjacency(function(adj) {
18300         var nodeTo = adj.nodeTo;
18301         if(!!nodeTo.visited === T) {
18302           $.each(property, function(p) {
18303             var vp = node.getPos(p), up = nodeTo.getPos(p);
18304             dpos.x = vp.x - up.x;
18305             dpos.y = vp.y - up.y;
18306             var norm = dpos.norm() || 1;
18307             node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18308             nodeTo.disp[p].$add(dpos.$scale(-1));
18309           });
18310         }
18311       });
18312       node.visited = !T;
18313     });
18314     //arrange positions to fit the canvas
18315     var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18316     graph.eachNode(function(u) {
18317       $.each(property, function(p) {
18318         var disp = u.disp[p];
18319         var norm = disp.norm() || 1;
18320         var p = u.getPos(p);
18321         p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
18322             disp.y * min(Math.abs(disp.y), t) / norm));
18323         p.x = min(w2, max(-w2, p.x));
18324         p.y = min(h2, max(-h2, p.y));
18325       });
18326     });
18327   }
18328 });
18329
18330 /*
18331  * File: ForceDirected.js
18332  */
18333
18334 /*
18335    Class: ForceDirected
18336       
18337    A visualization that lays graphs using a Force-Directed layout algorithm.
18338    
18339    Inspired by:
18340   
18341    Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18342    
18343   Implements:
18344   
18345   All <Loader> methods
18346   
18347    Constructor Options:
18348    
18349    Inherits options from
18350    
18351    - <Options.Canvas>
18352    - <Options.Controller>
18353    - <Options.Node>
18354    - <Options.Edge>
18355    - <Options.Label>
18356    - <Options.Events>
18357    - <Options.Tips>
18358    - <Options.NodeStyles>
18359    - <Options.Navigation>
18360    
18361    Additionally, there are two parameters
18362    
18363    levelDistance - (number) Default's *50*. The natural length desired for the edges.
18364    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*. 
18365      
18366    Instance Properties:
18367
18368    canvas - Access a <Canvas> instance.
18369    graph - Access a <Graph> instance.
18370    op - Access a <ForceDirected.Op> instance.
18371    fx - Access a <ForceDirected.Plot> instance.
18372    labels - Access a <ForceDirected.Label> interface implementation.
18373
18374 */
18375
18376 $jit.ForceDirected = new Class( {
18377
18378   Implements: [ Loader, Extras, Layouts.ForceDirected ],
18379
18380   initialize: function(controller) {
18381     var $ForceDirected = $jit.ForceDirected;
18382
18383     var config = {
18384       iterations: 50,
18385       levelDistance: 50
18386     };
18387
18388     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18389         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18390
18391     var canvasConfig = this.config;
18392     if(canvasConfig.useCanvas) {
18393       this.canvas = canvasConfig.useCanvas;
18394       this.config.labelContainer = this.canvas.id + '-label';
18395     } else {
18396       if(canvasConfig.background) {
18397         canvasConfig.background = $.merge({
18398           type: 'Circles'
18399         }, canvasConfig.background);
18400       }
18401       this.canvas = new Canvas(this, canvasConfig);
18402       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18403     }
18404
18405     this.graphOptions = {
18406       'complex': true,
18407       'Node': {
18408         'selected': false,
18409         'exist': true,
18410         'drawn': true
18411       }
18412     };
18413     this.graph = new Graph(this.graphOptions, this.config.Node,
18414         this.config.Edge);
18415     this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18416     this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18417     this.op = new $ForceDirected.Op(this);
18418     this.json = null;
18419     this.busy = false;
18420     // initialize extras
18421     this.initializeExtras();
18422   },
18423
18424   /* 
18425     Method: refresh 
18426     
18427     Computes positions and plots the tree.
18428   */
18429   refresh: function() {
18430     this.compute();
18431     this.plot();
18432   },
18433
18434   reposition: function() {
18435     this.compute('end');
18436   },
18437
18438 /*
18439   Method: computeIncremental
18440   
18441   Performs the Force Directed algorithm incrementally.
18442   
18443   Description:
18444   
18445   ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
18446   This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
18447   avoiding browser messages such as "This script is taking too long to complete".
18448   
18449   Parameters:
18450   
18451   opt - (object) The object properties are described below
18452   
18453   iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
18454   of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18455   
18456   property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
18457   You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
18458   computations for final animation positions then you can just choose 'end'.
18459   
18460   onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
18461   parameter a percentage value.
18462   
18463   onComplete - A callback function called when the algorithm completed.
18464   
18465   Example:
18466   
18467   In this example I calculate the end positions and then animate the graph to those positions
18468   
18469   (start code js)
18470   var fd = new $jit.ForceDirected(...);
18471   fd.computeIncremental({
18472     iter: 20,
18473     property: 'end',
18474     onStep: function(perc) {
18475       Log.write("loading " + perc + "%");
18476     },
18477     onComplete: function() {
18478       Log.write("done");
18479       fd.animate();
18480     }
18481   });
18482   (end code)
18483   
18484   In this example I calculate all positions and (re)plot the graph
18485   
18486   (start code js)
18487   var fd = new ForceDirected(...);
18488   fd.computeIncremental({
18489     iter: 20,
18490     property: ['end', 'start', 'current'],
18491     onStep: function(perc) {
18492       Log.write("loading " + perc + "%");
18493     },
18494     onComplete: function() {
18495       Log.write("done");
18496       fd.plot();
18497     }
18498   });
18499   (end code)
18500   
18501   */
18502   computeIncremental: function(opt) {
18503     opt = $.merge( {
18504       iter: 20,
18505       property: 'end',
18506       onStep: $.empty,
18507       onComplete: $.empty
18508     }, opt || {});
18509
18510     this.config.onBeforeCompute(this.graph.getNode(this.root));
18511     this.compute(opt.property, opt);
18512   },
18513
18514   /*
18515     Method: plot
18516    
18517     Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18518    */
18519   plot: function() {
18520     this.fx.plot();
18521   },
18522
18523   /*
18524      Method: animate
18525     
18526      Animates the graph from the current positions to the 'end' node positions.
18527   */
18528   animate: function(opt) {
18529     this.fx.animate($.merge( {
18530       modes: [ 'linear' ]
18531     }, opt || {}));
18532   }
18533 });
18534
18535 $jit.ForceDirected.$extend = true;
18536
18537 (function(ForceDirected) {
18538
18539   /*
18540      Class: ForceDirected.Op
18541      
18542      Custom extension of <Graph.Op>.
18543
18544      Extends:
18545
18546      All <Graph.Op> methods
18547      
18548      See also:
18549      
18550      <Graph.Op>
18551
18552   */
18553   ForceDirected.Op = new Class( {
18554
18555     Implements: Graph.Op
18556
18557   });
18558
18559   /*
18560     Class: ForceDirected.Plot
18561     
18562     Custom extension of <Graph.Plot>.
18563   
18564     Extends:
18565   
18566     All <Graph.Plot> methods
18567     
18568     See also:
18569     
18570     <Graph.Plot>
18571   
18572   */
18573   ForceDirected.Plot = new Class( {
18574
18575     Implements: Graph.Plot
18576
18577   });
18578
18579   /*
18580     Class: ForceDirected.Label
18581     
18582     Custom extension of <Graph.Label>. 
18583     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18584   
18585     Extends:
18586   
18587     All <Graph.Label> methods and subclasses.
18588   
18589     See also:
18590   
18591     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18592   
18593   */
18594   ForceDirected.Label = {};
18595
18596   /*
18597      ForceDirected.Label.Native
18598      
18599      Custom extension of <Graph.Label.Native>.
18600
18601      Extends:
18602
18603      All <Graph.Label.Native> methods
18604
18605      See also:
18606
18607      <Graph.Label.Native>
18608
18609   */
18610   ForceDirected.Label.Native = new Class( {
18611     Implements: Graph.Label.Native
18612   });
18613
18614   /*
18615     ForceDirected.Label.SVG
18616     
18617     Custom extension of <Graph.Label.SVG>.
18618   
18619     Extends:
18620   
18621     All <Graph.Label.SVG> methods
18622   
18623     See also:
18624   
18625     <Graph.Label.SVG>
18626   
18627   */
18628   ForceDirected.Label.SVG = new Class( {
18629     Implements: Graph.Label.SVG,
18630
18631     initialize: function(viz) {
18632       this.viz = viz;
18633     },
18634
18635     /* 
18636        placeLabel
18637
18638        Overrides abstract method placeLabel in <Graph.Label>.
18639
18640        Parameters:
18641
18642        tag - A DOM label element.
18643        node - A <Graph.Node>.
18644        controller - A configuration/controller object passed to the visualization.
18645       
18646      */
18647     placeLabel: function(tag, node, controller) {
18648       var pos = node.pos.getc(true), 
18649           canvas = this.viz.canvas,
18650           ox = canvas.translateOffsetX,
18651           oy = canvas.translateOffsetY,
18652           sx = canvas.scaleOffsetX,
18653           sy = canvas.scaleOffsetY,
18654           radius = canvas.getSize();
18655       var labelPos = {
18656         x: Math.round(pos.x * sx + ox + radius.width / 2),
18657         y: Math.round(pos.y * sy + oy + radius.height / 2)
18658       };
18659       tag.setAttribute('x', labelPos.x);
18660       tag.setAttribute('y', labelPos.y);
18661
18662       controller.onPlaceLabel(tag, node);
18663     }
18664   });
18665
18666   /*
18667      ForceDirected.Label.HTML
18668      
18669      Custom extension of <Graph.Label.HTML>.
18670
18671      Extends:
18672
18673      All <Graph.Label.HTML> methods.
18674
18675      See also:
18676
18677      <Graph.Label.HTML>
18678
18679   */
18680   ForceDirected.Label.HTML = new Class( {
18681     Implements: Graph.Label.HTML,
18682
18683     initialize: function(viz) {
18684       this.viz = viz;
18685     },
18686     /* 
18687        placeLabel
18688
18689        Overrides abstract method placeLabel in <Graph.Plot>.
18690
18691        Parameters:
18692
18693        tag - A DOM label element.
18694        node - A <Graph.Node>.
18695        controller - A configuration/controller object passed to the visualization.
18696       
18697      */
18698     placeLabel: function(tag, node, controller) {
18699       var pos = node.pos.getc(true), 
18700           canvas = this.viz.canvas,
18701           ox = canvas.translateOffsetX,
18702           oy = canvas.translateOffsetY,
18703           sx = canvas.scaleOffsetX,
18704           sy = canvas.scaleOffsetY,
18705           radius = canvas.getSize();
18706       var labelPos = {
18707         x: Math.round(pos.x * sx + ox + radius.width / 2),
18708         y: Math.round(pos.y * sy + oy + radius.height / 2)
18709       };
18710       var style = tag.style;
18711       style.left = labelPos.x + 'px';
18712       style.top = labelPos.y + 'px';
18713       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18714
18715       controller.onPlaceLabel(tag, node);
18716     }
18717   });
18718
18719   /*
18720     Class: ForceDirected.Plot.NodeTypes
18721
18722     This class contains a list of <Graph.Node> built-in types. 
18723     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18724
18725     You can add your custom node types, customizing your visualization to the extreme.
18726
18727     Example:
18728
18729     (start code js)
18730       ForceDirected.Plot.NodeTypes.implement({
18731         'mySpecialType': {
18732           'render': function(node, canvas) {
18733             //print your custom node to canvas
18734           },
18735           //optional
18736           'contains': function(node, pos) {
18737             //return true if pos is inside the node or false otherwise
18738           }
18739         }
18740       });
18741     (end code)
18742
18743   */
18744   ForceDirected.Plot.NodeTypes = new Class({
18745     'none': {
18746       'render': $.empty,
18747       'contains': $.lambda(false)
18748     },
18749     'circle': {
18750       'render': function(node, canvas){
18751         var pos = node.pos.getc(true), 
18752             dim = node.getData('dim');
18753         this.nodeHelper.circle.render('fill', pos, dim, canvas);
18754       },
18755       'contains': function(node, pos){
18756         var npos = node.pos.getc(true), 
18757             dim = node.getData('dim');
18758         return this.nodeHelper.circle.contains(npos, pos, dim);
18759       }
18760     },
18761     'ellipse': {
18762       'render': function(node, canvas){
18763         var pos = node.pos.getc(true), 
18764             width = node.getData('width'), 
18765             height = node.getData('height');
18766         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18767         },
18768       // TODO(nico): be more precise...
18769       'contains': function(node, pos){
18770         var npos = node.pos.getc(true), 
18771             width = node.getData('width'), 
18772             height = node.getData('height');
18773         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18774       }
18775     },
18776     'square': {
18777       'render': function(node, canvas){
18778         var pos = node.pos.getc(true), 
18779             dim = node.getData('dim');
18780         this.nodeHelper.square.render('fill', pos, dim, canvas);
18781       },
18782       'contains': function(node, pos){
18783         var npos = node.pos.getc(true), 
18784             dim = node.getData('dim');
18785         return this.nodeHelper.square.contains(npos, pos, dim);
18786       }
18787     },
18788     'rectangle': {
18789       'render': function(node, canvas){
18790         var pos = node.pos.getc(true), 
18791             width = node.getData('width'), 
18792             height = node.getData('height');
18793         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18794       },
18795       'contains': function(node, pos){
18796         var npos = node.pos.getc(true), 
18797             width = node.getData('width'), 
18798             height = node.getData('height');
18799         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18800       }
18801     },
18802     'triangle': {
18803       'render': function(node, canvas){
18804         var pos = node.pos.getc(true), 
18805             dim = node.getData('dim');
18806         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18807       },
18808       'contains': function(node, pos) {
18809         var npos = node.pos.getc(true), 
18810             dim = node.getData('dim');
18811         return this.nodeHelper.triangle.contains(npos, pos, dim);
18812       }
18813     },
18814     'star': {
18815       'render': function(node, canvas){
18816         var pos = node.pos.getc(true),
18817             dim = node.getData('dim');
18818         this.nodeHelper.star.render('fill', pos, dim, canvas);
18819       },
18820       'contains': function(node, pos) {
18821         var npos = node.pos.getc(true),
18822             dim = node.getData('dim');
18823         return this.nodeHelper.star.contains(npos, pos, dim);
18824       }
18825     }
18826   });
18827
18828   /*
18829     Class: ForceDirected.Plot.EdgeTypes
18830   
18831     This class contains a list of <Graph.Adjacence> built-in types. 
18832     Edge types implemented are 'none', 'line' and 'arrow'.
18833   
18834     You can add your custom edge types, customizing your visualization to the extreme.
18835   
18836     Example:
18837   
18838     (start code js)
18839       ForceDirected.Plot.EdgeTypes.implement({
18840         'mySpecialType': {
18841           'render': function(adj, canvas) {
18842             //print your custom edge to canvas
18843           },
18844           //optional
18845           'contains': function(adj, pos) {
18846             //return true if pos is inside the arc or false otherwise
18847           }
18848         }
18849       });
18850     (end code)
18851   
18852   */
18853   ForceDirected.Plot.EdgeTypes = new Class({
18854     'none': $.empty,
18855     'line': {
18856       'render': function(adj, canvas) {
18857         var from = adj.nodeFrom.pos.getc(true),
18858             to = adj.nodeTo.pos.getc(true);
18859         this.edgeHelper.line.render(from, to, canvas);
18860       },
18861       'contains': function(adj, pos) {
18862         var from = adj.nodeFrom.pos.getc(true),
18863             to = adj.nodeTo.pos.getc(true);
18864         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18865       }
18866     },
18867     'arrow': {
18868       'render': function(adj, canvas) {
18869         var from = adj.nodeFrom.pos.getc(true),
18870             to = adj.nodeTo.pos.getc(true),
18871             dim = adj.getData('dim'),
18872             direction = adj.data.$direction,
18873             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18874         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18875       },
18876       'contains': function(adj, pos) {
18877         var from = adj.nodeFrom.pos.getc(true),
18878             to = adj.nodeTo.pos.getc(true);
18879         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18880       }
18881     }
18882   });
18883
18884 })($jit.ForceDirected);
18885
18886
18887 /*
18888  * File: Treemap.js
18889  *
18890 */
18891
18892 $jit.TM = {};
18893
18894 var TM = $jit.TM;
18895
18896 $jit.TM.$extend = true;
18897
18898 /*
18899   Class: TM.Base
18900   
18901   Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18902   
18903   Implements:
18904   
18905   All <Loader> methods
18906   
18907   Constructor Options:
18908   
18909   Inherits options from
18910   
18911   - <Options.Canvas>
18912   - <Options.Controller>
18913   - <Options.Node>
18914   - <Options.Edge>
18915   - <Options.Label>
18916   - <Options.Events>
18917   - <Options.Tips>
18918   - <Options.NodeStyles>
18919   - <Options.Navigation>
18920   
18921   Additionally, there are other parameters and some default values changed
18922
18923   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18924   titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18925   offset - (number) Default's *2*. Boxes offset.
18926   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18927   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18928   animate - (boolean) Default's *false*. Whether to animate transitions.
18929   Node.type - Described in <Options.Node>. Default's *rectangle*.
18930   duration - Described in <Options.Fx>. Default's *700*.
18931   fps - Described in <Options.Fx>. Default's *45*.
18932   
18933   Instance Properties:
18934   
18935   canvas - Access a <Canvas> instance.
18936   graph - Access a <Graph> instance.
18937   op - Access a <TM.Op> instance.
18938   fx - Access a <TM.Plot> instance.
18939   labels - Access a <TM.Label> interface implementation.
18940
18941   Inspired by:
18942   
18943   Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18944   
18945   Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18946   
18947    Note:
18948    
18949    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.
18950
18951 */
18952 TM.Base = {
18953   layout: {
18954     orientation: "h",
18955     vertical: function(){
18956       return this.orientation == "v";
18957     },
18958     horizontal: function(){
18959       return this.orientation == "h";
18960     },
18961     change: function(){
18962       this.orientation = this.vertical()? "h" : "v";
18963     }
18964   },
18965
18966   initialize: function(controller){
18967     var config = {
18968       orientation: "h",
18969       titleHeight: 13,
18970       offset: 2,
18971       levelsToShow: 0,
18972       constrained: false,
18973       animate: false,
18974       Node: {
18975         type: 'rectangle',
18976         overridable: true,
18977         //we all know why this is not zero,
18978         //right, Firefox?
18979         width: 3,
18980         height: 3,
18981         color: '#444'
18982       },
18983       Label: {
18984         textAlign: 'center',
18985         textBaseline: 'top'
18986       },
18987       Edge: {
18988         type: 'none'
18989       },
18990       duration: 700,
18991       fps: 45
18992     };
18993
18994     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18995         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18996     this.layout.orientation = this.config.orientation;
18997
18998     var canvasConfig = this.config;
18999     if (canvasConfig.useCanvas) {
19000       this.canvas = canvasConfig.useCanvas;
19001       this.config.labelContainer = this.canvas.id + '-label';
19002     } else {
19003       if(canvasConfig.background) {
19004         canvasConfig.background = $.merge({
19005           type: 'Circles'
19006         }, canvasConfig.background);
19007       }
19008       this.canvas = new Canvas(this, canvasConfig);
19009       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19010     }
19011
19012     this.graphOptions = {
19013       'complex': true,
19014       'Node': {
19015         'selected': false,
19016         'exist': true,
19017         'drawn': true
19018       }
19019     };
19020     this.graph = new Graph(this.graphOptions, this.config.Node,
19021         this.config.Edge);
19022     this.labels = new TM.Label[canvasConfig.Label.type](this);
19023     this.fx = new TM.Plot(this);
19024     this.op = new TM.Op(this);
19025     this.group = new TM.Group(this);
19026     this.geom = new TM.Geom(this);
19027     this.clickedNode = null;
19028     this.busy = false;
19029     // initialize extras
19030     this.initializeExtras();
19031   },
19032
19033   /* 
19034     Method: refresh 
19035     
19036     Computes positions and plots the tree.
19037   */
19038   refresh: function(){
19039     if(this.busy) return;
19040     this.busy = true;
19041     var that = this;
19042     if(this.config.animate) {
19043       this.compute('end');
19044       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
19045           && this.clickedNode.id || this.root));
19046       this.fx.animate($.merge(this.config, {
19047         modes: ['linear', 'node-property:width:height'],
19048         onComplete: function() {
19049           that.busy = false;
19050         }
19051       }));
19052     } else {
19053       var labelType = this.config.Label.type;
19054       if(labelType != 'Native') {
19055         var that = this;
19056         this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
19057       }
19058       this.busy = false;
19059       this.compute();
19060       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
19061           && this.clickedNode.id || this.root));
19062       this.plot();
19063     }
19064   },
19065
19066   /* 
19067     Method: plot 
19068     
19069     Plots the TreeMap. This is a shortcut to *fx.plot*. 
19070   
19071    */
19072   plot: function(){
19073     this.fx.plot();
19074   },
19075
19076   /* 
19077   Method: leaf 
19078   
19079   Returns whether the node is a leaf.
19080   
19081    Parameters:
19082    
19083    n - (object) A <Graph.Node>.
19084
19085  */
19086   leaf: function(n){
19087     return n.getSubnodes([
19088         1, 1
19089     ], "ignore").length == 0;
19090   },
19091   
19092   /* 
19093   Method: enter 
19094   
19095   Sets the node as root.
19096   
19097    Parameters:
19098    
19099    n - (object) A <Graph.Node>.
19100
19101  */
19102   enter: function(n){
19103     if(this.busy) return;
19104     this.busy = true;
19105     
19106     var that = this,
19107         config = this.config,
19108         graph = this.graph,
19109         clickedNode = n,
19110         previousClickedNode = this.clickedNode;
19111
19112     var callback = {
19113       onComplete: function() {
19114         //ensure that nodes are shown for that level
19115         if(config.levelsToShow > 0) {
19116           that.geom.setRightLevelToShow(n);
19117         }
19118         //compute positions of newly inserted nodes
19119         if(config.levelsToShow > 0 || config.request) that.compute();
19120         if(config.animate) {
19121           //fade nodes
19122           graph.nodeList.setData('alpha', 0, 'end');
19123           n.eachSubgraph(function(n) {
19124             n.setData('alpha', 1, 'end');
19125           }, "ignore");
19126           that.fx.animate({
19127             duration: 500,
19128             modes:['node-property:alpha'],
19129             onComplete: function() {
19130               //compute end positions
19131               that.clickedNode = clickedNode;
19132               that.compute('end');
19133               //animate positions
19134               //TODO(nico) commenting this line didn't seem to throw errors...
19135               that.clickedNode = previousClickedNode;
19136               that.fx.animate({
19137                 modes:['linear', 'node-property:width:height'],
19138                 duration: 1000,
19139                 onComplete: function() { 
19140                   that.busy = false;
19141                   //TODO(nico) check comment above
19142                   that.clickedNode = clickedNode;
19143                 }
19144               });
19145             }
19146           });
19147         } else {
19148           that.busy = false;
19149           that.clickedNode = n;
19150           that.refresh();
19151         }
19152       }
19153     };
19154     if(config.request) {
19155       this.requestNodes(clickedNode, callback);
19156     } else {
19157       callback.onComplete();
19158     }
19159   },
19160
19161   /* 
19162   Method: out 
19163   
19164   Sets the parent node of the current selected node as root.
19165
19166  */
19167   out: function(){
19168     if(this.busy) return;
19169     this.busy = true;
19170     this.events.hoveredNode = false;
19171     var that = this,
19172         config = this.config,
19173         graph = this.graph,
19174         parents = graph.getNode(this.clickedNode 
19175             && this.clickedNode.id || this.root).getParents(),
19176         parent = parents[0],
19177         clickedNode = parent,
19178         previousClickedNode = this.clickedNode;
19179     
19180     //if no parents return
19181     if(!parent) {
19182       this.busy = false;
19183       return;
19184     }
19185     //final plot callback
19186     callback = {
19187       onComplete: function() {
19188         that.clickedNode = parent;
19189         if(config.request) {
19190           that.requestNodes(parent, {
19191             onComplete: function() {
19192               that.compute();
19193               that.plot();
19194               that.busy = false;
19195             }
19196           });
19197         } else {
19198           that.compute();
19199           that.plot();
19200           that.busy = false;
19201         }
19202       }
19203     };
19204     //prune tree
19205     if (config.levelsToShow > 0)
19206       this.geom.setRightLevelToShow(parent);
19207     //animate node positions
19208     if(config.animate) {
19209       this.clickedNode = clickedNode;
19210       this.compute('end');
19211       //animate the visible subtree only
19212       this.clickedNode = previousClickedNode;
19213       this.fx.animate({
19214         modes:['linear', 'node-property:width:height'],
19215         duration: 1000,
19216         onComplete: function() {
19217           //animate the parent subtree
19218           that.clickedNode = clickedNode;
19219           //change nodes alpha
19220           graph.eachNode(function(n) {
19221             n.setDataset(['current', 'end'], {
19222               'alpha': [0, 1]
19223             });
19224           }, "ignore");
19225           previousClickedNode.eachSubgraph(function(node) {
19226             node.setData('alpha', 1);
19227           }, "ignore");
19228           that.fx.animate({
19229             duration: 500,
19230             modes:['node-property:alpha'],
19231             onComplete: function() {
19232               callback.onComplete();
19233             }
19234           });
19235         }
19236       });
19237     } else {
19238       callback.onComplete();
19239     }
19240   },
19241
19242   requestNodes: function(node, onComplete){
19243     var handler = $.merge(this.controller, onComplete), 
19244         lev = this.config.levelsToShow;
19245     if (handler.request) {
19246       var leaves = [], d = node._depth;
19247       node.eachLevel(0, lev, function(n){
19248         var nodeLevel = lev - (n._depth - d);
19249         if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19250           leaves.push(n);
19251           n._level = nodeLevel;
19252         }
19253       });
19254       this.group.requestNodes(leaves, handler);
19255     } else {
19256       handler.onComplete();
19257     }
19258   }
19259 };
19260
19261 /*
19262   Class: TM.Op
19263   
19264   Custom extension of <Graph.Op>.
19265   
19266   Extends:
19267   
19268   All <Graph.Op> methods
19269   
19270   See also:
19271   
19272   <Graph.Op>
19273   
19274   */
19275 TM.Op = new Class({
19276   Implements: Graph.Op,
19277
19278   initialize: function(viz){
19279     this.viz = viz;
19280   }
19281 });
19282
19283 //extend level methods of Graph.Geom
19284 TM.Geom = new Class({
19285   Implements: Graph.Geom,
19286   
19287   getRightLevelToShow: function() {
19288     return this.viz.config.levelsToShow;
19289   },
19290   
19291   setRightLevelToShow: function(node) {
19292     var level = this.getRightLevelToShow(), 
19293         fx = this.viz.labels;
19294     node.eachLevel(0, level+1, function(n) {
19295       var d = n._depth - node._depth;
19296       if(d > level) {
19297         n.drawn = false; 
19298         n.exist = false;
19299         n.ignore = true;
19300         fx.hideLabel(n, false);
19301       } else {
19302         n.drawn = true;
19303         n.exist = true;
19304         delete n.ignore;
19305       }
19306     });
19307     node.drawn = true;
19308     delete node.ignore;
19309   }
19310 });
19311
19312 /*
19313
19314 Performs operations on group of nodes.
19315
19316 */
19317 TM.Group = new Class( {
19318
19319   initialize: function(viz){
19320     this.viz = viz;
19321     this.canvas = viz.canvas;
19322     this.config = viz.config;
19323   },
19324
19325   /*
19326   
19327     Calls the request method on the controller to request a subtree for each node. 
19328   */
19329   requestNodes: function(nodes, controller){
19330     var counter = 0, len = nodes.length, nodeSelected = {};
19331     var complete = function(){
19332       controller.onComplete();
19333     };
19334     var viz = this.viz;
19335     if (len == 0)
19336       complete();
19337     for ( var i = 0; i < len; i++) {
19338       nodeSelected[nodes[i].id] = nodes[i];
19339       controller.request(nodes[i].id, nodes[i]._level, {
19340         onComplete: function(nodeId, data){
19341           if (data && data.children) {
19342             data.id = nodeId;
19343             viz.op.sum(data, {
19344               type: 'nothing'
19345             });
19346           }
19347           if (++counter == len) {
19348             viz.graph.computeLevels(viz.root, 0);
19349             complete();
19350           }
19351         }
19352       });
19353     }
19354   }
19355 });
19356
19357 /*
19358   Class: TM.Plot
19359   
19360   Custom extension of <Graph.Plot>.
19361   
19362   Extends:
19363   
19364   All <Graph.Plot> methods
19365   
19366   See also:
19367   
19368   <Graph.Plot>
19369   
19370   */
19371 TM.Plot = new Class({
19372
19373   Implements: Graph.Plot,
19374
19375   initialize: function(viz){
19376     this.viz = viz;
19377     this.config = viz.config;
19378     this.node = this.config.Node;
19379     this.edge = this.config.Edge;
19380     this.animation = new Animation;
19381     this.nodeTypes = new TM.Plot.NodeTypes;
19382     this.edgeTypes = new TM.Plot.EdgeTypes;
19383     this.labels = viz.labels;
19384   },
19385
19386   plot: function(opt, animating){
19387     var viz = this.viz, 
19388         graph = viz.graph;
19389     viz.canvas.clear();
19390     this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19391       'withLabels': true,
19392       'hideLabels': false,
19393       'plotSubtree': function(n, ch){
19394         return n.anySubnode("exist");
19395       }
19396     }), animating);
19397   }
19398 });
19399
19400 /*
19401   Class: TM.Label
19402   
19403   Custom extension of <Graph.Label>. 
19404   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19405
19406   Extends:
19407
19408   All <Graph.Label> methods and subclasses.
19409
19410   See also:
19411
19412   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19413   
19414 */
19415 TM.Label = {};
19416
19417 /*
19418  TM.Label.Native
19419
19420  Custom extension of <Graph.Label.Native>.
19421
19422  Extends:
19423
19424  All <Graph.Label.Native> methods
19425
19426  See also:
19427
19428  <Graph.Label.Native>
19429 */
19430 TM.Label.Native = new Class({
19431   Implements: Graph.Label.Native,
19432
19433   initialize: function(viz) {
19434     this.config = viz.config;
19435     this.leaf = viz.leaf;
19436   },
19437   
19438   renderLabel: function(canvas, node, controller){
19439     if(!this.leaf(node) && !this.config.titleHeight) return;
19440     var pos = node.pos.getc(true), 
19441         ctx = canvas.getCtx(),
19442         width = node.getData('width'),
19443         height = node.getData('height'),
19444         x = pos.x + width/2,
19445         y = pos.y;
19446         
19447     ctx.fillText(node.name, x, y, width);
19448   }
19449 });
19450
19451 /*
19452  TM.Label.SVG
19453
19454   Custom extension of <Graph.Label.SVG>.
19455
19456   Extends:
19457
19458   All <Graph.Label.SVG> methods
19459
19460   See also:
19461
19462   <Graph.Label.SVG>
19463 */
19464 TM.Label.SVG = new Class( {
19465   Implements: Graph.Label.SVG,
19466
19467   initialize: function(viz){
19468     this.viz = viz;
19469     this.leaf = viz.leaf;
19470     this.config = viz.config;
19471   },
19472
19473   /* 
19474   placeLabel
19475
19476   Overrides abstract method placeLabel in <Graph.Plot>.
19477
19478   Parameters:
19479
19480   tag - A DOM label element.
19481   node - A <Graph.Node>.
19482   controller - A configuration/controller object passed to the visualization.
19483   
19484   */
19485   placeLabel: function(tag, node, controller){
19486     var pos = node.pos.getc(true), 
19487         canvas = this.viz.canvas,
19488         ox = canvas.translateOffsetX,
19489         oy = canvas.translateOffsetY,
19490         sx = canvas.scaleOffsetX,
19491         sy = canvas.scaleOffsetY,
19492         radius = canvas.getSize();
19493     var labelPos = {
19494       x: Math.round(pos.x * sx + ox + radius.width / 2),
19495       y: Math.round(pos.y * sy + oy + radius.height / 2)
19496     };
19497     tag.setAttribute('x', labelPos.x);
19498     tag.setAttribute('y', labelPos.y);
19499
19500     if(!this.leaf(node) && !this.config.titleHeight) {
19501       tag.style.display = 'none';
19502     }
19503     controller.onPlaceLabel(tag, node);
19504   }
19505 });
19506
19507 /*
19508  TM.Label.HTML
19509
19510  Custom extension of <Graph.Label.HTML>.
19511
19512  Extends:
19513
19514  All <Graph.Label.HTML> methods.
19515
19516  See also:
19517
19518  <Graph.Label.HTML>
19519
19520 */
19521 TM.Label.HTML = new Class( {
19522   Implements: Graph.Label.HTML,
19523
19524   initialize: function(viz){
19525     this.viz = viz;
19526     this.leaf = viz.leaf;
19527     this.config = viz.config;
19528   },
19529
19530   /* 
19531     placeLabel
19532   
19533     Overrides abstract method placeLabel in <Graph.Plot>.
19534   
19535     Parameters:
19536   
19537     tag - A DOM label element.
19538     node - A <Graph.Node>.
19539     controller - A configuration/controller object passed to the visualization.
19540   
19541   */
19542   placeLabel: function(tag, node, controller){
19543     var pos = node.pos.getc(true), 
19544         canvas = this.viz.canvas,
19545         ox = canvas.translateOffsetX,
19546         oy = canvas.translateOffsetY,
19547         sx = canvas.scaleOffsetX,
19548         sy = canvas.scaleOffsetY,
19549         radius = canvas.getSize();
19550     var labelPos = {
19551       x: Math.round(pos.x * sx + ox + radius.width / 2),
19552       y: Math.round(pos.y * sy + oy + radius.height / 2)
19553     };
19554
19555     var style = tag.style;
19556     style.left = labelPos.x + 'px';
19557     style.top = labelPos.y + 'px';
19558     style.width = node.getData('width') * sx + 'px';
19559     style.height = node.getData('height') * sy + 'px';
19560     style.zIndex = node._depth * 100;
19561     style.display = '';
19562
19563     if(!this.leaf(node) && !this.config.titleHeight) {
19564       tag.style.display = 'none';
19565     }
19566     controller.onPlaceLabel(tag, node);
19567   }
19568 });
19569
19570 /*
19571   Class: TM.Plot.NodeTypes
19572
19573   This class contains a list of <Graph.Node> built-in types. 
19574   Node types implemented are 'none', 'rectangle'.
19575
19576   You can add your custom node types, customizing your visualization to the extreme.
19577
19578   Example:
19579
19580   (start code js)
19581     TM.Plot.NodeTypes.implement({
19582       'mySpecialType': {
19583         'render': function(node, canvas) {
19584           //print your custom node to canvas
19585         },
19586         //optional
19587         'contains': function(node, pos) {
19588           //return true if pos is inside the node or false otherwise
19589         }
19590       }
19591     });
19592   (end code)
19593
19594 */
19595 TM.Plot.NodeTypes = new Class( {
19596   'none': {
19597     'render': $.empty
19598   },
19599
19600   'rectangle': {
19601     'render': function(node, canvas, animating){
19602       var leaf = this.viz.leaf(node),
19603           config = this.config,
19604           offst = config.offset,
19605           titleHeight = config.titleHeight,
19606           pos = node.pos.getc(true),
19607           width = node.getData('width'),
19608           height = node.getData('height'),
19609           border = node.getData('border'),
19610           ctx = canvas.getCtx(),
19611           posx = pos.x + offst / 2, 
19612           posy = pos.y + offst / 2;
19613       if(width <= offst || height <= offst) return;
19614       if (leaf) {
19615         if(config.cushion) {
19616           var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1, 
19617               posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19618           var color = node.getData('color');
19619           var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
19620               function(r) { return r * 0.2 >> 0; }));
19621           lg.addColorStop(0, color);
19622           lg.addColorStop(1, colorGrad);
19623           ctx.fillStyle = lg;
19624         }
19625         ctx.fillRect(posx, posy, width - offst, height - offst);
19626         if(border) {
19627           ctx.save();
19628           ctx.strokeStyle = border;
19629           ctx.strokeRect(posx, posy, width - offst, height - offst);
19630           ctx.restore();
19631         }
19632       } else if(titleHeight > 0){
19633         ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19634             titleHeight - offst);
19635         if(border) {
19636           ctx.save();
19637           ctx.strokeStyle = border;
19638           ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19639               height - offst);
19640           ctx.restore();
19641         }
19642       }
19643     },
19644     'contains': function(node, pos) {
19645       if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19646       var npos = node.pos.getc(true),
19647           width = node.getData('width'), 
19648           leaf = this.viz.leaf(node),
19649           height = leaf? node.getData('height') : this.config.titleHeight;
19650       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19651     }
19652   }
19653 });
19654
19655 TM.Plot.EdgeTypes = new Class( {
19656   'none': $.empty
19657 });
19658
19659 /*
19660   Class: TM.SliceAndDice
19661   
19662   A slice and dice TreeMap visualization.
19663   
19664   Implements:
19665   
19666   All <TM.Base> methods and properties.
19667 */
19668 TM.SliceAndDice = new Class( {
19669   Implements: [
19670       Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19671   ]
19672 });
19673
19674 /*
19675   Class: TM.Squarified
19676   
19677   A squarified TreeMap visualization.
19678
19679   Implements:
19680   
19681   All <TM.Base> methods and properties.
19682 */
19683 TM.Squarified = new Class( {
19684   Implements: [
19685       Loader, Extras, TM.Base, Layouts.TM.Squarified
19686   ]
19687 });
19688
19689 /*
19690   Class: TM.Strip
19691   
19692   A strip TreeMap visualization.
19693
19694   Implements:
19695   
19696   All <TM.Base> methods and properties.
19697 */
19698 TM.Strip = new Class( {
19699   Implements: [
19700       Loader, Extras, TM.Base, Layouts.TM.Strip
19701   ]
19702 });
19703
19704
19705 /*
19706  * File: RGraph.js
19707  *
19708  */
19709
19710 /*
19711    Class: RGraph
19712    
19713    A radial graph visualization with advanced animations.
19714    
19715    Inspired by:
19716  
19717    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>
19718    
19719    Note:
19720    
19721    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.
19722    
19723   Implements:
19724   
19725   All <Loader> methods
19726   
19727    Constructor Options:
19728    
19729    Inherits options from
19730    
19731    - <Options.Canvas>
19732    - <Options.Controller>
19733    - <Options.Node>
19734    - <Options.Edge>
19735    - <Options.Label>
19736    - <Options.Events>
19737    - <Options.Tips>
19738    - <Options.NodeStyles>
19739    - <Options.Navigation>
19740    
19741    Additionally, there are other parameters and some default values changed
19742    
19743    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19744    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
19745      
19746    Instance Properties:
19747
19748    canvas - Access a <Canvas> instance.
19749    graph - Access a <Graph> instance.
19750    op - Access a <RGraph.Op> instance.
19751    fx - Access a <RGraph.Plot> instance.
19752    labels - Access a <RGraph.Label> interface implementation.   
19753 */
19754
19755 $jit.RGraph = new Class( {
19756
19757   Implements: [
19758       Loader, Extras, Layouts.Radial
19759   ],
19760
19761   initialize: function(controller){
19762     var $RGraph = $jit.RGraph;
19763
19764     var config = {
19765       interpolation: 'linear',
19766       levelDistance: 100
19767     };
19768
19769     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19770         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19771
19772     var canvasConfig = this.config;
19773     if(canvasConfig.useCanvas) {
19774       this.canvas = canvasConfig.useCanvas;
19775       this.config.labelContainer = this.canvas.id + '-label';
19776     } else {
19777       if(canvasConfig.background) {
19778         canvasConfig.background = $.merge({
19779           type: 'Circles'
19780         }, canvasConfig.background);
19781       }
19782       this.canvas = new Canvas(this, canvasConfig);
19783       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19784     }
19785
19786     this.graphOptions = {
19787       'complex': false,
19788       'Node': {
19789         'selected': false,
19790         'exist': true,
19791         'drawn': true
19792       }
19793     };
19794     this.graph = new Graph(this.graphOptions, this.config.Node,
19795         this.config.Edge);
19796     this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19797     this.fx = new $RGraph.Plot(this, $RGraph);
19798     this.op = new $RGraph.Op(this);
19799     this.json = null;
19800     this.root = null;
19801     this.busy = false;
19802     this.parent = false;
19803     // initialize extras
19804     this.initializeExtras();
19805   },
19806
19807   /* 
19808   
19809     createLevelDistanceFunc 
19810   
19811     Returns the levelDistance function used for calculating a node distance 
19812     to its origin. This function returns a function that is computed 
19813     per level and not per node, such that all nodes with the same depth will have the 
19814     same distance to the origin. The resulting function gets the 
19815     parent node as parameter and returns a float.
19816
19817    */
19818   createLevelDistanceFunc: function(){
19819     var ld = this.config.levelDistance;
19820     return function(elem){
19821       return (elem._depth + 1) * ld;
19822     };
19823   },
19824
19825   /* 
19826      Method: refresh 
19827      
19828      Computes positions and plots the tree.
19829
19830    */
19831   refresh: function(){
19832     this.compute();
19833     this.plot();
19834   },
19835
19836   reposition: function(){
19837     this.compute('end');
19838   },
19839
19840   /*
19841    Method: plot
19842   
19843    Plots the RGraph. This is a shortcut to *fx.plot*.
19844   */
19845   plot: function(){
19846     this.fx.plot();
19847   },
19848   /*
19849    getNodeAndParentAngle
19850   
19851    Returns the _parent_ of the given node, also calculating its angle span.
19852   */
19853   getNodeAndParentAngle: function(id){
19854     var theta = false;
19855     var n = this.graph.getNode(id);
19856     var ps = n.getParents();
19857     var p = (ps.length > 0)? ps[0] : false;
19858     if (p) {
19859       var posParent = p.pos.getc(), posChild = n.pos.getc();
19860       var newPos = posParent.add(posChild.scale(-1));
19861       theta = Math.atan2(newPos.y, newPos.x);
19862       if (theta < 0)
19863         theta += 2 * Math.PI;
19864     }
19865     return {
19866       parent: p,
19867       theta: theta
19868     };
19869   },
19870   /*
19871    tagChildren
19872   
19873    Enumerates the children in order to maintain child ordering (second constraint of the paper).
19874   */
19875   tagChildren: function(par, id){
19876     if (par.angleSpan) {
19877       var adjs = [];
19878       par.eachAdjacency(function(elem){
19879         adjs.push(elem.nodeTo);
19880       }, "ignore");
19881       var len = adjs.length;
19882       for ( var i = 0; i < len && id != adjs[i].id; i++)
19883         ;
19884       for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19885         adjs[j].dist = k++;
19886       }
19887     }
19888   },
19889   /* 
19890   Method: onClick 
19891   
19892   Animates the <RGraph> to center the node specified by *id*.
19893
19894    Parameters:
19895
19896    id - A <Graph.Node> id.
19897    opt - (optional|object) An object containing some extra properties described below
19898    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19899
19900    Example:
19901
19902    (start code js)
19903      rgraph.onClick('someid');
19904      //or also...
19905      rgraph.onClick('someid', {
19906       hideLabels: false
19907      });
19908     (end code)
19909     
19910   */
19911   onClick: function(id, opt){
19912     if (this.root != id && !this.busy) {
19913       this.busy = true;
19914       this.root = id;
19915       that = this;
19916       this.controller.onBeforeCompute(this.graph.getNode(id));
19917       var obj = this.getNodeAndParentAngle(id);
19918
19919       // second constraint
19920       this.tagChildren(obj.parent, id);
19921       this.parent = obj.parent;
19922       this.compute('end');
19923
19924       // first constraint
19925       var thetaDiff = obj.theta - obj.parent.endPos.theta;
19926       this.graph.eachNode(function(elem){
19927         elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19928       });
19929
19930       var mode = this.config.interpolation;
19931       opt = $.merge( {
19932         onComplete: $.empty
19933       }, opt || {});
19934
19935       this.fx.animate($.merge( {
19936         hideLabels: true,
19937         modes: [
19938           mode
19939         ]
19940       }, opt, {
19941         onComplete: function(){
19942           that.busy = false;
19943           opt.onComplete();
19944         }
19945       }));
19946     }
19947   }
19948 });
19949
19950 $jit.RGraph.$extend = true;
19951
19952 (function(RGraph){
19953
19954   /*
19955      Class: RGraph.Op
19956      
19957      Custom extension of <Graph.Op>.
19958
19959      Extends:
19960
19961      All <Graph.Op> methods
19962      
19963      See also:
19964      
19965      <Graph.Op>
19966
19967   */
19968   RGraph.Op = new Class( {
19969
19970     Implements: Graph.Op
19971
19972   });
19973
19974   /*
19975      Class: RGraph.Plot
19976     
19977     Custom extension of <Graph.Plot>.
19978   
19979     Extends:
19980   
19981     All <Graph.Plot> methods
19982     
19983     See also:
19984     
19985     <Graph.Plot>
19986   
19987   */
19988   RGraph.Plot = new Class( {
19989
19990     Implements: Graph.Plot
19991
19992   });
19993
19994   /*
19995     Object: RGraph.Label
19996
19997     Custom extension of <Graph.Label>. 
19998     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19999   
20000     Extends:
20001   
20002     All <Graph.Label> methods and subclasses.
20003   
20004     See also:
20005   
20006     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20007   
20008    */
20009   RGraph.Label = {};
20010
20011   /*
20012      RGraph.Label.Native
20013
20014      Custom extension of <Graph.Label.Native>.
20015
20016      Extends:
20017
20018      All <Graph.Label.Native> methods
20019
20020      See also:
20021
20022      <Graph.Label.Native>
20023
20024   */
20025   RGraph.Label.Native = new Class( {
20026     Implements: Graph.Label.Native
20027   });
20028
20029   /*
20030      RGraph.Label.SVG
20031     
20032     Custom extension of <Graph.Label.SVG>.
20033   
20034     Extends:
20035   
20036     All <Graph.Label.SVG> methods
20037   
20038     See also:
20039   
20040     <Graph.Label.SVG>
20041   
20042   */
20043   RGraph.Label.SVG = new Class( {
20044     Implements: Graph.Label.SVG,
20045
20046     initialize: function(viz){
20047       this.viz = viz;
20048     },
20049
20050     /* 
20051        placeLabel
20052
20053        Overrides abstract method placeLabel in <Graph.Plot>.
20054
20055        Parameters:
20056
20057        tag - A DOM label element.
20058        node - A <Graph.Node>.
20059        controller - A configuration/controller object passed to the visualization.
20060       
20061      */
20062     placeLabel: function(tag, node, controller){
20063       var pos = node.pos.getc(true), 
20064           canvas = this.viz.canvas,
20065           ox = canvas.translateOffsetX,
20066           oy = canvas.translateOffsetY,
20067           sx = canvas.scaleOffsetX,
20068           sy = canvas.scaleOffsetY,
20069           radius = canvas.getSize();
20070       var labelPos = {
20071         x: Math.round(pos.x * sx + ox + radius.width / 2),
20072         y: Math.round(pos.y * sy + oy + radius.height / 2)
20073       };
20074       tag.setAttribute('x', labelPos.x);
20075       tag.setAttribute('y', labelPos.y);
20076
20077       controller.onPlaceLabel(tag, node);
20078     }
20079   });
20080
20081   /*
20082      RGraph.Label.HTML
20083
20084      Custom extension of <Graph.Label.HTML>.
20085
20086      Extends:
20087
20088      All <Graph.Label.HTML> methods.
20089
20090      See also:
20091
20092      <Graph.Label.HTML>
20093
20094   */
20095   RGraph.Label.HTML = new Class( {
20096     Implements: Graph.Label.HTML,
20097
20098     initialize: function(viz){
20099       this.viz = viz;
20100     },
20101     /* 
20102        placeLabel
20103
20104        Overrides abstract method placeLabel in <Graph.Plot>.
20105
20106        Parameters:
20107
20108        tag - A DOM label element.
20109        node - A <Graph.Node>.
20110        controller - A configuration/controller object passed to the visualization.
20111       
20112      */
20113     placeLabel: function(tag, node, controller){
20114       var pos = node.pos.getc(true), 
20115           canvas = this.viz.canvas,
20116           ox = canvas.translateOffsetX,
20117           oy = canvas.translateOffsetY,
20118           sx = canvas.scaleOffsetX,
20119           sy = canvas.scaleOffsetY,
20120           radius = canvas.getSize();
20121       var labelPos = {
20122         x: Math.round(pos.x * sx + ox + radius.width / 2),
20123         y: Math.round(pos.y * sy + oy + radius.height / 2)
20124       };
20125
20126       var style = tag.style;
20127       style.left = labelPos.x + 'px';
20128       style.top = labelPos.y + 'px';
20129       style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20130
20131       controller.onPlaceLabel(tag, node);
20132     }
20133   });
20134
20135   /*
20136     Class: RGraph.Plot.NodeTypes
20137
20138     This class contains a list of <Graph.Node> built-in types. 
20139     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20140
20141     You can add your custom node types, customizing your visualization to the extreme.
20142
20143     Example:
20144
20145     (start code js)
20146       RGraph.Plot.NodeTypes.implement({
20147         'mySpecialType': {
20148           'render': function(node, canvas) {
20149             //print your custom node to canvas
20150           },
20151           //optional
20152           'contains': function(node, pos) {
20153             //return true if pos is inside the node or false otherwise
20154           }
20155         }
20156       });
20157     (end code)
20158
20159   */
20160   RGraph.Plot.NodeTypes = new Class({
20161     'none': {
20162       'render': $.empty,
20163       'contains': $.lambda(false)
20164     },
20165     'circle': {
20166       'render': function(node, canvas){
20167         var pos = node.pos.getc(true), 
20168             dim = node.getData('dim');
20169         this.nodeHelper.circle.render('fill', pos, dim, canvas);
20170       },
20171       'contains': function(node, pos){
20172         var npos = node.pos.getc(true), 
20173             dim = node.getData('dim');
20174         return this.nodeHelper.circle.contains(npos, pos, dim);
20175       }
20176     },
20177     'ellipse': {
20178       'render': function(node, canvas){
20179         var pos = node.pos.getc(true), 
20180             width = node.getData('width'), 
20181             height = node.getData('height');
20182         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20183         },
20184       // TODO(nico): be more precise...
20185       'contains': function(node, pos){
20186         var npos = node.pos.getc(true), 
20187             width = node.getData('width'), 
20188             height = node.getData('height');
20189         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20190       }
20191     },
20192     'square': {
20193       'render': function(node, canvas){
20194         var pos = node.pos.getc(true), 
20195             dim = node.getData('dim');
20196         this.nodeHelper.square.render('fill', pos, dim, canvas);
20197       },
20198       'contains': function(node, pos){
20199         var npos = node.pos.getc(true), 
20200             dim = node.getData('dim');
20201         return this.nodeHelper.square.contains(npos, pos, dim);
20202       }
20203     },
20204     'rectangle': {
20205       'render': function(node, canvas){
20206         var pos = node.pos.getc(true), 
20207             width = node.getData('width'), 
20208             height = node.getData('height');
20209         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20210       },
20211       'contains': function(node, pos){
20212         var npos = node.pos.getc(true), 
20213             width = node.getData('width'), 
20214             height = node.getData('height');
20215         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20216       }
20217     },
20218     'triangle': {
20219       'render': function(node, canvas){
20220         var pos = node.pos.getc(true), 
20221             dim = node.getData('dim');
20222         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20223       },
20224       'contains': function(node, pos) {
20225         var npos = node.pos.getc(true), 
20226             dim = node.getData('dim');
20227         return this.nodeHelper.triangle.contains(npos, pos, dim);
20228       }
20229     },
20230     'star': {
20231       'render': function(node, canvas){
20232         var pos = node.pos.getc(true),
20233             dim = node.getData('dim');
20234         this.nodeHelper.star.render('fill', pos, dim, canvas);
20235       },
20236       'contains': function(node, pos) {
20237         var npos = node.pos.getc(true),
20238             dim = node.getData('dim');
20239         return this.nodeHelper.star.contains(npos, pos, dim);
20240       }
20241     }
20242   });
20243
20244   /*
20245     Class: RGraph.Plot.EdgeTypes
20246
20247     This class contains a list of <Graph.Adjacence> built-in types. 
20248     Edge types implemented are 'none', 'line' and 'arrow'.
20249   
20250     You can add your custom edge types, customizing your visualization to the extreme.
20251   
20252     Example:
20253   
20254     (start code js)
20255       RGraph.Plot.EdgeTypes.implement({
20256         'mySpecialType': {
20257           'render': function(adj, canvas) {
20258             //print your custom edge to canvas
20259           },
20260           //optional
20261           'contains': function(adj, pos) {
20262             //return true if pos is inside the arc or false otherwise
20263           }
20264         }
20265       });
20266     (end code)
20267   
20268   */
20269   RGraph.Plot.EdgeTypes = new Class({
20270     'none': $.empty,
20271     'line': {
20272       'render': function(adj, canvas) {
20273         var from = adj.nodeFrom.pos.getc(true),
20274             to = adj.nodeTo.pos.getc(true);
20275         this.edgeHelper.line.render(from, to, canvas);
20276       },
20277       'contains': function(adj, pos) {
20278         var from = adj.nodeFrom.pos.getc(true),
20279             to = adj.nodeTo.pos.getc(true);
20280         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20281       }
20282     },
20283     'arrow': {
20284       'render': function(adj, canvas) {
20285         var from = adj.nodeFrom.pos.getc(true),
20286             to = adj.nodeTo.pos.getc(true),
20287             dim = adj.getData('dim'),
20288             direction = adj.data.$direction,
20289             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20290         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20291       },
20292       'contains': function(adj, pos) {
20293         var from = adj.nodeFrom.pos.getc(true),
20294             to = adj.nodeTo.pos.getc(true);
20295         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20296       }
20297     }
20298   });
20299
20300 })($jit.RGraph);
20301
20302
20303 /*
20304  * File: Hypertree.js
20305  * 
20306 */
20307
20308 /* 
20309      Complex 
20310      
20311      A multi-purpose Complex Class with common methods. Extended for the Hypertree. 
20312  
20313 */
20314 /* 
20315    moebiusTransformation 
20316  
20317    Calculates a moebius transformation for this point / complex. 
20318     For more information go to: 
20319         http://en.wikipedia.org/wiki/Moebius_transformation. 
20320  
20321    Parameters: 
20322  
20323       c - An initialized Complex instance representing a translation Vector. 
20324 */
20325
20326 Complex.prototype.moebiusTransformation = function(c) {
20327   var num = this.add(c);
20328   var den = c.$conjugate().$prod(this);
20329   den.x++;
20330   return num.$div(den);
20331 };
20332
20333 /* 
20334     moebiusTransformation 
20335      
20336     Calculates a moebius transformation for the hyperbolic tree. 
20337      
20338     <http://en.wikipedia.org/wiki/Moebius_transformation> 
20339       
20340      Parameters: 
20341      
20342         graph - A <Graph> instance.
20343         pos - A <Complex>.
20344         prop - A property array.
20345         theta - Rotation angle. 
20346         startPos - _optional_ start position. 
20347 */
20348 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20349   this.eachNode(graph, function(elem) {
20350     for ( var i = 0; i < prop.length; i++) {
20351       var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20352       elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20353     }
20354   }, flags);
20355 };
20356
20357 /* 
20358    Class: Hypertree 
20359    
20360    A Hyperbolic Tree/Graph visualization.
20361    
20362    Inspired by:
20363  
20364    A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli). 
20365    <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20366  
20367   Note:
20368  
20369   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.
20370
20371   Implements:
20372   
20373   All <Loader> methods
20374   
20375   Constructor Options:
20376   
20377   Inherits options from
20378   
20379   - <Options.Canvas>
20380   - <Options.Controller>
20381   - <Options.Node>
20382   - <Options.Edge>
20383   - <Options.Label>
20384   - <Options.Events>
20385   - <Options.Tips>
20386   - <Options.NodeStyles>
20387   - <Options.Navigation>
20388   
20389   Additionally, there are other parameters and some default values changed
20390   
20391   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*.
20392   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.
20393   fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20394   duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20395   Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
20396   
20397   Instance Properties:
20398   
20399   canvas - Access a <Canvas> instance.
20400   graph - Access a <Graph> instance.
20401   op - Access a <Hypertree.Op> instance.
20402   fx - Access a <Hypertree.Plot> instance.
20403   labels - Access a <Hypertree.Label> interface implementation.
20404
20405 */
20406
20407 $jit.Hypertree = new Class( {
20408
20409   Implements: [ Loader, Extras, Layouts.Radial ],
20410
20411   initialize: function(controller) {
20412     var $Hypertree = $jit.Hypertree;
20413
20414     var config = {
20415       radius: "auto",
20416       offset: 0,
20417       Edge: {
20418         type: 'hyperline'
20419       },
20420       duration: 1500,
20421       fps: 35
20422     };
20423     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20424         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20425
20426     var canvasConfig = this.config;
20427     if(canvasConfig.useCanvas) {
20428       this.canvas = canvasConfig.useCanvas;
20429       this.config.labelContainer = this.canvas.id + '-label';
20430     } else {
20431       if(canvasConfig.background) {
20432         canvasConfig.background = $.merge({
20433           type: 'Circles'
20434         }, canvasConfig.background);
20435       }
20436       this.canvas = new Canvas(this, canvasConfig);
20437       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20438     }
20439
20440     this.graphOptions = {
20441       'complex': false,
20442       'Node': {
20443         'selected': false,
20444         'exist': true,
20445         'drawn': true
20446       }
20447     };
20448     this.graph = new Graph(this.graphOptions, this.config.Node,
20449         this.config.Edge);
20450     this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20451     this.fx = new $Hypertree.Plot(this, $Hypertree);
20452     this.op = new $Hypertree.Op(this);
20453     this.json = null;
20454     this.root = null;
20455     this.busy = false;
20456     // initialize extras
20457     this.initializeExtras();
20458   },
20459
20460   /* 
20461   
20462   createLevelDistanceFunc 
20463
20464   Returns the levelDistance function used for calculating a node distance 
20465   to its origin. This function returns a function that is computed 
20466   per level and not per node, such that all nodes with the same depth will have the 
20467   same distance to the origin. The resulting function gets the 
20468   parent node as parameter and returns a float.
20469
20470   */
20471   createLevelDistanceFunc: function() {
20472     // get max viz. length.
20473     var r = this.getRadius();
20474     // get max depth.
20475     var depth = 0, max = Math.max, config = this.config;
20476     this.graph.eachNode(function(node) {
20477       depth = max(node._depth, depth);
20478     }, "ignore");
20479     depth++;
20480     // node distance generator
20481     var genDistFunc = function(a) {
20482       return function(node) {
20483         node.scale = r;
20484         var d = node._depth + 1;
20485         var acum = 0, pow = Math.pow;
20486         while (d) {
20487           acum += pow(a, d--);
20488         }
20489         return acum - config.offset;
20490       };
20491     };
20492     // estimate better edge length.
20493     for ( var i = 0.51; i <= 1; i += 0.01) {
20494       var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20495       if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20496     }
20497     return genDistFunc(0.75);
20498   },
20499
20500   /* 
20501     Method: getRadius 
20502     
20503     Returns the current radius of the visualization. If *config.radius* is *auto* then it 
20504     calculates the radius by taking the smaller size of the <Canvas> widget.
20505     
20506     See also:
20507     
20508     <Canvas.getSize>
20509    
20510   */
20511   getRadius: function() {
20512     var rad = this.config.radius;
20513     if (rad !== "auto") { return rad; }
20514     var s = this.canvas.getSize();
20515     return Math.min(s.width, s.height) / 2;
20516   },
20517
20518   /* 
20519     Method: refresh 
20520     
20521     Computes positions and plots the tree.
20522
20523     Parameters:
20524
20525     reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20526
20527    */
20528   refresh: function(reposition) {
20529     if (reposition) {
20530       this.reposition();
20531       this.graph.eachNode(function(node) {
20532         node.startPos.rho = node.pos.rho = node.endPos.rho;
20533         node.startPos.theta = node.pos.theta = node.endPos.theta;
20534       });
20535     } else {
20536       this.compute();
20537     }
20538     this.plot();
20539   },
20540
20541   /* 
20542    reposition 
20543    
20544    Computes nodes' positions and restores the tree to its previous position.
20545
20546    For calculating nodes' positions the root must be placed on its origin. This method does this 
20547      and then attemps to restore the hypertree to its previous position.
20548     
20549   */
20550   reposition: function() {
20551     this.compute('end');
20552     var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20553     Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20554         'end', "ignore");
20555     this.graph.eachNode(function(node) {
20556       if (node.ignore) {
20557         node.endPos.rho = node.pos.rho;
20558         node.endPos.theta = node.pos.theta;
20559       }
20560     });
20561   },
20562
20563   /* 
20564    Method: plot 
20565    
20566    Plots the <Hypertree>. This is a shortcut to *fx.plot*. 
20567
20568   */
20569   plot: function() {
20570     this.fx.plot();
20571   },
20572
20573   /* 
20574    Method: onClick 
20575    
20576    Animates the <Hypertree> to center the node specified by *id*.
20577
20578    Parameters:
20579
20580    id - A <Graph.Node> id.
20581    opt - (optional|object) An object containing some extra properties described below
20582    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20583
20584    Example:
20585
20586    (start code js)
20587      ht.onClick('someid');
20588      //or also...
20589      ht.onClick('someid', {
20590       hideLabels: false
20591      });
20592     (end code)
20593     
20594   */
20595   onClick: function(id, opt) {
20596     var pos = this.graph.getNode(id).pos.getc(true);
20597     this.move(pos, opt);
20598   },
20599
20600   /* 
20601    Method: move 
20602
20603    Translates the tree to the given position. 
20604
20605    Parameters:
20606
20607    pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20608    opt - This object has been defined in <Hypertree.onClick>
20609    
20610    Example:
20611    
20612    (start code js)
20613      ht.move({ x: 0, y: 0.7 }, {
20614        hideLabels: false
20615      });
20616    (end code)
20617
20618   */
20619   move: function(pos, opt) {
20620     var versor = $C(pos.x, pos.y);
20621     if (this.busy === false && versor.norm() < 1) {
20622       this.busy = true;
20623       var root = this.graph.getClosestNodeToPos(versor), that = this;
20624       this.graph.computeLevels(root.id, 0);
20625       this.controller.onBeforeCompute(root);
20626       opt = $.merge( {
20627         onComplete: $.empty
20628       }, opt || {});
20629       this.fx.animate($.merge( {
20630         modes: [ 'moebius' ],
20631         hideLabels: true
20632       }, opt, {
20633         onComplete: function() {
20634           that.busy = false;
20635           opt.onComplete();
20636         }
20637       }), versor);
20638     }
20639   }
20640 });
20641
20642 $jit.Hypertree.$extend = true;
20643
20644 (function(Hypertree) {
20645
20646   /* 
20647      Class: Hypertree.Op 
20648    
20649      Custom extension of <Graph.Op>.
20650
20651      Extends:
20652
20653      All <Graph.Op> methods
20654      
20655      See also:
20656      
20657      <Graph.Op>
20658
20659   */
20660   Hypertree.Op = new Class( {
20661
20662     Implements: Graph.Op
20663
20664   });
20665
20666   /* 
20667      Class: Hypertree.Plot 
20668    
20669     Custom extension of <Graph.Plot>.
20670   
20671     Extends:
20672   
20673     All <Graph.Plot> methods
20674     
20675     See also:
20676     
20677     <Graph.Plot>
20678   
20679   */
20680   Hypertree.Plot = new Class( {
20681
20682     Implements: Graph.Plot
20683
20684   });
20685
20686   /*
20687     Object: Hypertree.Label
20688
20689     Custom extension of <Graph.Label>. 
20690     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20691   
20692     Extends:
20693   
20694     All <Graph.Label> methods and subclasses.
20695   
20696     See also:
20697   
20698     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20699
20700    */
20701   Hypertree.Label = {};
20702
20703   /*
20704      Hypertree.Label.Native
20705
20706      Custom extension of <Graph.Label.Native>.
20707
20708      Extends:
20709
20710      All <Graph.Label.Native> methods
20711
20712      See also:
20713
20714      <Graph.Label.Native>
20715
20716   */
20717   Hypertree.Label.Native = new Class( {
20718     Implements: Graph.Label.Native,
20719
20720     initialize: function(viz) {
20721       this.viz = viz;
20722     },
20723
20724     renderLabel: function(canvas, node, controller) {
20725       var ctx = canvas.getCtx();
20726       var coord = node.pos.getc(true);
20727       var s = this.viz.getRadius();
20728       ctx.fillText(node.name, coord.x * s, coord.y * s);
20729     }
20730   });
20731
20732   /*
20733      Hypertree.Label.SVG
20734
20735     Custom extension of <Graph.Label.SVG>.
20736   
20737     Extends:
20738   
20739     All <Graph.Label.SVG> methods
20740   
20741     See also:
20742   
20743     <Graph.Label.SVG>
20744   
20745   */
20746   Hypertree.Label.SVG = new Class( {
20747     Implements: Graph.Label.SVG,
20748
20749     initialize: function(viz) {
20750       this.viz = viz;
20751     },
20752
20753     /* 
20754        placeLabel
20755
20756        Overrides abstract method placeLabel in <Graph.Plot>.
20757
20758        Parameters:
20759
20760        tag - A DOM label element.
20761        node - A <Graph.Node>.
20762        controller - A configuration/controller object passed to the visualization.
20763       
20764      */
20765     placeLabel: function(tag, node, controller) {
20766       var pos = node.pos.getc(true), 
20767           canvas = this.viz.canvas,
20768           ox = canvas.translateOffsetX,
20769           oy = canvas.translateOffsetY,
20770           sx = canvas.scaleOffsetX,
20771           sy = canvas.scaleOffsetY,
20772           radius = canvas.getSize(),
20773           r = this.viz.getRadius();
20774       var labelPos = {
20775         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20776         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20777       };
20778       tag.setAttribute('x', labelPos.x);
20779       tag.setAttribute('y', labelPos.y);
20780       controller.onPlaceLabel(tag, node);
20781     }
20782   });
20783
20784   /*
20785      Hypertree.Label.HTML
20786
20787      Custom extension of <Graph.Label.HTML>.
20788
20789      Extends:
20790
20791      All <Graph.Label.HTML> methods.
20792
20793      See also:
20794
20795      <Graph.Label.HTML>
20796
20797   */
20798   Hypertree.Label.HTML = new Class( {
20799     Implements: Graph.Label.HTML,
20800
20801     initialize: function(viz) {
20802       this.viz = viz;
20803     },
20804     /* 
20805        placeLabel
20806
20807        Overrides abstract method placeLabel in <Graph.Plot>.
20808
20809        Parameters:
20810
20811        tag - A DOM label element.
20812        node - A <Graph.Node>.
20813        controller - A configuration/controller object passed to the visualization.
20814       
20815      */
20816     placeLabel: function(tag, node, controller) {
20817       var pos = node.pos.getc(true), 
20818           canvas = this.viz.canvas,
20819           ox = canvas.translateOffsetX,
20820           oy = canvas.translateOffsetY,
20821           sx = canvas.scaleOffsetX,
20822           sy = canvas.scaleOffsetY,
20823           radius = canvas.getSize(),
20824           r = this.viz.getRadius();
20825       var labelPos = {
20826         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20827         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20828       };
20829       var style = tag.style;
20830       style.left = labelPos.x + 'px';
20831       style.top = labelPos.y + 'px';
20832       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20833
20834       controller.onPlaceLabel(tag, node);
20835     }
20836   });
20837
20838   /*
20839     Class: Hypertree.Plot.NodeTypes
20840
20841     This class contains a list of <Graph.Node> built-in types. 
20842     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20843
20844     You can add your custom node types, customizing your visualization to the extreme.
20845
20846     Example:
20847
20848     (start code js)
20849       Hypertree.Plot.NodeTypes.implement({
20850         'mySpecialType': {
20851           'render': function(node, canvas) {
20852             //print your custom node to canvas
20853           },
20854           //optional
20855           'contains': function(node, pos) {
20856             //return true if pos is inside the node or false otherwise
20857           }
20858         }
20859       });
20860     (end code)
20861
20862   */
20863   Hypertree.Plot.NodeTypes = new Class({
20864     'none': {
20865       'render': $.empty,
20866       'contains': $.lambda(false)
20867     },
20868     'circle': {
20869       'render': function(node, canvas) {
20870         var nconfig = this.node,
20871             dim = node.getData('dim'),
20872             p = node.pos.getc();
20873         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20874         p.$scale(node.scale);
20875         if (dim > 0.2) {
20876           this.nodeHelper.circle.render('fill', p, dim, canvas);
20877         }
20878       },
20879       'contains': function(node, pos) {
20880         var dim = node.getData('dim'),
20881             npos = node.pos.getc().$scale(node.scale);
20882         return this.nodeHelper.circle.contains(npos, pos, dim);
20883       }
20884     },
20885     'ellipse': {
20886       'render': function(node, canvas) {
20887         var pos = node.pos.getc().$scale(node.scale),
20888             width = node.getData('width'),
20889             height = node.getData('height');
20890         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20891       },
20892       'contains': function(node, pos) {
20893         var width = node.getData('width'),
20894             height = node.getData('height'),
20895             npos = node.pos.getc().$scale(node.scale);
20896         return this.nodeHelper.circle.contains(npos, pos, width, height);
20897       }
20898     },
20899     'square': {
20900       'render': function(node, canvas) {
20901         var nconfig = this.node,
20902             dim = node.getData('dim'),
20903             p = node.pos.getc();
20904         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20905         p.$scale(node.scale);
20906         if (dim > 0.2) {
20907           this.nodeHelper.square.render('fill', p, dim, canvas);
20908         }
20909       },
20910       'contains': function(node, pos) {
20911         var dim = node.getData('dim'),
20912             npos = node.pos.getc().$scale(node.scale);
20913         return this.nodeHelper.square.contains(npos, pos, dim);
20914       }
20915     },
20916     'rectangle': {
20917       'render': function(node, canvas) {
20918         var nconfig = this.node,
20919             width = node.getData('width'),
20920             height = node.getData('height'),
20921             pos = node.pos.getc();
20922         width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20923         height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20924         pos.$scale(node.scale);
20925         if (width > 0.2 && height > 0.2) {
20926           this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20927         }
20928       },
20929       'contains': function(node, pos) {
20930         var width = node.getData('width'),
20931             height = node.getData('height'),
20932             npos = node.pos.getc().$scale(node.scale);
20933         return this.nodeHelper.square.contains(npos, pos, width, height);
20934       }
20935     },
20936     'triangle': {
20937       'render': function(node, canvas) {
20938         var nconfig = this.node,
20939             dim = node.getData('dim'),
20940             p = node.pos.getc();
20941         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20942         p.$scale(node.scale);
20943         if (dim > 0.2) {
20944           this.nodeHelper.triangle.render('fill', p, dim, canvas);
20945         }
20946       },
20947       'contains': function(node, pos) {
20948         var dim = node.getData('dim'),
20949             npos = node.pos.getc().$scale(node.scale);
20950         return this.nodeHelper.triangle.contains(npos, pos, dim);
20951       }
20952     },
20953     'star': {
20954       'render': function(node, canvas) {
20955         var nconfig = this.node,
20956             dim = node.getData('dim'),
20957             p = node.pos.getc();
20958         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20959         p.$scale(node.scale);
20960         if (dim > 0.2) {
20961           this.nodeHelper.star.render('fill', p, dim, canvas);
20962         }
20963       },
20964       'contains': function(node, pos) {
20965         var dim = node.getData('dim'),
20966             npos = node.pos.getc().$scale(node.scale);
20967         return this.nodeHelper.star.contains(npos, pos, dim);
20968       }
20969     }
20970   });
20971
20972   /*
20973    Class: Hypertree.Plot.EdgeTypes
20974
20975     This class contains a list of <Graph.Adjacence> built-in types. 
20976     Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20977   
20978     You can add your custom edge types, customizing your visualization to the extreme.
20979   
20980     Example:
20981   
20982     (start code js)
20983       Hypertree.Plot.EdgeTypes.implement({
20984         'mySpecialType': {
20985           'render': function(adj, canvas) {
20986             //print your custom edge to canvas
20987           },
20988           //optional
20989           'contains': function(adj, pos) {
20990             //return true if pos is inside the arc or false otherwise
20991           }
20992         }
20993       });
20994     (end code)
20995   
20996   */
20997   Hypertree.Plot.EdgeTypes = new Class({
20998     'none': $.empty,
20999     'line': {
21000       'render': function(adj, canvas) {
21001         var from = adj.nodeFrom.pos.getc(true),
21002           to = adj.nodeTo.pos.getc(true),
21003           r = adj.nodeFrom.scale;
21004           this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
21005       },
21006       'contains': function(adj, pos) {
21007         var from = adj.nodeFrom.pos.getc(true),
21008             to = adj.nodeTo.pos.getc(true),
21009             r = adj.nodeFrom.scale;
21010             this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21011       }
21012     },
21013     'arrow': {
21014       'render': function(adj, canvas) {
21015         var from = adj.nodeFrom.pos.getc(true),
21016             to = adj.nodeTo.pos.getc(true),
21017             r = adj.nodeFrom.scale,
21018             dim = adj.getData('dim'),
21019             direction = adj.data.$direction,
21020             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
21021         this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
21022       },
21023       'contains': function(adj, pos) {
21024         var from = adj.nodeFrom.pos.getc(true),
21025             to = adj.nodeTo.pos.getc(true),
21026             r = adj.nodeFrom.scale;
21027         this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21028       }
21029     },
21030     'hyperline': {
21031       'render': function(adj, canvas) {
21032         var from = adj.nodeFrom.pos.getc(),
21033             to = adj.nodeTo.pos.getc(),
21034             dim = this.viz.getRadius();
21035         this.edgeHelper.hyperline.render(from, to, dim, canvas);
21036       },
21037       'contains': $.lambda(false)
21038     }
21039   });
21040
21041 })($jit.Hypertree);
21042
21043
21044
21045
21046  })();