]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/SugarCharts/Jit/js/Jit/jit.js
Release 6.4.0RC1
[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         //this.translateToCenter(size);
3249       //} else {
3250         this.translateToCenter();
3251       //}
3252       this.translateOffsetX =
3253         this.translateOffsetY = 0;
3254       this.scaleOffsetX = 
3255         this.scaleOffsetY = 1;
3256       this.clear();
3257       this.viz.resize(width, height, this);
3258     },
3259     translate: function(x, y, disablePlot) {
3260       var sx = this.scaleOffsetX,
3261           sy = this.scaleOffsetY;
3262       this.translateOffsetX += x*sx;
3263       this.translateOffsetY += y*sy;
3264       this.getCtx().translate(x, y);
3265       !disablePlot && this.plot();
3266     },
3267     scale: function(x, y, disablePlot) {
3268       this.scaleOffsetX *= x;
3269       this.scaleOffsetY *= y;
3270       this.getCtx().scale(x, y);
3271       !disablePlot && this.plot();
3272     },
3273     clear: function(){
3274       var size = this.getSize(),
3275           ox = this.translateOffsetX,
3276           oy = this.translateOffsetY,
3277           sx = this.scaleOffsetX,
3278           sy = this.scaleOffsetY;
3279       this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx, 
3280                               (-size.height / 2 - oy) * 1/sy, 
3281                               size.width * 1/sx, size.height * 1/sy);
3282     },
3283     plot: function() {
3284       this.clear();
3285       this.viz.plot(this);
3286     }
3287   });
3288   //background canvases
3289   //TODO(nico): document this!
3290   Canvas.Background = {};
3291   Canvas.Background.Circles = new Class({
3292     initialize: function(viz, options) {
3293       this.viz = viz;
3294       this.config = $.merge({
3295         idSuffix: '-bkcanvas',
3296         levelDistance: 100,
3297         numberOfCircles: 6,
3298         CanvasStyles: {},
3299         offset: 0
3300       }, options);
3301     },
3302     resize: function(width, height, base) {
3303       this.plot(base);
3304     },
3305     plot: function(base) {
3306       var canvas = base.canvas,
3307           ctx = base.getCtx(),
3308           conf = this.config,
3309           styles = conf.CanvasStyles;
3310       //set canvas styles
3311       for(var s in styles) ctx[s] = styles[s];
3312       var n = conf.numberOfCircles,
3313           rho = conf.levelDistance;
3314       for(var i=1; i<=n; i++) {
3315         ctx.beginPath();
3316         ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3317         ctx.stroke();
3318         ctx.closePath();
3319       }
3320       //TODO(nico): print labels too!
3321     }
3322   });
3323   Canvas.Background.Fade = new Class({
3324     initialize: function(viz, options) {
3325       this.viz = viz;
3326       this.config = $.merge({
3327         idSuffix: '-bkcanvas',
3328         CanvasStyles: {},
3329         offset: 0
3330       }, options);
3331     },
3332     resize: function(width, height, base) {
3333       this.plot(base);
3334     },
3335     plot: function(base) {
3336       var canvas = base.canvas,
3337           ctx = base.getCtx(),
3338           conf = this.config,
3339           styles = conf.CanvasStyles,
3340           size = base.getSize();
3341                   ctx.fillStyle = 'rgb(255,255,255)';
3342                   ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3343       //TODO(nico): print labels too!
3344     }
3345   });
3346 })();
3347
3348
3349 /*
3350  * File: Polar.js
3351  * 
3352  * Defines the <Polar> class.
3353  *
3354  * Description:
3355  *
3356  * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3357  *
3358  * See also:
3359  *
3360  * <http://en.wikipedia.org/wiki/Polar_coordinates>
3361  *
3362 */
3363
3364 /*
3365    Class: Polar
3366
3367    A multi purpose polar representation.
3368
3369    Description:
3370  
3371    The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3372  
3373    See also:
3374  
3375    <http://en.wikipedia.org/wiki/Polar_coordinates>
3376  
3377    Parameters:
3378
3379       theta - An angle.
3380       rho - The norm.
3381 */
3382
3383 var Polar = function(theta, rho) {
3384   this.theta = theta;
3385   this.rho = rho;
3386 };
3387
3388 $jit.Polar = Polar;
3389
3390 Polar.prototype = {
3391     /*
3392        Method: getc
3393     
3394        Returns a complex number.
3395     
3396        Parameters:
3397
3398        simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3399
3400       Returns:
3401     
3402           A complex number.
3403     */
3404     getc: function(simple) {
3405         return this.toComplex(simple);
3406     },
3407
3408     /*
3409        Method: getp
3410     
3411        Returns a <Polar> representation.
3412     
3413        Returns:
3414     
3415           A variable in polar coordinates.
3416     */
3417     getp: function() {
3418         return this;
3419     },
3420
3421
3422     /*
3423        Method: set
3424     
3425        Sets a number.
3426
3427        Parameters:
3428
3429        v - A <Complex> or <Polar> instance.
3430     
3431     */
3432     set: function(v) {
3433         v = v.getp();
3434         this.theta = v.theta; this.rho = v.rho;
3435     },
3436
3437     /*
3438        Method: setc
3439     
3440        Sets a <Complex> number.
3441
3442        Parameters:
3443
3444        x - A <Complex> number real part.
3445        y - A <Complex> number imaginary part.
3446     
3447     */
3448     setc: function(x, y) {
3449         this.rho = Math.sqrt(x * x + y * y);
3450         this.theta = Math.atan2(y, x);
3451         if(this.theta < 0) this.theta += Math.PI * 2;
3452     },
3453
3454     /*
3455        Method: setp
3456     
3457        Sets a polar number.
3458
3459        Parameters:
3460
3461        theta - A <Polar> number angle property.
3462        rho - A <Polar> number rho property.
3463     
3464     */
3465     setp: function(theta, rho) {
3466         this.theta = theta; 
3467         this.rho = rho;
3468     },
3469
3470     /*
3471        Method: clone
3472     
3473        Returns a copy of the current object.
3474     
3475        Returns:
3476     
3477           A copy of the real object.
3478     */
3479     clone: function() {
3480         return new Polar(this.theta, this.rho);
3481     },
3482
3483     /*
3484        Method: toComplex
3485     
3486         Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3487     
3488         Parameters:
3489
3490         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*.
3491  
3492         Returns:
3493     
3494           A new <Complex> instance.
3495     */
3496     toComplex: function(simple) {
3497         var x = Math.cos(this.theta) * this.rho;
3498         var y = Math.sin(this.theta) * this.rho;
3499         if(simple) return { 'x': x, 'y': y};
3500         return new Complex(x, y);
3501     },
3502
3503     /*
3504        Method: add
3505     
3506         Adds two <Polar> instances.
3507     
3508        Parameters:
3509
3510        polar - A <Polar> number.
3511
3512        Returns:
3513     
3514           A new Polar instance.
3515     */
3516     add: function(polar) {
3517         return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3518     },
3519     
3520     /*
3521        Method: scale
3522     
3523         Scales a polar norm.
3524     
3525         Parameters:
3526
3527         number - A scale factor.
3528         
3529         Returns:
3530     
3531           A new Polar instance.
3532     */
3533     scale: function(number) {
3534         return new Polar(this.theta, this.rho * number);
3535     },
3536     
3537     /*
3538        Method: equals
3539     
3540        Comparison method.
3541
3542        Returns *true* if the theta and rho properties are equal.
3543
3544        Parameters:
3545
3546        c - A <Polar> number.
3547
3548        Returns:
3549
3550        *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3551     */
3552     equals: function(c) {
3553         return this.theta == c.theta && this.rho == c.rho;
3554     },
3555     
3556     /*
3557        Method: $add
3558     
3559         Adds two <Polar> instances affecting the current object.
3560     
3561        Paramters:
3562
3563        polar - A <Polar> instance.
3564
3565        Returns:
3566     
3567           The changed object.
3568     */
3569     $add: function(polar) {
3570         this.theta = this.theta + polar.theta; this.rho += polar.rho;
3571         return this;
3572     },
3573
3574     /*
3575        Method: $madd
3576     
3577         Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3578     
3579        Parameters:
3580
3581        polar - A <Polar> instance.
3582
3583        Returns:
3584     
3585           The changed object.
3586     */
3587     $madd: function(polar) {
3588         this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3589         return this;
3590     },
3591
3592     
3593     /*
3594        Method: $scale
3595     
3596         Scales a polar instance affecting the object.
3597     
3598       Parameters:
3599
3600       number - A scaling factor.
3601
3602       Returns:
3603     
3604           The changed object.
3605     */
3606     $scale: function(number) {
3607         this.rho *= number;
3608         return this;
3609     },
3610     
3611     /*
3612        Method: interpolate
3613     
3614         Calculates a polar interpolation between two points at a given delta moment.
3615
3616         Parameters:
3617       
3618         elem - A <Polar> instance.
3619         delta - A delta factor ranging [0, 1].
3620     
3621        Returns:
3622     
3623           A new <Polar> instance representing an interpolation between _this_ and _elem_
3624     */
3625     interpolate: function(elem, delta) {
3626         var pi = Math.PI, pi2 = pi * 2;
3627         var ch = function(t) {
3628             var a =  (t < 0)? (t % pi2) + pi2 : t % pi2;
3629             return a;
3630         };
3631         var tt = this.theta, et = elem.theta;
3632         var sum, diff = Math.abs(tt - et);
3633         if(diff == pi) {
3634           if(tt > et) {
3635             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3636           } else {
3637             sum = ch((et - pi2 + (tt - (et)) * delta));
3638           }
3639         } else if(diff >= pi) {
3640           if(tt > et) {
3641             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3642           } else {
3643             sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3644           }
3645         } else {  
3646           sum = ch((et + (tt - et) * delta)) ;
3647         }
3648         var r = (this.rho - elem.rho) * delta + elem.rho;
3649         return {
3650           'theta': sum,
3651           'rho': r
3652         };
3653     }
3654 };
3655
3656
3657 var $P = function(a, b) { return new Polar(a, b); };
3658
3659 Polar.KER = $P(0, 0);
3660
3661
3662
3663 /*
3664  * File: Complex.js
3665  * 
3666  * Defines the <Complex> class.
3667  *
3668  * Description:
3669  *
3670  * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3671  *
3672  * See also:
3673  *
3674  * <http://en.wikipedia.org/wiki/Complex_number>
3675  *
3676 */
3677
3678 /*
3679    Class: Complex
3680     
3681    A multi-purpose Complex Class with common methods.
3682  
3683    Description:
3684  
3685    The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3686  
3687    See also:
3688  
3689    <http://en.wikipedia.org/wiki/Complex_number>
3690
3691    Parameters:
3692
3693    x - _optional_ A Complex number real part.
3694    y - _optional_ A Complex number imaginary part.
3695  
3696 */
3697
3698 var Complex = function(x, y) {
3699   this.x = x;
3700   this.y = y;
3701 };
3702
3703 $jit.Complex = Complex;
3704
3705 Complex.prototype = {
3706     /*
3707        Method: getc
3708     
3709        Returns a complex number.
3710     
3711        Returns:
3712     
3713           A complex number.
3714     */
3715     getc: function() {
3716         return this;
3717     },
3718
3719     /*
3720        Method: getp
3721     
3722        Returns a <Polar> representation of this number.
3723     
3724        Parameters:
3725
3726        simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3727
3728        Returns:
3729     
3730           A variable in <Polar> coordinates.
3731     */
3732     getp: function(simple) {
3733         return this.toPolar(simple);
3734     },
3735
3736
3737     /*
3738        Method: set
3739     
3740        Sets a number.
3741
3742        Parameters:
3743
3744        c - A <Complex> or <Polar> instance.
3745     
3746     */
3747     set: function(c) {
3748       c = c.getc(true);
3749       this.x = c.x; 
3750       this.y = c.y;
3751     },
3752
3753     /*
3754        Method: setc
3755     
3756        Sets a complex number.
3757
3758        Parameters:
3759
3760        x - A <Complex> number Real part.
3761        y - A <Complex> number Imaginary part.
3762     
3763     */
3764     setc: function(x, y) {
3765         this.x = x; 
3766         this.y = y;
3767     },
3768
3769     /*
3770        Method: setp
3771     
3772        Sets a polar number.
3773
3774        Parameters:
3775
3776        theta - A <Polar> number theta property.
3777        rho - A <Polar> number rho property.
3778     
3779     */
3780     setp: function(theta, rho) {
3781         this.x = Math.cos(theta) * rho;
3782         this.y = Math.sin(theta) * rho;
3783     },
3784
3785     /*
3786        Method: clone
3787     
3788        Returns a copy of the current object.
3789     
3790        Returns:
3791     
3792           A copy of the real object.
3793     */
3794     clone: function() {
3795         return new Complex(this.x, this.y);
3796     },
3797
3798     /*
3799        Method: toPolar
3800     
3801        Transforms cartesian to polar coordinates.
3802     
3803        Parameters:
3804
3805        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*.
3806        
3807        Returns:
3808     
3809           A new <Polar> instance.
3810     */
3811     
3812     toPolar: function(simple) {
3813         var rho = this.norm();
3814         var atan = Math.atan2(this.y, this.x);
3815         if(atan < 0) atan += Math.PI * 2;
3816         if(simple) return { 'theta': atan, 'rho': rho };
3817         return new Polar(atan, rho);
3818     },
3819     /*
3820        Method: norm
3821     
3822        Calculates a <Complex> number norm.
3823     
3824        Returns:
3825     
3826           A real number representing the complex norm.
3827     */
3828     norm: function () {
3829         return Math.sqrt(this.squaredNorm());
3830     },
3831     
3832     /*
3833        Method: squaredNorm
3834     
3835        Calculates a <Complex> number squared norm.
3836     
3837        Returns:
3838     
3839           A real number representing the complex squared norm.
3840     */
3841     squaredNorm: function () {
3842         return this.x*this.x + this.y*this.y;
3843     },
3844
3845     /*
3846        Method: add
3847     
3848        Returns the result of adding two complex numbers.
3849        
3850        Does not alter the original object.
3851
3852        Parameters:
3853     
3854           pos - A <Complex> instance.
3855     
3856        Returns:
3857     
3858          The result of adding two complex numbers.
3859     */
3860     add: function(pos) {
3861         return new Complex(this.x + pos.x, this.y + pos.y);
3862     },
3863
3864     /*
3865        Method: prod
3866     
3867        Returns the result of multiplying two <Complex> numbers.
3868        
3869        Does not alter the original object.
3870
3871        Parameters:
3872     
3873           pos - A <Complex> instance.
3874     
3875        Returns:
3876     
3877          The result of multiplying two complex numbers.
3878     */
3879     prod: function(pos) {
3880         return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3881     },
3882
3883     /*
3884        Method: conjugate
3885     
3886        Returns the conjugate of this <Complex> number.
3887
3888        Does not alter the original object.
3889
3890        Returns:
3891     
3892          The conjugate of this <Complex> number.
3893     */
3894     conjugate: function() {
3895         return new Complex(this.x, -this.y);
3896     },
3897
3898
3899     /*
3900        Method: scale
3901     
3902        Returns the result of scaling a <Complex> instance.
3903        
3904        Does not alter the original object.
3905
3906        Parameters:
3907     
3908           factor - A scale factor.
3909     
3910        Returns:
3911     
3912          The result of scaling this complex to a factor.
3913     */
3914     scale: function(factor) {
3915         return new Complex(this.x * factor, this.y * factor);
3916     },
3917
3918     /*
3919        Method: equals
3920     
3921        Comparison method.
3922
3923        Returns *true* if both real and imaginary parts are equal.
3924
3925        Parameters:
3926
3927        c - A <Complex> instance.
3928
3929        Returns:
3930
3931        A boolean instance indicating if both <Complex> numbers are equal.
3932     */
3933     equals: function(c) {
3934         return this.x == c.x && this.y == c.y;
3935     },
3936
3937     /*
3938        Method: $add
3939     
3940        Returns the result of adding two <Complex> numbers.
3941        
3942        Alters the original object.
3943
3944        Parameters:
3945     
3946           pos - A <Complex> instance.
3947     
3948        Returns:
3949     
3950          The result of adding two complex numbers.
3951     */
3952     $add: function(pos) {
3953         this.x += pos.x; this.y += pos.y;
3954         return this;    
3955     },
3956     
3957     /*
3958        Method: $prod
3959     
3960        Returns the result of multiplying two <Complex> numbers.
3961        
3962        Alters the original object.
3963
3964        Parameters:
3965     
3966           pos - A <Complex> instance.
3967     
3968        Returns:
3969     
3970          The result of multiplying two complex numbers.
3971     */
3972     $prod:function(pos) {
3973         var x = this.x, y = this.y;
3974         this.x = x*pos.x - y*pos.y;
3975         this.y = y*pos.x + x*pos.y;
3976         return this;
3977     },
3978     
3979     /*
3980        Method: $conjugate
3981     
3982        Returns the conjugate for this <Complex>.
3983        
3984        Alters the original object.
3985
3986        Returns:
3987     
3988          The conjugate for this complex.
3989     */
3990     $conjugate: function() {
3991         this.y = -this.y;
3992         return this;
3993     },
3994     
3995     /*
3996        Method: $scale
3997     
3998        Returns the result of scaling a <Complex> instance.
3999        
4000        Alters the original object.
4001
4002        Parameters:
4003     
4004           factor - A scale factor.
4005     
4006        Returns:
4007     
4008          The result of scaling this complex to a factor.
4009     */
4010     $scale: function(factor) {
4011         this.x *= factor; this.y *= factor;
4012         return this;
4013     },
4014     
4015     /*
4016        Method: $div
4017     
4018        Returns the division of two <Complex> numbers.
4019        
4020        Alters the original object.
4021
4022        Parameters:
4023     
4024           pos - A <Complex> number.
4025     
4026        Returns:
4027     
4028          The result of scaling this complex to a factor.
4029     */
4030     $div: function(pos) {
4031         var x = this.x, y = this.y;
4032         var sq = pos.squaredNorm();
4033         this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4034         return this.$scale(1 / sq);
4035     }
4036 };
4037
4038 var $C = function(a, b) { return new Complex(a, b); };
4039
4040 Complex.KER = $C(0, 0);
4041
4042
4043
4044 /*
4045  * File: Graph.js
4046  *
4047 */
4048
4049 /*
4050  Class: Graph
4051
4052  A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4053
4054  An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4055  
4056  Example:
4057
4058  (start code js)
4059    //create new visualization
4060    var viz = new $jit.Viz(options);
4061    //load JSON data
4062    viz.loadJSON(json);
4063    //access model
4064    viz.graph; //<Graph> instance
4065  (end code)
4066  
4067  Implements:
4068  
4069  The following <Graph.Util> methods are implemented in <Graph>
4070  
4071   - <Graph.Util.getNode>
4072   - <Graph.Util.eachNode>
4073   - <Graph.Util.computeLevels>
4074   - <Graph.Util.eachBFS>
4075   - <Graph.Util.clean>
4076   - <Graph.Util.getClosestNodeToPos>
4077   - <Graph.Util.getClosestNodeToOrigin>
4078  
4079 */  
4080
4081 $jit.Graph = new Class({
4082
4083   initialize: function(opt, Node, Edge, Label) {
4084     var innerOptions = {
4085     'complex': false,
4086     'Node': {}
4087     };
4088     this.Node = Node;
4089     this.Edge = Edge;
4090     this.Label = Label;
4091     this.opt = $.merge(innerOptions, opt || {});
4092     this.nodes = {};
4093     this.edges = {};
4094     
4095     //add nodeList methods
4096     var that = this;
4097     this.nodeList = {};
4098     for(var p in Accessors) {
4099       that.nodeList[p] = (function(p) {
4100         return function() {
4101           var args = Array.prototype.slice.call(arguments);
4102           that.eachNode(function(n) {
4103             n[p].apply(n, args);
4104           });
4105         };
4106       })(p);
4107     }
4108
4109  },
4110
4111 /*
4112      Method: getNode
4113     
4114      Returns a <Graph.Node> by *id*.
4115
4116      Parameters:
4117
4118      id - (string) A <Graph.Node> id.
4119
4120      Example:
4121
4122      (start code js)
4123        var node = graph.getNode('nodeId');
4124      (end code)
4125 */  
4126  getNode: function(id) {
4127     if(this.hasNode(id)) return this.nodes[id];
4128     return false;
4129  },
4130
4131  /*
4132    Method: getByName
4133   
4134    Returns a <Graph.Node> by *name*.
4135   
4136    Parameters:
4137   
4138    name - (string) A <Graph.Node> name.
4139   
4140    Example:
4141   
4142    (start code js)
4143      var node = graph.getByName('someName');
4144    (end code)
4145   */  
4146   getByName: function(name) {
4147     for(var id in this.nodes) {
4148       var n = this.nodes[id];
4149       if(n.name == name) return n;
4150     }
4151     return false;
4152   },
4153
4154 /*
4155    Method: getAdjacence
4156   
4157    Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4158
4159    Parameters:
4160
4161    id - (string) A <Graph.Node> id.
4162    id2 - (string) A <Graph.Node> id.
4163 */  
4164   getAdjacence: function (id, id2) {
4165     if(id in this.edges) {
4166       return this.edges[id][id2];
4167     }
4168     return false;
4169  },
4170
4171     /*
4172      Method: addNode
4173     
4174      Adds a node.
4175      
4176      Parameters:
4177     
4178       obj - An object with the properties described below
4179
4180       id - (string) A node id
4181       name - (string) A node's name
4182       data - (object) A node's data hash
4183
4184     See also:
4185     <Graph.Node>
4186
4187   */  
4188   addNode: function(obj) { 
4189    if(!this.nodes[obj.id]) {  
4190      var edges = this.edges[obj.id] = {};
4191      this.nodes[obj.id] = new Graph.Node($.extend({
4192         'id': obj.id,
4193         'name': obj.name,
4194         'data': $.merge(obj.data || {}, {}),
4195         'adjacencies': edges 
4196       }, this.opt.Node), 
4197       this.opt.complex, 
4198       this.Node, 
4199       this.Edge,
4200       this.Label);
4201     }
4202     return this.nodes[obj.id];
4203   },
4204   
4205     /*
4206      Method: addAdjacence
4207     
4208      Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4209      
4210      Parameters:
4211     
4212       obj - (object) A <Graph.Node> object.
4213       obj2 - (object) Another <Graph.Node> object.
4214       data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4215
4216     See also:
4217
4218     <Graph.Node>, <Graph.Adjacence>
4219     */  
4220   addAdjacence: function (obj, obj2, data) {
4221     if(!this.hasNode(obj.id)) { this.addNode(obj); }
4222     if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4223     obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4224     if(!obj.adjacentTo(obj2)) {
4225       var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4226       var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4227       adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4228       return adjsObj[obj2.id];
4229     }
4230     return this.edges[obj.id][obj2.id];
4231  },
4232
4233     /*
4234      Method: removeNode
4235     
4236      Removes a <Graph.Node> matching the specified *id*.
4237
4238      Parameters:
4239
4240      id - (string) A node's id.
4241
4242     */  
4243   removeNode: function(id) {
4244     if(this.hasNode(id)) {
4245       delete this.nodes[id];
4246       var adjs = this.edges[id];
4247       for(var to in adjs) {
4248         delete this.edges[to][id];
4249       }
4250       delete this.edges[id];
4251     }
4252   },
4253   
4254 /*
4255      Method: removeAdjacence
4256     
4257      Removes a <Graph.Adjacence> matching *id1* and *id2*.
4258
4259      Parameters:
4260
4261      id1 - (string) A <Graph.Node> id.
4262      id2 - (string) A <Graph.Node> id.
4263 */  
4264   removeAdjacence: function(id1, id2) {
4265     delete this.edges[id1][id2];
4266     delete this.edges[id2][id1];
4267   },
4268
4269    /*
4270      Method: hasNode
4271     
4272      Returns a boolean indicating if the node belongs to the <Graph> or not.
4273      
4274      Parameters:
4275     
4276         id - (string) Node id.
4277    */  
4278   hasNode: function(id) {
4279     return id in this.nodes;
4280   },
4281   
4282   /*
4283     Method: empty
4284
4285     Empties the Graph
4286
4287   */
4288   empty: function() { this.nodes = {}; this.edges = {};}
4289
4290 });
4291
4292 var Graph = $jit.Graph;
4293
4294 /*
4295  Object: Accessors
4296  
4297  Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4298  
4299  */
4300 var Accessors;
4301
4302 (function () {
4303   var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4304     var data;
4305     type = type || 'current';
4306     prefix = "$" + (prefix ? prefix + "-" : "");
4307
4308     if(type == 'current') {
4309       data = this.data;
4310     } else if(type == 'start') {
4311       data = this.startData;
4312     } else if(type == 'end') {
4313       data = this.endData;
4314     }
4315
4316     var dollar = prefix + prop;
4317
4318     if(force) {
4319       return data[dollar];
4320     }
4321
4322     if(!this.Config.overridable)
4323       return prefixConfig[prop] || 0;
4324
4325     return (dollar in data) ?
4326       data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4327   }
4328
4329   var setDataInternal = function(prefix, prop, value, type) {
4330     type = type || 'current';
4331     prefix = '$' + (prefix ? prefix + '-' : '');
4332
4333     var data;
4334
4335     if(type == 'current') {
4336       data = this.data;
4337     } else if(type == 'start') {
4338       data = this.startData;
4339     } else if(type == 'end') {
4340       data = this.endData;
4341     }
4342
4343     data[prefix + prop] = value;
4344   }
4345
4346   var removeDataInternal = function(prefix, properties) {
4347     prefix = '$' + (prefix ? prefix + '-' : '');
4348     var that = this;
4349     $.each(properties, function(prop) {
4350       var pref = prefix + prop;
4351       delete that.data[pref];
4352       delete that.endData[pref];
4353       delete that.startData[pref];
4354     });
4355   }
4356
4357   Accessors = {
4358     /*
4359     Method: getData
4360
4361     Returns the specified data value property.
4362     This is useful for querying special/reserved <Graph.Node> data properties
4363     (i.e dollar prefixed properties).
4364
4365     Parameters:
4366
4367       prop  - (string) The name of the property. The dollar sign is not needed. For
4368               example *getData(width)* will return *data.$width*.
4369       type  - (string) The type of the data property queried. Default's "current". You can access *start* and *end* 
4370               data properties also. These properties are used when making animations.
4371       force - (boolean) Whether to obtain the true value of the property (equivalent to
4372               *data.$prop*) or to check for *node.overridable = true* first.
4373
4374     Returns:
4375
4376       The value of the dollar prefixed property or the global Node/Edge property
4377       value if *overridable=false*
4378
4379     Example:
4380     (start code js)
4381      node.getData('width'); //will return node.data.$width if Node.overridable=true;
4382     (end code)
4383     */
4384     getData: function(prop, type, force) {
4385       return getDataInternal.call(this, "", prop, type, force, this.Config);
4386     },
4387
4388
4389     /*
4390     Method: setData
4391
4392     Sets the current data property with some specific value.
4393     This method is only useful for reserved (dollar prefixed) properties.
4394
4395     Parameters:
4396
4397       prop  - (string) The name of the property. The dollar sign is not necessary. For
4398               example *setData(width)* will set *data.$width*.
4399       value - (mixed) The value to store.
4400       type  - (string) The type of the data property to store. Default's "current" but
4401               can also be "start" or "end".
4402
4403     Example:
4404     
4405     (start code js)
4406      node.setData('width', 30);
4407     (end code)
4408     
4409     If we were to make an animation of a node/edge width then we could do
4410     
4411     (start code js)
4412       var node = viz.getNode('nodeId');
4413       //set start and end values
4414       node.setData('width', 10, 'start');
4415       node.setData('width', 30, 'end');
4416       //will animate nodes width property
4417       viz.fx.animate({
4418         modes: ['node-property:width'],
4419         duration: 1000
4420       });
4421     (end code)
4422     */
4423     setData: function(prop, value, type) {
4424       setDataInternal.call(this, "", prop, value, type);
4425     },
4426
4427     /*
4428     Method: setDataset
4429
4430     Convenience method to set multiple data values at once.
4431     
4432     Parameters:
4433     
4434     types - (array|string) A set of 'current', 'end' or 'start' values.
4435     obj - (object) A hash containing the names and values of the properties to be altered.
4436
4437     Example:
4438     (start code js)
4439       node.setDataset(['current', 'end'], {
4440         'width': [100, 5],
4441         'color': ['#fff', '#ccc']
4442       });
4443       //...or also
4444       node.setDataset('end', {
4445         'width': 5,
4446         'color': '#ccc'
4447       });
4448     (end code)
4449     
4450     See also: 
4451     
4452     <Accessors.setData>
4453     
4454     */
4455     setDataset: function(types, obj) {
4456       types = $.splat(types);
4457       for(var attr in obj) {
4458         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4459           this.setData(attr, val[i], types[i]);
4460         }
4461       }
4462     },
4463     
4464     /*
4465     Method: removeData
4466
4467     Remove data properties.
4468
4469     Parameters:
4470
4471     One or more property names as arguments. The dollar sign is not needed.
4472
4473     Example:
4474     (start code js)
4475     node.removeData('width'); //now the default width value is returned
4476     (end code)
4477     */
4478     removeData: function() {
4479       removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4480     },
4481
4482     /*
4483     Method: getCanvasStyle
4484
4485     Returns the specified canvas style data value property. This is useful for
4486     querying special/reserved <Graph.Node> canvas style data properties (i.e.
4487     dollar prefixed properties that match with $canvas-<name of canvas style>).
4488
4489     Parameters:
4490
4491       prop  - (string) The name of the property. The dollar sign is not needed. For
4492               example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4493       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4494               data properties also.
4495               
4496     Example:
4497     (start code js)
4498       node.getCanvasStyle('shadowBlur');
4499     (end code)
4500     
4501     See also:
4502     
4503     <Accessors.getData>
4504     */
4505     getCanvasStyle: function(prop, type, force) {
4506       return getDataInternal.call(
4507           this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4508     },
4509
4510     /*
4511     Method: setCanvasStyle
4512
4513     Sets the canvas style data property with some specific value.
4514     This method is only useful for reserved (dollar prefixed) properties.
4515     
4516     Parameters:
4517     
4518     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4519     value - (mixed) The value to set to the property.
4520     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4521     
4522     Example:
4523     
4524     (start code js)
4525      node.setCanvasStyle('shadowBlur', 30);
4526     (end code)
4527     
4528     If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4529     
4530     (start code js)
4531       var node = viz.getNode('nodeId');
4532       //set start and end values
4533       node.setCanvasStyle('shadowBlur', 10, 'start');
4534       node.setCanvasStyle('shadowBlur', 30, 'end');
4535       //will animate nodes canvas style property for nodes
4536       viz.fx.animate({
4537         modes: ['node-style:shadowBlur'],
4538         duration: 1000
4539       });
4540     (end code)
4541     
4542     See also:
4543     
4544     <Accessors.setData>.
4545     */
4546     setCanvasStyle: function(prop, value, type) {
4547       setDataInternal.call(this, 'canvas', prop, value, type);
4548     },
4549
4550     /*
4551     Method: setCanvasStyles
4552
4553     Convenience method to set multiple styles at once.
4554
4555     Parameters:
4556     
4557     types - (array|string) A set of 'current', 'end' or 'start' values.
4558     obj - (object) A hash containing the names and values of the properties to be altered.
4559
4560     See also:
4561     
4562     <Accessors.setDataset>.
4563     */
4564     setCanvasStyles: function(types, obj) {
4565       types = $.splat(types);
4566       for(var attr in obj) {
4567         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4568           this.setCanvasStyle(attr, val[i], types[i]);
4569         }
4570       }
4571     },
4572
4573     /*
4574     Method: removeCanvasStyle
4575
4576     Remove canvas style properties from data.
4577
4578     Parameters:
4579     
4580     A variable number of canvas style strings.
4581
4582     See also:
4583     
4584     <Accessors.removeData>.
4585     */
4586     removeCanvasStyle: function() {
4587       removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4588     },
4589
4590     /*
4591     Method: getLabelData
4592
4593     Returns the specified label data value property. This is useful for
4594     querying special/reserved <Graph.Node> label options (i.e.
4595     dollar prefixed properties that match with $label-<name of label style>).
4596
4597     Parameters:
4598
4599       prop  - (string) The name of the property. The dollar sign prefix is not needed. For
4600               example *getLabelData(size)* will return *data[$label-size]*.
4601       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4602               data properties also.
4603               
4604     See also:
4605     
4606     <Accessors.getData>.
4607     */
4608     getLabelData: function(prop, type, force) {
4609       return getDataInternal.call(
4610           this, 'label', prop, type, force, this.Label);
4611     },
4612
4613     /*
4614     Method: setLabelData
4615
4616     Sets the current label data with some specific value.
4617     This method is only useful for reserved (dollar prefixed) properties.
4618
4619     Parameters:
4620     
4621     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4622     value - (mixed) The value to set to the property.
4623     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4624     
4625     Example:
4626     
4627     (start code js)
4628      node.setLabelData('size', 30);
4629     (end code)
4630     
4631     If we were to make an animation of a node label size then we could do
4632     
4633     (start code js)
4634       var node = viz.getNode('nodeId');
4635       //set start and end values
4636       node.setLabelData('size', 10, 'start');
4637       node.setLabelData('size', 30, 'end');
4638       //will animate nodes label size
4639       viz.fx.animate({
4640         modes: ['label-property:size'],
4641         duration: 1000
4642       });
4643     (end code)
4644     
4645     See also:
4646     
4647     <Accessors.setData>.
4648     */
4649     setLabelData: function(prop, value, type) {
4650       setDataInternal.call(this, 'label', prop, value, type);
4651     },
4652
4653     /*
4654     Method: setLabelDataset
4655
4656     Convenience function to set multiple label data at once.
4657
4658     Parameters:
4659     
4660     types - (array|string) A set of 'current', 'end' or 'start' values.
4661     obj - (object) A hash containing the names and values of the properties to be altered.
4662
4663     See also:
4664     
4665     <Accessors.setDataset>.
4666     */
4667     setLabelDataset: function(types, obj) {
4668       types = $.splat(types);
4669       for(var attr in obj) {
4670         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4671           this.setLabelData(attr, val[i], types[i]);
4672         }
4673       }
4674     },
4675
4676     /*
4677     Method: removeLabelData
4678
4679     Remove label properties from data.
4680     
4681     Parameters:
4682     
4683     A variable number of label property strings.
4684
4685     See also:
4686     
4687     <Accessors.removeData>.
4688     */
4689     removeLabelData: function() {
4690       removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4691     }
4692   };
4693 })();
4694
4695 /*
4696      Class: Graph.Node
4697
4698      A <Graph> node.
4699      
4700      Implements:
4701      
4702      <Accessors> methods.
4703      
4704      The following <Graph.Util> methods are implemented by <Graph.Node>
4705      
4706     - <Graph.Util.eachAdjacency>
4707     - <Graph.Util.eachLevel>
4708     - <Graph.Util.eachSubgraph>
4709     - <Graph.Util.eachSubnode>
4710     - <Graph.Util.anySubnode>
4711     - <Graph.Util.getSubnodes>
4712     - <Graph.Util.getParents>
4713     - <Graph.Util.isDescendantOf>     
4714 */
4715 Graph.Node = new Class({
4716     
4717   initialize: function(opt, complex, Node, Edge, Label) {
4718     var innerOptions = {
4719       'id': '',
4720       'name': '',
4721       'data': {},
4722       'startData': {},
4723       'endData': {},
4724       'adjacencies': {},
4725
4726       'selected': false,
4727       'drawn': false,
4728       'exist': false,
4729
4730       'angleSpan': {
4731         'begin': 0,
4732         'end' : 0
4733       },
4734
4735       'pos': (complex && $C(0, 0)) || $P(0, 0),
4736       'startPos': (complex && $C(0, 0)) || $P(0, 0),
4737       'endPos': (complex && $C(0, 0)) || $P(0, 0)
4738     };
4739     
4740     $.extend(this, $.extend(innerOptions, opt));
4741     this.Config = this.Node = Node;
4742     this.Edge = Edge;
4743     this.Label = Label;
4744   },
4745
4746     /*
4747        Method: adjacentTo
4748     
4749        Indicates if the node is adjacent to the node specified by id
4750
4751        Parameters:
4752     
4753           id - (string) A node id.
4754     
4755        Example:
4756        (start code js)
4757         node.adjacentTo('nodeId') == true;
4758        (end code)
4759     */
4760     adjacentTo: function(node) {
4761         return node.id in this.adjacencies;
4762     },
4763
4764     /*
4765        Method: getAdjacency
4766     
4767        Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4768
4769        Parameters:
4770     
4771           id - (string) A node id.
4772     */  
4773     getAdjacency: function(id) {
4774         return this.adjacencies[id];
4775     },
4776
4777     /*
4778       Method: getPos
4779    
4780       Returns the position of the node.
4781   
4782       Parameters:
4783    
4784          type - (string) Default's *current*. Possible values are "start", "end" or "current".
4785    
4786       Returns:
4787    
4788         A <Complex> or <Polar> instance.
4789   
4790       Example:
4791       (start code js)
4792        var pos = node.getPos('end');
4793       (end code)
4794    */
4795    getPos: function(type) {
4796        type = type || "current";
4797        if(type == "current") {
4798          return this.pos;
4799        } else if(type == "end") {
4800          return this.endPos;
4801        } else if(type == "start") {
4802          return this.startPos;
4803        }
4804    },
4805    /*
4806      Method: setPos
4807   
4808      Sets the node's position.
4809   
4810      Parameters:
4811   
4812         value - (object) A <Complex> or <Polar> instance.
4813         type - (string) Default's *current*. Possible values are "start", "end" or "current".
4814   
4815      Example:
4816      (start code js)
4817       node.setPos(new $jit.Complex(0, 0), 'end');
4818      (end code)
4819   */
4820   setPos: function(value, type) {
4821       type = type || "current";
4822       var pos;
4823       if(type == "current") {
4824         pos = this.pos;
4825       } else if(type == "end") {
4826         pos = this.endPos;
4827       } else if(type == "start") {
4828         pos = this.startPos;
4829       }
4830       pos.set(value);
4831   }
4832 });
4833
4834 Graph.Node.implement(Accessors);
4835
4836 /*
4837      Class: Graph.Adjacence
4838
4839      A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4840      
4841      Implements:
4842      
4843      <Accessors> methods.
4844
4845      See also:
4846
4847      <Graph>, <Graph.Node>
4848
4849      Properties:
4850      
4851       nodeFrom - A <Graph.Node> connected by this edge.
4852       nodeTo - Another  <Graph.Node> connected by this edge.
4853       data - Node data property containing a hash (i.e {}) with custom options.
4854 */
4855 Graph.Adjacence = new Class({
4856   
4857   initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4858     this.nodeFrom = nodeFrom;
4859     this.nodeTo = nodeTo;
4860     this.data = data || {};
4861     this.startData = {};
4862     this.endData = {};
4863     this.Config = this.Edge = Edge;
4864     this.Label = Label;
4865   }
4866 });
4867
4868 Graph.Adjacence.implement(Accessors);
4869
4870 /*
4871    Object: Graph.Util
4872
4873    <Graph> traversal and processing utility object.
4874    
4875    Note:
4876    
4877    For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4878 */
4879 Graph.Util = {
4880     /*
4881        filter
4882     
4883        For internal use only. Provides a filtering function based on flags.
4884     */
4885     filter: function(param) {
4886         if(!param || !($.type(param) == 'string')) return function() { return true; };
4887         var props = param.split(" ");
4888         return function(elem) {
4889             for(var i=0; i<props.length; i++) { 
4890               if(elem[props[i]]) { 
4891                 return false; 
4892               }
4893             }
4894             return true;
4895         };
4896     },
4897     /*
4898        Method: getNode
4899     
4900        Returns a <Graph.Node> by *id*.
4901        
4902        Also implemented by:
4903        
4904        <Graph>
4905
4906        Parameters:
4907
4908        graph - (object) A <Graph> instance.
4909        id - (string) A <Graph.Node> id.
4910
4911        Example:
4912
4913        (start code js)
4914          $jit.Graph.Util.getNode(graph, 'nodeid');
4915          //or...
4916          graph.getNode('nodeid');
4917        (end code)
4918     */
4919     getNode: function(graph, id) {
4920         return graph.nodes[id];
4921     },
4922     
4923     /*
4924        Method: eachNode
4925     
4926        Iterates over <Graph> nodes performing an *action*.
4927        
4928        Also implemented by:
4929        
4930        <Graph>.
4931
4932        Parameters:
4933
4934        graph - (object) A <Graph> instance.
4935        action - (function) A callback function having a <Graph.Node> as first formal parameter.
4936
4937        Example:
4938        (start code js)
4939          $jit.Graph.Util.eachNode(graph, function(node) {
4940           alert(node.name);
4941          });
4942          //or...
4943          graph.eachNode(function(node) {
4944            alert(node.name);
4945          });
4946        (end code)
4947     */
4948     eachNode: function(graph, action, flags) {
4949         var filter = this.filter(flags);
4950         for(var i in graph.nodes) {
4951           if(filter(graph.nodes[i])) action(graph.nodes[i]);
4952         } 
4953     },
4954     
4955     /*
4956        Method: eachAdjacency
4957     
4958        Iterates over <Graph.Node> adjacencies applying the *action* function.
4959        
4960        Also implemented by:
4961        
4962        <Graph.Node>.
4963
4964        Parameters:
4965
4966        node - (object) A <Graph.Node>.
4967        action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4968
4969        Example:
4970        (start code js)
4971          $jit.Graph.Util.eachAdjacency(node, function(adj) {
4972           alert(adj.nodeTo.name);
4973          });
4974          //or...
4975          node.eachAdjacency(function(adj) {
4976            alert(adj.nodeTo.name);
4977          });
4978        (end code)
4979     */
4980     eachAdjacency: function(node, action, flags) {
4981         var adj = node.adjacencies, filter = this.filter(flags);
4982         for(var id in adj) {
4983           var a = adj[id];
4984           if(filter(a)) {
4985             if(a.nodeFrom != node) {
4986               var tmp = a.nodeFrom;
4987               a.nodeFrom = a.nodeTo;
4988               a.nodeTo = tmp;
4989             }
4990             action(a, id);
4991           }
4992         }
4993     },
4994
4995      /*
4996        Method: computeLevels
4997     
4998        Performs a BFS traversal setting the correct depth for each node.
4999         
5000        Also implemented by:
5001        
5002        <Graph>.
5003        
5004        Note:
5005        
5006        The depth of each node can then be accessed by 
5007        >node._depth
5008
5009        Parameters:
5010
5011        graph - (object) A <Graph>.
5012        id - (string) A starting node id for the BFS traversal.
5013        startDepth - (optional|number) A minimum depth value. Default's 0.
5014
5015     */
5016     computeLevels: function(graph, id, startDepth, flags) {
5017         startDepth = startDepth || 0;
5018         var filter = this.filter(flags);
5019         this.eachNode(graph, function(elem) {
5020             elem._flag = false;
5021             elem._depth = -1;
5022         }, flags);
5023         var root = graph.getNode(id);
5024         root._depth = startDepth;
5025         var queue = [root];
5026         while(queue.length != 0) {
5027             var node = queue.pop();
5028             node._flag = true;
5029             this.eachAdjacency(node, function(adj) {
5030                 var n = adj.nodeTo;
5031                 if(n._flag == false && filter(n)) {
5032                     if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5033                     queue.unshift(n);
5034                 }
5035             }, flags);
5036         }
5037     },
5038
5039     /*
5040        Method: eachBFS
5041     
5042        Performs a BFS traversal applying *action* to each <Graph.Node>.
5043        
5044        Also implemented by:
5045        
5046        <Graph>.
5047
5048        Parameters:
5049
5050        graph - (object) A <Graph>.
5051        id - (string) A starting node id for the BFS traversal.
5052        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5053
5054        Example:
5055        (start code js)
5056          $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5057           alert(node.name);
5058          });
5059          //or...
5060          graph.eachBFS('mynodeid', function(node) {
5061            alert(node.name);
5062          });
5063        (end code)
5064     */
5065     eachBFS: function(graph, id, action, flags) {
5066         var filter = this.filter(flags);
5067         this.clean(graph);
5068         var queue = [graph.getNode(id)];
5069         while(queue.length != 0) {
5070             var node = queue.pop();
5071             node._flag = true;
5072             action(node, node._depth);
5073             this.eachAdjacency(node, function(adj) {
5074                 var n = adj.nodeTo;
5075                 if(n._flag == false && filter(n)) {
5076                     n._flag = true;
5077                     queue.unshift(n);
5078                 }
5079             }, flags);
5080         }
5081     },
5082     
5083     /*
5084        Method: eachLevel
5085     
5086        Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5087        
5088        Also implemented by:
5089        
5090        <Graph.Node>.
5091
5092        Parameters:
5093        
5094        node - (object) A <Graph.Node>.
5095        levelBegin - (number) A relative level value.
5096        levelEnd - (number) A relative level value.
5097        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5098
5099     */
5100     eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5101         var d = node._depth, filter = this.filter(flags), that = this;
5102         levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5103         (function loopLevel(node, levelBegin, levelEnd) {
5104             var d = node._depth;
5105             if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5106             if(d < levelEnd) {
5107                 that.eachAdjacency(node, function(adj) {
5108                     var n = adj.nodeTo;
5109                     if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5110                 });
5111             }
5112         })(node, levelBegin + d, levelEnd + d);      
5113     },
5114
5115     /*
5116        Method: eachSubgraph
5117     
5118        Iterates over a node's children recursively.
5119        
5120        Also implemented by:
5121        
5122        <Graph.Node>.
5123
5124        Parameters:
5125        node - (object) A <Graph.Node>.
5126        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5127
5128        Example:
5129        (start code js)
5130          $jit.Graph.Util.eachSubgraph(node, function(node) {
5131            alert(node.name);
5132          });
5133          //or...
5134          node.eachSubgraph(function(node) {
5135            alert(node.name);
5136          });
5137        (end code)
5138     */
5139     eachSubgraph: function(node, action, flags) {
5140       this.eachLevel(node, 0, false, action, flags);
5141     },
5142
5143     /*
5144        Method: eachSubnode
5145     
5146        Iterates over a node's children (without deeper recursion).
5147        
5148        Also implemented by:
5149        
5150        <Graph.Node>.
5151        
5152        Parameters:
5153        node - (object) A <Graph.Node>.
5154        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5155
5156        Example:
5157        (start code js)
5158          $jit.Graph.Util.eachSubnode(node, function(node) {
5159           alert(node.name);
5160          });
5161          //or...
5162          node.eachSubnode(function(node) {
5163            alert(node.name);
5164          });
5165        (end code)
5166     */
5167     eachSubnode: function(node, action, flags) {
5168         this.eachLevel(node, 1, 1, action, flags);
5169     },
5170
5171     /*
5172        Method: anySubnode
5173     
5174        Returns *true* if any subnode matches the given condition.
5175        
5176        Also implemented by:
5177        
5178        <Graph.Node>.
5179
5180        Parameters:
5181        node - (object) A <Graph.Node>.
5182        cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5183
5184        Example:
5185        (start code js)
5186          $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5187          //or...
5188          node.anySubnode(function(node) { return node.name == 'mynodename'; });
5189        (end code)
5190     */
5191     anySubnode: function(node, cond, flags) {
5192       var flag = false;
5193       cond = cond || $.lambda(true);
5194       var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5195       this.eachSubnode(node, function(elem) {
5196         if(c(elem)) flag = true;
5197       }, flags);
5198       return flag;
5199     },
5200   
5201     /*
5202        Method: getSubnodes
5203     
5204        Collects all subnodes for a specified node. 
5205        The *level* parameter filters nodes having relative depth of *level* from the root node. 
5206        
5207        Also implemented by:
5208        
5209        <Graph.Node>.
5210
5211        Parameters:
5212        node - (object) A <Graph.Node>.
5213        level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5214
5215        Returns:
5216        An array of nodes.
5217
5218     */
5219     getSubnodes: function(node, level, flags) {
5220         var ans = [], that = this;
5221         level = level || 0;
5222         var levelStart, levelEnd;
5223         if($.type(level) == 'array') {
5224             levelStart = level[0];
5225             levelEnd = level[1];
5226         } else {
5227             levelStart = level;
5228             levelEnd = Number.MAX_VALUE - node._depth;
5229         }
5230         this.eachLevel(node, levelStart, levelEnd, function(n) {
5231             ans.push(n);
5232         }, flags);
5233         return ans;
5234     },
5235   
5236   
5237     /*
5238        Method: getParents
5239     
5240        Returns an Array of <Graph.Nodes> which are parents of the given node.
5241        
5242        Also implemented by:
5243        
5244        <Graph.Node>.
5245
5246        Parameters:
5247        node - (object) A <Graph.Node>.
5248
5249        Returns:
5250        An Array of <Graph.Nodes>.
5251
5252        Example:
5253        (start code js)
5254          var pars = $jit.Graph.Util.getParents(node);
5255          //or...
5256          var pars = node.getParents();
5257          
5258          if(pars.length > 0) {
5259            //do stuff with parents
5260          }
5261        (end code)
5262     */
5263     getParents: function(node) {
5264         var ans = [];
5265         this.eachAdjacency(node, function(adj) {
5266             var n = adj.nodeTo;
5267             if(n._depth < node._depth) ans.push(n);
5268         });
5269         return ans;
5270     },
5271     
5272     /*
5273     Method: isDescendantOf
5274  
5275     Returns a boolean indicating if some node is descendant of the node with the given id. 
5276
5277     Also implemented by:
5278     
5279     <Graph.Node>.
5280     
5281     
5282     Parameters:
5283     node - (object) A <Graph.Node>.
5284     id - (string) A <Graph.Node> id.
5285
5286     Example:
5287     (start code js)
5288       $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5289       //or...
5290       node.isDescendantOf('nodeid');//true|false
5291     (end code)
5292  */
5293  isDescendantOf: function(node, id) {
5294     if(node.id == id) return true;
5295     var pars = this.getParents(node), ans = false;
5296     for ( var i = 0; !ans && i < pars.length; i++) {
5297     ans = ans || this.isDescendantOf(pars[i], id);
5298   }
5299     return ans;
5300  },
5301
5302  /*
5303      Method: clean
5304   
5305      Cleans flags from nodes.
5306
5307      Also implemented by:
5308      
5309      <Graph>.
5310      
5311      Parameters:
5312      graph - A <Graph> instance.
5313   */
5314   clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5315   
5316   /* 
5317     Method: getClosestNodeToOrigin 
5318   
5319     Returns the closest node to the center of canvas.
5320   
5321     Also implemented by:
5322     
5323     <Graph>.
5324     
5325     Parameters:
5326    
5327      graph - (object) A <Graph> instance.
5328      prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5329   
5330   */
5331   getClosestNodeToOrigin: function(graph, prop, flags) {
5332    return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5333   },
5334   
5335   /* 
5336     Method: getClosestNodeToPos
5337   
5338     Returns the closest node to the given position.
5339   
5340     Also implemented by:
5341     
5342     <Graph>.
5343     
5344     Parameters:
5345    
5346      graph - (object) A <Graph> instance.
5347      pos - (object) A <Complex> or <Polar> instance.
5348      prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5349   
5350   */
5351   getClosestNodeToPos: function(graph, pos, prop, flags) {
5352    var node = null;
5353    prop = prop || 'current';
5354    pos = pos && pos.getc(true) || Complex.KER;
5355    var distance = function(a, b) {
5356      var d1 = a.x - b.x, d2 = a.y - b.y;
5357      return d1 * d1 + d2 * d2;
5358    };
5359    this.eachNode(graph, function(elem) {
5360      node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5361          node.getPos(prop).getc(true), pos)) ? elem : node;
5362    }, flags);
5363    return node;
5364   } 
5365 };
5366
5367 //Append graph methods to <Graph>
5368 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5369   Graph.prototype[m] = function() {
5370     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5371   };
5372 });
5373
5374 //Append node methods to <Graph.Node>
5375 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5376   Graph.Node.prototype[m] = function() {
5377     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5378   };
5379 });
5380
5381 /*
5382  * File: Graph.Op.js
5383  *
5384 */
5385
5386 /*
5387    Object: Graph.Op
5388
5389    Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>, 
5390    morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5391
5392 */
5393 Graph.Op = {
5394
5395     options: {
5396       type: 'nothing',
5397       duration: 2000,
5398       hideLabels: true,
5399       fps:30
5400     },
5401     
5402     initialize: function(viz) {
5403       this.viz = viz;
5404     },
5405
5406     /*
5407        Method: removeNode
5408     
5409        Removes one or more <Graph.Nodes> from the visualization. 
5410        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5411
5412        Parameters:
5413     
5414         node - (string|array) The node's id. Can also be an array having many ids.
5415         opt - (object) Animation options. It's an object with optional properties described below
5416         type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5417         duration - Described in <Options.Fx>.
5418         fps - Described in <Options.Fx>.
5419         transition - Described in <Options.Fx>.
5420         hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5421    
5422       Example:
5423       (start code js)
5424         var viz = new $jit.Viz(options);
5425         viz.op.removeNode('nodeId', {
5426           type: 'fade:seq',
5427           duration: 1000,
5428           hideLabels: false,
5429           transition: $jit.Trans.Quart.easeOut
5430         });
5431         //or also
5432         viz.op.removeNode(['someId', 'otherId'], {
5433           type: 'fade:con',
5434           duration: 1500
5435         });
5436       (end code)
5437     */
5438   
5439     removeNode: function(node, opt) {
5440         var viz = this.viz;
5441         var options = $.merge(this.options, viz.controller, opt);
5442         var n = $.splat(node);
5443         var i, that, nodeObj;
5444         switch(options.type) {
5445             case 'nothing':
5446                 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5447                 break;
5448             
5449             case 'replot':
5450                 this.removeNode(n, { type: 'nothing' });
5451                 viz.labels.clearLabels();
5452                 viz.refresh(true);
5453                 break;
5454             
5455             case 'fade:seq': case 'fade':
5456                 that = this;
5457                 //set alpha to 0 for nodes to remove.
5458                 for(i=0; i<n.length; i++) {
5459                     nodeObj = viz.graph.getNode(n[i]);
5460                     nodeObj.setData('alpha', 0, 'end');
5461                 }
5462                 viz.fx.animate($.merge(options, {
5463                     modes: ['node-property:alpha'],
5464                     onComplete: function() {
5465                         that.removeNode(n, { type: 'nothing' });
5466                         viz.labels.clearLabels();
5467                         viz.reposition();
5468                         viz.fx.animate($.merge(options, {
5469                             modes: ['linear']
5470                         }));
5471                     }
5472                 }));
5473                 break;
5474             
5475             case 'fade:con':
5476                 that = this;
5477                 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5478                 for(i=0; i<n.length; i++) {
5479                     nodeObj = viz.graph.getNode(n[i]);
5480                     nodeObj.setData('alpha', 0, 'end');
5481                     nodeObj.ignore = true;
5482                 }
5483                 viz.reposition();
5484                 viz.fx.animate($.merge(options, {
5485                     modes: ['node-property:alpha', 'linear'],
5486                     onComplete: function() {
5487                         that.removeNode(n, { type: 'nothing' });
5488                     }
5489                 }));
5490                 break;
5491             
5492             case 'iter':
5493                 that = this;
5494                 viz.fx.sequence({
5495                     condition: function() { return n.length != 0; },
5496                     step: function() { that.removeNode(n.shift(), { type: 'nothing' });  viz.labels.clearLabels(); },
5497                     onComplete: function() { options.onComplete(); },
5498                     duration: Math.ceil(options.duration / n.length)
5499                 });
5500                 break;
5501                 
5502             default: this.doError();
5503         }
5504     },
5505     
5506     /*
5507        Method: removeEdge
5508     
5509        Removes one or more <Graph.Adjacences> from the visualization. 
5510        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5511
5512        Parameters:
5513     
5514        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'], ...]).
5515        opt - (object) Animation options. It's an object with optional properties described below
5516        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5517        duration - Described in <Options.Fx>.
5518        fps - Described in <Options.Fx>.
5519        transition - Described in <Options.Fx>.
5520        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5521    
5522       Example:
5523       (start code js)
5524         var viz = new $jit.Viz(options);
5525         viz.op.removeEdge(['nodeId', 'otherId'], {
5526           type: 'fade:seq',
5527           duration: 1000,
5528           hideLabels: false,
5529           transition: $jit.Trans.Quart.easeOut
5530         });
5531         //or also
5532         viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5533           type: 'fade:con',
5534           duration: 1500
5535         });
5536       (end code)
5537     
5538     */
5539     removeEdge: function(vertex, opt) {
5540         var viz = this.viz;
5541         var options = $.merge(this.options, viz.controller, opt);
5542         var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5543         var i, that, adj;
5544         switch(options.type) {
5545             case 'nothing':
5546                 for(i=0; i<v.length; i++)   viz.graph.removeAdjacence(v[i][0], v[i][1]);
5547                 break;
5548             
5549             case 'replot':
5550                 this.removeEdge(v, { type: 'nothing' });
5551                 viz.refresh(true);
5552                 break;
5553             
5554             case 'fade:seq': case 'fade':
5555                 that = this;
5556                 //set alpha to 0 for edges to remove.
5557                 for(i=0; i<v.length; i++) {
5558                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5559                     if(adj) {
5560                         adj.setData('alpha', 0,'end');
5561                     }
5562                 }
5563                 viz.fx.animate($.merge(options, {
5564                     modes: ['edge-property:alpha'],
5565                     onComplete: function() {
5566                         that.removeEdge(v, { type: 'nothing' });
5567                         viz.reposition();
5568                         viz.fx.animate($.merge(options, {
5569                             modes: ['linear']
5570                         }));
5571                     }
5572                 }));
5573                 break;
5574             
5575             case 'fade:con':
5576                 that = this;
5577                 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5578                 for(i=0; i<v.length; i++) {
5579                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5580                     if(adj) {
5581                         adj.setData('alpha',0 ,'end');
5582                         adj.ignore = true;
5583                     }
5584                 }
5585                 viz.reposition();
5586                 viz.fx.animate($.merge(options, {
5587                     modes: ['edge-property:alpha', 'linear'],
5588                     onComplete: function() {
5589                         that.removeEdge(v, { type: 'nothing' });
5590                     }
5591                 }));
5592                 break;
5593             
5594             case 'iter':
5595                 that = this;
5596                 viz.fx.sequence({
5597                     condition: function() { return v.length != 0; },
5598                     step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5599                     onComplete: function() { options.onComplete(); },
5600                     duration: Math.ceil(options.duration / v.length)
5601                 });
5602                 break;
5603                 
5604             default: this.doError();
5605         }
5606     },
5607     
5608     /*
5609        Method: sum
5610     
5611        Adds a new graph to the visualization. 
5612        The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization. 
5613        The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5614
5615        Parameters:
5616     
5617        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5618        opt - (object) Animation options. It's an object with optional properties described below
5619        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con".
5620        duration - Described in <Options.Fx>.
5621        fps - Described in <Options.Fx>.
5622        transition - Described in <Options.Fx>.
5623        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5624    
5625       Example:
5626       (start code js)
5627         //...json contains a tree or graph structure...
5628
5629         var viz = new $jit.Viz(options);
5630         viz.op.sum(json, {
5631           type: 'fade:seq',
5632           duration: 1000,
5633           hideLabels: false,
5634           transition: $jit.Trans.Quart.easeOut
5635         });
5636         //or also
5637         viz.op.sum(json, {
5638           type: 'fade:con',
5639           duration: 1500
5640         });
5641       (end code)
5642     
5643     */
5644     sum: function(json, opt) {
5645         var viz = this.viz;
5646         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5647         var graph;
5648         viz.root = opt.id || viz.root;
5649         switch(options.type) {
5650             case 'nothing':
5651                 graph = viz.construct(json);
5652                 graph.eachNode(function(elem) {
5653                     elem.eachAdjacency(function(adj) {
5654                         viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5655                     });
5656                 });
5657                 break;
5658             
5659             case 'replot':
5660                 viz.refresh(true);
5661                 this.sum(json, { type: 'nothing' });
5662                 viz.refresh(true);
5663                 break;
5664             
5665             case 'fade:seq': case 'fade': case 'fade:con':
5666                 that = this;
5667                 graph = viz.construct(json);
5668
5669                 //set alpha to 0 for nodes to add.
5670                 var fadeEdges = this.preprocessSum(graph);
5671                 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5672                 viz.reposition();
5673                 if(options.type != 'fade:con') {
5674                     viz.fx.animate($.merge(options, {
5675                         modes: ['linear'],
5676                         onComplete: function() {
5677                             viz.fx.animate($.merge(options, {
5678                                 modes: modes,
5679                                 onComplete: function() {
5680                                     options.onComplete();
5681                                 }
5682                             }));
5683                         }
5684                     }));
5685                 } else {
5686                     viz.graph.eachNode(function(elem) {
5687                         if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5688                           elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5689                         }
5690                     });
5691                     viz.fx.animate($.merge(options, {
5692                         modes: ['linear'].concat(modes)
5693                     }));
5694                 }
5695                 break;
5696
5697             default: this.doError();
5698         }
5699     },
5700     
5701     /*
5702        Method: morph
5703     
5704        This method will transform the current visualized graph into the new JSON representation passed in the method. 
5705        The JSON object must at least have the root node in common with the current visualized graph.
5706
5707        Parameters:
5708     
5709        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5710        opt - (object) Animation options. It's an object with optional properties described below
5711        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5712        duration - Described in <Options.Fx>.
5713        fps - Described in <Options.Fx>.
5714        transition - Described in <Options.Fx>.
5715        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5716        id - (string) The shared <Graph.Node> id between both graphs.
5717        
5718        extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to 
5719                     *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation. 
5720                     For animating these extra-parameters you have to specify an object that has animation groups as keys and animation 
5721                     properties as values, just like specified in <Graph.Plot.animate>.
5722    
5723       Example:
5724       (start code js)
5725         //...json contains a tree or graph structure...
5726
5727         var viz = new $jit.Viz(options);
5728         viz.op.morph(json, {
5729           type: 'fade',
5730           duration: 1000,
5731           hideLabels: false,
5732           transition: $jit.Trans.Quart.easeOut
5733         });
5734         //or also
5735         viz.op.morph(json, {
5736           type: 'fade',
5737           duration: 1500
5738         });
5739         //if the json data contains dollar prefixed params
5740         //like $width or $height these too can be animated
5741         viz.op.morph(json, {
5742           type: 'fade',
5743           duration: 1500
5744         }, {
5745           'node-property': ['width', 'height']
5746         });
5747       (end code)
5748     
5749     */
5750     morph: function(json, opt, extraModes) {
5751         var viz = this.viz;
5752         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5753         var graph;
5754         //TODO(nico) this hack makes morphing work with the Hypertree. 
5755         //Need to check if it has been solved and this can be removed.
5756         viz.root = opt.id || viz.root;
5757         switch(options.type) {
5758             case 'nothing':
5759                 graph = viz.construct(json);
5760                 graph.eachNode(function(elem) {
5761                   var nodeExists = viz.graph.hasNode(elem.id);  
5762                   elem.eachAdjacency(function(adj) {
5763                     var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5764                     viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5765                     //Update data properties if the node existed
5766                     if(adjExists) {
5767                       var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5768                       for(var prop in (adj.data || {})) {
5769                         addedAdj.data[prop] = adj.data[prop];
5770                       }
5771                     }
5772                   });
5773                   //Update data properties if the node existed
5774                   if(nodeExists) {
5775                     var addedNode = viz.graph.getNode(elem.id);
5776                     for(var prop in (elem.data || {})) {
5777                       addedNode.data[prop] = elem.data[prop];
5778                     }
5779                   }
5780                 });
5781                 viz.graph.eachNode(function(elem) {
5782                     elem.eachAdjacency(function(adj) {
5783                         if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5784                             viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5785                         }
5786                     });
5787                     if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5788                 });
5789                 
5790                 break;
5791             
5792             case 'replot':
5793                 viz.labels.clearLabels(true);
5794                 this.morph(json, { type: 'nothing' });
5795                 viz.refresh(true);
5796                 viz.refresh(true);
5797                 break;
5798                 
5799             case 'fade:seq': case 'fade': case 'fade:con':
5800                 that = this;
5801                 graph = viz.construct(json);
5802                 //preprocessing for nodes to delete.
5803                 //get node property modes to interpolate
5804                 var nodeModes = extraModes && ('node-property' in extraModes) 
5805                   && $.map($.splat(extraModes['node-property']), 
5806                       function(n) { return '$' + n; });
5807                 viz.graph.eachNode(function(elem) {
5808                   var graphNode = graph.getNode(elem.id);   
5809                   if(!graphNode) {
5810                       elem.setData('alpha', 1);
5811                       elem.setData('alpha', 1, 'start');
5812                       elem.setData('alpha', 0, 'end');
5813                       elem.ignore = true;
5814                     } else {
5815                       //Update node data information
5816                       var graphNodeData = graphNode.data;
5817                       for(var prop in graphNodeData) {
5818                         if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5819                           elem.endData[prop] = graphNodeData[prop];
5820                         } else {
5821                           elem.data[prop] = graphNodeData[prop];
5822                         }
5823                       }
5824                     }
5825                 }); 
5826                 viz.graph.eachNode(function(elem) {
5827                     if(elem.ignore) return;
5828                     elem.eachAdjacency(function(adj) {
5829                         if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5830                         var nodeFrom = graph.getNode(adj.nodeFrom.id);
5831                         var nodeTo = graph.getNode(adj.nodeTo.id);
5832                         if(!nodeFrom.adjacentTo(nodeTo)) {
5833                             var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5834                             fadeEdges = true;
5835                             adj.setData('alpha', 1);
5836                             adj.setData('alpha', 1, 'start');
5837                             adj.setData('alpha', 0, 'end');
5838                         }
5839                     });
5840                 }); 
5841                 //preprocessing for adding nodes.
5842                 var fadeEdges = this.preprocessSum(graph);
5843
5844                 var modes = !fadeEdges? ['node-property:alpha'] : 
5845                                         ['node-property:alpha', 
5846                                          'edge-property:alpha'];
5847                 //Append extra node-property animations (if any)
5848                 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))? 
5849                     (':' + $.splat(extraModes['node-property']).join(':')) : '');
5850                 //Append extra edge-property animations (if any)
5851                 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))? 
5852                     (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5853                 //Add label-property animations (if any)
5854                 if(extraModes && ('label-property' in extraModes)) {
5855                   modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5856                 }
5857                 viz.reposition();
5858                 viz.graph.eachNode(function(elem) {
5859                     if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5860                       elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5861                     }
5862                 });
5863                 viz.fx.animate($.merge(options, {
5864                     modes: ['polar'].concat(modes),
5865                     onComplete: function() {
5866                         viz.graph.eachNode(function(elem) {
5867                             if(elem.ignore) viz.graph.removeNode(elem.id);
5868                         });
5869                         viz.graph.eachNode(function(elem) {
5870                             elem.eachAdjacency(function(adj) {
5871                                 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5872                             });
5873                         });
5874                         options.onComplete();
5875                     }
5876                 }));
5877                 break;
5878
5879             default:;
5880         }
5881     },
5882
5883     
5884   /*
5885     Method: contract
5886  
5887     Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5888     
5889     Parameters:
5890  
5891     node - (object) A <Graph.Node>.
5892     opt - (object) An object containing options described below
5893     type - (string) Whether to 'replot' or 'animate' the contraction.
5894    
5895     There are also a number of Animation options. For more information see <Options.Fx>.
5896
5897     Example:
5898     (start code js)
5899      var viz = new $jit.Viz(options);
5900      viz.op.contract(node, {
5901        type: 'animate',
5902        duration: 1000,
5903        hideLabels: true,
5904        transition: $jit.Trans.Quart.easeOut
5905      });
5906    (end code)
5907  
5908    */
5909     contract: function(node, opt) {
5910       var viz = this.viz;
5911       if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5912       opt = $.merge(this.options, viz.config, opt || {}, {
5913         'modes': ['node-property:alpha:span', 'linear']
5914       });
5915       node.collapsed = true;
5916       (function subn(n) {
5917         n.eachSubnode(function(ch) {
5918           ch.ignore = true;
5919           ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5920           subn(ch);
5921         });
5922       })(node);
5923       if(opt.type == 'animate') {
5924         viz.compute('end');
5925         if(viz.rotated) {
5926           viz.rotate(viz.rotated, 'none', {
5927             'property':'end'
5928           });
5929         }
5930         (function subn(n) {
5931           n.eachSubnode(function(ch) {
5932             ch.setPos(node.getPos('end'), 'end');
5933             subn(ch);
5934           });
5935         })(node);
5936         viz.fx.animate(opt);
5937       } else if(opt.type == 'replot'){
5938         viz.refresh();
5939       }
5940     },
5941     
5942     /*
5943     Method: expand
5944  
5945     Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5946     
5947     Parameters:
5948  
5949     node - (object) A <Graph.Node>.
5950     opt - (object) An object containing options described below
5951     type - (string) Whether to 'replot' or 'animate'.
5952      
5953     There are also a number of Animation options. For more information see <Options.Fx>.
5954
5955     Example:
5956     (start code js)
5957       var viz = new $jit.Viz(options);
5958       viz.op.expand(node, {
5959         type: 'animate',
5960         duration: 1000,
5961         hideLabels: true,
5962         transition: $jit.Trans.Quart.easeOut
5963       });
5964     (end code)
5965  
5966    */
5967     expand: function(node, opt) {
5968       if(!('collapsed' in node)) return;
5969       var viz = this.viz;
5970       opt = $.merge(this.options, viz.config, opt || {}, {
5971         'modes': ['node-property:alpha:span', 'linear']
5972       });
5973       delete node.collapsed;
5974       (function subn(n) {
5975         n.eachSubnode(function(ch) {
5976           delete ch.ignore;
5977           ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5978           subn(ch);
5979         });
5980       })(node);
5981       if(opt.type == 'animate') {
5982         viz.compute('end');
5983         if(viz.rotated) {
5984           viz.rotate(viz.rotated, 'none', {
5985             'property':'end'
5986           });
5987         }
5988         viz.fx.animate(opt);
5989       } else if(opt.type == 'replot'){
5990         viz.refresh();
5991       }
5992     },
5993
5994     preprocessSum: function(graph) {
5995         var viz = this.viz;
5996         graph.eachNode(function(elem) {
5997             if(!viz.graph.hasNode(elem.id)) {
5998                 viz.graph.addNode(elem);
5999                 var n = viz.graph.getNode(elem.id);
6000                 n.setData('alpha', 0);
6001                 n.setData('alpha', 0, 'start');
6002                 n.setData('alpha', 1, 'end');
6003             }
6004         }); 
6005         var fadeEdges = false;
6006         graph.eachNode(function(elem) {
6007             elem.eachAdjacency(function(adj) {
6008                 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6009                 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6010                 if(!nodeFrom.adjacentTo(nodeTo)) {
6011                     var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6012                     if(nodeFrom.startAlpha == nodeFrom.endAlpha 
6013                     && nodeTo.startAlpha == nodeTo.endAlpha) {
6014                         fadeEdges = true;
6015                         adj.setData('alpha', 0);
6016                         adj.setData('alpha', 0, 'start');
6017                         adj.setData('alpha', 1, 'end');
6018                     } 
6019                 }
6020             });
6021         }); 
6022         return fadeEdges;
6023     }
6024 };
6025
6026
6027
6028 /*
6029    File: Helpers.js
6030  
6031    Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6032    Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6033    position is over the rendered shape.
6034    
6035    Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and 
6036    *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6037    
6038    Example:
6039    (start code js)
6040    //implement a new node type
6041    $jit.Viz.Plot.NodeTypes.implement({
6042      'customNodeType': {
6043        'render': function(node, canvas) {
6044          this.nodeHelper.circle.render ...
6045        },
6046        'contains': function(node, pos) {
6047          this.nodeHelper.circle.contains ...
6048        }
6049      }
6050    });
6051    //implement an edge type
6052    $jit.Viz.Plot.EdgeTypes.implement({
6053      'customNodeType': {
6054        'render': function(node, canvas) {
6055          this.edgeHelper.circle.render ...
6056        },
6057        //optional
6058        'contains': function(node, pos) {
6059          this.edgeHelper.circle.contains ...
6060        }
6061      }
6062    });
6063    (end code)
6064
6065 */
6066
6067 /*
6068    Object: NodeHelper
6069    
6070    Contains rendering and other type of primitives for simple shapes.
6071  */
6072 var NodeHelper = {
6073   'none': {
6074     'render': $.empty,
6075     'contains': $.lambda(false)
6076   },
6077   /*
6078    Object: NodeHelper.circle
6079    */
6080   'circle': {
6081     /*
6082      Method: render
6083      
6084      Renders a circle into the canvas.
6085      
6086      Parameters:
6087      
6088      type - (string) Possible options are 'fill' or 'stroke'.
6089      pos - (object) An *x*, *y* object with the position of the center of the circle.
6090      radius - (number) The radius of the circle to be rendered.
6091      canvas - (object) A <Canvas> instance.
6092      
6093      Example:
6094      (start code js)
6095      NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6096      (end code)
6097      */
6098     'render': function(type, pos, radius, canvas){
6099       var ctx = canvas.getCtx();
6100       ctx.beginPath();
6101       ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6102       ctx.closePath();
6103       ctx[type]();
6104     },
6105     /*
6106     Method: contains
6107     
6108     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6109     
6110     Parameters:
6111     
6112     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6113     pos - (object) An *x*, *y* object with the position to check.
6114     radius - (number) The radius of the rendered circle.
6115     
6116     Example:
6117     (start code js)
6118     NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6119     (end code)
6120     */
6121     'contains': function(npos, pos, radius){
6122       var diffx = npos.x - pos.x, 
6123           diffy = npos.y - pos.y, 
6124           diff = diffx * diffx + diffy * diffy;
6125       return diff <= radius * radius;
6126     }
6127   },
6128   /*
6129   Object: NodeHelper.ellipse
6130   */
6131   'ellipse': {
6132     /*
6133     Method: render
6134     
6135     Renders an ellipse into the canvas.
6136     
6137     Parameters:
6138     
6139     type - (string) Possible options are 'fill' or 'stroke'.
6140     pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6141     width - (number) The width of the ellipse.
6142     height - (number) The height of the ellipse.
6143     canvas - (object) A <Canvas> instance.
6144     
6145     Example:
6146     (start code js)
6147     NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6148     (end code)
6149     */
6150     'render': function(type, pos, width, height, canvas){
6151       var ctx = canvas.getCtx();
6152       height /= 2;
6153       width /= 2;
6154       ctx.save();
6155       ctx.scale(width / height, height / width);
6156       ctx.beginPath();
6157       ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6158           Math.PI * 2, true);
6159       ctx.closePath();
6160       ctx[type]();
6161       ctx.restore();
6162     },
6163     /*
6164     Method: contains
6165     
6166     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6167     
6168     Parameters:
6169     
6170     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6171     pos - (object) An *x*, *y* object with the position to check.
6172     width - (number) The width of the rendered ellipse.
6173     height - (number) The height of the rendered ellipse.
6174     
6175     Example:
6176     (start code js)
6177     NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6178     (end code)
6179     */
6180     'contains': function(npos, pos, width, height){
6181       // TODO(nico): be more precise...
6182       width /= 2; 
6183       height /= 2;
6184       var dist = (width + height) / 2, 
6185           diffx = npos.x - pos.x, 
6186           diffy = npos.y - pos.y, 
6187           diff = diffx * diffx + diffy * diffy;
6188       return diff <= dist * dist;
6189     }
6190   },
6191   /*
6192   Object: NodeHelper.square
6193   */
6194   'square': {
6195     /*
6196     Method: render
6197     
6198     Renders a square into the canvas.
6199     
6200     Parameters:
6201     
6202     type - (string) Possible options are 'fill' or 'stroke'.
6203     pos - (object) An *x*, *y* object with the position of the center of the square.
6204     dim - (number) The radius (or half-diameter) of the square.
6205     canvas - (object) A <Canvas> instance.
6206     
6207     Example:
6208     (start code js)
6209     NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6210     (end code)
6211     */
6212     'render': function(type, pos, dim, canvas){
6213       canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6214     },
6215     /*
6216     Method: contains
6217     
6218     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6219     
6220     Parameters:
6221     
6222     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6223     pos - (object) An *x*, *y* object with the position to check.
6224     dim - (number) The radius (or half-diameter) of the square.
6225     
6226     Example:
6227     (start code js)
6228     NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6229     (end code)
6230     */
6231     'contains': function(npos, pos, dim){
6232       return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6233     }
6234   },
6235   /*
6236   Object: NodeHelper.rectangle
6237   */
6238   'rectangle': {
6239     /*
6240     Method: render
6241     
6242     Renders a rectangle into the canvas.
6243     
6244     Parameters:
6245     
6246     type - (string) Possible options are 'fill' or 'stroke'.
6247     pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6248     width - (number) The width of the rectangle.
6249     height - (number) The height of the rectangle.
6250     canvas - (object) A <Canvas> instance.
6251     
6252     Example:
6253     (start code js)
6254     NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6255     (end code)
6256     */
6257     'render': function(type, pos, width, height, canvas){
6258       canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2, 
6259                                       width, height);
6260     },
6261     /*
6262     Method: contains
6263     
6264     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6265     
6266     Parameters:
6267     
6268     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6269     pos - (object) An *x*, *y* object with the position to check.
6270     width - (number) The width of the rendered rectangle.
6271     height - (number) The height of the rendered rectangle.
6272     
6273     Example:
6274     (start code js)
6275     NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6276     (end code)
6277     */
6278     'contains': function(npos, pos, width, height){
6279       return Math.abs(pos.x - npos.x) <= width / 2
6280           && Math.abs(pos.y - npos.y) <= height / 2;
6281     }
6282   },
6283   /*
6284   Object: NodeHelper.triangle
6285   */
6286   'triangle': {
6287     /*
6288     Method: render
6289     
6290     Renders a triangle into the canvas.
6291     
6292     Parameters:
6293     
6294     type - (string) Possible options are 'fill' or 'stroke'.
6295     pos - (object) An *x*, *y* object with the position of the center of the triangle.
6296     dim - (number) The dimension of the triangle.
6297     canvas - (object) A <Canvas> instance.
6298     
6299     Example:
6300     (start code js)
6301     NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6302     (end code)
6303     */
6304     'render': function(type, pos, dim, canvas){
6305       var ctx = canvas.getCtx(), 
6306           c1x = pos.x, 
6307           c1y = pos.y - dim, 
6308           c2x = c1x - dim, 
6309           c2y = pos.y + dim, 
6310           c3x = c1x + dim, 
6311           c3y = c2y;
6312       ctx.beginPath();
6313       ctx.moveTo(c1x, c1y);
6314       ctx.lineTo(c2x, c2y);
6315       ctx.lineTo(c3x, c3y);
6316       ctx.closePath();
6317       ctx[type]();
6318     },
6319     /*
6320     Method: contains
6321     
6322     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6323     
6324     Parameters:
6325     
6326     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6327     pos - (object) An *x*, *y* object with the position to check.
6328     dim - (number) The dimension of the shape.
6329     
6330     Example:
6331     (start code js)
6332     NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6333     (end code)
6334     */
6335     'contains': function(npos, pos, dim) {
6336       return NodeHelper.circle.contains(npos, pos, dim);
6337     }
6338   },
6339   /*
6340   Object: NodeHelper.star
6341   */
6342   'star': {
6343     /*
6344     Method: render
6345     
6346     Renders a star into the canvas.
6347     
6348     Parameters:
6349     
6350     type - (string) Possible options are 'fill' or 'stroke'.
6351     pos - (object) An *x*, *y* object with the position of the center of the star.
6352     dim - (number) The dimension of the star.
6353     canvas - (object) A <Canvas> instance.
6354     
6355     Example:
6356     (start code js)
6357     NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6358     (end code)
6359     */
6360     'render': function(type, pos, dim, canvas){
6361       var ctx = canvas.getCtx(), 
6362           pi5 = Math.PI / 5;
6363       ctx.save();
6364       ctx.translate(pos.x, pos.y);
6365       ctx.beginPath();
6366       ctx.moveTo(dim, 0);
6367       for (var i = 0; i < 9; i++) {
6368         ctx.rotate(pi5);
6369         if (i % 2 == 0) {
6370           ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6371         } else {
6372           ctx.lineTo(dim, 0);
6373         }
6374       }
6375       ctx.closePath();
6376       ctx[type]();
6377       ctx.restore();
6378     },
6379     /*
6380     Method: contains
6381     
6382     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6383     
6384     Parameters:
6385     
6386     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6387     pos - (object) An *x*, *y* object with the position to check.
6388     dim - (number) The dimension of the shape.
6389     
6390     Example:
6391     (start code js)
6392     NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6393     (end code)
6394     */
6395     'contains': function(npos, pos, dim) {
6396       return NodeHelper.circle.contains(npos, pos, dim);
6397     }
6398   }
6399 };
6400
6401 /*
6402   Object: EdgeHelper
6403   
6404   Contains rendering primitives for simple edge shapes.
6405 */
6406 var EdgeHelper = {
6407   /*
6408     Object: EdgeHelper.line
6409   */
6410   'line': {
6411       /*
6412       Method: render
6413       
6414       Renders a line into the canvas.
6415       
6416       Parameters:
6417       
6418       from - (object) An *x*, *y* object with the starting position of the line.
6419       to - (object) An *x*, *y* object with the ending position of the line.
6420       canvas - (object) A <Canvas> instance.
6421       
6422       Example:
6423       (start code js)
6424       EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6425       (end code)
6426       */
6427       'render': function(from, to, canvas){
6428         var ctx = canvas.getCtx();
6429         ctx.beginPath();
6430         ctx.moveTo(from.x, from.y);
6431         ctx.lineTo(to.x, to.y);
6432         ctx.stroke();
6433       },
6434       /*
6435       Method: contains
6436       
6437       Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6438       
6439       Parameters:
6440       
6441       posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6442       posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6443       pos - (object) An *x*, *y* object with the position to check.
6444       epsilon - (number) The dimension of the shape.
6445       
6446       Example:
6447       (start code js)
6448       EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6449       (end code)
6450       */
6451       'contains': function(posFrom, posTo, pos, epsilon) {
6452         var min = Math.min, 
6453             max = Math.max,
6454             minPosX = min(posFrom.x, posTo.x),
6455             maxPosX = max(posFrom.x, posTo.x),
6456             minPosY = min(posFrom.y, posTo.y),
6457             maxPosY = max(posFrom.y, posTo.y);
6458         
6459         if(pos.x >= minPosX && pos.x <= maxPosX 
6460             && pos.y >= minPosY && pos.y <= maxPosY) {
6461           if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6462             return true;
6463           }
6464           var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6465           return Math.abs(dist - pos.y) <= epsilon;
6466         }
6467         return false;
6468       }
6469     },
6470   /*
6471     Object: EdgeHelper.arrow
6472   */
6473   'arrow': {
6474       /*
6475       Method: render
6476       
6477       Renders an arrow into the canvas.
6478       
6479       Parameters:
6480       
6481       from - (object) An *x*, *y* object with the starting position of the arrow.
6482       to - (object) An *x*, *y* object with the ending position of the arrow.
6483       dim - (number) The dimension of the arrow.
6484       swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6485       canvas - (object) A <Canvas> instance.
6486       
6487       Example:
6488       (start code js)
6489       EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6490       (end code)
6491       */
6492     'render': function(from, to, dim, swap, canvas){
6493         var ctx = canvas.getCtx();
6494         // invert edge direction
6495         if (swap) {
6496           var tmp = from;
6497           from = to;
6498           to = tmp;
6499         }
6500         var vect = new Complex(to.x - from.x, to.y - from.y);
6501         vect.$scale(dim / vect.norm());
6502         var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6503             normal = new Complex(-vect.y / 2, vect.x / 2),
6504             v1 = intermediatePoint.add(normal), 
6505             v2 = intermediatePoint.$add(normal.$scale(-1));
6506         
6507         ctx.beginPath();
6508         ctx.moveTo(from.x, from.y);
6509         ctx.lineTo(to.x, to.y);
6510         ctx.stroke();
6511         ctx.beginPath();
6512         ctx.moveTo(v1.x, v1.y);
6513         ctx.lineTo(v2.x, v2.y);
6514         ctx.lineTo(to.x, to.y);
6515         ctx.closePath();
6516         ctx.fill();
6517     },
6518     /*
6519     Method: contains
6520     
6521     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6522     
6523     Parameters:
6524     
6525     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6526     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6527     pos - (object) An *x*, *y* object with the position to check.
6528     epsilon - (number) The dimension of the shape.
6529     
6530     Example:
6531     (start code js)
6532     EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6533     (end code)
6534     */
6535     'contains': function(posFrom, posTo, pos, epsilon) {
6536       return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6537     }
6538   },
6539   /*
6540     Object: EdgeHelper.hyperline
6541   */
6542   'hyperline': {
6543     /*
6544     Method: render
6545     
6546     Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6547     
6548     Parameters:
6549     
6550     from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6551     to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6552     r - (number) The scaling factor.
6553     canvas - (object) A <Canvas> instance.
6554     
6555     Example:
6556     (start code js)
6557     EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6558     (end code)
6559     */
6560     'render': function(from, to, r, canvas){
6561       var ctx = canvas.getCtx();  
6562       var centerOfCircle = computeArcThroughTwoPoints(from, to);
6563       if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6564           || centerOfCircle.ratio < 0) {
6565         ctx.beginPath();
6566         ctx.moveTo(from.x * r, from.y * r);
6567         ctx.lineTo(to.x * r, to.y * r);
6568         ctx.stroke();
6569       } else {
6570         var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6571             - centerOfCircle.x);
6572         var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6573             - centerOfCircle.x);
6574         var sense = sense(angleBegin, angleEnd);
6575         ctx.beginPath();
6576         ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6577             * r, angleBegin, angleEnd, sense);
6578         ctx.stroke();
6579       }
6580       /*      
6581         Calculates the arc parameters through two points.
6582         
6583         More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane> 
6584       
6585         Parameters:
6586       
6587         p1 - A <Complex> instance.
6588         p2 - A <Complex> instance.
6589         scale - The Disk's diameter.
6590       
6591         Returns:
6592       
6593         An object containing some arc properties.
6594       */
6595       function computeArcThroughTwoPoints(p1, p2){
6596         var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6597         var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6598         // Fall back to a straight line
6599         if (aDen == 0)
6600           return {
6601             x: 0,
6602             y: 0,
6603             ratio: -1
6604           };
6605     
6606         var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6607         var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6608         var x = -a / 2;
6609         var y = -b / 2;
6610         var squaredRatio = (a * a + b * b) / 4 - 1;
6611         // Fall back to a straight line
6612         if (squaredRatio < 0)
6613           return {
6614             x: 0,
6615             y: 0,
6616             ratio: -1
6617           };
6618         var ratio = Math.sqrt(squaredRatio);
6619         var out = {
6620           x: x,
6621           y: y,
6622           ratio: ratio > 1000? -1 : ratio,
6623           a: a,
6624           b: b
6625         };
6626     
6627         return out;
6628       }
6629       /*      
6630         Sets angle direction to clockwise (true) or counterclockwise (false). 
6631          
6632         Parameters: 
6633       
6634            angleBegin - Starting angle for drawing the arc. 
6635            angleEnd - The HyperLine will be drawn from angleBegin to angleEnd. 
6636       
6637         Returns: 
6638       
6639            A Boolean instance describing the sense for drawing the HyperLine. 
6640       */
6641       function sense(angleBegin, angleEnd){
6642         return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6643             : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6644       }
6645     },
6646     /*
6647     Method: contains
6648     
6649     Not Implemented
6650     
6651     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6652     
6653     Parameters:
6654     
6655     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6656     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6657     pos - (object) An *x*, *y* object with the position to check.
6658     epsilon - (number) The dimension of the shape.
6659     
6660     Example:
6661     (start code js)
6662     EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6663     (end code)
6664     */
6665     'contains': $.lambda(false)
6666   }
6667 };
6668
6669
6670 /*
6671  * File: Graph.Plot.js
6672  */
6673
6674 /*
6675    Object: Graph.Plot
6676
6677    <Graph> rendering and animation methods.
6678    
6679    Properties:
6680    
6681    nodeHelper - <NodeHelper> object.
6682    edgeHelper - <EdgeHelper> object.
6683 */
6684 Graph.Plot = {
6685     //Default intializer
6686     initialize: function(viz, klass){
6687       this.viz = viz;
6688       this.config = viz.config;
6689       this.node = viz.config.Node;
6690       this.edge = viz.config.Edge;
6691       this.animation = new Animation;
6692       this.nodeTypes = new klass.Plot.NodeTypes;
6693       this.edgeTypes = new klass.Plot.EdgeTypes;
6694       this.labels = viz.labels;
6695    },
6696
6697     //Add helpers
6698     nodeHelper: NodeHelper,
6699     edgeHelper: EdgeHelper,
6700     
6701     Interpolator: {
6702         //node/edge property parsers
6703         'map': {
6704           'border': 'color',
6705           'color': 'color',
6706           'width': 'number',
6707           'height': 'number',
6708           'dim': 'number',
6709           'alpha': 'number',
6710           'lineWidth': 'number',
6711           'angularWidth':'number',
6712           'span':'number',
6713           'valueArray':'array-number',
6714           'dimArray':'array-number'
6715           //'colorArray':'array-color'
6716         },
6717         
6718         //canvas specific parsers
6719         'canvas': {
6720           'globalAlpha': 'number',
6721           'fillStyle': 'color',
6722           'strokeStyle': 'color',
6723           'lineWidth': 'number',
6724           'shadowBlur': 'number',
6725           'shadowColor': 'color',
6726           'shadowOffsetX': 'number',
6727           'shadowOffsetY': 'number',
6728           'miterLimit': 'number'
6729         },
6730   
6731         //label parsers
6732         'label': {
6733           'size': 'number',
6734           'color': 'color'
6735         },
6736   
6737         //Number interpolator
6738         'compute': function(from, to, delta) {
6739           return from + (to - from) * delta;
6740         },
6741         
6742         //Position interpolators
6743         'moebius': function(elem, props, delta, vector) {
6744           var v = vector.scale(-delta);  
6745           if(v.norm() < 1) {
6746               var x = v.x, y = v.y;
6747               var ans = elem.startPos
6748                 .getc().moebiusTransformation(v);
6749               elem.pos.setc(ans.x, ans.y);
6750               v.x = x; v.y = y;
6751             }           
6752         },
6753
6754         'linear': function(elem, props, delta) {
6755             var from = elem.startPos.getc(true);
6756             var to = elem.endPos.getc(true);
6757             elem.pos.setc(this.compute(from.x, to.x, delta), 
6758                           this.compute(from.y, to.y, delta));
6759         },
6760
6761         'polar': function(elem, props, delta) {
6762           var from = elem.startPos.getp(true);
6763           var to = elem.endPos.getp();
6764           var ans = to.interpolate(from, delta);
6765           elem.pos.setp(ans.theta, ans.rho);
6766         },
6767         
6768         //Graph's Node/Edge interpolators
6769         'number': function(elem, prop, delta, getter, setter) {
6770           var from = elem[getter](prop, 'start');
6771           var to = elem[getter](prop, 'end');
6772           elem[setter](prop, this.compute(from, to, delta));
6773         },
6774
6775         'color': function(elem, prop, delta, getter, setter) {
6776           var from = $.hexToRgb(elem[getter](prop, 'start'));
6777           var to = $.hexToRgb(elem[getter](prop, 'end'));
6778           var comp = this.compute;
6779           var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6780                                 parseInt(comp(from[1], to[1], delta)),
6781                                 parseInt(comp(from[2], to[2], delta))]);
6782           
6783           elem[setter](prop, val);
6784         },
6785         
6786         'array-number': function(elem, prop, delta, getter, setter) {
6787           var from = elem[getter](prop, 'start'),
6788               to = elem[getter](prop, 'end'),
6789               cur = [];
6790           for(var i=0, l=from.length; i<l; i++) {
6791             var fromi = from[i], toi = to[i];
6792             if(fromi.length) {
6793               for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6794                 curi.push(this.compute(fromi[j], toi[j], delta));
6795               }
6796               cur.push(curi);
6797             } else {
6798               cur.push(this.compute(fromi, toi, delta));
6799             }
6800           }
6801           elem[setter](prop, cur);
6802         },
6803         
6804         'node': function(elem, props, delta, map, getter, setter) {
6805           map = this[map];
6806           if(props) {
6807             var len = props.length;
6808             for(var i=0; i<len; i++) {
6809               var pi = props[i];
6810               this[map[pi]](elem, pi, delta, getter, setter);
6811             }
6812           } else {
6813             for(var pi in map) {
6814               this[map[pi]](elem, pi, delta, getter, setter);
6815             }
6816           }
6817         },
6818         
6819         'edge': function(elem, props, delta, mapKey, getter, setter) {
6820             var adjs = elem.adjacencies;
6821             for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6822         },
6823         
6824         'node-property': function(elem, props, delta) {
6825           this['node'](elem, props, delta, 'map', 'getData', 'setData');
6826         },
6827         
6828         'edge-property': function(elem, props, delta) {
6829           this['edge'](elem, props, delta, 'map', 'getData', 'setData');  
6830         },
6831
6832         'label-property': function(elem, props, delta) {
6833           this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6834         },
6835         
6836         'node-style': function(elem, props, delta) {
6837           this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6838         },
6839         
6840         'edge-style': function(elem, props, delta) {
6841           this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');  
6842         }
6843     },
6844     
6845   
6846     /*
6847        sequence
6848     
6849        Iteratively performs an action while refreshing the state of the visualization.
6850
6851        Parameters:
6852
6853        options - (object) An object containing some sequence options described below
6854        condition - (function) A function returning a boolean instance in order to stop iterations.
6855        step - (function) A function to execute on each step of the iteration.
6856        onComplete - (function) A function to execute when the sequence finishes.
6857        duration - (number) Duration (in milliseconds) of each step.
6858
6859       Example:
6860        (start code js)
6861         var rg = new $jit.RGraph(options);
6862         var i = 0;
6863         rg.fx.sequence({
6864           condition: function() {
6865            return i == 10;
6866           },
6867           step: function() {
6868             alert(i++);
6869           },
6870           onComplete: function() {
6871            alert('done!');
6872           }
6873         });
6874        (end code)
6875
6876     */
6877     sequence: function(options) {
6878         var that = this;
6879         options = $.merge({
6880           condition: $.lambda(false),
6881           step: $.empty,
6882           onComplete: $.empty,
6883           duration: 200
6884         }, options || {});
6885
6886         var interval = setInterval(function() {
6887           if(options.condition()) {
6888             options.step();
6889           } else {
6890             clearInterval(interval);
6891             options.onComplete();
6892           }
6893           that.viz.refresh(true);
6894         }, options.duration);
6895     },
6896     
6897     /*
6898       prepare
6899  
6900       Prepare graph position and other attribute values before performing an Animation. 
6901       This method is used internally by the Toolkit.
6902       
6903       See also:
6904        
6905        <Animation>, <Graph.Plot.animate>
6906
6907     */
6908     prepare: function(modes) {
6909       var graph = this.viz.graph,
6910           accessors = {
6911             'node-property': {
6912               'getter': 'getData',
6913               'setter': 'setData'
6914             },
6915             'edge-property': {
6916               'getter': 'getData',
6917               'setter': 'setData'
6918             },
6919             'node-style': {
6920               'getter': 'getCanvasStyle',
6921               'setter': 'setCanvasStyle'
6922             },
6923             'edge-style': {
6924               'getter': 'getCanvasStyle',
6925               'setter': 'setCanvasStyle'
6926             }
6927           };
6928
6929       //parse modes
6930       var m = {};
6931       if($.type(modes) == 'array') {
6932         for(var i=0, len=modes.length; i < len; i++) {
6933           var elems = modes[i].split(':');
6934           m[elems.shift()] = elems;
6935         }
6936       } else {
6937         for(var p in modes) {
6938           if(p == 'position') {
6939             m[modes.position] = [];
6940           } else {
6941             m[p] = $.splat(modes[p]);
6942           }
6943         }
6944       }
6945       
6946       graph.eachNode(function(node) { 
6947         node.startPos.set(node.pos);
6948         $.each(['node-property', 'node-style'], function(p) {
6949           if(p in m) {
6950             var prop = m[p];
6951             for(var i=0, l=prop.length; i < l; i++) {
6952               node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6953             }
6954           }
6955         });
6956         $.each(['edge-property', 'edge-style'], function(p) {
6957           if(p in m) {
6958             var prop = m[p];
6959             node.eachAdjacency(function(adj) {
6960               for(var i=0, l=prop.length; i < l; i++) {
6961                 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6962               }
6963             });
6964           }
6965         });
6966       });
6967       return m;
6968     },
6969     
6970     /*
6971        Method: animate
6972     
6973        Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6974
6975        Parameters:
6976
6977        opt - (object) Animation options. The object properties are described below
6978        duration - (optional) Described in <Options.Fx>.
6979        fps - (optional) Described in <Options.Fx>.
6980        hideLabels - (optional|boolean) Whether to hide labels during the animation.
6981        modes - (required|object) An object with animation modes (described below).
6982
6983        Animation modes:
6984        
6985        Animation modes are strings representing different node/edge and graph properties that you'd like to animate. 
6986        They are represented by an object that has as keys main categories of properties to animate and as values a list 
6987        of these specific properties. The properties are described below
6988        
6989        position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6990        node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6991        edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6992        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.
6993        node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6994        edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6995
6996        Example:
6997        (start code js)
6998        var viz = new $jit.Viz(options);
6999        //...tweak some Data, CanvasStyles or LabelData properties...
7000        viz.fx.animate({
7001          modes: {
7002            'position': 'linear',
7003            'node-property': ['width', 'height'],
7004            'node-style': 'shadowColor',
7005            'label-property': 'size'
7006          },
7007          hideLabels: false
7008        });
7009        //...can also be written like this...
7010        viz.fx.animate({
7011          modes: ['linear',
7012                  'node-property:width:height',
7013                  'node-style:shadowColor',
7014                  'label-property:size'],
7015          hideLabels: false
7016        });
7017        (end code)
7018     */
7019     animate: function(opt, versor) {
7020       opt = $.merge(this.viz.config, opt || {});
7021       var that = this,
7022           viz = this.viz,
7023           graph  = viz.graph,
7024           interp = this.Interpolator,
7025           animation =  opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7026       //prepare graph values
7027       var m = this.prepare(opt.modes);
7028       
7029       //animate
7030       if(opt.hideLabels) this.labels.hideLabels(true);
7031       animation.setOptions($.merge(opt, {
7032         $animating: false,
7033         compute: function(delta) {
7034           graph.eachNode(function(node) { 
7035             for(var p in m) {
7036               interp[p](node, m[p], delta, versor);
7037             }
7038           });
7039           that.plot(opt, this.$animating, delta);
7040           this.$animating = true;
7041         },
7042         complete: function() {
7043           if(opt.hideLabels) that.labels.hideLabels(false);
7044           that.plot(opt);
7045           opt.onComplete();
7046           opt.onAfterCompute();
7047         }       
7048       })).start();
7049     },
7050     
7051     /*
7052       nodeFx
7053    
7054       Apply animation to node properties like color, width, height, dim, etc.
7055   
7056       Parameters:
7057   
7058       options - Animation options. This object properties is described below
7059       elements - The Elements to be transformed. This is an object that has a properties
7060       
7061       (start code js)
7062       'elements': {
7063         //can also be an array of ids
7064         'id': 'id-of-node-to-transform',
7065         //properties to be modified. All properties are optional.
7066         'properties': {
7067           'color': '#ccc', //some color
7068           'width': 10, //some width
7069           'height': 10, //some height
7070           'dim': 20, //some dim
7071           'lineWidth': 10 //some line width
7072         } 
7073       }
7074       (end code)
7075       
7076       - _reposition_ Whether to recalculate positions and add a motion animation. 
7077       This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7078       
7079       - _onComplete_ A method that is called when the animation completes.
7080       
7081       ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7082   
7083       Example:
7084       (start code js)
7085        var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7086        rg.fx.nodeFx({
7087          'elements': {
7088            'id':'mynodeid',
7089            'properties': {
7090              'color':'#ccf'
7091            },
7092            'transition': Trans.Quart.easeOut
7093          }
7094        });
7095       (end code)    
7096    */
7097    nodeFx: function(opt) {
7098      var viz = this.viz,
7099          graph  = viz.graph,
7100          animation = this.nodeFxAnimation,
7101          options = $.merge(this.viz.config, {
7102            'elements': {
7103              'id': false,
7104              'properties': {}
7105            },
7106            'reposition': false
7107          });
7108      opt = $.merge(options, opt || {}, {
7109        onBeforeCompute: $.empty,
7110        onAfterCompute: $.empty
7111      });
7112      //check if an animation is running
7113      animation.stopTimer();
7114      var props = opt.elements.properties;
7115      //set end values for nodes
7116      if(!opt.elements.id) {
7117        graph.eachNode(function(n) {
7118          for(var prop in props) {
7119            n.setData(prop, props[prop], 'end');
7120          }
7121        });
7122      } else {
7123        var ids = $.splat(opt.elements.id);
7124        $.each(ids, function(id) {
7125          var n = graph.getNode(id);
7126          if(n) {
7127            for(var prop in props) {
7128              n.setData(prop, props[prop], 'end');
7129            }
7130          }
7131        });
7132      }
7133      //get keys
7134      var propnames = [];
7135      for(var prop in props) propnames.push(prop);
7136      //add node properties modes
7137      var modes = ['node-property:' + propnames.join(':')];
7138      //set new node positions
7139      if(opt.reposition) {
7140        modes.push('linear');
7141        viz.compute('end');
7142      }
7143      //animate
7144      this.animate($.merge(opt, {
7145        modes: modes,
7146        type: 'nodefx'
7147      }));
7148    },
7149
7150     
7151     /*
7152        Method: plot
7153     
7154        Plots a <Graph>.
7155
7156        Parameters:
7157
7158        opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7159
7160        Example:
7161
7162        (start code js)
7163        var viz = new $jit.Viz(options);
7164        viz.fx.plot(); 
7165        (end code)
7166
7167     */
7168     plot: function(opt, animating) {
7169       var viz = this.viz, 
7170       aGraph = viz.graph, 
7171       canvas = viz.canvas, 
7172       id = viz.root, 
7173       that = this, 
7174       ctx = canvas.getCtx(), 
7175       min = Math.min,
7176       opt = opt || this.viz.controller;
7177       opt.clearCanvas && canvas.clear();
7178         
7179       var root = aGraph.getNode(id);
7180       if(!root) return;
7181       
7182       var T = !!root.visited;
7183       aGraph.eachNode(function(node) {
7184         var nodeAlpha = node.getData('alpha');
7185         node.eachAdjacency(function(adj) {
7186           var nodeTo = adj.nodeTo;
7187           if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7188             !animating && opt.onBeforePlotLine(adj);
7189             ctx.save();
7190             ctx.globalAlpha = min(nodeAlpha, 
7191                 nodeTo.getData('alpha'), 
7192                 adj.getData('alpha'));
7193             that.plotLine(adj, canvas, animating);
7194             ctx.restore();
7195             !animating && opt.onAfterPlotLine(adj);
7196           }
7197         });
7198         ctx.save();
7199         if(node.drawn) {
7200           !animating && opt.onBeforePlotNode(node);
7201           that.plotNode(node, canvas, animating);
7202           !animating && opt.onAfterPlotNode(node);
7203         }
7204         if(!that.labelsHidden && opt.withLabels) {
7205           if(node.drawn && nodeAlpha >= 0.95) {
7206             that.labels.plotLabel(canvas, node, opt);
7207           } else {
7208             that.labels.hideLabel(node, false);
7209           }
7210         }
7211         ctx.restore();
7212         node.visited = !T;
7213       });
7214     },
7215
7216   /*
7217       Plots a Subtree.
7218    */
7219    plotTree: function(node, opt, animating) {
7220        var that = this, 
7221        viz = this.viz, 
7222        canvas = viz.canvas,
7223        config = this.config,
7224        ctx = canvas.getCtx();
7225        var nodeAlpha = node.getData('alpha');
7226        node.eachSubnode(function(elem) {
7227          if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7228              var adj = node.getAdjacency(elem.id);
7229              !animating && opt.onBeforePlotLine(adj);
7230              ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7231              that.plotLine(adj, canvas, animating);
7232              !animating && opt.onAfterPlotLine(adj);
7233              that.plotTree(elem, opt, animating);
7234          }
7235        });
7236        if(node.drawn) {
7237            !animating && opt.onBeforePlotNode(node);
7238            this.plotNode(node, canvas, animating);
7239            !animating && opt.onAfterPlotNode(node);
7240            if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95) 
7241                this.labels.plotLabel(canvas, node, opt);
7242            else 
7243                this.labels.hideLabel(node, false);
7244        } else {
7245            this.labels.hideLabel(node, true);
7246        }
7247    },
7248
7249   /*
7250        Method: plotNode
7251     
7252        Plots a <Graph.Node>.
7253
7254        Parameters:
7255        
7256        node - (object) A <Graph.Node>.
7257        canvas - (object) A <Canvas> element.
7258
7259     */
7260     plotNode: function(node, canvas, animating) {
7261         var f = node.getData('type'), 
7262             ctxObj = this.node.CanvasStyles;
7263         if(f != 'none') {
7264           var width = node.getData('lineWidth'),
7265               color = node.getData('color'),
7266               alpha = node.getData('alpha'),
7267               ctx = canvas.getCtx();
7268           
7269           ctx.lineWidth = width;
7270           ctx.fillStyle = ctx.strokeStyle = color;
7271           ctx.globalAlpha = alpha;
7272           
7273           for(var s in ctxObj) {
7274             ctx[s] = node.getCanvasStyle(s);
7275           }
7276
7277           this.nodeTypes[f].render.call(this, node, canvas, animating);
7278         }
7279     },
7280     
7281     /*
7282        Method: plotLine
7283     
7284        Plots a <Graph.Adjacence>.
7285
7286        Parameters:
7287
7288        adj - (object) A <Graph.Adjacence>.
7289        canvas - (object) A <Canvas> instance.
7290
7291     */
7292     plotLine: function(adj, canvas, animating) {
7293       var f = adj.getData('type'),
7294           ctxObj = this.edge.CanvasStyles;
7295       if(f != 'none') {
7296         var width = adj.getData('lineWidth'),
7297             color = adj.getData('color'),
7298             ctx = canvas.getCtx();
7299         
7300         ctx.lineWidth = width;
7301         ctx.fillStyle = ctx.strokeStyle = color;
7302         
7303         for(var s in ctxObj) {
7304           ctx[s] = adj.getCanvasStyle(s);
7305         }
7306
7307         this.edgeTypes[f].render.call(this, adj, canvas, animating);
7308       }
7309     }    
7310   
7311 };
7312
7313
7314
7315 /*
7316  * File: Graph.Label.js
7317  *
7318 */
7319
7320 /*
7321    Object: Graph.Label
7322
7323    An interface for plotting/hiding/showing labels.
7324
7325    Description:
7326
7327    This is a generic interface for plotting/hiding/showing labels.
7328    The <Graph.Label> interface is implemented in multiple ways to provide
7329    different label types.
7330
7331    For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7332    HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels. 
7333    The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7334    
7335    All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7336 */
7337
7338 Graph.Label = {};
7339
7340 /*
7341    Class: Graph.Label.Native
7342
7343    Implements labels natively, using the Canvas text API.
7344 */
7345 Graph.Label.Native = new Class({
7346     /*
7347        Method: plotLabel
7348
7349        Plots a label for a given node.
7350
7351        Parameters:
7352
7353        canvas - (object) A <Canvas> instance.
7354        node - (object) A <Graph.Node>.
7355        controller - (object) A configuration object.
7356        
7357        Example:
7358        
7359        (start code js)
7360        var viz = new $jit.Viz(options);
7361        var node = viz.graph.getNode('nodeId');
7362        viz.labels.plotLabel(viz.canvas, node, viz.config);
7363        (end code)
7364     */
7365     plotLabel: function(canvas, node, controller) {
7366       var ctx = canvas.getCtx();
7367       var pos = node.pos.getc(true);
7368
7369       ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7370       ctx.textAlign = node.getLabelData('textAlign');
7371       ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7372       ctx.textBaseline = node.getLabelData('textBaseline');
7373
7374       this.renderLabel(canvas, node, controller);
7375     },
7376
7377     /*
7378        renderLabel
7379
7380        Does the actual rendering of the label in the canvas. The default
7381        implementation renders the label close to the position of the node, this
7382        method should be overriden to position the labels differently.
7383
7384        Parameters:
7385
7386        canvas - A <Canvas> instance.
7387        node - A <Graph.Node>.
7388        controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7389     */
7390     renderLabel: function(canvas, node, controller) {
7391       var ctx = canvas.getCtx();
7392       var pos = node.pos.getc(true);
7393       ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7394     },
7395
7396     hideLabel: $.empty,
7397     hideLabels: $.empty
7398 });
7399
7400 /*
7401    Class: Graph.Label.DOM
7402
7403    Abstract Class implementing some DOM label methods.
7404
7405    Implemented by:
7406
7407    <Graph.Label.HTML> and <Graph.Label.SVG>.
7408
7409 */
7410 Graph.Label.DOM = new Class({
7411     //A flag value indicating if node labels are being displayed or not.
7412     labelsHidden: false,
7413     //Label container
7414     labelContainer: false,
7415     //Label elements hash.
7416     labels: {},
7417
7418     /*
7419        Method: getLabelContainer
7420
7421        Lazy fetcher for the label container.
7422
7423        Returns:
7424
7425        The label container DOM element.
7426
7427        Example:
7428
7429       (start code js)
7430         var viz = new $jit.Viz(options);
7431         var labelContainer = viz.labels.getLabelContainer();
7432         alert(labelContainer.innerHTML);
7433       (end code)
7434     */
7435     getLabelContainer: function() {
7436       return this.labelContainer ?
7437         this.labelContainer :
7438         this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7439     },
7440
7441     /*
7442        Method: getLabel
7443
7444        Lazy fetcher for the label element.
7445
7446        Parameters:
7447
7448        id - (string) The label id (which is also a <Graph.Node> id).
7449
7450        Returns:
7451
7452        The label element.
7453
7454        Example:
7455
7456       (start code js)
7457         var viz = new $jit.Viz(options);
7458         var label = viz.labels.getLabel('someid');
7459         alert(label.innerHTML);
7460       (end code)
7461
7462     */
7463     getLabel: function(id) {
7464       return (id in this.labels && this.labels[id] != null) ?
7465         this.labels[id] :
7466         this.labels[id] = document.getElementById(id);
7467     },
7468
7469     /*
7470        Method: hideLabels
7471
7472        Hides all labels (by hiding the label container).
7473
7474        Parameters:
7475
7476        hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7477
7478        Example:
7479        (start code js)
7480         var viz = new $jit.Viz(options);
7481         rg.labels.hideLabels(true);
7482        (end code)
7483
7484     */
7485     hideLabels: function (hide) {
7486       var container = this.getLabelContainer();
7487       if(hide)
7488         container.style.display = 'none';
7489       else
7490         container.style.display = '';
7491       this.labelsHidden = hide;
7492     },
7493
7494     /*
7495        Method: clearLabels
7496
7497        Clears the label container.
7498
7499        Useful when using a new visualization with the same canvas element/widget.
7500
7501        Parameters:
7502
7503        force - (boolean) Forces deletion of all labels.
7504
7505        Example:
7506        (start code js)
7507         var viz = new $jit.Viz(options);
7508         viz.labels.clearLabels();
7509         (end code)
7510     */
7511     clearLabels: function(force) {
7512       for(var id in this.labels) {
7513         if (force || !this.viz.graph.hasNode(id)) {
7514           this.disposeLabel(id);
7515           delete this.labels[id];
7516         }
7517       }
7518     },
7519
7520     /*
7521        Method: disposeLabel
7522
7523        Removes a label.
7524
7525        Parameters:
7526
7527        id - (string) A label id (which generally is also a <Graph.Node> id).
7528
7529        Example:
7530        (start code js)
7531         var viz = new $jit.Viz(options);
7532         viz.labels.disposeLabel('labelid');
7533        (end code)
7534     */
7535     disposeLabel: function(id) {
7536       var elem = this.getLabel(id);
7537       if(elem && elem.parentNode) {
7538         elem.parentNode.removeChild(elem);
7539       }
7540     },
7541
7542     /*
7543        Method: hideLabel
7544
7545        Hides the corresponding <Graph.Node> label.
7546
7547        Parameters:
7548
7549        node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7550        show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7551
7552        Example:
7553        (start code js)
7554         var rg = new $jit.Viz(options);
7555         viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7556        (end code)
7557     */
7558     hideLabel: function(node, show) {
7559       node = $.splat(node);
7560       var st = show ? "" : "none", lab, that = this;
7561       $.each(node, function(n) {
7562         var lab = that.getLabel(n.id);
7563         if (lab) {
7564           lab.style.display = st;
7565         }
7566       });
7567     },
7568
7569     /*
7570        fitsInCanvas
7571
7572        Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7573
7574        Parameters:
7575
7576        pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7577        canvas - A <Canvas> instance.
7578
7579        Returns:
7580
7581        A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7582
7583     */
7584     fitsInCanvas: function(pos, canvas) {
7585       var size = canvas.getSize();
7586       if(pos.x >= size.width || pos.x < 0
7587          || pos.y >= size.height || pos.y < 0) return false;
7588        return true;
7589     }
7590 });
7591
7592 /*
7593    Class: Graph.Label.HTML
7594
7595    Implements HTML labels.
7596
7597    Extends:
7598
7599    All <Graph.Label.DOM> methods.
7600
7601 */
7602 Graph.Label.HTML = new Class({
7603     Implements: Graph.Label.DOM,
7604
7605     /*
7606        Method: plotLabel
7607
7608        Plots a label for a given node.
7609
7610        Parameters:
7611
7612        canvas - (object) A <Canvas> instance.
7613        node - (object) A <Graph.Node>.
7614        controller - (object) A configuration object.
7615        
7616       Example:
7617        
7618        (start code js)
7619        var viz = new $jit.Viz(options);
7620        var node = viz.graph.getNode('nodeId');
7621        viz.labels.plotLabel(viz.canvas, node, viz.config);
7622        (end code)
7623
7624
7625     */
7626     plotLabel: function(canvas, node, controller) {
7627       var id = node.id, tag = this.getLabel(id);
7628
7629       if(!tag && !(tag = document.getElementById(id))) {
7630         tag = document.createElement('div');
7631         var container = this.getLabelContainer();
7632         tag.id = id;
7633         tag.className = 'node';
7634         tag.style.position = 'absolute';
7635         controller.onCreateLabel(tag, node);
7636         container.appendChild(tag);
7637         this.labels[node.id] = tag;
7638       }
7639
7640       this.placeLabel(tag, node, controller);
7641     }
7642 });
7643
7644 /*
7645    Class: Graph.Label.SVG
7646
7647    Implements SVG labels.
7648
7649    Extends:
7650
7651    All <Graph.Label.DOM> methods.
7652 */
7653 Graph.Label.SVG = new Class({
7654     Implements: Graph.Label.DOM,
7655
7656     /*
7657        Method: plotLabel
7658
7659        Plots a label for a given node.
7660
7661        Parameters:
7662
7663        canvas - (object) A <Canvas> instance.
7664        node - (object) A <Graph.Node>.
7665        controller - (object) A configuration object.
7666        
7667        Example:
7668        
7669        (start code js)
7670        var viz = new $jit.Viz(options);
7671        var node = viz.graph.getNode('nodeId');
7672        viz.labels.plotLabel(viz.canvas, node, viz.config);
7673        (end code)
7674
7675
7676     */
7677     plotLabel: function(canvas, node, controller) {
7678       var id = node.id, tag = this.getLabel(id);
7679       if(!tag && !(tag = document.getElementById(id))) {
7680         var ns = 'http://www.w3.org/2000/svg';
7681           tag = document.createElementNS(ns, 'svg:text');
7682         var tspan = document.createElementNS(ns, 'svg:tspan');
7683         tag.appendChild(tspan);
7684         var container = this.getLabelContainer();
7685         tag.setAttribute('id', id);
7686         tag.setAttribute('class', 'node');
7687         container.appendChild(tag);
7688         controller.onCreateLabel(tag, node);
7689         this.labels[node.id] = tag;
7690       }
7691       this.placeLabel(tag, node, controller);
7692     }
7693 });
7694
7695
7696
7697 Graph.Geom = new Class({
7698
7699   initialize: function(viz) {
7700     this.viz = viz;
7701     this.config = viz.config;
7702     this.node = viz.config.Node;
7703     this.edge = viz.config.Edge;
7704   },
7705   /*
7706     Applies a translation to the tree.
7707   
7708     Parameters:
7709   
7710     pos - A <Complex> number specifying translation vector.
7711     prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7712   
7713     Example:
7714   
7715     (start code js)
7716       st.geom.translate(new Complex(300, 100), 'end');
7717     (end code)
7718   */  
7719   translate: function(pos, prop) {
7720      prop = $.splat(prop);
7721      this.viz.graph.eachNode(function(elem) {
7722          $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7723      });
7724   },
7725   /*
7726     Hides levels of the tree until it properly fits in canvas.
7727   */  
7728   setRightLevelToShow: function(node, canvas, callback) {
7729      var level = this.getRightLevelToShow(node, canvas), 
7730          fx = this.viz.labels,
7731          opt = $.merge({
7732            execShow:true,
7733            execHide:true,
7734            onHide: $.empty,
7735            onShow: $.empty
7736          }, callback || {});
7737      node.eachLevel(0, this.config.levelsToShow, function(n) {
7738          var d = n._depth - node._depth;
7739          if(d > level) {
7740              opt.onHide(n);
7741              if(opt.execHide) {
7742                n.drawn = false; 
7743                n.exist = false;
7744                fx.hideLabel(n, false);
7745              }
7746          } else {
7747              opt.onShow(n);
7748              if(opt.execShow) {
7749                n.exist = true;
7750              }
7751          }
7752      });
7753      node.drawn= true;
7754   },
7755   /*
7756     Returns the right level to show for the current tree in order to fit in canvas.
7757   */  
7758   getRightLevelToShow: function(node, canvas) {
7759      var config = this.config;
7760      var level = config.levelsToShow;
7761      var constrained = config.constrained;
7762      if(!constrained) return level;
7763      while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7764      return level;
7765   }
7766 });
7767
7768 /*
7769  * File: Loader.js
7770  * 
7771  */
7772
7773 /*
7774    Object: Loader
7775
7776    Provides methods for loading and serving JSON data.
7777 */
7778 var Loader = {
7779      construct: function(json) {
7780         var isGraph = ($.type(json) == 'array');
7781         var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7782         if(!isGraph) 
7783             //make tree
7784             (function (ans, json) {
7785                 ans.addNode(json);
7786                 if(json.children) {
7787                   for(var i=0, ch = json.children; i<ch.length; i++) {
7788                     ans.addAdjacence(json, ch[i]);
7789                     arguments.callee(ans, ch[i]);
7790                   }
7791                 }
7792             })(ans, json);
7793         else
7794             //make graph
7795             (function (ans, json) {
7796                 var getNode = function(id) {
7797                   for(var i=0, l=json.length; i<l; i++) {
7798                     if(json[i].id == id) {
7799                       return json[i];
7800                     }
7801                   }
7802                   // The node was not defined in the JSON
7803                   // Let's create it
7804                   var newNode = {
7805                                 "id" : id,
7806                                 "name" : id
7807                         };
7808                   return ans.addNode(newNode);
7809                 };
7810
7811                 for(var i=0, l=json.length; i<l; i++) {
7812                   ans.addNode(json[i]);
7813                   var adj = json[i].adjacencies;
7814                   if (adj) {
7815                     for(var j=0, lj=adj.length; j<lj; j++) {
7816                       var node = adj[j], data = {};
7817                       if(typeof adj[j] != 'string') {
7818                         data = $.merge(node.data, {});
7819                         node = node.nodeTo;
7820                       }
7821                       ans.addAdjacence(json[i], getNode(node), data);
7822                     }
7823                   }
7824                 }
7825             })(ans, json);
7826
7827         return ans;
7828     },
7829
7830     /*
7831      Method: loadJSON
7832     
7833      Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7834      
7835       A JSON tree or graph structure consists of nodes, each having as properties
7836        
7837        id - (string) A unique identifier for the node
7838        name - (string) A node's name
7839        data - (object) The data optional property contains a hash (i.e {}) 
7840        where you can store all the information you want about this node.
7841         
7842       For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7843       
7844       Example:
7845
7846       (start code js)
7847         var json = {  
7848           "id": "aUniqueIdentifier",  
7849           "name": "usually a nodes name",  
7850           "data": {
7851             "some key": "some value",
7852             "some other key": "some other value"
7853            },  
7854           "children": [ *other nodes or empty* ]  
7855         };  
7856       (end code)
7857         
7858         JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected. 
7859         For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7860         
7861         There are two types of *Graph* structures, *simple* and *extended* graph structures.
7862         
7863         For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the 
7864         id of the node connected to the main node.
7865         
7866         Example:
7867         
7868         (start code js)
7869         var json = [  
7870           {  
7871             "id": "aUniqueIdentifier",  
7872             "name": "usually a nodes name",  
7873             "data": {
7874               "some key": "some value",
7875               "some other key": "some other value"
7876              },  
7877             "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']  
7878           },
7879
7880           'other nodes go here...' 
7881         ];          
7882         (end code)
7883         
7884         For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7885         
7886         nodeTo - (string) The other node connected by this adjacency.
7887         data - (object) A data property, where we can store custom key/value information.
7888         
7889         Example:
7890         
7891         (start code js)
7892         var json = [  
7893           {  
7894             "id": "aUniqueIdentifier",  
7895             "name": "usually a nodes name",  
7896             "data": {
7897               "some key": "some value",
7898               "some other key": "some other value"
7899              },  
7900             "adjacencies": [  
7901             {  
7902               nodeTo:"aNodeId",  
7903               data: {} //put whatever you want here  
7904             },
7905             'other adjacencies go here...'  
7906           },
7907
7908           'other nodes go here...' 
7909         ];          
7910         (end code)
7911        
7912        About the data property:
7913        
7914        As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*. 
7915        You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and 
7916        have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7917        
7918        For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in 
7919        <Options.Node> will override the general value for that option with that particular value. For this to work 
7920        however, you do have to set *overridable = true* in <Options.Node>.
7921        
7922        The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge> 
7923        if <Options.Edge> has *overridable = true*.
7924        
7925        When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key, 
7926        since this is the value which will be taken into account when creating the layout. 
7927        The same thing goes for the *$color* parameter.
7928        
7929        In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example, 
7930        *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set 
7931        canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer 
7932        to the *shadowBlur* property.
7933        
7934        These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences> 
7935        by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7936        
7937        Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more 
7938        information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7939        
7940        loadJSON Parameters:
7941     
7942         json - A JSON Tree or Graph structure.
7943         i - For Graph structures only. Sets the indexed node as root for the visualization.
7944
7945     */
7946     loadJSON: function(json, i) {
7947       this.json = json;
7948       //if they're canvas labels erase them.
7949       if(this.labels && this.labels.clearLabels) {
7950         this.labels.clearLabels(true);
7951       }
7952       this.graph = this.construct(json);
7953       if($.type(json) != 'array'){
7954         this.root = json.id;
7955       } else {
7956         this.root = json[i? i : 0].id;
7957       }
7958     },
7959     
7960     /*
7961       Method: toJSON
7962    
7963       Returns a JSON tree/graph structure from the visualization's <Graph>. 
7964       See <Loader.loadJSON> for the graph formats available.
7965       
7966       See also:
7967       
7968       <Loader.loadJSON>
7969       
7970       Parameters:
7971       
7972       type - (string) Default's "tree". The type of the JSON structure to be returned. 
7973       Possible options are "tree" or "graph".
7974     */    
7975     toJSON: function(type) {
7976       type = type || "tree";
7977       if(type == 'tree') {
7978         var ans = {};
7979         var rootNode = this.graph.getNode(this.root);
7980         var ans = (function recTree(node) {
7981           var ans = {};
7982           ans.id = node.id;
7983           ans.name = node.name;
7984           ans.data = node.data;
7985           var ch =[];
7986           node.eachSubnode(function(n) {
7987             ch.push(recTree(n));
7988           });
7989           ans.children = ch;
7990           return ans;
7991         })(rootNode);
7992         return ans;
7993       } else {
7994         var ans = [];
7995         var T = !!this.graph.getNode(this.root).visited;
7996         this.graph.eachNode(function(node) {
7997           var ansNode = {};
7998           ansNode.id = node.id;
7999           ansNode.name = node.name;
8000           ansNode.data = node.data;
8001           var adjs = [];
8002           node.eachAdjacency(function(adj) {
8003             var nodeTo = adj.nodeTo;
8004             if(!!nodeTo.visited === T) {
8005               var ansAdj = {};
8006               ansAdj.nodeTo = nodeTo.id;
8007               ansAdj.data = adj.data;
8008               adjs.push(ansAdj);
8009             }
8010           });
8011           ansNode.adjacencies = adjs;
8012           ans.push(ansNode);
8013           node.visited = !T;
8014         });
8015         return ans;
8016       }
8017     }
8018 };
8019
8020
8021
8022 /*
8023  * File: Layouts.js
8024  * 
8025  * Implements base Tree and Graph layouts.
8026  *
8027  * Description:
8028  *
8029  * Implements base Tree and Graph layouts like Radial, Tree, etc.
8030  * 
8031  */
8032
8033 /*
8034  * Object: Layouts
8035  * 
8036  * Parent object for common layouts.
8037  *
8038  */
8039 var Layouts = $jit.Layouts = {};
8040
8041
8042 //Some util shared layout functions are defined here.
8043 var NodeDim = {
8044   label: null,
8045   
8046   compute: function(graph, prop, opt) {
8047     this.initializeLabel(opt);
8048     var label = this.label, style = label.style;
8049     graph.eachNode(function(n) {
8050       var autoWidth  = n.getData('autoWidth'),
8051           autoHeight = n.getData('autoHeight');
8052       if(autoWidth || autoHeight) {
8053         //delete dimensions since these are
8054         //going to be overridden now.
8055         delete n.data.$width;
8056         delete n.data.$height;
8057         delete n.data.$dim;
8058         
8059         var width  = n.getData('width'),
8060             height = n.getData('height');
8061         //reset label dimensions
8062         style.width  = autoWidth? 'auto' : width + 'px';
8063         style.height = autoHeight? 'auto' : height + 'px';
8064         
8065         //TODO(nico) should let the user choose what to insert here.
8066         label.innerHTML = n.name;
8067         
8068         var offsetWidth  = label.offsetWidth,
8069             offsetHeight = label.offsetHeight;
8070         var type = n.getData('type');
8071         if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8072           n.setData('width', offsetWidth);
8073           n.setData('height', offsetHeight);
8074         } else {
8075           var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8076           n.setData('width', dim);
8077           n.setData('height', dim);
8078           n.setData('dim', dim); 
8079         }
8080       }
8081     });
8082   },
8083   
8084   initializeLabel: function(opt) {
8085     if(!this.label) {
8086       this.label = document.createElement('div');
8087       document.body.appendChild(this.label);
8088     }
8089     this.setLabelStyles(opt);
8090   },
8091   
8092   setLabelStyles: function(opt) {
8093     $.extend(this.label.style, {
8094       'visibility': 'hidden',
8095       'position': 'absolute',
8096       'width': 'auto',
8097       'height': 'auto'
8098     });
8099     this.label.className = 'jit-autoadjust-label';
8100   }
8101 };
8102
8103
8104 /*
8105  * Class: Layouts.Tree
8106  * 
8107  * Implements a Tree Layout.
8108  * 
8109  * Implemented By:
8110  * 
8111  * <ST>
8112  * 
8113  * Inspired by:
8114  * 
8115  * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8116  * 
8117  */
8118 Layouts.Tree = (function() {
8119   //Layout functions
8120   var slice = Array.prototype.slice;
8121
8122   /*
8123      Calculates the max width and height nodes for a tree level
8124   */  
8125   function getBoundaries(graph, config, level, orn, prop) {
8126     var dim = config.Node;
8127     var multitree = config.multitree;
8128     if (dim.overridable) {
8129       var w = -1, h = -1;
8130       graph.eachNode(function(n) {
8131         if (n._depth == level
8132             && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8133           var dw = n.getData('width', prop);
8134           var dh = n.getData('height', prop);
8135           w = (w < dw) ? dw : w;
8136           h = (h < dh) ? dh : h;
8137         }
8138       });
8139       return {
8140         'width' : w < 0 ? dim.width : w,
8141         'height' : h < 0 ? dim.height : h
8142       };
8143     } else {
8144       return dim;
8145     }
8146   }
8147
8148
8149   function movetree(node, prop, val, orn) {
8150     var p = (orn == "left" || orn == "right") ? "y" : "x";
8151     node.getPos(prop)[p] += val;
8152   }
8153
8154
8155   function moveextent(extent, val) {
8156     var ans = [];
8157     $.each(extent, function(elem) {
8158       elem = slice.call(elem);
8159       elem[0] += val;
8160       elem[1] += val;
8161       ans.push(elem);
8162     });
8163     return ans;
8164   }
8165
8166
8167   function merge(ps, qs) {
8168     if (ps.length == 0)
8169       return qs;
8170     if (qs.length == 0)
8171       return ps;
8172     var p = ps.shift(), q = qs.shift();
8173     return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8174   }
8175
8176
8177   function mergelist(ls, def) {
8178     def = def || [];
8179     if (ls.length == 0)
8180       return def;
8181     var ps = ls.pop();
8182     return mergelist(ls, merge(ps, def));
8183   }
8184
8185
8186   function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8187     if (ext1.length <= i || ext2.length <= i)
8188       return 0;
8189
8190     var p = ext1[i][1], q = ext2[i][0];
8191     return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8192         + subtreeOffset, p - q + siblingOffset);
8193   }
8194
8195
8196   function fitlistl(es, subtreeOffset, siblingOffset) {
8197     function $fitlistl(acc, es, i) {
8198       if (es.length <= i)
8199         return [];
8200       var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8201       return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8202     }
8203     ;
8204     return $fitlistl( [], es, 0);
8205   }
8206
8207
8208   function fitlistr(es, subtreeOffset, siblingOffset) {
8209     function $fitlistr(acc, es, i) {
8210       if (es.length <= i)
8211         return [];
8212       var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8213       return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8214     }
8215     ;
8216     es = slice.call(es);
8217     var ans = $fitlistr( [], es.reverse(), 0);
8218     return ans.reverse();
8219   }
8220
8221
8222   function fitlist(es, subtreeOffset, siblingOffset, align) {
8223     var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8224         subtreeOffset, siblingOffset);
8225
8226     if (align == "left")
8227       esr = esl;
8228     else if (align == "right")
8229       esl = esr;
8230
8231     for ( var i = 0, ans = []; i < esl.length; i++) {
8232       ans[i] = (esl[i] + esr[i]) / 2;
8233     }
8234     return ans;
8235   }
8236
8237
8238   function design(graph, node, prop, config, orn) {
8239     var multitree = config.multitree;
8240     var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8241     var ind = +(orn == "left" || orn == "right");
8242     var p = auxp[ind], notp = auxp[1 - ind];
8243
8244     var cnode = config.Node;
8245     var s = auxs[ind], nots = auxs[1 - ind];
8246
8247     var siblingOffset = config.siblingOffset;
8248     var subtreeOffset = config.subtreeOffset;
8249     var align = config.align;
8250
8251     function $design(node, maxsize, acum) {
8252       var sval = node.getData(s, prop);
8253       var notsval = maxsize
8254           || (node.getData(nots, prop));
8255
8256       var trees = [], extents = [], chmaxsize = false;
8257       var chacum = notsval + config.levelDistance;
8258       node.eachSubnode(function(n) {
8259             if (n.exist
8260                 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8261
8262               if (!chmaxsize)
8263                 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8264
8265               var s = $design(n, chmaxsize[nots], acum + chacum);
8266               trees.push(s.tree);
8267               extents.push(s.extent);
8268             }
8269           });
8270       var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8271       for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8272         movetree(trees[i], prop, positions[i], orn);
8273         pextents.push(moveextent(extents[i], positions[i]));
8274       }
8275       var resultextent = [ [ -sval / 2, sval / 2 ] ]
8276           .concat(mergelist(pextents));
8277       node.getPos(prop)[p] = 0;
8278
8279       if (orn == "top" || orn == "left") {
8280         node.getPos(prop)[notp] = acum;
8281       } else {
8282         node.getPos(prop)[notp] = -acum;
8283       }
8284
8285       return {
8286         tree : node,
8287         extent : resultextent
8288       };
8289     }
8290
8291     $design(node, false, 0);
8292   }
8293
8294
8295   return new Class({
8296     /*
8297     Method: compute
8298     
8299     Computes nodes' positions.
8300
8301      */
8302     compute : function(property, computeLevels) {
8303       var prop = property || 'start';
8304       var node = this.graph.getNode(this.root);
8305       $.extend(node, {
8306         'drawn' : true,
8307         'exist' : true,
8308         'selected' : true
8309       });
8310       NodeDim.compute(this.graph, prop, this.config);
8311       if (!!computeLevels || !("_depth" in node)) {
8312         this.graph.computeLevels(this.root, 0, "ignore");
8313       }
8314       
8315       this.computePositions(node, prop);
8316     },
8317
8318     computePositions : function(node, prop) {
8319       var config = this.config;
8320       var multitree = config.multitree;
8321       var align = config.align;
8322       var indent = align !== 'center' && config.indent;
8323       var orn = config.orientation;
8324       var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8325       var that = this;
8326       $.each(orns, function(orn) {
8327         //calculate layout
8328           design(that.graph, node, prop, that.config, orn, prop);
8329           var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8330           //absolutize
8331           (function red(node) {
8332             node.eachSubnode(function(n) {
8333               if (n.exist
8334                   && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8335
8336                 n.getPos(prop)[i] += node.getPos(prop)[i];
8337                 if (indent) {
8338                   n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8339                 }
8340                 red(n);
8341               }
8342             });
8343           })(node);
8344         });
8345     }
8346   });
8347   
8348 })();
8349
8350 /*
8351  * File: Spacetree.js
8352  */
8353
8354 /*
8355    Class: ST
8356    
8357   A Tree layout with advanced contraction and expansion animations.
8358      
8359   Inspired by:
8360  
8361   SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson) 
8362   <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8363   
8364   Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8365   
8366   Note:
8367  
8368   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.
8369  
8370   Implements:
8371   
8372   All <Loader> methods
8373   
8374   Constructor Options:
8375   
8376   Inherits options from
8377   
8378   - <Options.Canvas>
8379   - <Options.Controller>
8380   - <Options.Tree>
8381   - <Options.Node>
8382   - <Options.Edge>
8383   - <Options.Label>
8384   - <Options.Events>
8385   - <Options.Tips>
8386   - <Options.NodeStyles>
8387   - <Options.Navigation>
8388   
8389   Additionally, there are other parameters and some default values changed
8390   
8391   constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8392   levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8393   levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8394   Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8395   offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8396   offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8397   duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8398   
8399   Instance Properties:
8400   
8401   canvas - Access a <Canvas> instance.
8402   graph - Access a <Graph> instance.
8403   op - Access a <ST.Op> instance.
8404   fx - Access a <ST.Plot> instance.
8405   labels - Access a <ST.Label> interface implementation.
8406
8407  */
8408
8409 $jit.ST= (function() {
8410     // Define some private methods first...
8411     // Nodes in path
8412     var nodesInPath = [];
8413     // Nodes to contract
8414     function getNodesToHide(node) {
8415       node = node || this.clickedNode;
8416       if(!this.config.constrained) {
8417         return [];
8418       }
8419       var Geom = this.geom;
8420       var graph = this.graph;
8421       var canvas = this.canvas;
8422       var level = node._depth, nodeArray = [];
8423           graph.eachNode(function(n) {
8424           if(n.exist && !n.selected) {
8425               if(n.isDescendantOf(node.id)) {
8426                 if(n._depth <= level) nodeArray.push(n);
8427               } else {
8428                 nodeArray.push(n);
8429               }
8430           }
8431           });
8432           var leafLevel = Geom.getRightLevelToShow(node, canvas);
8433           node.eachLevel(leafLevel, leafLevel, function(n) {
8434           if(n.exist && !n.selected) nodeArray.push(n);
8435           });
8436             
8437           for (var i = 0; i < nodesInPath.length; i++) {
8438             var n = this.graph.getNode(nodesInPath[i]);
8439             if(!n.isDescendantOf(node.id)) {
8440               nodeArray.push(n);
8441             }
8442           } 
8443           return nodeArray;       
8444     };
8445     // Nodes to expand
8446      function getNodesToShow(node) {
8447         var nodeArray = [], config = this.config;
8448         node = node || this.clickedNode;
8449         this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8450             if(config.multitree && !('$orn' in n.data) 
8451                         && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8452                 nodeArray.push(n);
8453             } else if(n.drawn && !n.anySubnode("drawn")) {
8454               nodeArray.push(n);
8455             }
8456         });
8457         return nodeArray;
8458      };
8459     // Now define the actual class.
8460     return new Class({
8461     
8462         Implements: [Loader, Extras, Layouts.Tree],
8463         
8464         initialize: function(controller) {            
8465           var $ST = $jit.ST;
8466           
8467           var config= {
8468                 levelsToShow: 2,
8469                 levelDistance: 30,
8470                 constrained: true,                
8471                 Node: {
8472                   type: 'rectangle'
8473                 },
8474                 duration: 700,
8475                 offsetX: 0,
8476                 offsetY: 0
8477             };
8478             
8479             this.controller = this.config = $.merge(
8480                 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller", 
8481                     "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8482
8483             var canvasConfig = this.config;
8484             if(canvasConfig.useCanvas) {
8485               this.canvas = canvasConfig.useCanvas;
8486               this.config.labelContainer = this.canvas.id + '-label';
8487             } else {
8488               if(canvasConfig.background) {
8489                 canvasConfig.background = $.merge({
8490                   type: 'Fade',
8491                   colorStop1: this.config.colorStop1,
8492                   colorStop2: this.config.colorStop2
8493                 }, canvasConfig.background);
8494               }
8495               this.canvas = new Canvas(this, canvasConfig);
8496               this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8497             }
8498
8499             this.graphOptions = {
8500                 'complex': true
8501             };
8502             this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8503             this.labels = new $ST.Label[canvasConfig.Label.type](this);
8504             this.fx = new $ST.Plot(this, $ST);
8505             this.op = new $ST.Op(this);
8506             this.group = new $ST.Group(this);
8507             this.geom = new $ST.Geom(this);
8508             this.clickedNode=  null;
8509             // initialize extras
8510             this.initializeExtras();
8511         },
8512     
8513         /*
8514          Method: plot
8515         
8516          Plots the <ST>. This is a shortcut to *fx.plot*.
8517
8518         */  
8519         plot: function() { this.fx.plot(this.controller); },
8520     
8521       
8522         /*
8523          Method: switchPosition
8524         
8525          Switches the tree orientation.
8526
8527          Parameters:
8528
8529         pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8530         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.
8531         onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8532
8533          Example:
8534
8535          (start code js)
8536            st.switchPosition("right", "animate", {
8537             onComplete: function() {
8538               alert('completed!');
8539             } 
8540            });
8541          (end code)
8542         */  
8543         switchPosition: function(pos, method, onComplete) {
8544           var Geom = this.geom, Plot = this.fx, that = this;
8545           if(!Plot.busy) {
8546               Plot.busy = true;
8547               this.contract({
8548                   onComplete: function() {
8549                       Geom.switchOrientation(pos);
8550                       that.compute('end', false);
8551                       Plot.busy = false;
8552                       if(method == 'animate') {
8553                           that.onClick(that.clickedNode.id, onComplete);  
8554                       } else if(method == 'replot') {
8555                           that.select(that.clickedNode.id, onComplete);
8556                       }
8557                   }
8558               }, pos);
8559           }
8560         },
8561
8562         /*
8563         Method: switchAlignment
8564        
8565         Switches the tree alignment.
8566
8567         Parameters:
8568
8569        align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8570        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.
8571        onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8572
8573         Example:
8574
8575         (start code js)
8576           st.switchAlignment("right", "animate", {
8577            onComplete: function() {
8578              alert('completed!');
8579            } 
8580           });
8581         (end code)
8582        */  
8583        switchAlignment: function(align, method, onComplete) {
8584         this.config.align = align;
8585         if(method == 'animate') {
8586                 this.select(this.clickedNode.id, onComplete);
8587         } else if(method == 'replot') {
8588                 this.onClick(this.clickedNode.id, onComplete);  
8589         }
8590        },
8591
8592        /*
8593         Method: addNodeInPath
8594        
8595         Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8596         
8597
8598         Parameters:
8599
8600        id - (string) A <Graph.Node> id.
8601
8602         Example:
8603
8604         (start code js)
8605           st.addNodeInPath("nodeId");
8606         (end code)
8607        */  
8608        addNodeInPath: function(id) {
8609            nodesInPath.push(id);
8610            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8611        },       
8612
8613        /*
8614        Method: clearNodesInPath
8615       
8616        Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8617        
8618        See also:
8619        
8620        <ST.addNodeInPath>
8621      
8622        Example:
8623
8624        (start code js)
8625          st.clearNodesInPath();
8626        (end code)
8627       */  
8628        clearNodesInPath: function(id) {
8629            nodesInPath.length = 0;
8630            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8631        },
8632         
8633        /*
8634          Method: refresh
8635         
8636          Computes positions and plots the tree.
8637          
8638        */
8639        refresh: function() {
8640            this.reposition();
8641            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8642        },    
8643
8644        reposition: function() {
8645             this.graph.computeLevels(this.root, 0, "ignore");
8646              this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8647             this.graph.eachNode(function(n) {
8648                 if(n.exist) n.drawn = true;
8649             });
8650             this.compute('end');
8651         },
8652         
8653         requestNodes: function(node, onComplete) {
8654           var handler = $.merge(this.controller, onComplete), 
8655           lev = this.config.levelsToShow;
8656           if(handler.request) {
8657               var leaves = [], d = node._depth;
8658               node.eachLevel(0, lev, function(n) {
8659                   if(n.drawn && 
8660                    !n.anySubnode()) {
8661                    leaves.push(n);
8662                    n._level = lev - (n._depth - d);
8663                   }
8664               });
8665               this.group.requestNodes(leaves, handler);
8666           }
8667             else
8668               handler.onComplete();
8669         },
8670      
8671         contract: function(onComplete, switched) {
8672           var orn  = this.config.orientation;
8673           var Geom = this.geom, Group = this.group;
8674           if(switched) Geom.switchOrientation(switched);
8675           var nodes = getNodesToHide.call(this);
8676           if(switched) Geom.switchOrientation(orn);
8677           Group.contract(nodes, $.merge(this.controller, onComplete));
8678         },
8679       
8680          move: function(node, onComplete) {
8681             this.compute('end', false);
8682             var move = onComplete.Move, offset = {
8683                 'x': move.offsetX,
8684                 'y': move.offsetY 
8685             };
8686             if(move.enable) {
8687                 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8688             }
8689             this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8690          },
8691       
8692         expand: function (node, onComplete) {
8693             var nodeArray = getNodesToShow.call(this, node);
8694             this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8695         },
8696     
8697         selectPath: function(node) {
8698           var that = this;
8699           this.graph.eachNode(function(n) { n.selected = false; }); 
8700           function path(node) {
8701               if(node == null || node.selected) return;
8702               node.selected = true;
8703               $.each(that.group.getSiblings([node])[node.id], 
8704               function(n) { 
8705                    n.exist = true; 
8706                    n.drawn = true; 
8707               });    
8708               var parents = node.getParents();
8709               parents = (parents.length > 0)? parents[0] : null;
8710               path(parents);
8711           };
8712           for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8713               path(this.graph.getNode(ns[i]));
8714           }
8715         },
8716       
8717         /*
8718         Method: setRoot
8719      
8720          Switches the current root node. Changes the topology of the Tree.
8721      
8722         Parameters:
8723            id - (string) The id of the node to be set as root.
8724            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.
8725            onComplete - (optional|object) An action to perform after the animation (if any).
8726  
8727         Example:
8728
8729         (start code js)
8730           st.setRoot('nodeId', 'animate', {
8731              onComplete: function() {
8732                alert('complete!');
8733              }
8734           });
8735         (end code)
8736      */
8737      setRoot: function(id, method, onComplete) {
8738                 if(this.busy) return;
8739                 this.busy = true;
8740           var that = this, canvas = this.canvas;
8741                 var rootNode = this.graph.getNode(this.root);
8742                 var clickedNode = this.graph.getNode(id);
8743                 function $setRoot() {
8744                 if(this.config.multitree && clickedNode.data.$orn) {
8745                         var orn = clickedNode.data.$orn;
8746                         var opp = {
8747                                         'left': 'right',
8748                                         'right': 'left',
8749                                         'top': 'bottom',
8750                                         'bottom': 'top'
8751                         }[orn];
8752                         rootNode.data.$orn = opp;
8753                         (function tag(rootNode) {
8754                                 rootNode.eachSubnode(function(n) {
8755                                         if(n.id != id) {
8756                                                 n.data.$orn = opp;
8757                                                 tag(n);
8758                                         }
8759                                 });
8760                         })(rootNode);
8761                         delete clickedNode.data.$orn;
8762                 }
8763                 this.root = id;
8764                 this.clickedNode = clickedNode;
8765                 this.graph.computeLevels(this.root, 0, "ignore");
8766                 this.geom.setRightLevelToShow(clickedNode, canvas, {
8767                   execHide: false,
8768                   onShow: function(node) {
8769                     if(!node.drawn) {
8770                     node.drawn = true;
8771                     node.setData('alpha', 1, 'end');
8772                     node.setData('alpha', 0);
8773                     node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8774                     }
8775                   }
8776                 });
8777               this.compute('end');
8778               this.busy = true;
8779               this.fx.animate({
8780                 modes: ['linear', 'node-property:alpha'],
8781                 onComplete: function() {
8782                   that.busy = false;
8783                   that.onClick(id, {
8784                     onComplete: function() {
8785                       onComplete && onComplete.onComplete();
8786                     }
8787                   });
8788                 }
8789               });
8790                 }
8791
8792                 // delete previous orientations (if any)
8793                 delete rootNode.data.$orns;
8794
8795                 if(method == 'animate') {
8796                   $setRoot.call(this);
8797                   that.selectPath(clickedNode);
8798                 } else if(method == 'replot') {
8799                         $setRoot.call(this);
8800                         this.select(this.root);
8801                 }
8802      },
8803
8804      /*
8805            Method: addSubtree
8806         
8807             Adds a subtree.
8808         
8809            Parameters:
8810               subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8811               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.
8812               onComplete - (optional|object) An action to perform after the animation (if any).
8813     
8814            Example:
8815
8816            (start code js)
8817              st.addSubtree(json, 'animate', {
8818                 onComplete: function() {
8819                   alert('complete!');
8820                 }
8821              });
8822            (end code)
8823         */
8824         addSubtree: function(subtree, method, onComplete) {
8825             if(method == 'replot') {
8826                 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8827             } else if (method == 'animate') {
8828                 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8829             }
8830         },
8831     
8832         /*
8833            Method: removeSubtree
8834         
8835             Removes a subtree.
8836         
8837            Parameters:
8838               id - (string) The _id_ of the subtree to be removed.
8839               removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8840               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.
8841               onComplete - (optional|object) An action to perform after the animation (if any).
8842
8843           Example:
8844
8845           (start code js)
8846             st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8847               onComplete: function() {
8848                 alert('complete!');
8849               }
8850             });
8851           (end code)
8852     
8853         */
8854         removeSubtree: function(id, removeRoot, method, onComplete) {
8855             var node = this.graph.getNode(id), subids = [];
8856             node.eachLevel(+!removeRoot, false, function(n) {
8857                 subids.push(n.id);
8858             });
8859             if(method == 'replot') {
8860                 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8861             } else if (method == 'animate') {
8862                 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8863             }
8864         },
8865     
8866         /*
8867            Method: select
8868         
8869             Selects a node in the <ST> without performing an animation. Useful when selecting 
8870             nodes which are currently hidden or deep inside the tree.
8871
8872           Parameters:
8873             id - (string) The id of the node to select.
8874             onComplete - (optional|object) an onComplete callback.
8875
8876           Example:
8877           (start code js)
8878             st.select('mynodeid', {
8879               onComplete: function() {
8880                 alert('complete!');
8881               }
8882             });
8883           (end code)
8884         */
8885         select: function(id, onComplete) {
8886             var group = this.group, geom = this.geom;
8887             var node=  this.graph.getNode(id), canvas = this.canvas;
8888             var root  = this.graph.getNode(this.root);
8889             var complete = $.merge(this.controller, onComplete);
8890             var that = this;
8891     
8892             complete.onBeforeCompute(node);
8893             this.selectPath(node);
8894             this.clickedNode= node;
8895             this.requestNodes(node, {
8896                 onComplete: function(){
8897                     group.hide(group.prepare(getNodesToHide.call(that)), complete);
8898                     geom.setRightLevelToShow(node, canvas);
8899                     that.compute("current");
8900                     that.graph.eachNode(function(n) { 
8901                         var pos = n.pos.getc(true);
8902                         n.startPos.setc(pos.x, pos.y);
8903                         n.endPos.setc(pos.x, pos.y);
8904                         n.visited = false; 
8905                     });
8906                     var offset = { x: complete.offsetX, y: complete.offsetY };
8907                     that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8908                     group.show(getNodesToShow.call(that));              
8909                     that.plot();
8910                     complete.onAfterCompute(that.clickedNode);
8911                     complete.onComplete();
8912                 }
8913             });     
8914         },
8915     
8916       /*
8917          Method: onClick
8918     
8919         Animates the <ST> to center the node specified by *id*.
8920             
8921         Parameters:
8922         
8923         id - (string) A node id.
8924         options - (optional|object) A group of options and callbacks described below.
8925         onComplete - (object) An object callback called when the animation finishes.
8926         Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8927
8928         Example:
8929
8930         (start code js)
8931           st.onClick('mynodeid', {
8932                   Move: {
8933                         enable: true,
8934                     offsetX: 30,
8935                     offsetY: 5
8936                   },
8937                   onComplete: function() {
8938                       alert('yay!');
8939                   }
8940           });
8941         (end code)
8942     
8943         */    
8944       onClick: function (id, options) {
8945         var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8946         var innerController = {
8947             Move: {
8948                     enable: true,
8949               offsetX: config.offsetX || 0,
8950               offsetY: config.offsetY || 0  
8951             },
8952             setRightLevelToShowConfig: false,
8953             onBeforeRequest: $.empty,
8954             onBeforeContract: $.empty,
8955             onBeforeMove: $.empty,
8956             onBeforeExpand: $.empty
8957         };
8958         var complete = $.merge(this.controller, innerController, options);
8959         
8960         if(!this.busy) {
8961             this.busy = true;
8962             var node = this.graph.getNode(id);
8963             this.selectPath(node, this.clickedNode);
8964                 this.clickedNode = node;
8965             complete.onBeforeCompute(node);
8966             complete.onBeforeRequest(node);
8967             this.requestNodes(node, {
8968                 onComplete: function() {
8969                     complete.onBeforeContract(node);
8970                     that.contract({
8971                         onComplete: function() {
8972                             Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8973                             complete.onBeforeMove(node);
8974                             that.move(node, {
8975                                 Move: complete.Move,
8976                                 onComplete: function() {
8977                                     complete.onBeforeExpand(node);
8978                                     that.expand(node, {
8979                                         onComplete: function() {
8980                                             that.busy = false;
8981                                             complete.onAfterCompute(id);
8982                                             complete.onComplete();
8983                                         }
8984                                     }); // expand
8985                                 }
8986                             }); // move
8987                         }
8988                     });// contract
8989                 }
8990             });// request
8991         }
8992       }
8993     });
8994
8995 })();
8996
8997 $jit.ST.$extend = true;
8998
8999 /*
9000    Class: ST.Op
9001     
9002    Custom extension of <Graph.Op>.
9003
9004    Extends:
9005
9006    All <Graph.Op> methods
9007    
9008    See also:
9009    
9010    <Graph.Op>
9011
9012 */
9013 $jit.ST.Op = new Class({
9014
9015   Implements: Graph.Op
9016     
9017 });
9018
9019 /*
9020     
9021      Performs operations on group of nodes.
9022
9023 */
9024 $jit.ST.Group = new Class({
9025     
9026     initialize: function(viz) {
9027         this.viz = viz;
9028         this.canvas = viz.canvas;
9029         this.config = viz.config;
9030         this.animation = new Animation;
9031         this.nodes = null;
9032     },
9033     
9034     /*
9035     
9036        Calls the request method on the controller to request a subtree for each node. 
9037     */
9038     requestNodes: function(nodes, controller) {
9039         var counter = 0, len = nodes.length, nodeSelected = {};
9040         var complete = function() { controller.onComplete(); };
9041         var viz = this.viz;
9042         if(len == 0) complete();
9043         for(var i=0; i<len; i++) {
9044             nodeSelected[nodes[i].id] = nodes[i];
9045             controller.request(nodes[i].id, nodes[i]._level, {
9046                 onComplete: function(nodeId, data) {
9047                     if(data && data.children) {
9048                         data.id = nodeId;
9049                         viz.op.sum(data, { type: 'nothing' });
9050                     }
9051                     if(++counter == len) {
9052                         viz.graph.computeLevels(viz.root, 0);
9053                         complete();
9054                     }
9055                 }
9056             });
9057         }
9058     },
9059     
9060     /*
9061     
9062        Collapses group of nodes. 
9063     */
9064     contract: function(nodes, controller) {
9065         var viz = this.viz;
9066         var that = this;
9067
9068         nodes = this.prepare(nodes);
9069         this.animation.setOptions($.merge(controller, {
9070             $animating: false,
9071             compute: function(delta) {
9072               if(delta == 1) delta = 0.99;
9073               that.plotStep(1 - delta, controller, this.$animating);
9074               this.$animating = 'contract';
9075             },
9076             
9077             complete: function() {
9078                 that.hide(nodes, controller);
9079             }       
9080         })).start();
9081     },
9082     
9083     hide: function(nodes, controller) {
9084         var viz = this.viz;
9085         for(var i=0; i<nodes.length; i++) {
9086             // TODO nodes are requested on demand, but not
9087             // deleted when hidden. Would that be a good feature?
9088             // Currently that feature is buggy, so I'll turn it off
9089             // Actually this feature is buggy because trimming should take
9090             // place onAfterCompute and not right after collapsing nodes.
9091             if (true || !controller || !controller.request) {
9092                 nodes[i].eachLevel(1, false, function(elem){
9093                     if (elem.exist) {
9094                         $.extend(elem, {
9095                             'drawn': false,
9096                             'exist': false
9097                         });
9098                     }
9099                 });
9100             } else {
9101                 var ids = [];
9102                 nodes[i].eachLevel(1, false, function(n) {
9103                     ids.push(n.id);
9104                 });
9105                 viz.op.removeNode(ids, { 'type': 'nothing' });
9106                 viz.labels.clearLabels();
9107             }
9108         }
9109         controller.onComplete();
9110     },    
9111     
9112
9113     /*
9114        Expands group of nodes. 
9115     */
9116     expand: function(nodes, controller) {
9117         var that = this;
9118         this.show(nodes);
9119         this.animation.setOptions($.merge(controller, {
9120             $animating: false,
9121             compute: function(delta) {
9122                 that.plotStep(delta, controller, this.$animating);
9123                 this.$animating = 'expand';
9124             },
9125             
9126             complete: function() {
9127                 that.plotStep(undefined, controller, false);
9128                 controller.onComplete();
9129             }       
9130         })).start();
9131         
9132     },
9133     
9134     show: function(nodes) {
9135         var config = this.config;
9136         this.prepare(nodes);
9137         $.each(nodes, function(n) {
9138                 // check for root nodes if multitree
9139                 if(config.multitree && !('$orn' in n.data)) {
9140                         delete n.data.$orns;
9141                         var orns = ' ';
9142                         n.eachSubnode(function(ch) {
9143                                 if(('$orn' in ch.data) 
9144                                                 && orns.indexOf(ch.data.$orn) < 0 
9145                                                 && ch.exist && !ch.drawn) {
9146                                         orns += ch.data.$orn + ' ';
9147                                 }
9148                         });
9149                         n.data.$orns = orns;
9150                 }
9151             n.eachLevel(0, config.levelsToShow, function(n) {
9152                 if(n.exist) n.drawn = true;
9153             });     
9154         });
9155     },
9156     
9157     prepare: function(nodes) {
9158         this.nodes = this.getNodesWithChildren(nodes);
9159         return this.nodes;
9160     },
9161     
9162     /*
9163        Filters an array of nodes leaving only nodes with children.
9164     */
9165     getNodesWithChildren: function(nodes) {
9166         var ans = [], config = this.config, root = this.viz.root;
9167         nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9168         for(var i=0; i<nodes.length; i++) {
9169             if(nodes[i].anySubnode("exist")) {
9170                 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9171                     if(!config.multitree || '$orn' in nodes[j].data) {
9172                                 desc = desc || nodes[i].isDescendantOf(nodes[j].id);                            
9173                     }
9174                 }
9175                 if(!desc) ans.push(nodes[i]);
9176             }
9177         }
9178         return ans;
9179     },
9180     
9181     plotStep: function(delta, controller, animating) {
9182         var viz = this.viz,
9183         config = this.config,
9184         canvas = viz.canvas, 
9185         ctx = canvas.getCtx(),
9186         nodes = this.nodes;
9187         var i, node;
9188         // hide nodes that are meant to be collapsed/expanded
9189         var nds = {};
9190         for(i=0; i<nodes.length; i++) {
9191           node = nodes[i];
9192           nds[node.id] = [];
9193           var root = config.multitree && !('$orn' in node.data);
9194           var orns = root && node.data.$orns;
9195           node.eachSubgraph(function(n) { 
9196             // TODO(nico): Cleanup
9197                   // special check for root node subnodes when
9198                   // multitree is checked.
9199                   if(root && orns && orns.indexOf(n.data.$orn) > 0 
9200                                   && n.drawn) {
9201                           n.drawn = false;
9202                   nds[node.id].push(n);
9203               } else if((!root || !orns) && n.drawn) {
9204                 n.drawn = false;
9205                 nds[node.id].push(n);
9206               }
9207             }); 
9208             node.drawn = true;
9209         }
9210         // plot the whole (non-scaled) tree
9211         if(nodes.length > 0) viz.fx.plot();
9212         // show nodes that were previously hidden
9213         for(i in nds) {
9214           $.each(nds[i], function(n) { n.drawn = true; });
9215         }
9216         // plot each scaled subtree
9217         for(i=0; i<nodes.length; i++) {
9218           node = nodes[i];
9219           ctx.save();
9220           viz.fx.plotSubtree(node, controller, delta, animating);                
9221           ctx.restore();
9222         }
9223       },
9224
9225       getSiblings: function(nodes) {
9226         var siblings = {};
9227         $.each(nodes, function(n) {
9228             var par = n.getParents();
9229             if (par.length == 0) {
9230                 siblings[n.id] = [n];
9231             } else {
9232                 var ans = [];
9233                 par[0].eachSubnode(function(sn) {
9234                     ans.push(sn);
9235                 });
9236                 siblings[n.id] = ans;
9237             }
9238         });
9239         return siblings;
9240     }
9241 });
9242
9243 /*
9244    ST.Geom
9245
9246    Performs low level geometrical computations.
9247
9248    Access:
9249
9250    This instance can be accessed with the _geom_ parameter of the st instance created.
9251
9252    Example:
9253
9254    (start code js)
9255     var st = new ST(canvas, config);
9256     st.geom.translate //or can also call any other <ST.Geom> method
9257    (end code)
9258
9259 */
9260
9261 $jit.ST.Geom = new Class({
9262     Implements: Graph.Geom,
9263     /*
9264        Changes the tree current orientation to the one specified.
9265
9266        You should usually use <ST.switchPosition> instead.
9267     */  
9268     switchOrientation: function(orn) {
9269         this.config.orientation = orn;
9270     },
9271
9272     /*
9273        Makes a value dispatch according to the current layout
9274        Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9275      */
9276     dispatch: function() {
9277           // TODO(nico) should store Array.prototype.slice.call somewhere.
9278         var args = Array.prototype.slice.call(arguments);
9279         var s = args.shift(), len = args.length;
9280         var val = function(a) { return typeof a == 'function'? a() : a; };
9281         if(len == 2) {
9282             return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9283         } else if(len == 4) {
9284             switch(s) {
9285                 case "top": return val(args[0]);
9286                 case "right": return val(args[1]);
9287                 case "bottom": return val(args[2]);
9288                 case "left": return val(args[3]);
9289             }
9290         }
9291         return undefined;
9292     },
9293
9294     /*
9295        Returns label height or with, depending on the tree current orientation.
9296     */  
9297     getSize: function(n, invert) {
9298         var data = n.data, config = this.config;
9299         var siblingOffset = config.siblingOffset;
9300         var s = (config.multitree 
9301                         && ('$orn' in data) 
9302                         && data.$orn) || config.orientation;
9303         var w = n.getData('width') + siblingOffset;
9304         var h = n.getData('height') + siblingOffset;
9305         if(!invert)
9306             return this.dispatch(s, h, w);
9307         else
9308             return this.dispatch(s, w, h);
9309     },
9310     
9311     /*
9312        Calculates a subtree base size. This is an utility function used by _getBaseSize_
9313     */  
9314     getTreeBaseSize: function(node, level, leaf) {
9315         var size = this.getSize(node, true), baseHeight = 0, that = this;
9316         if(leaf(level, node)) return size;
9317         if(level === 0) return 0;
9318         node.eachSubnode(function(elem) {
9319             baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9320         });
9321         return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9322     },
9323
9324
9325     /*
9326        getEdge
9327        
9328        Returns a Complex instance with the begin or end position of the edge to be plotted.
9329
9330        Parameters:
9331
9332        node - A <Graph.Node> that is connected to this edge.
9333        type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9334
9335        Returns:
9336
9337        A <Complex> number specifying the begin or end position.
9338     */  
9339     getEdge: function(node, type, s) {
9340         var $C = function(a, b) { 
9341           return function(){
9342             return node.pos.add(new Complex(a, b));
9343           }; 
9344         };
9345         var dim = this.node;
9346         var w = node.getData('width');
9347         var h = node.getData('height');
9348
9349         if(type == 'begin') {
9350             if(dim.align == "center") {
9351                 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9352                                      $C(0, -h/2),$C(w/2, 0));
9353             } else if(dim.align == "left") {
9354                 return this.dispatch(s, $C(0, h), $C(0, 0),
9355                                      $C(0, 0), $C(w, 0));
9356             } else if(dim.align == "right") {
9357                 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9358                                      $C(0, -h),$C(0, 0));
9359             } else throw "align: not implemented";
9360             
9361             
9362         } else if(type == 'end') {
9363             if(dim.align == "center") {
9364                 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9365                                      $C(0, h/2),  $C(-w/2, 0));
9366             } else if(dim.align == "left") {
9367                 return this.dispatch(s, $C(0, 0), $C(w, 0),
9368                                      $C(0, h), $C(0, 0));
9369             } else if(dim.align == "right") {
9370                 return this.dispatch(s, $C(0, -h),$C(0, 0),
9371                                      $C(0, 0), $C(-w, 0));
9372             } else throw "align: not implemented";
9373         }
9374     },
9375
9376     /*
9377        Adjusts the tree position due to canvas scaling or translation.
9378     */  
9379     getScaledTreePosition: function(node, scale) {
9380         var dim = this.node;
9381         var w = node.getData('width');
9382         var h = node.getData('height');
9383         var s = (this.config.multitree 
9384                         && ('$orn' in node.data) 
9385                         && node.data.$orn) || this.config.orientation;
9386
9387         var $C = function(a, b) { 
9388           return function(){
9389             return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9390           }; 
9391         };
9392         if(dim.align == "left") {
9393             return this.dispatch(s, $C(0, h), $C(0, 0),
9394                                  $C(0, 0), $C(w, 0));
9395         } else if(dim.align == "center") {
9396             return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9397                                  $C(0, -h / 2),$C(w / 2, 0));
9398         } else if(dim.align == "right") {
9399             return this.dispatch(s, $C(0, 0), $C(-w, 0),
9400                                  $C(0, -h),$C(0, 0));
9401         } else throw "align: not implemented";
9402     },
9403
9404     /*
9405        treeFitsInCanvas
9406        
9407        Returns a Boolean if the current subtree fits in canvas.
9408
9409        Parameters:
9410
9411        node - A <Graph.Node> which is the current root of the subtree.
9412        canvas - The <Canvas> object.
9413        level - The depth of the subtree to be considered.
9414     */  
9415     treeFitsInCanvas: function(node, canvas, level) {
9416         var csize = canvas.getSize();
9417         var s = (this.config.multitree 
9418                         && ('$orn' in node.data) 
9419                         && node.data.$orn) || this.config.orientation;
9420
9421         var size = this.dispatch(s, csize.width, csize.height);
9422         var baseSize = this.getTreeBaseSize(node, level, function(level, node) { 
9423           return level === 0 || !node.anySubnode();
9424         });
9425         return (baseSize < size);
9426     }
9427 });
9428
9429 /*
9430   Class: ST.Plot
9431   
9432   Custom extension of <Graph.Plot>.
9433
9434   Extends:
9435
9436   All <Graph.Plot> methods
9437   
9438   See also:
9439   
9440   <Graph.Plot>
9441
9442 */
9443 $jit.ST.Plot = new Class({
9444     
9445     Implements: Graph.Plot,
9446     
9447     /*
9448        Plots a subtree from the spacetree.
9449     */
9450     plotSubtree: function(node, opt, scale, animating) {
9451         var viz = this.viz, canvas = viz.canvas, config = viz.config;
9452         scale = Math.min(Math.max(0.001, scale), 1);
9453         if(scale >= 0) {
9454             node.drawn = false;     
9455             var ctx = canvas.getCtx();
9456             var diff = viz.geom.getScaledTreePosition(node, scale);
9457             ctx.translate(diff.x, diff.y);
9458             ctx.scale(scale, scale);
9459         }
9460         this.plotTree(node, $.merge(opt, {
9461           'withLabels': true,
9462           'hideLabels': !!scale,
9463           'plotSubtree': function(n, ch) {
9464             var root = config.multitree && !('$orn' in node.data);
9465             var orns = root && node.getData('orns');
9466             return !root || orns.indexOf(elem.getData('orn')) > -1;
9467           }
9468         }), animating);
9469         if(scale >= 0) node.drawn = true;
9470     },   
9471    
9472     /*
9473         Method: getAlignedPos
9474         
9475         Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9476         
9477         Parameters:
9478         
9479         pos - (object) A <Graph.Node> position.
9480         width - (number) The width of the node.
9481         height - (number) The height of the node.
9482         
9483      */
9484     getAlignedPos: function(pos, width, height) {
9485         var nconfig = this.node;
9486         var square, orn;
9487         if(nconfig.align == "center") {
9488             square = {
9489                 x: pos.x - width / 2,
9490                 y: pos.y - height / 2
9491             };
9492         } else if (nconfig.align == "left") {
9493             orn = this.config.orientation;
9494             if(orn == "bottom" || orn == "top") {
9495                 square = {
9496                     x: pos.x - width / 2,
9497                     y: pos.y
9498                 };
9499             } else {
9500                 square = {
9501                     x: pos.x,
9502                     y: pos.y - height / 2
9503                 };
9504             }
9505         } else if(nconfig.align == "right") {
9506             orn = this.config.orientation;
9507             if(orn == "bottom" || orn == "top") {
9508                 square = {
9509                     x: pos.x - width / 2,
9510                     y: pos.y - height
9511                 };
9512             } else {
9513                 square = {
9514                     x: pos.x - width,
9515                     y: pos.y - height / 2
9516                 };
9517             }
9518         } else throw "align: not implemented";
9519         
9520         return square;
9521     },
9522     
9523     getOrientation: function(adj) {
9524         var config = this.config;
9525         var orn = config.orientation;
9526
9527         if(config.multitree) {
9528                 var nodeFrom = adj.nodeFrom;
9529                 var nodeTo = adj.nodeTo;
9530                 orn = (('$orn' in nodeFrom.data) 
9531                         && nodeFrom.data.$orn) 
9532                         || (('$orn' in nodeTo.data) 
9533                         && nodeTo.data.$orn);
9534         }
9535
9536         return orn; 
9537     }
9538 });
9539
9540 /*
9541   Class: ST.Label
9542
9543   Custom extension of <Graph.Label>. 
9544   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9545
9546   Extends:
9547
9548   All <Graph.Label> methods and subclasses.
9549
9550   See also:
9551
9552   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9553  */ 
9554 $jit.ST.Label = {};
9555
9556 /*
9557    ST.Label.Native
9558
9559    Custom extension of <Graph.Label.Native>.
9560
9561    Extends:
9562
9563    All <Graph.Label.Native> methods
9564
9565    See also:
9566
9567    <Graph.Label.Native>
9568 */
9569 $jit.ST.Label.Native = new Class({
9570   Implements: Graph.Label.Native,
9571
9572   renderLabel: function(canvas, node, controller) {
9573     var ctx = canvas.getCtx();
9574     var coord = node.pos.getc(true);
9575     ctx.fillText(node.name, coord.x, coord.y);
9576   }
9577 });
9578
9579 $jit.ST.Label.DOM = new Class({
9580   Implements: Graph.Label.DOM,
9581
9582   /* 
9583       placeLabel
9584
9585       Overrides abstract method placeLabel in <Graph.Plot>.
9586
9587       Parameters:
9588
9589       tag - A DOM label element.
9590       node - A <Graph.Node>.
9591       controller - A configuration/controller object passed to the visualization.
9592      
9593     */
9594     placeLabel: function(tag, node, controller) {
9595         var pos = node.pos.getc(true), 
9596             config = this.viz.config, 
9597             dim = config.Node, 
9598             canvas = this.viz.canvas,
9599             w = node.getData('width'),
9600             h = node.getData('height'),
9601             radius = canvas.getSize(),
9602             labelPos, orn;
9603         
9604         var ox = canvas.translateOffsetX,
9605             oy = canvas.translateOffsetY,
9606             sx = canvas.scaleOffsetX,
9607             sy = canvas.scaleOffsetY,
9608             posx = pos.x * sx + ox,
9609             posy = pos.y * sy + oy;
9610
9611         if(dim.align == "center") {
9612             labelPos= {
9613                 x: Math.round(posx - w / 2 + radius.width/2),
9614                 y: Math.round(posy - h / 2 + radius.height/2)
9615             };
9616         } else if (dim.align == "left") {
9617             orn = config.orientation;
9618             if(orn == "bottom" || orn == "top") {
9619                 labelPos= {
9620                     x: Math.round(posx - w / 2 + radius.width/2),
9621                     y: Math.round(posy + radius.height/2)
9622                 };
9623             } else {
9624                 labelPos= {
9625                     x: Math.round(posx + radius.width/2),
9626                     y: Math.round(posy - h / 2 + radius.height/2)
9627                 };
9628             }
9629         } else if(dim.align == "right") {
9630             orn = config.orientation;
9631             if(orn == "bottom" || orn == "top") {
9632                 labelPos= {
9633                     x: Math.round(posx - w / 2 + radius.width/2),
9634                     y: Math.round(posy - h + radius.height/2)
9635                 };
9636             } else {
9637                 labelPos= {
9638                     x: Math.round(posx - w + radius.width/2),
9639                     y: Math.round(posy - h / 2 + radius.height/2)
9640                 };
9641             }
9642         } else throw "align: not implemented";
9643
9644         var style = tag.style;
9645         style.left = labelPos.x + 'px';
9646         style.top  = labelPos.y + 'px';
9647         style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9648         controller.onPlaceLabel(tag, node);
9649     }
9650 });
9651
9652 /*
9653   ST.Label.SVG
9654
9655   Custom extension of <Graph.Label.SVG>.
9656
9657   Extends:
9658
9659   All <Graph.Label.SVG> methods
9660
9661   See also:
9662
9663   <Graph.Label.SVG>
9664 */
9665 $jit.ST.Label.SVG = new Class({
9666   Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9667
9668   initialize: function(viz) {
9669     this.viz = viz;
9670   }
9671 });
9672
9673 /*
9674    ST.Label.HTML
9675
9676    Custom extension of <Graph.Label.HTML>.
9677
9678    Extends:
9679
9680    All <Graph.Label.HTML> methods.
9681
9682    See also:
9683
9684    <Graph.Label.HTML>
9685
9686 */
9687 $jit.ST.Label.HTML = new Class({
9688   Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9689
9690   initialize: function(viz) {
9691     this.viz = viz;
9692   }
9693 });
9694
9695
9696 /*
9697   Class: ST.Plot.NodeTypes
9698
9699   This class contains a list of <Graph.Node> built-in types. 
9700   Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9701
9702   You can add your custom node types, customizing your visualization to the extreme.
9703
9704   Example:
9705
9706   (start code js)
9707     ST.Plot.NodeTypes.implement({
9708       'mySpecialType': {
9709         'render': function(node, canvas) {
9710           //print your custom node to canvas
9711         },
9712         //optional
9713         'contains': function(node, pos) {
9714           //return true if pos is inside the node or false otherwise
9715         }
9716       }
9717     });
9718   (end code)
9719
9720 */
9721 $jit.ST.Plot.NodeTypes = new Class({
9722   'none': {
9723     'render': $.empty,
9724     'contains': $.lambda(false)
9725   },
9726   'circle': {
9727     'render': function(node, canvas) {
9728       var dim  = node.getData('dim'),
9729           pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9730           dim2 = dim/2;
9731       this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9732     },
9733     'contains': function(node, pos) {
9734       var dim  = node.getData('dim'),
9735           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9736           dim2 = dim/2;
9737       this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9738     }
9739   },
9740   'square': {
9741     'render': function(node, canvas) {
9742       var dim  = node.getData('dim'),
9743           dim2 = dim/2,
9744           pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9745       this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9746     },
9747     'contains': function(node, pos) {
9748       var dim  = node.getData('dim'),
9749           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9750           dim2 = dim/2;
9751       this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9752     }
9753   },
9754   'ellipse': {
9755     'render': function(node, canvas) {
9756       var width = node.getData('width'),
9757           height = node.getData('height'),
9758           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9759       this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9760     },
9761     'contains': function(node, pos) {
9762       var width = node.getData('width'),
9763           height = node.getData('height'),
9764           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9765       this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9766     }
9767   },
9768   'rectangle': {
9769     'render': function(node, canvas) {
9770       var width = node.getData('width'),
9771           height = node.getData('height'),
9772           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9773       this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9774     },
9775     'contains': function(node, pos) {
9776       var width = node.getData('width'),
9777           height = node.getData('height'),
9778           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9779       this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9780     }
9781   }
9782 });
9783
9784 /*
9785   Class: ST.Plot.EdgeTypes
9786
9787   This class contains a list of <Graph.Adjacence> built-in types. 
9788   Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9789
9790   You can add your custom edge types, customizing your visualization to the extreme.
9791
9792   Example:
9793
9794   (start code js)
9795     ST.Plot.EdgeTypes.implement({
9796       'mySpecialType': {
9797         'render': function(adj, canvas) {
9798           //print your custom edge to canvas
9799         },
9800         //optional
9801         'contains': function(adj, pos) {
9802           //return true if pos is inside the arc or false otherwise
9803         }
9804       }
9805     });
9806   (end code)
9807
9808 */
9809 $jit.ST.Plot.EdgeTypes = new Class({
9810     'none': $.empty,
9811     'line': {
9812       'render': function(adj, canvas) {
9813         var orn = this.getOrientation(adj),
9814             nodeFrom = adj.nodeFrom, 
9815             nodeTo = adj.nodeTo,
9816             rel = nodeFrom._depth < nodeTo._depth,
9817             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9818             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9819         this.edgeHelper.line.render(from, to, canvas);
9820       },
9821       'contains': function(adj, pos) {
9822         var orn = this.getOrientation(adj),
9823             nodeFrom = adj.nodeFrom, 
9824             nodeTo = adj.nodeTo,
9825             rel = nodeFrom._depth < nodeTo._depth,
9826             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9827             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9828         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
9829       }
9830     },
9831      'arrow': {
9832        'render': function(adj, canvas) {
9833          var orn = this.getOrientation(adj),
9834              node = adj.nodeFrom, 
9835              child = adj.nodeTo,
9836              dim = adj.getData('dim'),
9837              from = this.viz.geom.getEdge(node, 'begin', orn),
9838              to = this.viz.geom.getEdge(child, 'end', orn),
9839              direction = adj.data.$direction,
9840              inv = (direction && direction.length>1 && direction[0] != node.id);
9841          this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
9842        },
9843        'contains': function(adj, pos) {
9844          var orn = this.getOrientation(adj),
9845              nodeFrom = adj.nodeFrom, 
9846              nodeTo = adj.nodeTo,
9847              rel = nodeFrom._depth < nodeTo._depth,
9848              from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9849              to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
9850          return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
9851        }
9852      },
9853     'quadratic:begin': {
9854        'render': function(adj, canvas) {
9855           var orn = this.getOrientation(adj);
9856           var nodeFrom = adj.nodeFrom, 
9857               nodeTo = adj.nodeTo,
9858               rel = nodeFrom._depth < nodeTo._depth,
9859               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9860               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9861               dim = adj.getData('dim'),
9862               ctx = canvas.getCtx();
9863           ctx.beginPath();
9864           ctx.moveTo(begin.x, begin.y);
9865           switch(orn) {
9866             case "left":
9867               ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
9868               break;
9869             case "right":
9870               ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
9871               break;
9872             case "top":
9873               ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
9874               break;
9875             case "bottom":
9876               ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
9877               break;
9878           }
9879           ctx.stroke();
9880         }
9881      },
9882     'quadratic:end': {
9883        'render': function(adj, canvas) {
9884           var orn = this.getOrientation(adj);
9885           var nodeFrom = adj.nodeFrom, 
9886               nodeTo = adj.nodeTo,
9887               rel = nodeFrom._depth < nodeTo._depth,
9888               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9889               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9890               dim = adj.getData('dim'),
9891               ctx = canvas.getCtx();
9892           ctx.beginPath();
9893           ctx.moveTo(begin.x, begin.y);
9894           switch(orn) {
9895             case "left":
9896               ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
9897               break;
9898             case "right":
9899               ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
9900               break;
9901             case "top":
9902               ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
9903               break;
9904             case "bottom":
9905               ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
9906               break;
9907           }
9908           ctx.stroke();
9909        }
9910      },
9911     'bezier': {
9912        'render': function(adj, canvas) {
9913          var orn = this.getOrientation(adj),
9914              nodeFrom = adj.nodeFrom, 
9915              nodeTo = adj.nodeTo,
9916              rel = nodeFrom._depth < nodeTo._depth,
9917              begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
9918              end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
9919              dim = adj.getData('dim'),
9920              ctx = canvas.getCtx();
9921          ctx.beginPath();
9922          ctx.moveTo(begin.x, begin.y);
9923          switch(orn) {
9924            case "left":
9925              ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
9926              break;
9927            case "right":
9928              ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
9929              break;
9930            case "top":
9931              ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
9932              break;
9933            case "bottom":
9934              ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
9935              break;
9936          }
9937          ctx.stroke();
9938        }
9939     }
9940 });
9941
9942
9943 Options.LineChart = {
9944   $extend: true,
9945
9946   animate: false,
9947   labelOffset: 3, // label offset
9948   type: 'basic', // gradient
9949   dataPointSize: 10,
9950   Tips: {
9951     enable: false,
9952     onShow: $.empty,
9953     onHide: $.empty
9954   },
9955   Ticks: {
9956         enable: false,
9957         segments: 4,
9958         color: '#000000'
9959   },
9960   Events: {
9961     enable: false,
9962     onClick: $.empty
9963   },
9964   selectOnHover: true,
9965   showAggregates: true,
9966   showLabels: true,
9967   filterOnClick: false,
9968   restoreOnRightClick: false
9969 };
9970
9971
9972 /*
9973  * File: LineChart.js
9974  *
9975 */
9976
9977 $jit.ST.Plot.NodeTypes.implement({
9978   'linechart-basic' : {
9979     'render' : function(node, canvas) {
9980       var pos = node.pos.getc(true), 
9981           width = node.getData('width'),
9982           height = node.getData('height'),
9983           algnPos = this.getAlignedPos(pos, width, height),
9984           x = algnPos.x + width/2 , y = algnPos.y,
9985           stringArray = node.getData('stringArray'),
9986           lastNode = node.getData('lastNode'),
9987           dimArray = node.getData('dimArray'),
9988           valArray = node.getData('valueArray'),
9989           colorArray = node.getData('colorArray'),
9990           colorLength = colorArray.length,
9991           config = node.getData('config'),
9992           gradient = node.getData('gradient'),
9993           showLabels = config.showLabels,
9994           aggregates = config.showAggregates,
9995           label = config.Label,
9996           prev = node.getData('prev'),
9997           dataPointSize = config.dataPointSize;
9998
9999       var ctx = canvas.getCtx(), border = node.getData('border');
10000       if (colorArray && dimArray && stringArray) {
10001         
10002                for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10003                 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10004                         ctx.lineWidth = 4;
10005                         ctx.lineCap = "round";
10006                   if(!lastNode) {
10007
10008                           ctx.save();
10009                                   //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
10010                           ctx.beginPath();
10011                           ctx.moveTo(x, y  - dimArray[i][0]); 
10012                           ctx.lineTo(x + width, y - dimArray[i][1]);
10013                           ctx.stroke();
10014                           ctx.restore();
10015                   }
10016                   //render data point
10017                   ctx.fillRect(x - (dataPointSize/2), y  - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10018                 }
10019         
10020
10021           if(label.type == 'Native' && showLabels) {
10022           //bottom labels
10023           ctx.fillStyle = ctx.strokeStyle = label.color;
10024           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10025           ctx.textAlign = 'center';
10026           ctx.textBaseline = 'middle';
10027           ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10028           }
10029           
10030
10031       }
10032     },
10033     'contains': function(node, mpos) {
10034       var pos = node.pos.getc(true), 
10035           width = node.getData('width'),
10036           height = node.getData('height'),
10037           config = node.getData('config'),
10038           dataPointSize = config.dataPointSize,
10039           dataPointMidPoint = dataPointSize/2,
10040           algnPos = this.getAlignedPos(pos, width, height),
10041           x = algnPos.x + width/2, y = algnPos.y,
10042           dimArray = node.getData('dimArray');
10043       //bounding box check
10044       if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10045         return false;
10046       }
10047       //deep check
10048       for(var i=0, l=dimArray.length; i<l; i++) {
10049         var dimi = dimArray[i];
10050                 var url = Url.decode(node.getData('linkArray')[i]);
10051           if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10052                 var valArrayCur = node.getData('valArrayCur');
10053           var results = array_match(valArrayCur[i],valArrayCur);
10054           var matches = results[0];
10055           var indexValues = results[1];
10056           if(matches > 1) {
10057                         var names = new Array(),
10058                                 values = new Array(),
10059                                 percentages = new Array(),
10060                                 linksArr = new Array();
10061                                 for(var j=0, il=indexValues.length; j<il; j++) {
10062                                         names[j] = node.getData('stringArray')[indexValues[j]];
10063                                         values[j] = valArrayCur[indexValues[j]];
10064                                         percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10065                                         linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10066                                         
10067                                 }       
10068                         return {
10069                             'name': names,
10070                             'color': node.getData('colorArray')[i],
10071                             'value': values,
10072                             'percentage': percentages,
10073                             'link': false,
10074                             'collision': true
10075                         };
10076                 }
10077           else {
10078                   return {
10079                     'name': node.getData('stringArray')[i],
10080                     'color': node.getData('colorArray')[i],
10081                     'value': node.getData('valueArray')[i][0],
10082         //            'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10083                     'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10084                     'link': url,
10085                     'collision': false
10086                   };
10087           }
10088         }
10089       }
10090       return false;
10091     }
10092   }
10093 });
10094
10095 /*
10096   Class: Line
10097   
10098   A visualization that displays line charts.
10099   
10100   Constructor Options:
10101   
10102   See <Options.Line>.
10103
10104 */
10105 $jit.LineChart = new Class({
10106   st: null,
10107   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10108   selected: {},
10109   busy: false,
10110   
10111   initialize: function(opt) {
10112     this.controller = this.config = 
10113       $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10114         Label: { type: 'Native' }
10115       }, opt);
10116     //set functions for showLabels and showAggregates
10117     var showLabels = this.config.showLabels,
10118         typeLabels = $.type(showLabels),
10119         showAggregates = this.config.showAggregates,
10120         typeAggregates = $.type(showAggregates);
10121     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10122     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10123     Options.Fx.clearCanvas = false;
10124     this.initializeViz();
10125   },
10126   
10127   initializeViz: function() {
10128     var config = this.config,
10129         that = this,
10130         nodeType = config.type.split(":")[0],
10131         nodeLabels = {};
10132
10133     var st = new $jit.ST({
10134       injectInto: config.injectInto,
10135       orientation: "bottom",
10136       backgroundColor: config.backgroundColor,
10137       renderBackground: config.renderBackground,
10138       levelDistance: 0,
10139       siblingOffset: 0,
10140       subtreeOffset: 0,
10141       withLabels: config.Label.type != 'Native',
10142       useCanvas: config.useCanvas,
10143       Label: {
10144         type: config.Label.type
10145       },
10146       Node: {
10147         overridable: true,
10148         type: 'linechart-' + nodeType,
10149         align: 'left',
10150         width: 1,
10151         height: 1
10152       },
10153       Edge: {
10154         type: 'none'
10155       },
10156       Tips: {
10157         enable: config.Tips.enable,
10158         type: 'Native',
10159         force: true,
10160         onShow: function(tip, node, contains) {
10161           var elem = contains;
10162           config.Tips.onShow(tip, elem, node);
10163         }
10164       },
10165       Events: {
10166         enable: true,
10167         type: 'Native',
10168         onClick: function(node, eventInfo, evt) {
10169           if(!config.filterOnClick && !config.Events.enable) return;
10170           var elem = eventInfo.getContains();
10171           if(elem) config.filterOnClick && that.filter(elem.name);
10172           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10173         },
10174         onRightClick: function(node, eventInfo, evt) {
10175           if(!config.restoreOnRightClick) return;
10176           that.restore();
10177         },
10178         onMouseMove: function(node, eventInfo, evt) {
10179           if(!config.selectOnHover) return;
10180           if(node) {
10181             var elem = eventInfo.getContains();
10182             that.select(node.id, elem.name, elem.index);
10183           } else {
10184             that.select(false, false, false);
10185           }
10186         }
10187       },
10188       onCreateLabel: function(domElement, node) {
10189         var labelConf = config.Label,
10190             valueArray = node.getData('valueArray'),
10191             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10192             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10193         if(node.getData('prev')) {
10194           var nlbs = {
10195             wrapper: document.createElement('div'),
10196             aggregate: document.createElement('div'),
10197             label: document.createElement('div')
10198           };
10199           var wrapper = nlbs.wrapper,
10200               label = nlbs.label,
10201               aggregate = nlbs.aggregate,
10202               wrapperStyle = wrapper.style,
10203               labelStyle = label.style,
10204               aggregateStyle = aggregate.style;
10205           //store node labels
10206           nodeLabels[node.id] = nlbs;
10207           //append labels
10208           wrapper.appendChild(label);
10209           wrapper.appendChild(aggregate);
10210           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10211             label.style.display = 'none';
10212           }
10213           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10214             aggregate.style.display = 'none';
10215           }
10216           wrapperStyle.position = 'relative';
10217           wrapperStyle.overflow = 'visible';
10218           wrapperStyle.fontSize = labelConf.size + 'px';
10219           wrapperStyle.fontFamily = labelConf.family;
10220           wrapperStyle.color = labelConf.color;
10221           wrapperStyle.textAlign = 'center';
10222           aggregateStyle.position = labelStyle.position = 'absolute';
10223           
10224           domElement.style.width = node.getData('width') + 'px';
10225           domElement.style.height = node.getData('height') + 'px';
10226           label.innerHTML = node.name;
10227           
10228           domElement.appendChild(wrapper);
10229         }
10230       },
10231       onPlaceLabel: function(domElement, node) {
10232         if(!node.getData('prev')) return;
10233         var labels = nodeLabels[node.id],
10234             wrapperStyle = labels.wrapper.style,
10235             labelStyle = labels.label.style,
10236             aggregateStyle = labels.aggregate.style,
10237             width = node.getData('width'),
10238             height = node.getData('height'),
10239             dimArray = node.getData('dimArray'),
10240             valArray = node.getData('valueArray'),
10241             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10242             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10243             font = parseInt(wrapperStyle.fontSize, 10),
10244             domStyle = domElement.style;
10245         
10246         if(dimArray && valArray) {
10247           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10248             labelStyle.display = '';
10249           } else {
10250             labelStyle.display = 'none';
10251           }
10252           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10253             aggregateStyle.display = '';
10254           } else {
10255             aggregateStyle.display = 'none';
10256           }
10257           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10258           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10259           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10260             if(dimArray[i][0] > 0) {
10261               acum+= valArray[i][0];
10262               leftAcum+= dimArray[i][0];
10263             }
10264           }
10265           aggregateStyle.top = (-font - config.labelOffset) + 'px';
10266           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10267           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10268           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10269           labels.aggregate.innerHTML = acum;
10270         }
10271       }
10272     });
10273     
10274     var size = st.canvas.getSize(),
10275         margin = config.Margin;
10276     st.config.offsetY = -size.height/2 + margin.bottom 
10277       + (config.showLabels && (config.labelOffset + config.Label.size));
10278     st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10279     this.st = st;
10280     this.canvas = this.st.canvas;
10281   },
10282   
10283     renderTitle: function() {
10284         var canvas = this.canvas,
10285         size = canvas.getSize(),
10286         config = this.config,
10287         margin = config.Margin,
10288         label = config.Label,
10289         title = config.Title;
10290         ctx = canvas.getCtx();
10291         ctx.fillStyle = title.color;
10292         ctx.textAlign = 'left';
10293         ctx.textBaseline = 'top';
10294         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10295         if(label.type == 'Native') {
10296                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10297         }
10298   },  
10299   
10300     renderTicks: function() {
10301
10302         var canvas = this.canvas,
10303         size = canvas.getSize(),
10304         config = this.config,
10305         margin = config.Margin,
10306         ticks = config.Ticks,
10307         title = config.Title,
10308         subtitle = config.Subtitle,
10309         label = config.Label,
10310         maxValue = this.maxValue,
10311         maxTickValue = Math.ceil(maxValue*.1)*10;
10312         if(maxTickValue == maxValue) {
10313                 var length = maxTickValue.toString().length;
10314                 maxTickValue = maxTickValue + parseInt(pad(1,length));
10315         }
10316
10317
10318         labelValue = 0,
10319         labelIncrement = maxTickValue/ticks.segments,
10320         ctx = canvas.getCtx();
10321         ctx.strokeStyle = ticks.color;
10322     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10323         ctx.textAlign = 'center';
10324         ctx.textBaseline = 'middle';
10325         
10326         idLabel = canvas.id + "-label";
10327         labelDim = 100;
10328         container = document.getElementById(idLabel);
10329                   
10330                   
10331                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10332                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10333                 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)),
10334                 segmentLength = grid/ticks.segments;
10335                 ctx.fillStyle = ticks.color;
10336                 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));
10337
10338                 while(axis>=grid) {
10339                         ctx.save();
10340                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10341                         ctx.rotate(Math.PI / 2);
10342                         ctx.fillStyle = label.color;
10343                         if(config.showLabels) {
10344                                 if(label.type == 'Native') { 
10345                                         ctx.fillText(labelValue, 0, 0);
10346                                 } else {
10347                                         //html labels on y axis
10348                                         labelDiv = document.createElement('div');
10349                                         labelDiv.innerHTML = labelValue;
10350                                         labelDiv.className = "rotatedLabel";
10351 //                                      labelDiv.class = "rotatedLabel";
10352                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10353                                         labelDiv.style.left = margin.left + "px";
10354                                         labelDiv.style.width = labelDim + "px";
10355                                         labelDiv.style.height = labelDim + "px";
10356                                         labelDiv.style.textAlign = "center";
10357                                         labelDiv.style.verticalAlign = "middle";
10358                                         labelDiv.style.position = "absolute";
10359                                         container.appendChild(labelDiv);
10360                                 }
10361                         }
10362                         ctx.restore();
10363                         ctx.fillStyle = ticks.color;
10364                         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 );
10365                         htmlOrigin += segmentLength;
10366                         axis += segmentLength;
10367                         labelValue += labelIncrement;
10368                 }
10369         
10370
10371         
10372         
10373         
10374
10375   },
10376   
10377   renderBackground: function() {
10378                 var canvas = this.canvas,
10379                 config = this.config,
10380                 backgroundColor = config.backgroundColor,
10381                 size = canvas.getSize(),
10382                 ctx = canvas.getCtx();
10383                 //ctx.globalCompositeOperation = "destination-over";
10384             ctx.fillStyle = backgroundColor;
10385             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
10386   },
10387   clear: function() {
10388         var canvas = this.canvas;
10389         var ctx = canvas.getCtx(),
10390         size = canvas.getSize();
10391         ctx.fillStyle = "rgba(255,255,255,0)";
10392         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10393         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
10394  },
10395   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
10396         var canvas = this.canvas,
10397         size = canvas.getSize(),
10398         config = this.config,
10399         orgHeight = size.height,
10400         margin = config.Margin,
10401         st = this.st,
10402         horz = config.orientation == 'horizontal';
10403         
10404
10405         var newWindowWidth = document.body.offsetWidth;
10406         var diff = newWindowWidth - orgWindowWidth;     
10407         var newWidth = orgContainerDivWidth + (diff/cols);
10408         canvas.resize(newWidth,orgHeight);
10409         if(typeof FlashCanvas == "undefined") {
10410                 canvas.clear();
10411         } else {
10412                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
10413         }
10414         this.loadJSON(json);
10415
10416         },
10417  /*
10418   Method: loadJSON
10419  
10420   Loads JSON data into the visualization. 
10421   
10422   Parameters:
10423   
10424   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>.
10425   
10426   Example:
10427   (start code js)
10428   var areaChart = new $jit.AreaChart(options);
10429   areaChart.loadJSON(json);
10430   (end code)
10431  */  
10432   loadJSON: function(json) {
10433     var prefix = $.time(), 
10434         ch = [], 
10435         st = this.st,
10436         name = $.splat(json.label), 
10437         color = $.splat(json.color || this.colors),
10438         config = this.config,
10439         ticks = config.Ticks,
10440         renderBackground = config.renderBackground,
10441         gradient = !!config.type.split(":")[1],
10442         animate = config.animate,
10443         title = config.Title,
10444         groupTotalValue = 0;
10445     
10446     var valArrayAll = new Array();
10447
10448     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10449         var val = values[i];
10450         var valArray = $.splat(val.values);
10451         for (var j=0, len=valArray.length; j<len; j++) {
10452                 valArrayAll.push(parseInt(valArray[j]));
10453         }
10454         groupTotalValue += parseInt(valArray.sum());
10455     }
10456     
10457     this.maxValue =  Math.max.apply(null, valArrayAll);
10458     
10459     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10460       var val = values[i], prev = values[i-1];
10461
10462       var next = (i+1 < l) ? values[i+1] : 0;
10463       var valLeft = $.splat(values[i].values);
10464       var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10465       var valArray = $.zip(valLeft, valRight);
10466       var valArrayCur = $.splat(values[i].values);
10467       var linkArray = $.splat(values[i].links);
10468       var acumLeft = 0, acumRight = 0;
10469       var lastNode = (l-1 == i) ? true : false; 
10470       ch.push({
10471         'id': prefix + val.label,
10472         'name': val.label,
10473         'data': {
10474           'value': valArray,
10475           '$valueArray': valArray,
10476           '$valArrayCur': valArrayCur,
10477           '$colorArray': color,
10478           '$linkArray': linkArray,
10479           '$stringArray': name,
10480           '$next': next? next.label:false,
10481           '$prev': prev? prev.label:false,
10482           '$config': config,
10483           '$lastNode': lastNode,
10484           '$groupTotalValue': groupTotalValue,
10485           '$gradient': gradient
10486         },
10487         'children': []
10488       });
10489     }
10490     var root = {
10491       'id': prefix + '$root',
10492       'name': '',
10493       'data': {
10494         '$type': 'none',
10495         '$width': 1,
10496         '$height': 1
10497       },
10498       'children': ch
10499     };
10500     st.loadJSON(root);
10501     
10502     this.normalizeDims();
10503     
10504     if(renderBackground) {
10505         this.renderBackground();        
10506     }
10507     
10508     if(!animate && ticks.enable) {
10509                 this.renderTicks();
10510         }
10511         
10512         
10513         if(title.text) {
10514                 this.renderTitle();     
10515         }
10516         
10517     st.compute();
10518     st.select(st.root);
10519     if(animate) {
10520       st.fx.animate({
10521         modes: ['node-property:height:dimArray'],
10522         duration:1500
10523       });
10524     }
10525   },
10526   
10527  /*
10528   Method: updateJSON
10529  
10530   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.
10531   
10532   Parameters:
10533   
10534   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10535   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10536   
10537   Example:
10538   
10539   (start code js)
10540   areaChart.updateJSON(json, {
10541     onComplete: function() {
10542       alert('update complete!');
10543     }
10544   });
10545   (end code)
10546  */  
10547   updateJSON: function(json, onComplete) {
10548     if(this.busy) return;
10549     this.busy = true;
10550     
10551     var st = this.st,
10552         graph = st.graph,
10553         labels = json.label && $.splat(json.label),
10554         values = json.values,
10555         animate = this.config.animate,
10556         that = this;
10557     $.each(values, function(v) {
10558       var n = graph.getByName(v.label);
10559       if(n) {
10560         v.values = $.splat(v.values);
10561         var stringArray = n.getData('stringArray'),
10562             valArray = n.getData('valueArray');
10563         $.each(valArray, function(a, i) {
10564           a[0] = v.values[i];
10565           if(labels) stringArray[i] = labels[i];
10566         });
10567         n.setData('valueArray', valArray);
10568         var prev = n.getData('prev'),
10569             next = n.getData('next'),
10570             nextNode = graph.getByName(next);
10571         if(prev) {
10572           var p = graph.getByName(prev);
10573           if(p) {
10574             var valArray = p.getData('valueArray');
10575             $.each(valArray, function(a, i) {
10576               a[1] = v.values[i];
10577             });
10578           }
10579         }
10580         if(!nextNode) {
10581           var valArray = n.getData('valueArray');
10582           $.each(valArray, function(a, i) {
10583             a[1] = v.values[i];
10584           });
10585         }
10586       }
10587     });
10588     this.normalizeDims();
10589     st.compute();
10590     
10591     st.select(st.root);
10592     if(animate) {
10593       st.fx.animate({
10594         modes: ['node-property:height:dimArray'],
10595         duration:1500,
10596         onComplete: function() {
10597           that.busy = false;
10598           onComplete && onComplete.onComplete();
10599         }
10600       });
10601     }
10602   },
10603   
10604 /*
10605   Method: filter
10606  
10607   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10608   
10609   Parameters:
10610   
10611   Variable strings arguments with the name of the stacks.
10612   
10613   Example:
10614   
10615   (start code js)
10616   areaChart.filter('label A', 'label C');
10617   (end code)
10618   
10619   See also:
10620   
10621   <AreaChart.restore>.
10622  */  
10623   filter: function() {
10624     if(this.busy) return;
10625     this.busy = true;
10626     if(this.config.Tips.enable) this.st.tips.hide();
10627     this.select(false, false, false);
10628     var args = Array.prototype.slice.call(arguments);
10629     var rt = this.st.graph.getNode(this.st.root);
10630     var that = this;
10631     rt.eachAdjacency(function(adj) {
10632       var n = adj.nodeTo, 
10633           dimArray = n.getData('dimArray'),
10634           stringArray = n.getData('stringArray');
10635       n.setData('dimArray', $.map(dimArray, function(d, i) {
10636         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10637       }), 'end');
10638     });
10639     this.st.fx.animate({
10640       modes: ['node-property:dimArray'],
10641       duration:1500,
10642       onComplete: function() {
10643         that.busy = false;
10644       }
10645     });
10646   },
10647   
10648   /*
10649   Method: restore
10650  
10651   Sets all stacks that could have been filtered visible.
10652   
10653   Example:
10654   
10655   (start code js)
10656   areaChart.restore();
10657   (end code)
10658   
10659   See also:
10660   
10661   <AreaChart.filter>.
10662  */  
10663   restore: function() {
10664     if(this.busy) return;
10665     this.busy = true;
10666     if(this.config.Tips.enable) this.st.tips.hide();
10667     this.select(false, false, false);
10668     this.normalizeDims();
10669     var that = this;
10670     this.st.fx.animate({
10671       modes: ['node-property:height:dimArray'],
10672       duration:1500,
10673       onComplete: function() {
10674         that.busy = false;
10675       }
10676     });
10677   },
10678   //adds the little brown bar when hovering the node
10679   select: function(id, name, index) {
10680     if(!this.config.selectOnHover) return;
10681     var s = this.selected;
10682     if(s.id != id || s.name != name 
10683         || s.index != index) {
10684       s.id = id;
10685       s.name = name;
10686       s.index = index;
10687       this.st.graph.eachNode(function(n) {
10688         n.setData('border', false);
10689       });
10690       if(id) {
10691         var n = this.st.graph.getNode(id);
10692         n.setData('border', s);
10693         var link = index === 0? 'prev':'next';
10694         link = n.getData(link);
10695         if(link) {
10696           n = this.st.graph.getByName(link);
10697           if(n) {
10698             n.setData('border', {
10699               name: name,
10700               index: 1-index
10701             });
10702           }
10703         }
10704       }
10705       this.st.plot();
10706     }
10707   },
10708   
10709   /*
10710     Method: getLegend
10711    
10712     Returns an object containing as keys the legend names and as values hex strings with color values.
10713     
10714     Example:
10715     
10716     (start code js)
10717     var legend = areaChart.getLegend();
10718     (end code)
10719  */  
10720   getLegend: function() {
10721     var legend = new Array();
10722     var name = new Array();
10723     var color = new Array();
10724     var n;
10725     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10726       n = adj.nodeTo;
10727     });
10728     var colors = n.getData('colorArray'),
10729         len = colors.length;
10730     $.each(n.getData('stringArray'), function(s, i) {
10731       color[i] = colors[i % len];
10732       name[i] = s;
10733     });
10734         legend['name'] = name;
10735         legend['color'] = color;
10736     return legend;
10737   },
10738   
10739   /*
10740     Method: getMaxValue
10741    
10742     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10743     
10744     Example:
10745     
10746     (start code js)
10747     var ans = areaChart.getMaxValue();
10748     (end code)
10749     
10750     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10751     
10752     Example:
10753     
10754     (start code js)
10755     //will return 100 for all AreaChart instances,
10756     //displaying all of them with the same scale
10757     $jit.AreaChart.implement({
10758       'getMaxValue': function() {
10759         return 100;
10760       }
10761     });
10762     (end code)
10763     
10764 */  
10765
10766   normalizeDims: function() {
10767     //number of elements
10768     var root = this.st.graph.getNode(this.st.root), l=0;
10769     root.eachAdjacency(function() {
10770       l++;
10771     });
10772     
10773
10774     var maxValue = this.maxValue || 1,
10775         size = this.st.canvas.getSize(),
10776         config = this.config,
10777         margin = config.Margin,
10778         labelOffset = config.labelOffset + config.Label.size,
10779         fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10780         animate = config.animate,
10781         ticks = config.Ticks,
10782         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
10783           - (config.showLabels && labelOffset);
10784           
10785           
10786         var maxTickValue = Math.ceil(maxValue*.1)*10;
10787                 if(maxTickValue == maxValue) {
10788                         var length = maxTickValue.toString().length;
10789                         maxTickValue = maxTickValue + parseInt(pad(1,length));
10790                 }
10791                 
10792                 
10793                 
10794     this.st.graph.eachNode(function(n) {
10795       var acumLeft = 0, acumRight = 0, animateValue = [];
10796       $.each(n.getData('valueArray'), function(v) {
10797         acumLeft += +v[0];
10798         acumRight += +v[1];
10799         animateValue.push([0, 0]);
10800       });
10801       var acum = acumRight>acumLeft? acumRight:acumLeft;
10802       
10803       n.setData('width', fixedDim);
10804       if(animate) {
10805         n.setData('height', acum * height / maxValue, 'end');
10806         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10807           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10808         }), 'end');
10809         var dimArray = n.getData('dimArray');
10810         if(!dimArray) {
10811           n.setData('dimArray', animateValue);
10812         }
10813       } else {
10814         
10815         if(ticks.enable) {
10816                 n.setData('height', acum * height / maxValue);
10817                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10818                   return [n[0] * height / maxTickValue, n[1] * height / maxTickValue]; 
10819                 }));
10820         } else {
10821                 n.setData('height', acum * height / maxValue);
10822                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10823                   return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10824                 }));
10825         }
10826         
10827         
10828       }
10829     });
10830   }
10831 });
10832
10833
10834
10835
10836
10837 /*
10838  * File: AreaChart.js
10839  *
10840 */
10841
10842 $jit.ST.Plot.NodeTypes.implement({
10843   'areachart-stacked' : {
10844     'render' : function(node, canvas) {
10845       var pos = node.pos.getc(true), 
10846           width = node.getData('width'),
10847           height = node.getData('height'),
10848           algnPos = this.getAlignedPos(pos, width, height),
10849           x = algnPos.x, y = algnPos.y,
10850           stringArray = node.getData('stringArray'),
10851           dimArray = node.getData('dimArray'),
10852           valArray = node.getData('valueArray'),
10853           valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10854           valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10855           colorArray = node.getData('colorArray'),
10856           colorLength = colorArray.length,
10857           config = node.getData('config'),
10858           gradient = node.getData('gradient'),
10859           showLabels = config.showLabels,
10860           aggregates = config.showAggregates,
10861           label = config.Label,
10862           prev = node.getData('prev');
10863
10864       var ctx = canvas.getCtx(), border = node.getData('border');
10865       if (colorArray && dimArray && stringArray) {
10866         for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10867           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10868           ctx.save();
10869           if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
10870             var h1 = acumLeft + dimArray[i][0],
10871                 h2 = acumRight + dimArray[i][1],
10872                 alpha = Math.atan((h2 - h1) / width),
10873                 delta = 55;
10874             var linear = ctx.createLinearGradient(x + width/2, 
10875                 y - (h1 + h2)/2,
10876                 x + width/2 + delta * Math.sin(alpha),
10877                 y - (h1 + h2)/2 + delta * Math.cos(alpha));
10878             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10879                 function(v) { return (v * 0.85) >> 0; }));
10880             linear.addColorStop(0, colorArray[i % colorLength]);
10881             linear.addColorStop(1, color);
10882             ctx.fillStyle = linear;
10883           }
10884           ctx.beginPath();
10885           ctx.moveTo(x, y - acumLeft);
10886           ctx.lineTo(x + width, y - acumRight);
10887           ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10888           ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10889           ctx.lineTo(x, y - acumLeft);
10890           ctx.fill();
10891           ctx.restore();
10892           if(border) {
10893             var strong = border.name == stringArray[i];
10894             var perc = strong? 0.7 : 0.8;
10895             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
10896                 function(v) { return (v * perc) >> 0; }));
10897             ctx.strokeStyle = color;
10898             ctx.lineWidth = strong? 4 : 1;
10899             ctx.save();
10900             ctx.beginPath();
10901             if(border.index === 0) {
10902               ctx.moveTo(x, y - acumLeft);
10903               ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
10904             } else {
10905               ctx.moveTo(x + width, y - acumRight);
10906               ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
10907             }
10908             ctx.stroke();
10909             ctx.restore();
10910           }
10911           acumLeft += (dimArray[i][0] || 0);
10912           acumRight += (dimArray[i][1] || 0);
10913           
10914           if(dimArray[i][0] > 0)
10915             valAcum += (valArray[i][0] || 0);
10916         }
10917         if(prev && label.type == 'Native') {
10918           ctx.save();
10919           ctx.beginPath();
10920           ctx.fillStyle = ctx.strokeStyle = label.color;
10921           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10922           ctx.textAlign = 'center';
10923           ctx.textBaseline = 'middle';
10924           if(aggregates(node.name, valLeft, valRight, node)) {
10925             ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
10926           }
10927           if(showLabels(node.name, valLeft, valRight, node)) {
10928             ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
10929           }
10930           ctx.restore();
10931         }
10932       }
10933     },
10934     'contains': function(node, mpos) {
10935       var pos = node.pos.getc(true), 
10936           width = node.getData('width'),
10937           height = node.getData('height'),
10938           algnPos = this.getAlignedPos(pos, width, height),
10939           x = algnPos.x, y = algnPos.y,
10940           dimArray = node.getData('dimArray'),
10941           rx = mpos.x - x;
10942       //bounding box check
10943       if(mpos.x < x || mpos.x > x + width
10944         || mpos.y > y || mpos.y < y - height) {
10945         return false;
10946       }
10947       //deep check
10948       for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
10949         var dimi = dimArray[i];
10950         lAcum -= dimi[0];
10951         rAcum -= dimi[1];
10952         var intersec = lAcum + (rAcum - lAcum) * rx / width;
10953         if(mpos.y >= intersec) {
10954           var index = +(rx > width/2);
10955           return {
10956             'name': node.getData('stringArray')[i],
10957             'color': node.getData('colorArray')[i],
10958             'value': node.getData('valueArray')[i][index],
10959             'index': index
10960           };
10961         }
10962       }
10963       return false;
10964     }
10965   }
10966 });
10967
10968 /*
10969   Class: AreaChart
10970   
10971   A visualization that displays stacked area charts.
10972   
10973   Constructor Options:
10974   
10975   See <Options.AreaChart>.
10976
10977 */
10978 $jit.AreaChart = new Class({
10979   st: null,
10980   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10981   selected: {},
10982   busy: false,
10983   
10984   initialize: function(opt) {
10985     this.controller = this.config = 
10986       $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
10987         Label: { type: 'Native' }
10988       }, opt);
10989     //set functions for showLabels and showAggregates
10990     var showLabels = this.config.showLabels,
10991         typeLabels = $.type(showLabels),
10992         showAggregates = this.config.showAggregates,
10993         typeAggregates = $.type(showAggregates);
10994     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10995     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10996     
10997     this.initializeViz();
10998   },
10999   
11000   initializeViz: function() {
11001     var config = this.config,
11002         that = this,
11003         nodeType = config.type.split(":")[0],
11004         nodeLabels = {};
11005
11006     var st = new $jit.ST({
11007       injectInto: config.injectInto,
11008       orientation: "bottom",
11009       levelDistance: 0,
11010       siblingOffset: 0,
11011       subtreeOffset: 0,
11012       withLabels: config.Label.type != 'Native',
11013       useCanvas: config.useCanvas,
11014       Label: {
11015         type: config.Label.type
11016       },
11017       Node: {
11018         overridable: true,
11019         type: 'areachart-' + nodeType,
11020         align: 'left',
11021         width: 1,
11022         height: 1
11023       },
11024       Edge: {
11025         type: 'none'
11026       },
11027       Tips: {
11028         enable: config.Tips.enable,
11029         type: 'Native',
11030         force: true,
11031         onShow: function(tip, node, contains) {
11032           var elem = contains;
11033           config.Tips.onShow(tip, elem, node);
11034         }
11035       },
11036       Events: {
11037         enable: true,
11038         type: 'Native',
11039         onClick: function(node, eventInfo, evt) {
11040           if(!config.filterOnClick && !config.Events.enable) return;
11041           var elem = eventInfo.getContains();
11042           if(elem) config.filterOnClick && that.filter(elem.name);
11043           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11044         },
11045         onRightClick: function(node, eventInfo, evt) {
11046           if(!config.restoreOnRightClick) return;
11047           that.restore();
11048         },
11049         onMouseMove: function(node, eventInfo, evt) {
11050           if(!config.selectOnHover) return;
11051           if(node) {
11052             var elem = eventInfo.getContains();
11053             that.select(node.id, elem.name, elem.index);
11054           } else {
11055             that.select(false, false, false);
11056           }
11057         }
11058       },
11059       onCreateLabel: function(domElement, node) {
11060         var labelConf = config.Label,
11061             valueArray = node.getData('valueArray'),
11062             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11063             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11064         if(node.getData('prev')) {
11065           var nlbs = {
11066             wrapper: document.createElement('div'),
11067             aggregate: document.createElement('div'),
11068             label: document.createElement('div')
11069           };
11070           var wrapper = nlbs.wrapper,
11071               label = nlbs.label,
11072               aggregate = nlbs.aggregate,
11073               wrapperStyle = wrapper.style,
11074               labelStyle = label.style,
11075               aggregateStyle = aggregate.style;
11076           //store node labels
11077           nodeLabels[node.id] = nlbs;
11078           //append labels
11079           wrapper.appendChild(label);
11080           wrapper.appendChild(aggregate);
11081           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11082             label.style.display = 'none';
11083           }
11084           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11085             aggregate.style.display = 'none';
11086           }
11087           wrapperStyle.position = 'relative';
11088           wrapperStyle.overflow = 'visible';
11089           wrapperStyle.fontSize = labelConf.size + 'px';
11090           wrapperStyle.fontFamily = labelConf.family;
11091           wrapperStyle.color = labelConf.color;
11092           wrapperStyle.textAlign = 'center';
11093           aggregateStyle.position = labelStyle.position = 'absolute';
11094           
11095           domElement.style.width = node.getData('width') + 'px';
11096           domElement.style.height = node.getData('height') + 'px';
11097           label.innerHTML = node.name;
11098           
11099           domElement.appendChild(wrapper);
11100         }
11101       },
11102       onPlaceLabel: function(domElement, node) {
11103         if(!node.getData('prev')) return;
11104         var labels = nodeLabels[node.id],
11105             wrapperStyle = labels.wrapper.style,
11106             labelStyle = labels.label.style,
11107             aggregateStyle = labels.aggregate.style,
11108             width = node.getData('width'),
11109             height = node.getData('height'),
11110             dimArray = node.getData('dimArray'),
11111             valArray = node.getData('valueArray'),
11112             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11113             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11114             font = parseInt(wrapperStyle.fontSize, 10),
11115             domStyle = domElement.style;
11116         
11117         if(dimArray && valArray) {
11118           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11119             labelStyle.display = '';
11120           } else {
11121             labelStyle.display = 'none';
11122           }
11123           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11124             aggregateStyle.display = '';
11125           } else {
11126             aggregateStyle.display = 'none';
11127           }
11128           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11129           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11130           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11131             if(dimArray[i][0] > 0) {
11132               acum+= valArray[i][0];
11133               leftAcum+= dimArray[i][0];
11134             }
11135           }
11136           aggregateStyle.top = (-font - config.labelOffset) + 'px';
11137           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11138           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11139           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11140           labels.aggregate.innerHTML = acum;
11141         }
11142       }
11143     });
11144     
11145     var size = st.canvas.getSize(),
11146         margin = config.Margin;
11147     st.config.offsetY = -size.height/2 + margin.bottom 
11148       + (config.showLabels && (config.labelOffset + config.Label.size));
11149     st.config.offsetX = (margin.right - margin.left)/2;
11150     this.st = st;
11151     this.canvas = this.st.canvas;
11152   },
11153   
11154  /*
11155   Method: loadJSON
11156  
11157   Loads JSON data into the visualization. 
11158   
11159   Parameters:
11160   
11161   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>.
11162   
11163   Example:
11164   (start code js)
11165   var areaChart = new $jit.AreaChart(options);
11166   areaChart.loadJSON(json);
11167   (end code)
11168  */  
11169   loadJSON: function(json) {
11170     var prefix = $.time(), 
11171         ch = [], 
11172         st = this.st,
11173         name = $.splat(json.label), 
11174         color = $.splat(json.color || this.colors),
11175         config = this.config,
11176         gradient = !!config.type.split(":")[1],
11177         animate = config.animate;
11178     
11179     for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11180       var val = values[i], prev = values[i-1], next = values[i+1];
11181       var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11182       var valArray = $.zip(valLeft, valRight);
11183       var acumLeft = 0, acumRight = 0;
11184       ch.push({
11185         'id': prefix + val.label,
11186         'name': val.label,
11187         'data': {
11188           'value': valArray,
11189           '$valueArray': valArray,
11190           '$colorArray': color,
11191           '$stringArray': name,
11192           '$next': next.label,
11193           '$prev': prev? prev.label:false,
11194           '$config': config,
11195           '$gradient': gradient
11196         },
11197         'children': []
11198       });
11199     }
11200     var root = {
11201       'id': prefix + '$root',
11202       'name': '',
11203       'data': {
11204         '$type': 'none',
11205         '$width': 1,
11206         '$height': 1
11207       },
11208       'children': ch
11209     };
11210     st.loadJSON(root);
11211     
11212     this.normalizeDims();
11213     st.compute();
11214     st.select(st.root);
11215     if(animate) {
11216       st.fx.animate({
11217         modes: ['node-property:height:dimArray'],
11218         duration:1500
11219       });
11220     }
11221   },
11222   
11223  /*
11224   Method: updateJSON
11225  
11226   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.
11227   
11228   Parameters:
11229   
11230   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11231   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11232   
11233   Example:
11234   
11235   (start code js)
11236   areaChart.updateJSON(json, {
11237     onComplete: function() {
11238       alert('update complete!');
11239     }
11240   });
11241   (end code)
11242  */  
11243   updateJSON: function(json, onComplete) {
11244     if(this.busy) return;
11245     this.busy = true;
11246     
11247     var st = this.st,
11248         graph = st.graph,
11249         labels = json.label && $.splat(json.label),
11250         values = json.values,
11251         animate = this.config.animate,
11252         that = this;
11253     $.each(values, function(v) {
11254       var n = graph.getByName(v.label);
11255       if(n) {
11256         v.values = $.splat(v.values);
11257         var stringArray = n.getData('stringArray'),
11258             valArray = n.getData('valueArray');
11259         $.each(valArray, function(a, i) {
11260           a[0] = v.values[i];
11261           if(labels) stringArray[i] = labels[i];
11262         });
11263         n.setData('valueArray', valArray);
11264         var prev = n.getData('prev'),
11265             next = n.getData('next'),
11266             nextNode = graph.getByName(next);
11267         if(prev) {
11268           var p = graph.getByName(prev);
11269           if(p) {
11270             var valArray = p.getData('valueArray');
11271             $.each(valArray, function(a, i) {
11272               a[1] = v.values[i];
11273             });
11274           }
11275         }
11276         if(!nextNode) {
11277           var valArray = n.getData('valueArray');
11278           $.each(valArray, function(a, i) {
11279             a[1] = v.values[i];
11280           });
11281         }
11282       }
11283     });
11284     this.normalizeDims();
11285     st.compute();
11286     st.select(st.root);
11287     if(animate) {
11288       st.fx.animate({
11289         modes: ['node-property:height:dimArray'],
11290         duration:1500,
11291         onComplete: function() {
11292           that.busy = false;
11293           onComplete && onComplete.onComplete();
11294         }
11295       });
11296     }
11297   },
11298   
11299 /*
11300   Method: filter
11301  
11302   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11303   
11304   Parameters:
11305   
11306   Variable strings arguments with the name of the stacks.
11307   
11308   Example:
11309   
11310   (start code js)
11311   areaChart.filter('label A', 'label C');
11312   (end code)
11313   
11314   See also:
11315   
11316   <AreaChart.restore>.
11317  */  
11318   filter: function() {
11319     if(this.busy) return;
11320     this.busy = true;
11321     if(this.config.Tips.enable) this.st.tips.hide();
11322     this.select(false, false, false);
11323     var args = Array.prototype.slice.call(arguments);
11324     var rt = this.st.graph.getNode(this.st.root);
11325     var that = this;
11326     rt.eachAdjacency(function(adj) {
11327       var n = adj.nodeTo, 
11328           dimArray = n.getData('dimArray'),
11329           stringArray = n.getData('stringArray');
11330       n.setData('dimArray', $.map(dimArray, function(d, i) {
11331         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11332       }), 'end');
11333     });
11334     this.st.fx.animate({
11335       modes: ['node-property:dimArray'],
11336       duration:1500,
11337       onComplete: function() {
11338         that.busy = false;
11339       }
11340     });
11341   },
11342   
11343   /*
11344   Method: restore
11345  
11346   Sets all stacks that could have been filtered visible.
11347   
11348   Example:
11349   
11350   (start code js)
11351   areaChart.restore();
11352   (end code)
11353   
11354   See also:
11355   
11356   <AreaChart.filter>.
11357  */  
11358   restore: function() {
11359     if(this.busy) return;
11360     this.busy = true;
11361     if(this.config.Tips.enable) this.st.tips.hide();
11362     this.select(false, false, false);
11363     this.normalizeDims();
11364     var that = this;
11365     this.st.fx.animate({
11366       modes: ['node-property:height:dimArray'],
11367       duration:1500,
11368       onComplete: function() {
11369         that.busy = false;
11370       }
11371     });
11372   },
11373   //adds the little brown bar when hovering the node
11374   select: function(id, name, index) {
11375     if(!this.config.selectOnHover) return;
11376     var s = this.selected;
11377     if(s.id != id || s.name != name 
11378         || s.index != index) {
11379       s.id = id;
11380       s.name = name;
11381       s.index = index;
11382       this.st.graph.eachNode(function(n) {
11383         n.setData('border', false);
11384       });
11385       if(id) {
11386         var n = this.st.graph.getNode(id);
11387         n.setData('border', s);
11388         var link = index === 0? 'prev':'next';
11389         link = n.getData(link);
11390         if(link) {
11391           n = this.st.graph.getByName(link);
11392           if(n) {
11393             n.setData('border', {
11394               name: name,
11395               index: 1-index
11396             });
11397           }
11398         }
11399       }
11400       this.st.plot();
11401     }
11402   },
11403   
11404   /*
11405     Method: getLegend
11406    
11407     Returns an object containing as keys the legend names and as values hex strings with color values.
11408     
11409     Example:
11410     
11411     (start code js)
11412     var legend = areaChart.getLegend();
11413     (end code)
11414  */  
11415   getLegend: function() {
11416     var legend = {};
11417     var n;
11418     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11419       n = adj.nodeTo;
11420     });
11421     var colors = n.getData('colorArray'),
11422         len = colors.length;
11423     $.each(n.getData('stringArray'), function(s, i) {
11424       legend[s] = colors[i % len];
11425     });
11426     return legend;
11427   },
11428   
11429   /*
11430     Method: getMaxValue
11431    
11432     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11433     
11434     Example:
11435     
11436     (start code js)
11437     var ans = areaChart.getMaxValue();
11438     (end code)
11439     
11440     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11441     
11442     Example:
11443     
11444     (start code js)
11445     //will return 100 for all AreaChart instances,
11446     //displaying all of them with the same scale
11447     $jit.AreaChart.implement({
11448       'getMaxValue': function() {
11449         return 100;
11450       }
11451     });
11452     (end code)
11453     
11454 */  
11455   getMaxValue: function() {
11456     var maxValue = 0;
11457     this.st.graph.eachNode(function(n) {
11458       var valArray = n.getData('valueArray'),
11459           acumLeft = 0, acumRight = 0;
11460       $.each(valArray, function(v) { 
11461         acumLeft += +v[0];
11462         acumRight += +v[1];
11463       });
11464       var acum = acumRight>acumLeft? acumRight:acumLeft;
11465       maxValue = maxValue>acum? maxValue:acum;
11466     });
11467     return maxValue;
11468   },
11469   
11470   normalizeDims: function() {
11471     //number of elements
11472     var root = this.st.graph.getNode(this.st.root), l=0;
11473     root.eachAdjacency(function() {
11474       l++;
11475     });
11476     var maxValue = this.getMaxValue() || 1,
11477         size = this.st.canvas.getSize(),
11478         config = this.config,
11479         margin = config.Margin,
11480         labelOffset = config.labelOffset + config.Label.size,
11481         fixedDim = (size.width - (margin.left + margin.right)) / l,
11482         animate = config.animate,
11483         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
11484           - (config.showLabels && labelOffset);
11485     this.st.graph.eachNode(function(n) {
11486       var acumLeft = 0, acumRight = 0, animateValue = [];
11487       $.each(n.getData('valueArray'), function(v) {
11488         acumLeft += +v[0];
11489         acumRight += +v[1];
11490         animateValue.push([0, 0]);
11491       });
11492       var acum = acumRight>acumLeft? acumRight:acumLeft;
11493       n.setData('width', fixedDim);
11494       if(animate) {
11495         n.setData('height', acum * height / maxValue, 'end');
11496         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11497           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11498         }), 'end');
11499         var dimArray = n.getData('dimArray');
11500         if(!dimArray) {
11501           n.setData('dimArray', animateValue);
11502         }
11503       } else {
11504         n.setData('height', acum * height / maxValue);
11505         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11506           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11507         }));
11508       }
11509     });
11510   }
11511 });
11512
11513 /*
11514  * File: Options.BarChart.js
11515  *
11516 */
11517
11518 /*
11519   Object: Options.BarChart
11520   
11521   <BarChart> options. 
11522   Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11523   
11524   Syntax:
11525   
11526   (start code js)
11527
11528   Options.BarChart = {
11529     animate: true,
11530     labelOffset: 3,
11531     barsOffset: 0,
11532     type: 'stacked',
11533     hoveredColor: '#9fd4ff',
11534     orientation: 'horizontal',
11535     showAggregates: true,
11536     showLabels: true
11537   };
11538   
11539   (end code)
11540   
11541   Example:
11542   
11543   (start code js)
11544
11545   var barChart = new $jit.BarChart({
11546     animate: true,
11547     barsOffset: 10,
11548     type: 'stacked:gradient'
11549   });
11550   
11551   (end code)
11552
11553   Parameters:
11554   
11555   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11556   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11557   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11558   barsOffset - (number) Default's *0*. Separation between bars.
11559   type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11560   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11561   orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11562   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11563   showLabels - (boolean) Default's *true*. Display the name of the slots.
11564   
11565 */
11566
11567 Options.BarChart = {
11568   $extend: true,
11569   
11570   animate: true,
11571   type: 'stacked', //stacked, grouped, : gradient
11572   labelOffset: 3, //label offset
11573   barsOffset: 0, //distance between bars
11574   nodeCount: 0, //number of bars
11575   hoveredColor: '#9fd4ff',
11576   background: false,
11577   renderBackground: false,
11578   orientation: 'horizontal',
11579   showAggregates: true,
11580   showLabels: true,
11581   Ticks: {
11582         enable: false,
11583         segments: 4,
11584         color: '#000000'
11585   },
11586   Tips: {
11587     enable: false,
11588     onShow: $.empty,
11589     onHide: $.empty
11590   },
11591   Events: {
11592     enable: false,
11593     onClick: $.empty
11594   }
11595 };
11596
11597 /*
11598  * File: BarChart.js
11599  *
11600 */
11601
11602 $jit.ST.Plot.NodeTypes.implement({
11603   'barchart-stacked' : {
11604     'render' : function(node, canvas) {
11605       var pos = node.pos.getc(true), 
11606           width = node.getData('width'),
11607           height = node.getData('height'),
11608           algnPos = this.getAlignedPos(pos, width, height),
11609           x = algnPos.x, y = algnPos.y,
11610           dimArray = node.getData('dimArray'),
11611           valueArray = node.getData('valueArray'),
11612           stringArray = node.getData('stringArray'),
11613           linkArray = node.getData('linkArray'),
11614           gvl = node.getData('gvl'),
11615           colorArray = node.getData('colorArray'),
11616           colorLength = colorArray.length,
11617           nodeCount = node.getData('nodeCount');
11618       var ctx = canvas.getCtx(),
11619           canvasSize = canvas.getSize(),
11620           opt = {},
11621           border = node.getData('border'),
11622           gradient = node.getData('gradient'),
11623           config = node.getData('config'),
11624           horz = config.orientation == 'horizontal',
11625           aggregates = config.showAggregates,
11626           showLabels = config.showLabels,
11627           label = config.Label,
11628           margin = config.Margin;
11629           
11630           
11631       if (colorArray && dimArray && stringArray) {
11632         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11633                 acum += (dimArray[i] || 0);
11634         }
11635       }
11636       
11637        //drop shadow 
11638        if(config.shadow.enable) {
11639        shadowThickness = config.shadow.size;
11640        ctx.fillStyle = "rgba(0,0,0,.2)";
11641           if(horz) {
11642             ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11643           } else {
11644             ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11645           }
11646        }
11647        
11648       if (colorArray && dimArray && stringArray) {
11649         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11650           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11651           if(gradient) {
11652             var linear;
11653             
11654
11655           
11656             if(horz) {
11657               linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y, 
11658                   x + acum + dimArray[i]/2, y + height);
11659             } else {
11660               linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2, 
11661                   x + width, y - acum- dimArray[i]/2);
11662             }
11663             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11664                 function(v) { return (v * 0.8) >> 0; }));
11665             linear.addColorStop(0, color);
11666             linear.addColorStop(0.3, colorArray[i % colorLength]);
11667             linear.addColorStop(0.7, colorArray[i % colorLength]);
11668             linear.addColorStop(1, color);
11669             ctx.fillStyle = linear;
11670           }
11671         
11672         if(horz) {
11673             xCoord = x + acum;
11674             yCoord = y;
11675             chartBarWidth = dimArray[i];
11676             chartBarHeight = height;
11677         } else {
11678             xCoord = x;
11679             yCoord = y - acum - dimArray[i];
11680             chartBarWidth = width;
11681             chartBarHeight = dimArray[i];
11682         }
11683         
11684         ctx.fillRect(xCoord, yCoord, chartBarWidth, chartBarHeight);
11685           
11686         // add label
11687         if(chartBarHeight > 0) {
11688             ctx.font = label.style + ' ' + (label.size - 2) + 'px ' + label.family;
11689             labelText = valueArray[i];
11690             mtxt = ctx.measureText(labelText);
11691             
11692             labelTextPaddingX = 10;
11693             labelTextPaddingY = 6;
11694             
11695             labelBoxWidth = mtxt.width + labelTextPaddingX;
11696             labelBoxHeight = label.size + labelTextPaddingY;
11697             
11698             // do NOT draw label if label box is smaller than chartBarHeight
11699             if((horz && (labelBoxWidth < chartBarWidth)) || (!horz && (labelBoxHeight < chartBarHeight))) {
11700                 labelBoxX = xCoord + chartBarWidth/2 - mtxt.width/2 - labelTextPaddingX/2;
11701                 labelBoxY = yCoord + chartBarHeight/2 - labelBoxHeight/2;
11702                 
11703                 ctx.fillStyle = "rgba(255,255,255,.2)";
11704                 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "fill");
11705                 ctx.fillStyle = "rgba(0,0,0,.8)";
11706                 $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "stroke");
11707                 ctx.textAlign = 'center';
11708                 ctx.fillStyle = "rgba(255,255,255,.6)";
11709                 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2, labelBoxY + labelBoxHeight/2);
11710                 ctx.fillStyle = "rgba(0,0,0,.6)";
11711                 ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2 + 1, labelBoxY + labelBoxHeight/2 + 1);
11712             }
11713         }
11714         
11715           if(border && border.name == stringArray[i]) {
11716             opt.acum = acum;
11717             opt.dimValue = dimArray[i];
11718           }
11719           acum += (dimArray[i] || 0);
11720           valAcum += (valueArray[i] || 0);
11721         }
11722         if(border) {
11723           ctx.save();
11724           ctx.lineWidth = 2;
11725           ctx.strokeStyle = border.color;
11726           if(horz) {
11727             ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11728           } else {
11729             ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11730           }
11731           ctx.restore();
11732         }
11733         if(label.type == 'Native') {
11734           ctx.save();
11735           ctx.fillStyle = ctx.strokeStyle = label.color;
11736           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11737           ctx.textBaseline = 'middle';
11738                         if(gvl) {
11739                                 acumValueLabel = gvl;
11740                         } else {
11741                                 acumValueLabel = valAcum;
11742                         }
11743           if(aggregates(node.name, valAcum)) {
11744             if(!horz) {
11745                           ctx.textAlign = 'center';
11746                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11747                           //background box
11748                           ctx.save();
11749                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11750                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11751                                  (label ? label.size + config.labelOffset : 0));
11752                           mtxt = ctx.measureText(acumValueLabel);
11753                           boxWidth = mtxt.width+10;
11754                           inset = 10;
11755                           boxHeight = label.size+6;
11756                           
11757                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11758                                 bottomPadding = acum - config.labelOffset - boxHeight;
11759                           } else {
11760                                 bottomPadding = acum + config.labelOffset + inset;
11761                           }
11762                         
11763                         
11764                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11765                           cornerRadius = 4;     
11766                           boxX = -inset/2;
11767                           boxY = -boxHeight/2;
11768                           
11769                           ctx.rotate(0 * Math.PI / 180);
11770                           ctx.fillStyle = "rgba(255,255,255,.8)";
11771                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11772                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11773                           }
11774                           //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11775                           ctx.fillStyle = ctx.strokeStyle = label.color;
11776                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11777                           ctx.restore();
11778
11779             }
11780           }
11781           if(showLabels(node.name, valAcum, node)) {
11782             if(horz) {
11783
11784
11785                 //background box
11786                 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11787                                 inset = 10;
11788                                 
11789                                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11790                 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11791                 boxWidth = mtxt.width+10;
11792                 inset = 10;
11793                 
11794                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11795                         leftPadding = acum - config.labelOffset - boxWidth - inset;
11796                 } else {
11797                         leftPadding = acum + config.labelOffset;
11798                 }
11799                 
11800                 
11801                                 ctx.textAlign = 'left';
11802                                 ctx.translate(x + inset + leftPadding, y + height/2);
11803                                 boxHeight = label.size+6;
11804                                 boxX = -inset/2;
11805                                 boxY = -boxHeight/2;
11806                                 ctx.fillStyle = "rgba(255,255,255,.8)";
11807                                 cornerRadius = 4;
11808                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {  
11809                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11810                                 }
11811                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11812                                 
11813                           ctx.fillStyle = label.color;
11814               ctx.rotate(0 * Math.PI / 180);
11815               ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11816
11817
11818             } else {
11819               //if the number of nodes greater than 8 rotate labels 45 degrees
11820               if(nodeCount > 8) {
11821                                 ctx.textAlign = 'left';
11822                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11823                                 ctx.rotate(45* Math.PI / 180);
11824                                 ctx.fillText(node.name, 0, 0);
11825                           } else {
11826                                 ctx.textAlign = 'center';
11827                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11828                           }
11829             }
11830           }
11831           ctx.restore();
11832         }
11833       }
11834     },
11835     'contains': function(node, mpos) {
11836       var pos = node.pos.getc(true), 
11837           width = node.getData('width'),
11838           height = node.getData('height'),
11839           algnPos = this.getAlignedPos(pos, width, height),
11840           x = algnPos.x, y = algnPos.y,
11841           dimArray = node.getData('dimArray'),
11842           config = node.getData('config'),
11843           rx = mpos.x - x,
11844           horz = config.orientation == 'horizontal';
11845       //bounding box check
11846       if(horz) {
11847         if(mpos.x < x || mpos.x > x + width
11848             || mpos.y > y + height || mpos.y < y) {
11849             return false;
11850           }
11851       } else {
11852         if(mpos.x < x || mpos.x > x + width
11853             || mpos.y > y || mpos.y < y - height) {
11854             return false;
11855           }
11856       }
11857       //deep check
11858       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11859         var dimi = dimArray[i];
11860                 var url = Url.decode(node.getData('linkArray')[i]);
11861         if(horz) {
11862           acum += dimi;
11863           var intersec = acum;
11864           if(mpos.x <= intersec) {
11865             return {
11866               'name': node.getData('stringArray')[i],
11867               'color': node.getData('colorArray')[i],
11868               'value': node.getData('valueArray')[i],
11869               'valuelabel': node.getData('valuelabelArray')[i],
11870                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11871                           'link': url,
11872               'label': node.name
11873             };
11874           }
11875         } else {
11876           acum -= dimi;
11877           var intersec = acum;
11878           if(mpos.y >= intersec) {
11879             return {
11880               'name': node.getData('stringArray')[i],
11881               'color': node.getData('colorArray')[i],
11882               'value': node.getData('valueArray')[i],
11883                           'valuelabel': node.getData('valuelabelArray')[i],
11884                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11885               'link': url,
11886               'label': node.name
11887             };
11888           }
11889         }
11890       }
11891       return false;
11892     }
11893   },
11894   'barchart-grouped' : {
11895     'render' : function(node, canvas) {
11896       var pos = node.pos.getc(true), 
11897           width = node.getData('width'),
11898           height = node.getData('height'),
11899           algnPos = this.getAlignedPos(pos, width, height),
11900           x = algnPos.x, y = algnPos.y,
11901           dimArray = node.getData('dimArray'),
11902           valueArray = node.getData('valueArray'),
11903           valuelabelArray = node.getData('valuelabelArray'),
11904           linkArray = node.getData('linkArray'),
11905           valueLength = valueArray.length,
11906           colorArray = node.getData('colorArray'),
11907           colorLength = colorArray.length,
11908           stringArray = node.getData('stringArray'); 
11909
11910       var ctx = canvas.getCtx(),
11911           canvasSize = canvas.getSize(),
11912           opt = {},
11913           border = node.getData('border'),
11914           gradient = node.getData('gradient'),
11915           config = node.getData('config'),
11916           horz = config.orientation == 'horizontal',
11917           aggregates = config.showAggregates,
11918           showLabels = config.showLabels,
11919           label = config.Label,
11920           shadow = config.shadow,
11921           margin = config.Margin,
11922           fixedDim = (horz? height : width) / valueLength;
11923       
11924       //drop shadow
11925       
11926        maxValue = Math.max.apply(null, dimArray);
11927        
11928        
11929           
11930            ctx.fillStyle = "rgba(0,0,0,.2)";
11931       if (colorArray && dimArray && stringArray && shadow.enable) {
11932                  shadowThickness = shadow.size;
11933
11934         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11935                 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11936                 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11937                 if(horz) {
11938                                     
11939                         ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11940                                         
11941                 } else {
11942                         
11943                         if(i == 0) {
11944                                 if(nextBar && nextBar > dimArray[i]) {
11945                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11946                                 } else if (nextBar && nextBar < dimArray[i]){
11947                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11948                                 } else {
11949                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11950                                 }
11951                         } else if (i> 0 && i<l-1) {
11952                                 if(nextBar && nextBar > dimArray[i]) {
11953                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11954                                 } else if (nextBar && nextBar < dimArray[i]){
11955                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11956                                 } else {
11957                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11958                                 }
11959                         } else if (i == l-1) {
11960                                 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11961                         }
11962                         
11963                         
11964                 }
11965         }
11966
11967       } 
11968                         
11969       
11970       if (colorArray && dimArray && stringArray) {
11971         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11972           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11973           if(gradient) {
11974             var linear;
11975             if(horz) {
11976               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
11977                   x + dimArray[i]/2, y + fixedDim * (i + 1));
11978             } else {
11979               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
11980                   x + fixedDim * (i + 1), y - dimArray[i]/2);
11981             }
11982             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11983                 function(v) { return (v * 0.8) >> 0; }));
11984             linear.addColorStop(0, color);
11985             linear.addColorStop(0.3, colorArray[i % colorLength]);
11986             linear.addColorStop(0.7, colorArray[i % colorLength]);
11987             linear.addColorStop(1, color);
11988             ctx.fillStyle = linear;
11989           }
11990           if(horz) {
11991             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11992           } else {
11993             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11994           }
11995           if(border && border.name == stringArray[i]) {
11996             opt.acum = fixedDim * i;
11997             opt.dimValue = dimArray[i];
11998           }
11999           acum += (dimArray[i] || 0);
12000           valAcum += (valueArray[i] || 0);
12001                   ctx.fillStyle = ctx.strokeStyle = label.color;
12002           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12003           
12004           inset = 10;
12005                   if(aggregates(node.name, valAcum) && label.type == 'Native') {
12006                                 if(valuelabelArray[i]) {
12007                                         acumValueLabel = valuelabelArray[i];
12008                                 } else {
12009                                         acumValueLabel = valueArray[i];
12010                                 }
12011                            if(horz) {
12012                                   ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12013                                   ctx.textAlign = 'left';
12014                                   ctx.textBaseline = 'top';
12015                                   ctx.fillStyle = "rgba(255,255,255,.8)";
12016                                   //background box
12017                                   gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
12018                                   mtxt = ctx.measureText(acumValueLabel);
12019                                   boxWidth = mtxt.width+10;
12020                                   
12021                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12022                                         leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
12023                                   } else {
12024                                         leftPadding = dimArray[i] + config.labelOffset + inset;
12025                                   }
12026                               boxHeight = label.size+6;
12027                                   boxX = x + leftPadding;
12028                                   boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
12029                                   cornerRadius = 4;     
12030                                   
12031                                   
12032                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12033                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12034                                   }
12035                                 //  $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12036                                   
12037                                   ctx.fillStyle = ctx.strokeStyle = label.color;
12038                                   ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12039                                   
12040
12041                                         
12042                                         
12043                                 } else {
12044                                   
12045                                         ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12046                                         ctx.save();
12047                                         ctx.textAlign = 'center';
12048                                         
12049                                         //background box
12050                                         gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12051                                          (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12052                                          (label ? label.size + config.labelOffset : 0));
12053                                         
12054                                         mtxt = ctx.measureText(acumValueLabel);
12055                                         boxWidth = mtxt.width+10;
12056                                         boxHeight = label.size+6;
12057                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12058                                                 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12059                                         } else {
12060                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12061                                         }
12062                                                                                                 
12063                                         
12064                                         ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12065                                         
12066                                         boxX = -boxWidth/2;
12067                                         boxY = -boxHeight/2;
12068                                         ctx.fillStyle = "rgba(255,255,255,.8)";
12069                                         
12070                                         cornerRadius = 4;       
12071
12072                                         //ctx.rotate(270* Math.PI / 180);
12073                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12074                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12075                                         }
12076                                         //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12077                                         
12078                                         ctx.fillStyle = ctx.strokeStyle = label.color;
12079                                         ctx.fillText(acumValueLabel, 0,0);
12080                                         ctx.restore();
12081
12082                                 }
12083                         }
12084         }
12085         if(border) {
12086           ctx.save();
12087           ctx.lineWidth = 2;
12088           ctx.strokeStyle = border.color;
12089           if(horz) {
12090             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12091           } else {
12092             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12093           }
12094           ctx.restore();
12095         }
12096         if(label.type == 'Native') {
12097           ctx.save();
12098           ctx.fillStyle = ctx.strokeStyle = label.color;
12099           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12100           ctx.textBaseline = 'middle';
12101
12102           if(showLabels(node.name, valAcum, node)) {
12103             if(horz) {
12104               ctx.textAlign = 'center';
12105               ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12106               ctx.rotate(Math.PI / 2);
12107               ctx.fillText(node.name, 0, 0);
12108             } else {
12109               ctx.textAlign = 'center';
12110               ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12111             }
12112           }
12113           ctx.restore();
12114         }
12115       }
12116     },
12117     'contains': function(node, mpos) {
12118       var pos = node.pos.getc(true), 
12119           width = node.getData('width'),
12120           height = node.getData('height'),
12121           algnPos = this.getAlignedPos(pos, width, height),
12122           x = algnPos.x, y = algnPos.y,
12123           dimArray = node.getData('dimArray'),
12124           len = dimArray.length,
12125           config = node.getData('config'),
12126           rx = mpos.x - x,
12127           horz = config.orientation == 'horizontal',
12128           fixedDim = (horz? height : width) / len;
12129       //bounding box check
12130       if(horz) {
12131         if(mpos.x < x || mpos.x > x + width
12132             || mpos.y > y + height || mpos.y < y) {
12133             return false;
12134           }
12135       } else {
12136         if(mpos.x < x || mpos.x > x + width
12137             || mpos.y > y || mpos.y < y - height) {
12138             return false;
12139           }
12140       }
12141       //deep check
12142       for(var i=0, l=dimArray.length; i<l; i++) {
12143         var dimi = dimArray[i];
12144                 var url = Url.decode(node.getData('linkArray')[i]);
12145         if(horz) {
12146           var limit = y + fixedDim * i;
12147           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12148             return {
12149               'name': node.getData('stringArray')[i],
12150               'color': node.getData('colorArray')[i],
12151               'value': node.getData('valueArray')[i],
12152                           'valuelabel': node.getData('valuelabelArray')[i],
12153               'title': node.getData('titleArray')[i],
12154                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12155               'link': url,
12156               'label': node.name
12157             };
12158           }
12159         } else {
12160           var limit = x + fixedDim * i;
12161           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12162             return {
12163               'name': node.getData('stringArray')[i],
12164               'color': node.getData('colorArray')[i],
12165               'value': node.getData('valueArray')[i],
12166                           'valuelabel': node.getData('valuelabelArray')[i],
12167               'title': node.getData('titleArray')[i],
12168                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12169               'link': url,
12170               'label': node.name
12171             };
12172           }
12173         }
12174       }
12175       return false;
12176     }
12177   },
12178   'barchart-basic' : {
12179     'render' : function(node, canvas) {
12180       var pos = node.pos.getc(true), 
12181           width = node.getData('width'),
12182           height = node.getData('height'),
12183           algnPos = this.getAlignedPos(pos, width, height),
12184           x = algnPos.x, y = algnPos.y,
12185           dimArray = node.getData('dimArray'),
12186           valueArray = node.getData('valueArray'),
12187                   valuelabelArray = node.getData('valuelabelArray'),
12188           linkArray = node.getData('linkArray'),
12189           valueLength = valueArray.length,
12190           colorArray = node.getData('colorMono'),
12191           colorLength = colorArray.length,
12192           stringArray = node.getData('stringArray'); 
12193
12194       var ctx = canvas.getCtx(),
12195           canvasSize = canvas.getSize(),
12196           opt = {},
12197           border = node.getData('border'),
12198           gradient = node.getData('gradient'),
12199           config = node.getData('config'),
12200           horz = config.orientation == 'horizontal',
12201           aggregates = config.showAggregates,
12202           showLabels = config.showLabels,
12203           label = config.Label,
12204           fixedDim = (horz? height : width) / valueLength,
12205           margin = config.Margin;
12206       
12207       if (colorArray && dimArray && stringArray) {
12208         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12209           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12210
12211           if(gradient) {
12212             var linear;
12213             if(horz) {
12214               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
12215                   x + dimArray[i]/2, y + fixedDim * (i + 1));
12216             } else {
12217               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
12218                   x + fixedDim * (i + 1), y - dimArray[i]/2);
12219             }
12220             //drop shadow 
12221            if(config.shadow.size) {
12222                   shadowThickness = config.shadow.size;
12223                   ctx.fillStyle = "rgba(0,0,0,.2)";
12224                   if(horz) {
12225                     ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12226                   } else {
12227                     ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12228                   }
12229           }
12230           
12231             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
12232                 function(v) { return (v * 0.8) >> 0; }));
12233             linear.addColorStop(0, color);
12234             linear.addColorStop(0.3, colorArray[i % colorLength]);
12235             linear.addColorStop(0.7, colorArray[i % colorLength]);
12236             linear.addColorStop(1, color);
12237             ctx.fillStyle = linear;
12238           }
12239           if(horz) {
12240             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12241           } else {
12242             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12243           }
12244           if(border && border.name == stringArray[i]) {
12245             opt.acum = fixedDim * i;
12246             opt.dimValue = dimArray[i];
12247           }
12248           acum += (dimArray[i] || 0);
12249           valAcum += (valueArray[i] || 0);
12250                   
12251               if(label.type == 'Native') {
12252                                  ctx.fillStyle = ctx.strokeStyle = label.color;
12253                                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12254                                  if(aggregates(node.name, valAcum)) {
12255                                         if(valuelabelArray[i]) {
12256                                                 acumValueLabel = valuelabelArray[i];
12257                                           } else {
12258                                                 acumValueLabel = valueArray[i];
12259                                           }
12260                                          if(!horz) {
12261                                           ctx.textAlign = 'center';
12262                                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12263                                           //background box
12264                                           ctx.save();
12265                                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12266                                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12267                                                  (label ? label.size + config.labelOffset : 0));
12268                           mtxt = ctx.measureText(acumValueLabel);
12269                                           boxWidth = mtxt.width+10;
12270                                           inset = 10;
12271                                           boxHeight = label.size+6;
12272                                           
12273                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12274                                                 bottomPadding = dimArray[i] - config.labelOffset  - inset;
12275                                           } else {
12276                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12277                                           }
12278                                         
12279                                         
12280                                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12281                                           cornerRadius = 4;     
12282                                           boxX = -inset/2;
12283                                           boxY = -boxHeight/2;
12284                                           
12285                                           //ctx.rotate(270* Math.PI / 180);
12286                                           ctx.fillStyle = "rgba(255,255,255,.6)";
12287                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12288                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12289                                           }
12290                                          // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12291                                           ctx.fillStyle = ctx.strokeStyle = label.color;
12292                                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12293                                           ctx.restore();
12294                                         }
12295                                 }
12296                 }
12297         }
12298         if(border) {
12299           ctx.save();
12300           ctx.lineWidth = 2;
12301           ctx.strokeStyle = border.color;
12302           if(horz) {
12303             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12304           } else {
12305             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12306           }
12307           ctx.restore();
12308         }
12309         if(label.type == 'Native') {
12310           ctx.save();
12311           ctx.fillStyle = ctx.strokeStyle = label.color;
12312           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12313           ctx.textBaseline = 'middle';
12314           if(showLabels(node.name, valAcum, node)) {
12315             if(horz) {
12316                 
12317                 //background box
12318                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12319                 mtxt = ctx.measureText(node.name + ": " + valAcum);
12320                 boxWidth = mtxt.width+10;
12321                 inset = 10;
12322                 
12323                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12324                         leftPadding = acum - config.labelOffset - boxWidth - inset;
12325                 } else {
12326                         leftPadding = acum + config.labelOffset;
12327                 }
12328                 
12329                                 
12330                                 ctx.textAlign = 'left';
12331                                 ctx.translate(x + inset + leftPadding, y + height/2);
12332                                 boxHeight = label.size+6;
12333                                 boxX = -inset/2;
12334                                 boxY = -boxHeight/2;
12335                                 ctx.fillStyle = "rgba(255,255,255,.8)";
12336                                 
12337                                 cornerRadius = 4;       
12338                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12339                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12340                                 }
12341                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12342                 
12343                                 
12344                                 ctx.fillStyle = label.color;
12345                                 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12346
12347             } else {
12348               
12349                           if(stringArray.length > 8) {
12350                                 ctx.textAlign = 'left';
12351                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12352                                 ctx.rotate(45* Math.PI / 180);
12353                                 ctx.fillText(node.name, 0, 0);
12354                           } else {
12355                                 ctx.textAlign = 'center';
12356                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12357                           }
12358               
12359             }
12360           }
12361           ctx.restore();
12362         }
12363       }
12364     },
12365     'contains': function(node, mpos) {
12366       var pos = node.pos.getc(true), 
12367           width = node.getData('width'),
12368           height = node.getData('height'),
12369           config = node.getData('config'),
12370           algnPos = this.getAlignedPos(pos, width, height),
12371           x = algnPos.x, y = algnPos.y ,
12372           dimArray = node.getData('dimArray'),
12373           len = dimArray.length,
12374           rx = mpos.x - x,
12375           horz = config.orientation == 'horizontal',
12376           fixedDim = (horz? height : width) / len;
12377
12378       //bounding box check
12379       if(horz) {
12380         if(mpos.x < x || mpos.x > x + width
12381             || mpos.y > y + height || mpos.y < y) {
12382             return false;
12383           }
12384       } else {
12385         if(mpos.x < x || mpos.x > x + width
12386             || mpos.y > y || mpos.y < y - height) {
12387             return false;
12388           }
12389       }
12390       //deep check
12391       for(var i=0, l=dimArray.length; i<l; i++) {
12392         var dimi = dimArray[i];
12393                 var url = Url.decode(node.getData('linkArray')[i]);
12394         if(horz) {
12395           var limit = y + fixedDim * i;
12396           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12397             return {
12398               'name': node.getData('stringArray')[i],
12399               'color': node.getData('colorArray')[i],
12400               'value': node.getData('valueArray')[i],
12401                           'valuelabel': node.getData('valuelabelArray')[i],
12402                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12403               'link': url,
12404               'label': node.name
12405             };
12406           }
12407         } else {
12408           var limit = x + fixedDim * i;
12409           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12410             return {
12411               'name': node.getData('stringArray')[i],
12412               'color': node.getData('colorArray')[i],
12413               'value': node.getData('valueArray')[i],
12414                           'valuelabel': node.getData('valuelabelArray')[i],
12415                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12416               'link': url,
12417               'label': node.name
12418             };
12419           }
12420         }
12421       }
12422       return false;
12423     }
12424   }
12425 });
12426
12427 /*
12428   Class: BarChart
12429   
12430   A visualization that displays stacked bar charts.
12431   
12432   Constructor Options:
12433   
12434   See <Options.BarChart>.
12435
12436 */
12437 $jit.BarChart = new Class({
12438   st: null,
12439   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12440   selected: {},
12441   busy: false,
12442   
12443   initialize: function(opt) {
12444     this.controller = this.config = 
12445       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12446         Label: { type: 'Native' }
12447       }, opt);
12448     //set functions for showLabels and showAggregates
12449     var showLabels = this.config.showLabels,
12450         typeLabels = $.type(showLabels),
12451         showAggregates = this.config.showAggregates,
12452         typeAggregates = $.type(showAggregates);
12453     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12454     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12455     Options.Fx.clearCanvas = false;
12456     this.initializeViz();
12457   },
12458   
12459   initializeViz: function() {
12460     var config = this.config, that = this;
12461     var nodeType = config.type.split(":")[0],
12462         horz = config.orientation == 'horizontal',
12463         nodeLabels = {};
12464     var st = new $jit.ST({
12465       injectInto: config.injectInto,
12466       orientation: horz? 'left' : 'bottom',
12467       background: config.background,
12468       renderBackground: config.renderBackground,
12469       backgroundColor: config.backgroundColor,
12470       colorStop1: config.colorStop1,
12471       colorStop2: config.colorStop2,
12472       levelDistance: 0,
12473       nodeCount: config.nodeCount,
12474       siblingOffset: config.barsOffset,
12475       subtreeOffset: 0,
12476       withLabels: config.Label.type != 'Native',      
12477       useCanvas: config.useCanvas,
12478       Label: {
12479         type: config.Label.type
12480       },
12481       Node: {
12482         overridable: true,
12483         type: 'barchart-' + nodeType,
12484         align: 'left',
12485         width: 1,
12486         height: 1
12487       },
12488       Edge: {
12489         type: 'none'
12490       },
12491       Tips: {
12492         enable: config.Tips.enable,
12493         type: 'Native',
12494         force: true,
12495         onShow: function(tip, node, contains) {
12496           var elem = contains;
12497           config.Tips.onShow(tip, elem, node);
12498                           if(elem.link != 'undefined' && elem.link != '') {
12499                                 document.body.style.cursor = 'pointer';
12500                           }
12501         },
12502                 onHide: function(call) {
12503                         document.body.style.cursor = 'default';
12504
12505         }
12506       },
12507       Events: {
12508         enable: true,
12509         type: 'Native',
12510         onClick: function(node, eventInfo, evt) {
12511           if(!config.Events.enable) return;
12512           var elem = eventInfo.getContains();
12513           config.Events.onClick(elem, eventInfo, evt);
12514         },
12515         onMouseMove: function(node, eventInfo, evt) {
12516           if(!config.hoveredColor) return;
12517           if(node) {
12518             var elem = eventInfo.getContains();
12519             that.select(node.id, elem.name, elem.index);
12520           } else {
12521             that.select(false, false, false);
12522           }
12523         }
12524       },
12525       onCreateLabel: function(domElement, node) {
12526         var labelConf = config.Label,
12527             valueArray = node.getData('valueArray'),
12528             acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12529             grouped = config.type.split(':')[0] == 'grouped',
12530             horz = config.orientation == 'horizontal';
12531         var nlbs = {
12532           wrapper: document.createElement('div'),
12533           aggregate: document.createElement('div'),
12534           label: document.createElement('div')
12535         };
12536         
12537         var wrapper = nlbs.wrapper,
12538             label = nlbs.label,
12539             aggregate = nlbs.aggregate,
12540             wrapperStyle = wrapper.style,
12541             labelStyle = label.style,
12542             aggregateStyle = aggregate.style;
12543         //store node labels
12544         nodeLabels[node.id] = nlbs;
12545         //append labels
12546         wrapper.appendChild(label);
12547         wrapper.appendChild(aggregate);
12548         if(!config.showLabels(node.name, acum, node)) {
12549           labelStyle.display = 'none';
12550         }
12551         if(!config.showAggregates(node.name, acum, node)) {
12552           aggregateStyle.display = 'none';
12553         }
12554         wrapperStyle.position = 'relative';
12555         wrapperStyle.overflow = 'visible';
12556         wrapperStyle.fontSize = labelConf.size + 'px';
12557         wrapperStyle.fontFamily = labelConf.family;
12558         wrapperStyle.color = labelConf.color;
12559         wrapperStyle.textAlign = 'center';
12560         aggregateStyle.position = labelStyle.position = 'absolute';
12561         
12562         domElement.style.width = node.getData('width') + 'px';
12563         domElement.style.height = node.getData('height') + 'px';
12564         aggregateStyle.left = "0px";
12565         labelStyle.left =  config.labelOffset + 'px';
12566         labelStyle.whiteSpace =  "nowrap";
12567                 label.innerHTML = node.name;       
12568         
12569         domElement.appendChild(wrapper);
12570       },
12571       onPlaceLabel: function(domElement, node) {
12572         if(!nodeLabels[node.id]) return;
12573         var labels = nodeLabels[node.id],
12574             wrapperStyle = labels.wrapper.style,
12575             labelStyle = labels.label.style,
12576             aggregateStyle = labels.aggregate.style,
12577             grouped = config.type.split(':')[0] == 'grouped',
12578             horz = config.orientation == 'horizontal',
12579             dimArray = node.getData('dimArray'),
12580             valArray = node.getData('valueArray'),
12581             nodeCount = node.getData('nodeCount'),
12582             valueLength = valArray.length;
12583             valuelabelArray = node.getData('valuelabelArray'),
12584             stringArray = node.getData('stringArray'),
12585             width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12586             height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12587             font = parseInt(wrapperStyle.fontSize, 10),
12588             domStyle = domElement.style,
12589             fixedDim = (horz? height : width) / valueLength;
12590             
12591         
12592         if(dimArray && valArray) {
12593           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12594           
12595           aggregateStyle.width = width  - config.labelOffset + "px";
12596           for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12597             if(dimArray[i] > 0) {
12598               acum+= valArray[i];
12599             }
12600           }
12601           if(config.showLabels(node.name, acum, node)) {
12602             labelStyle.display = '';
12603           } else {
12604             labelStyle.display = 'none';
12605           }
12606           if(config.showAggregates(node.name, acum, node)) {
12607             aggregateStyle.display = '';
12608           } else {
12609             aggregateStyle.display = 'none';
12610           }
12611           if(config.orientation == 'horizontal') {
12612             aggregateStyle.textAlign = 'right';
12613             labelStyle.textAlign = 'left';
12614             labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12615             aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12616             domElement.style.height = wrapperStyle.height = height + 'px';
12617           } else {
12618             aggregateStyle.top = (-font - config.labelOffset) + 'px';
12619             labelStyle.top = (config.labelOffset + height) + 'px';
12620             domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12621             domElement.style.height = wrapperStyle.height = height + 'px';
12622             if(stringArray.length > 8) {
12623                 labels.label.className = "rotatedLabelReverse";
12624                 labelStyle.textAlign = "left";
12625                 labelStyle.top = config.labelOffset + height + width/2 + "px";
12626             }
12627           }
12628           
12629           if(horz) {
12630
12631                         labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12632                         labels.aggregate.innerHTML = "";
12633
12634           } else {
12635                 
12636                         if(grouped) {
12637                                 maxValue = Math.max.apply(null,dimArray);
12638                                 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12639                                         valueLabelDim = 50;
12640                                         valueLabel = document.createElement('div');
12641                                         valueLabel.innerHTML =  valuelabelArray[i];
12642 //                                      valueLabel.class = "rotatedLabel";
12643                                         valueLabel.className = "rotatedLabel";
12644                                         valueLabel.style.position = "absolute";
12645                                                 valueLabel.style.textAlign = "left";
12646                                                 valueLabel.style.verticalAlign = "middle";
12647                                         valueLabel.style.height = valueLabelDim + "px";
12648                                         valueLabel.style.width = valueLabelDim + "px";
12649                                         valueLabel.style.top =  (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12650                                         valueLabel.style.left = (fixedDim * i) + "px";
12651                                         labels.wrapper.appendChild(valueLabel);
12652                                 }
12653                         } else {
12654                                 labels.aggregate.innerHTML = acum;
12655                         }
12656           }
12657         }
12658       }
12659     });
12660
12661     var size = st.canvas.getSize(),
12662         l = config.nodeCount,
12663         margin = config.Margin;
12664         title = config.Title;
12665         subtitle = config.Subtitle,
12666         grouped = config.type.split(':')[0] == 'grouped',
12667         margin = config.Margin,
12668         ticks = config.Ticks,
12669         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12670         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12671         horz = config.orientation == 'horizontal',
12672         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12673         fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12674         whiteSpace = size.width - (marginWidth + (fixedDim * l));
12675         //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
12676         if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12677         location.reload();
12678         //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12679         if(!grouped && !horz) {
12680                 st.config.siblingOffset = whiteSpace/(l+1);
12681         }
12682         
12683         
12684         
12685         //Bars offset
12686     if(horz) {
12687       st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);    
12688           if(config.Ticks.enable)       {
12689                 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;
12690           } else {
12691                 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12692           }
12693     } else {
12694       st.config.offsetY = -size.height/2 + margin.bottom 
12695         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12696           if(config.Ticks.enable)       {
12697                 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12698           } else {
12699                 st.config.offsetX = (margin.right - margin.left)/2;
12700           }
12701     }
12702     this.st = st;
12703     this.canvas = this.st.canvas;
12704   },
12705   
12706  
12707   
12708   renderTitle: function() {
12709         var canvas = this.canvas,
12710         size = canvas.getSize(),
12711         config = this.config,
12712         margin = config.Margin,
12713         label = config.Label,
12714         title = config.Title;
12715         ctx = canvas.getCtx();
12716         ctx.fillStyle = title.color;
12717         ctx.textAlign = 'left';
12718         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12719         if(label.type == 'Native') {
12720                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12721         }
12722   },  
12723   
12724   renderSubtitle: function() {
12725         var canvas = this.canvas,
12726         size = canvas.getSize(),
12727         config = this.config,
12728         margin = config.Margin,
12729         label = config.Label,
12730         subtitle = config.Subtitle,
12731         nodeCount = config.nodeCount,
12732         horz = config.orientation == 'horizontal' ? true : false,
12733         ctx = canvas.getCtx();
12734         ctx.fillStyle = title.color;
12735         ctx.textAlign = 'left';
12736         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12737         if(label.type == 'Native') {
12738                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12739         }
12740   },
12741   
12742   renderScrollNote: function() {
12743         var canvas = this.canvas,
12744         size = canvas.getSize(),
12745         config = this.config,
12746         margin = config.Margin,
12747         label = config.Label,
12748         note = config.ScrollNote;
12749         ctx = canvas.getCtx();
12750         ctx.fillStyle = title.color;
12751         title = config.Title;
12752         ctx.textAlign = 'center';
12753         ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12754         if(label.type == 'Native') {
12755                 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12756         }
12757   },  
12758   
12759   renderTicks: function() {
12760
12761         var canvas = this.canvas,
12762         size = canvas.getSize(),
12763         config = this.config,
12764         margin = config.Margin,
12765         ticks = config.Ticks,
12766         title = config.Title,
12767         subtitle = config.Subtitle,
12768         label = config.Label,
12769         shadow = config.shadow;
12770         horz = config.orientation == 'horizontal',
12771         maxValue = this.getMaxValue(),
12772         maxTickValue = Math.ceil(maxValue*.1)*10;
12773         if(maxTickValue == maxValue) {
12774                 var length = maxTickValue.toString().length;
12775                 maxTickValue = maxTickValue + parseInt(pad(1,length));
12776         }
12777         grouped = config.type.split(':')[0] == 'grouped',
12778         labelValue = 0,
12779         labelIncrement = maxTickValue/ticks.segments,
12780         ctx = canvas.getCtx();
12781         ctx.strokeStyle = ticks.color;
12782     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12783
12784         ctx.textAlign = 'center';
12785         ctx.textBaseline = 'middle';
12786         
12787         idLabel = canvas.id + "-label";
12788         labelDim = 100;
12789         container = document.getElementById(idLabel);
12790                   
12791                   
12792         if(horz) {
12793                 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12794                 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12795                 segmentLength = grid/ticks.segments;
12796                 ctx.fillStyle = ticks.color;
12797                 ctx.fillRect(axis,
12798                  (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12799                  size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12800                  1);
12801                 while(axis<=grid) {
12802                         ctx.fillStyle = ticks.color;
12803                         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);
12804                         ctx.fillRect(Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0) - (shadow.enable ? shadow.size : 0), 1, lineHeight + (shadow.enable ? shadow.size * 2: 0));
12805                         ctx.fillStyle = label.color;
12806                         
12807                         if(label.type == 'Native' && config.showLabels) {            
12808                  ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12809                         }
12810                         axis += segmentLength;
12811                         labelValue += labelIncrement;
12812                 }
12813         
12814         } else {
12815         
12816                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12817                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12818                 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)),
12819                 segmentLength = grid/ticks.segments;
12820                 ctx.fillStyle = ticks.color;
12821                 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));
12822
12823                 while(axis>=grid) {
12824                         ctx.save();
12825                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12826                         ctx.rotate(0 * Math.PI / 180 );
12827                         ctx.fillStyle = label.color;
12828                         if(config.showLabels) {
12829                                 if(label.type == 'Native') { 
12830                                         ctx.fillText(labelValue, 0, 0);
12831                                 } else {
12832                                         //html labels on y axis
12833                                         labelDiv = document.createElement('div');
12834                                         labelDiv.innerHTML = labelValue;
12835                                         labelDiv.className = "rotatedLabel";
12836 //                                      labelDiv.class = "rotatedLabel";
12837                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12838                                         labelDiv.style.left = margin.left + "px";
12839                                         labelDiv.style.width = labelDim + "px";
12840                                         labelDiv.style.height = labelDim + "px";
12841                                         labelDiv.style.textAlign = "center";
12842                                         labelDiv.style.verticalAlign = "middle";
12843                                         labelDiv.style.position = "absolute";
12844                                         container.appendChild(labelDiv);
12845                                 }
12846                         }
12847                         ctx.restore();
12848                         ctx.fillStyle = ticks.color;
12849                         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 );
12850                         htmlOrigin += segmentLength;
12851                         axis += segmentLength;
12852                         labelValue += labelIncrement;
12853                 }
12854         }
12855         
12856         
12857         
12858
12859   },
12860   
12861   renderBackground: function() {
12862                 var canvas = this.canvas,
12863                 config = this.config,
12864                 backgroundColor = config.backgroundColor,
12865                 size = canvas.getSize(),
12866                 ctx = canvas.getCtx();
12867             ctx.fillStyle = backgroundColor;
12868             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
12869   },
12870   
12871   clear: function() {
12872         var canvas = this.canvas;
12873         var ctx = canvas.getCtx(),
12874         size = canvas.getSize();
12875         ctx.fillStyle = "rgba(255,255,255,0)";
12876         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12877         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
12878  },
12879   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
12880         var canvas = this.canvas,
12881         size = canvas.getSize(),
12882         config = this.config,
12883         orgHeight = size.height,
12884         margin = config.Margin,
12885         st = this.st,
12886         grouped = config.type.split(':')[0] == 'grouped',
12887         horz = config.orientation == 'horizontal',
12888                 ctx = canvas.getCtx();
12889         
12890         var newWindowWidth = document.body.offsetWidth;
12891         var diff = newWindowWidth - orgWindowWidth;     
12892         var newWidth = orgContainerDivWidth + (diff/cols);
12893         var scale = newWidth/orgContainerDivWidth;
12894         canvas.resize(newWidth,orgHeight);
12895         if(typeof FlashCanvas == "undefined") {
12896                 canvas.clear();
12897         } else {
12898                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
12899         }
12900         if(horz) {
12901                 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12902         }
12903         
12904         this.loadJSON(json);
12905
12906         
12907         },
12908   /*
12909     Method: loadJSON
12910    
12911     Loads JSON data into the visualization. 
12912     
12913     Parameters:
12914     
12915     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>.
12916     
12917     Example:
12918     (start code js)
12919     var barChart = new $jit.BarChart(options);
12920     barChart.loadJSON(json);
12921     (end code)
12922  */  
12923   loadJSON: function(json) {
12924     if(this.busy) return;
12925     this.busy = true;
12926     
12927     var prefix = $.time(), 
12928         ch = [], 
12929         st = this.st,
12930         name = $.splat(json.label), 
12931         color = $.splat(json.color || this.colors),
12932         config = this.config,
12933         gradient = !!config.type.split(":")[1],
12934         renderBackground = config.renderBackground,
12935         animate = config.animate,
12936         ticks = config.Ticks,
12937         title = config.Title,
12938         note = config.ScrollNote,
12939         subtitle = config.Subtitle,
12940         horz = config.orientation == 'horizontal',
12941         that = this,
12942                 colorLength = color.length,
12943                 nameLength = name.length;
12944         groupTotalValue = 0;
12945     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12946         var val = values[i];
12947         var valArray = $.splat(val.values);
12948         groupTotalValue += parseFloat(valArray.sum());
12949     }
12950
12951     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12952       var val = values[i];
12953       var valArray = $.splat(values[i].values);
12954       var valuelabelArray = $.splat(values[i].valuelabels);
12955       var linkArray = $.splat(values[i].links);
12956       var titleArray = $.splat(values[i].titles);
12957       var barTotalValue = valArray.sum();
12958       var acum = 0;
12959       ch.push({
12960         'id': prefix + val.label,
12961         'name': val.label,
12962         
12963         'data': {
12964           'value': valArray,
12965           '$linkArray': linkArray,
12966                   '$gvl': val.gvaluelabel,
12967           '$titleArray': titleArray,
12968           '$valueArray': valArray,
12969           '$valuelabelArray': valuelabelArray,
12970           '$colorArray': color,
12971           '$colorMono': $.splat(color[i % colorLength]),
12972           '$stringArray': name,
12973           '$barTotalValue': barTotalValue,
12974           '$groupTotalValue': groupTotalValue,
12975           '$nodeCount': values.length,
12976           '$gradient': gradient,
12977           '$config': config
12978         },
12979         'children': []
12980       });
12981     }
12982     var root = {
12983       'id': prefix + '$root',
12984       'name': '',
12985       'data': {
12986         '$type': 'none',
12987         '$width': 1,
12988         '$height': 1
12989       },
12990       'children': ch
12991     };
12992     st.loadJSON(root);
12993     
12994     this.normalizeDims();
12995     
12996     if(renderBackground) {
12997                 this.renderBackground();
12998     }
12999         
13000         if(!animate && ticks.enable) {
13001                 this.renderTicks();
13002         }
13003         if(!animate && note.text) {
13004                 this.renderScrollNote();
13005         }
13006         if(!animate && title.text) {
13007                 this.renderTitle();
13008         }
13009         if(!animate && subtitle.text) {
13010                 this.renderSubtitle();
13011         }
13012
13013     st.compute();
13014     st.select(st.root);
13015     if(animate) {
13016       if(horz) {
13017         st.fx.animate({
13018           modes: ['node-property:width:dimArray'],
13019           duration:1500,
13020           onComplete: function() {
13021             that.busy = false;
13022           }
13023         });
13024       } else {
13025         st.fx.animate({
13026           modes: ['node-property:height:dimArray'],
13027           duration:1500,
13028           onComplete: function() {
13029             that.busy = false;
13030           }
13031         });
13032       }
13033     } else {
13034       this.busy = false;
13035     }
13036   },
13037   
13038   /*
13039     Method: updateJSON
13040    
13041     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.
13042     
13043     Parameters:
13044     
13045     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13046     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13047     
13048     Example:
13049     
13050     (start code js)
13051     barChart.updateJSON(json, {
13052       onComplete: function() {
13053         alert('update complete!');
13054       }
13055     });
13056     (end code)
13057  */  
13058   updateJSON: function(json, onComplete) {
13059     if(this.busy) return;
13060     this.busy = true;
13061     
13062     var st = this.st;
13063     var graph = st.graph;
13064     var values = json.values;
13065     var animate = this.config.animate;
13066     var that = this;
13067     var horz = this.config.orientation == 'horizontal';
13068     $.each(values, function(v) {
13069       var n = graph.getByName(v.label);
13070       if(n) {
13071         n.setData('valueArray', $.splat(v.values));
13072         if(json.label) {
13073           n.setData('stringArray', $.splat(json.label));
13074         }
13075       }
13076     });
13077     this.normalizeDims();
13078     st.compute();
13079     st.select(st.root);
13080     if(animate) {
13081       if(horz) {
13082         st.fx.animate({
13083           modes: ['node-property:width:dimArray'],
13084           duration:1500,
13085           onComplete: function() {
13086             that.busy = false;
13087             onComplete && onComplete.onComplete();
13088           }
13089         });
13090       } else {
13091         st.fx.animate({
13092           modes: ['node-property:height:dimArray'],
13093           duration:1500,
13094           onComplete: function() {
13095             that.busy = false;
13096             onComplete && onComplete.onComplete();
13097           }
13098         });
13099       }
13100     }
13101   },
13102   
13103   //adds the little brown bar when hovering the node
13104   select: function(id, name) {
13105
13106     if(!this.config.hoveredColor) return;
13107     var s = this.selected;
13108     if(s.id != id || s.name != name) {
13109       s.id = id;
13110       s.name = name;
13111       s.color = this.config.hoveredColor;
13112       this.st.graph.eachNode(function(n) {
13113         if(id == n.id) {
13114           n.setData('border', s);
13115         } else {
13116           n.setData('border', false);
13117         }
13118       });
13119       this.st.plot();
13120     }
13121   },
13122   
13123   /*
13124     Method: getLegend
13125    
13126     Returns an object containing as keys the legend names and as values hex strings with color values.
13127     
13128     Example:
13129     
13130     (start code js)
13131     var legend = barChart.getLegend();
13132     (end code)
13133   */  
13134   getLegend: function() {
13135     var legend = new Array();
13136     var name = new Array();
13137     var color = new Array();
13138     var n;
13139     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13140       n = adj.nodeTo;
13141     });
13142     var colors = n.getData('colorArray'),
13143         len = colors.length;
13144     $.each(n.getData('stringArray'), function(s, i) {
13145       color[i] = colors[i % len];
13146       name[i] = s;
13147     });
13148         legend['name'] = name;
13149         legend['color'] = color;
13150     return legend;
13151   },
13152   
13153   /*
13154     Method: getMaxValue
13155    
13156     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13157     
13158     Example:
13159     
13160     (start code js)
13161     var ans = barChart.getMaxValue();
13162     (end code)
13163     
13164     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13165     
13166     Example:
13167     
13168     (start code js)
13169     //will return 100 for all BarChart instances,
13170     //displaying all of them with the same scale
13171     $jit.BarChart.implement({
13172       'getMaxValue': function() {
13173         return 100;
13174       }
13175     });
13176     (end code)
13177     
13178   */  
13179   getMaxValue: function() {
13180     var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13181     this.st.graph.eachNode(function(n) {
13182       var valArray = n.getData('valueArray'),
13183           acum = 0;
13184       if(!valArray) return;
13185       if(stacked) {
13186         $.each(valArray, function(v) { 
13187           acum += +v;
13188         });
13189       } else {
13190         acum = Math.max.apply(null, valArray);
13191       }
13192       maxValue = maxValue>acum? maxValue:acum;
13193     });
13194     return maxValue;
13195   },
13196   
13197   setBarType: function(type) {
13198     this.config.type = type;
13199     this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13200   },
13201   
13202   normalizeDims: function() {
13203     //number of elements
13204     var root = this.st.graph.getNode(this.st.root), l=0;
13205     root.eachAdjacency(function() {
13206       l++;
13207     });
13208     var maxValue = this.getMaxValue() || 1,
13209         size = this.st.canvas.getSize(),
13210         config = this.config,
13211         margin = config.Margin,
13212         ticks = config.Ticks,
13213         title = config.Title,
13214         subtitle = config.Subtitle,
13215         grouped = config.type.split(':')[0] == 'grouped',
13216         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13217         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13218         horz = config.orientation == 'horizontal',
13219         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13220         animate = config.animate,
13221         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13222
13223           - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13224         dim1 = horz? 'height':'width',
13225         dim2 = horz? 'width':'height',
13226         basic = config.type.split(':')[0] == 'basic';
13227         
13228         
13229                 var maxTickValue = Math.ceil(maxValue*.1)*10;
13230                 if(maxTickValue == maxValue) {
13231                         var length = maxTickValue.toString().length;
13232                         maxTickValue = maxTickValue + parseInt(pad(1,length));
13233                 }
13234
13235                 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13236
13237                 
13238     this.st.graph.eachNode(function(n) {
13239       var acum = 0, animateValue = [];
13240       $.each(n.getData('valueArray'), function(v) {
13241         acum += +v;
13242         animateValue.push(0);
13243       });
13244       
13245       if(grouped) {
13246         fixedDim = animateValue.length * 40;
13247       }
13248       n.setData(dim1, fixedDim);
13249       
13250       
13251       if(animate) {
13252         n.setData(dim2, acum * height / maxValue, 'end');
13253         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13254           return n * height / maxValue; 
13255         }), 'end');
13256         var dimArray = n.getData('dimArray');
13257         if(!dimArray) {
13258           n.setData('dimArray', animateValue);
13259         }
13260       } else {
13261         
13262
13263                 if(ticks.enable) {
13264                         n.setData(dim2, acum * height / maxTickValue);
13265                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13266                           return n * height / maxTickValue; 
13267                         }));
13268                 } else {
13269                         n.setData(dim2, acum * height / maxValue);
13270                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13271                           return n * height / maxValue; 
13272                         }));
13273                 }
13274       }
13275     });
13276   }
13277 });
13278
13279 //funnel chart options
13280
13281
13282 Options.FunnelChart = {
13283   $extend: true,
13284   
13285   animate: true,
13286   type: 'stacked', //stacked, grouped, : gradient
13287   labelOffset: 3, //label offset
13288   barsOffset: 0, //distance between bars
13289   hoveredColor: '#9fd4ff',
13290   orientation: 'vertical',
13291   showAggregates: true,
13292   showLabels: true,
13293   Tips: {
13294     enable: false,
13295     onShow: $.empty,
13296     onHide: $.empty
13297   },
13298   Events: {
13299     enable: false,
13300     onClick: $.empty
13301   }
13302 };
13303
13304 $jit.ST.Plot.NodeTypes.implement({
13305   'funnelchart-basic' : {
13306     'render' : function(node, canvas) {
13307       var pos = node.pos.getc(true), 
13308           width  = node.getData('width'),
13309           height = node.getData('height'),
13310           algnPos = this.getAlignedPos(pos, width, height),
13311           x = algnPos.x, y = algnPos.y,
13312           dimArray = node.getData('dimArray'),
13313           valueArray = node.getData('valueArray'),
13314           valuelabelArray = node.getData('valuelabelArray'),
13315           linkArray = node.getData('linkArray'),
13316           colorArray = node.getData('colorArray'),
13317           colorLength = colorArray.length,
13318           stringArray = node.getData('stringArray');
13319       var ctx = canvas.getCtx(),
13320           opt = {},
13321           border = node.getData('border'),
13322           gradient = node.getData('gradient'),
13323           config = node.getData('config'),
13324           horz = config.orientation == 'horizontal',
13325           aggregates = config.showAggregates,
13326           showLabels = config.showLabels,
13327           label = config.Label,
13328           size = canvas.getSize(),
13329           labelOffset = config.labelOffset + 10;
13330           minWidth =  width * .25;
13331           ratio = .65;
13332
13333       if (colorArray && dimArray && stringArray) {
13334         
13335         
13336         // horizontal lines
13337         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13338         ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13339
13340         if(label.type == 'Native') {      
13341        if(showLabels(node.name, valAcum, node)) {
13342                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13343                  var stringValue = stringArray[i];
13344                  var valueLabel = String(valuelabelArray[i]);
13345              var mV = ctx.measureText(stringValue);
13346              var mVL = ctx.measureText(valueLabel);
13347                  var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13348                          var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13349                          var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13350                  var bottomWidth = minWidth + ((acum) * ratio);  
13351                  var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);  
13352                          var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13353                          var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13354 //             ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13355
13356                         //right lines
13357                         ctx.beginPath();
13358                         ctx.moveTo(bottomWidth/2,y - acum); //
13359                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13360                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight);  // bottom right
13361                         ctx.stroke();
13362                         //left lines
13363                         ctx.beginPath();
13364                         ctx.moveTo(-bottomWidth/2,y - acum); //
13365                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13366                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight);  // bottom right
13367                         ctx.stroke();
13368        }
13369         }
13370
13371                 acum += (dimArray[i] || 0);
13372           valAcum += (valueArray[i] || 0);
13373           
13374           
13375                 }
13376                 
13377  
13378   
13379         //funnel segments and labels
13380         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13381           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13382                           var colori = colorArray[i % colorLength];
13383                           if(label.type == 'Native') { 
13384                                   var stringValue = stringArray[i];
13385                           var valueLabel = String(valuelabelArray[i]);
13386                               var mV = ctx.measureText(stringValue);
13387                       var mVL = ctx.measureText(valueLabel);
13388                           } else {
13389                                   var mV = 10;
13390                       var mVL = 10;     
13391                           }
13392                       var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13393                       var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13394                       var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13395                       var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13396                       
13397           var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13398           var bottomWidth = minWidth + ((acum) * ratio);
13399           var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13400           
13401
13402           if(gradient) {
13403             var linear;
13404               linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13405                         var colorRgb = $.hexToRgb(colori);
13406             var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
13407                 function(v) { return (v * .5) >> 0; });
13408             linear.addColorStop(0, 'rgba('+color+',1)');
13409             linear.addColorStop(0.5,  'rgba('+colorRgb+',1)');
13410             linear.addColorStop(1, 'rgba('+color+',1)');
13411             ctx.fillStyle = linear;
13412           }
13413           
13414                         ctx.beginPath();
13415                         ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13416                         ctx.lineTo(topWidth/2,y - acum - dimArray[i]);  // top right
13417                         ctx.lineTo(bottomWidth/2,y - acum);  // bottom right
13418                         ctx.lineTo(-bottomWidth/2,y - acum);  // bottom left
13419                         ctx.closePath(); 
13420                         ctx.fill();
13421                 
13422           
13423           if(border && border.name == stringArray[i]) {
13424             opt.acum = acum;
13425             opt.dimValue = dimArray[i];
13426           }
13427           
13428           
13429         if(border) {
13430           ctx.save();
13431           ctx.lineWidth = 2;
13432           ctx.strokeStyle = border.color;
13433
13434             //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13435          
13436           ctx.restore();
13437         }
13438         if(label.type == 'Native') {
13439           ctx.save();
13440           ctx.fillStyle = ctx.strokeStyle = label.color;
13441           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13442           ctx.textBaseline = 'middle';
13443
13444                                 acumValueLabel = valAcum;
13445
13446           if(showLabels(node.name, valAcum, node)) {
13447
13448                       
13449               ctx.textAlign = 'left';
13450               ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13451               ctx.textAlign = 'right';
13452               ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13453               }
13454           ctx.restore();
13455         }
13456
13457           acum += (dimArray[i] || 0);
13458           valAcum += (valueArray[i] || 0);
13459           
13460         }
13461
13462       }
13463     },
13464     'contains': function(node, mpos) {
13465       var pos = node.pos.getc(true), 
13466           width = node.getData('width'),
13467           height = node.getData('height'),
13468           algnPos = this.getAlignedPos(pos, width, height),
13469           x = algnPos.x, y = algnPos.y,
13470           dimArray = node.getData('dimArray'),
13471           config = node.getData('config'),
13472           st = node.getData('st'),
13473           rx = mpos.x - x,
13474           horz = config.orientation == 'horizontal',
13475            minWidth =  width * .25;
13476           ratio = .65,
13477           canvas = node.getData('canvas'),
13478           size = canvas.getSize(),
13479           offsetY = st.config.offsetY;
13480       //bounding box check
13481
13482         if(mpos.y > y || mpos.y < y - height) {
13483             return false;
13484           }
13485           
13486          var newY = Math.abs(mpos.y + offsetY);
13487         var bound = minWidth + (newY * ratio);
13488         var boundLeft = -bound/2;
13489         var boundRight = bound/2;
13490          if(mpos.x < boundLeft || mpos.x > boundRight ) {
13491             return false;
13492           }
13493
13494       
13495       //deep check
13496       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13497         var dimi = dimArray[i];
13498
13499           
13500           
13501                 var url = Url.decode(node.getData('linkArray')[i]);
13502           acum -= dimi;  
13503           var intersec = acum;
13504           if(mpos.y >= intersec) {
13505             return {
13506               'name': node.getData('stringArray')[i],
13507               'color': node.getData('colorArray')[i],
13508               'value': node.getData('valueArray')[i],
13509               'percentage': node.getData('percentageArray')[i],
13510                           'valuelabel': node.getData('valuelabelArray')[i],
13511               'link': url,
13512               'label': node.name
13513             };
13514           }
13515         
13516       }
13517       return false;
13518     }
13519   }
13520 });
13521
13522 /*
13523   Class: FunnelChart
13524   
13525   A visualization that displays funnel charts.
13526   
13527   Constructor Options:
13528   
13529   See <Options.FunnelChart>.
13530
13531 */
13532 $jit.FunnelChart = new Class({
13533   st: null,
13534   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13535   selected: {},
13536   busy: false,
13537   
13538   initialize: function(opt) {
13539     this.controller = this.config = 
13540       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13541         Label: { type: 'Native' }
13542       }, opt);
13543     //set functions for showLabels and showAggregates
13544     var showLabels = this.config.showLabels,
13545         typeLabels = $.type(showLabels),
13546         showAggregates = this.config.showAggregates,
13547         typeAggregates = $.type(showAggregates);
13548     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13549     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13550     Options.Fx.clearCanvas = false;
13551     this.initializeViz();
13552   },
13553   
13554   initializeViz: function() {
13555     var config = this.config, that = this;
13556     var nodeType = config.type.split(":")[0],
13557         horz = config.orientation == 'horizontal',
13558         nodeLabels = {};
13559     var st = new $jit.ST({
13560       injectInto: config.injectInto,
13561       orientation: horz? 'left' : 'bottom',
13562       levelDistance: 0,
13563       background: config.background,
13564       renderBackground: config.renderBackground,
13565       backgroundColor: config.backgroundColor,
13566       colorStop1: config.colorStop1,
13567       colorStop2: config.colorStop2,
13568       siblingOffset: config.segmentOffset,
13569       subtreeOffset: 0,
13570       withLabels: config.Label.type != 'Native',      
13571       useCanvas: config.useCanvas,
13572       Label: {
13573         type: config.Label.type
13574       },
13575       Node: {
13576         overridable: true,
13577         type: 'funnelchart-' + nodeType,
13578         align: 'left',
13579         width: 1,
13580         height: 1
13581       },
13582       Edge: {
13583         type: 'none'
13584       },
13585       Tips: {
13586         enable: config.Tips.enable,
13587         type: 'Native',
13588         force: true,
13589         onShow: function(tip, node, contains) {
13590           var elem = contains;
13591           config.Tips.onShow(tip, elem, node);
13592                           if(elem.link != 'undefined' && elem.link != '') {
13593                                 document.body.style.cursor = 'pointer';
13594                           }
13595         },
13596                 onHide: function(call) {
13597                         document.body.style.cursor = 'default';
13598
13599         }
13600       },
13601       Events: {
13602         enable: true,
13603         type: 'Native',
13604         onClick: function(node, eventInfo, evt) {
13605           if(!config.Events.enable) return;
13606           var elem = eventInfo.getContains();
13607           config.Events.onClick(elem, eventInfo, evt);
13608         },
13609         onMouseMove: function(node, eventInfo, evt) {
13610           if(!config.hoveredColor) return;
13611           if(node) {
13612             var elem = eventInfo.getContains();
13613             that.select(node.id, elem.name, elem.index);
13614           } else {
13615             that.select(false, false, false);
13616           }
13617         }
13618       },
13619       onCreateLabel: function(domElement, node) {
13620         var labelConf = config.Label,
13621             valueArray = node.getData('valueArray'),
13622             idArray = node.getData('idArray'),
13623             valuelabelArray = node.getData('valuelabelArray'),
13624             stringArray = node.getData('stringArray');
13625             size = st.canvas.getSize()
13626             prefix = $.time();
13627                 
13628                 for(var i=0, l=valueArray.length; i<l; i++) {
13629         var nlbs = {
13630           wrapper: document.createElement('div'),
13631           valueLabel: document.createElement('div'),
13632           label: document.createElement('div')
13633         };
13634         var wrapper = nlbs.wrapper,
13635             label = nlbs.label,
13636             valueLabel = nlbs.valueLabel,
13637             wrapperStyle = wrapper.style,
13638             labelStyle = label.style,
13639             valueLabelStyle = valueLabel.style;
13640         //store node labels
13641         nodeLabels[idArray[i]] = nlbs;
13642         //append labels
13643         wrapper.appendChild(label);
13644         wrapper.appendChild(valueLabel);
13645
13646         wrapperStyle.position = 'relative';
13647         wrapperStyle.overflow = 'visible';
13648         wrapperStyle.fontSize = labelConf.size + 'px';
13649         wrapperStyle.fontFamily = labelConf.family;
13650         wrapperStyle.color = labelConf.color;
13651         wrapperStyle.textAlign = 'center';
13652         wrapperStyle.width = size.width + 'px';
13653         valueLabelStyle.position = labelStyle.position = 'absolute';
13654         valueLabelStyle.left = labelStyle.left =  '0px';
13655                 valueLabelStyle.width = (size.width/3) + 'px';
13656                 valueLabelStyle.textAlign = 'right';
13657         label.innerHTML = stringArray[i];
13658         valueLabel.innerHTML = valuelabelArray[i];
13659         domElement.id = prefix+'funnel';
13660         domElement.style.width = size.width + 'px';
13661         
13662                 domElement.appendChild(wrapper);
13663                 }
13664
13665       },
13666       onPlaceLabel: function(domElement, node) {
13667
13668             var dimArray = node.getData('dimArray'),
13669             idArray = node.getData('idArray'),
13670             valueArray = node.getData('valueArray'),
13671             valuelabelArray = node.getData('valuelabelArray'),
13672             stringArray = node.getData('stringArray');
13673             size = st.canvas.getSize(),
13674             pos = node.pos.getc(true),
13675              domElement.style.left = "0px",
13676              domElement.style.top = "0px",
13677              minWidth = node.getData('width') * .25,
13678              ratio = .65,
13679              pos = node.pos.getc(true),
13680              labelConf = config.Label;
13681              
13682              
13683                 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13684
13685         var labels = nodeLabels[idArray[i]],
13686             wrapperStyle = labels.wrapper.style,
13687             labelStyle = labels.label.style,
13688             valueLabelStyle = labels.valueLabel.style;
13689                 var bottomWidth = minWidth + (acum * ratio); 
13690                 
13691             font = parseInt(wrapperStyle.fontSize, 10),
13692             domStyle = domElement.style;
13693            
13694                 
13695        
13696                         wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13697             valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13698             labelStyle.left =  (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13699
13700                         acum += (dimArray[i] || 0);
13701
13702                 }
13703
13704       }
13705
13706     });
13707
13708     var size = st.canvas.getSize(),
13709         margin = config.Margin;
13710         title = config.Title;
13711         subtitle = config.Subtitle;
13712         //y offset
13713
13714       st.config.offsetY = -size.height/2 + margin.bottom 
13715         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13716
13717                 st.config.offsetX = (margin.right - margin.left)/2;
13718           
13719     
13720     this.st = st;
13721     this.canvas = this.st.canvas;
13722   },
13723   
13724   renderTitle: function() {
13725         var canvas = this.canvas,
13726         size = canvas.getSize(),
13727         config = this.config,
13728         margin = config.Margin,
13729         label = config.Label,
13730         title = config.Title;
13731         ctx = canvas.getCtx();
13732         ctx.fillStyle = title.color;
13733         ctx.textAlign = 'left';
13734         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13735         if(label.type == 'Native') {
13736                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13737         }
13738   },  
13739   
13740   renderSubtitle: function() {
13741         var canvas = this.canvas,
13742         size = canvas.getSize(),
13743         config = this.config,
13744         margin = config.Margin,
13745         label = config.Label,
13746         subtitle = config.Subtitle;
13747         ctx = canvas.getCtx();
13748         ctx.fillStyle = title.color;
13749         ctx.textAlign = 'left';
13750         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13751         if(label.type == 'Native') {
13752                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13753         }
13754   },
13755   
13756   
13757   renderDropShadow: function() {
13758         var canvas = this.canvas,
13759         size = canvas.getSize(),
13760         config = this.config,
13761         margin = config.Margin,
13762         horz = config.orientation == 'horizontal',
13763         label = config.Label,
13764         title = config.Title,
13765         shadowThickness = 4,
13766         subtitle = config.Subtitle,
13767         ctx = canvas.getCtx(),
13768         minwidth = (size.width/8) * .25,
13769         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13770         topMargin = (title.text? title.size + title.offset : 0)  + margin.top,
13771     height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13772           - (config.showLabels && (config.Label.size + config.labelOffset)),
13773     ratio = .65,
13774         topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13775         topY = (-size.height/2) + topMargin - shadowThickness;
13776         bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13777         bottomWidth = minwidth + shadowThickness;
13778         ctx.beginPath();
13779         ctx.fillStyle = "rgba(0,0,0,.2)";
13780         ctx.moveTo(0,topY);
13781         ctx.lineTo(-topWidth/2,topY); //top left
13782         ctx.lineTo(-bottomWidth/2,bottomY);  // bottom left
13783         ctx.lineTo(bottomWidth/2,bottomY);  // bottom right
13784         ctx.lineTo(topWidth/2,topY);  // top right
13785         ctx.closePath(); 
13786         ctx.fill();
13787                         
13788                         
13789   },
13790
13791    renderBackground: function() {
13792                 var canvas = this.canvas,
13793                 config = this.config,
13794                 backgroundColor = config.backgroundColor,
13795                 size = canvas.getSize(),
13796                 ctx = canvas.getCtx();
13797                 //ctx.globalCompositeOperation = "destination-over";
13798             ctx.fillStyle = backgroundColor;
13799             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
13800   },
13801   clear: function() {
13802         var canvas = this.canvas;
13803         var ctx = canvas.getCtx(),
13804         size = canvas.getSize();
13805         ctx.fillStyle = "rgba(255,255,255,0)";
13806         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13807         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13808   },
13809    resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
13810         var canvas = this.canvas,
13811         size = canvas.getSize(),
13812         config = this.config,
13813         orgHeight = size.height,
13814         margin = config.Margin,
13815         st = this.st,
13816         label = config.Label,
13817         horz = config.orientation == 'horizontal',
13818         ctx = canvas.getCtx();
13819         
13820
13821         var newWindowWidth = document.body.offsetWidth;
13822         var diff = newWindowWidth - orgWindowWidth;     
13823         var newWidth = orgContainerDivWidth + (diff/cols);
13824         canvas.resize(newWidth,orgHeight);
13825
13826         if(typeof FlashCanvas == "undefined") {
13827                 canvas.clear();
13828         } else {
13829                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13830         }
13831         this.loadJSON(json);
13832
13833         },
13834         
13835   loadJSON: function(json) {
13836     if(this.busy) return;
13837     this.busy = true;
13838     var prefix = $.time(), 
13839         ch = [], 
13840         st = this.st,
13841         name = $.splat(json.label), 
13842         color = $.splat(json.color || this.colors),
13843         config = this.config,
13844         canvas = this.canvas,
13845         gradient = !!config.type.split(":")[1],
13846         animate = config.animate,
13847         title = config.Title,
13848         subtitle = config.Subtitle,
13849         renderBackground = config.renderBackground,
13850         horz = config.orientation == 'horizontal',
13851         that = this,
13852                 colorLength = color.length,
13853                 nameLength = name.length,
13854                 totalValue = 0;
13855     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13856         var val = values[i];
13857         var valArray = $.splat(val.values);
13858         totalValue += parseFloat(valArray.sum());
13859     }
13860     
13861     
13862     var nameArray = new Array();
13863     var idArray = new Array();
13864     var valArray = new Array();
13865     var valuelabelArray = new Array();
13866     var linkArray = new Array();
13867     var titleArray = new Array();
13868     var percentageArray = new Array();
13869     
13870     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13871       var val = values[i];
13872       nameArray[i] = $.splat(val.label);
13873       idArray[i] = $.splat(prefix + val.label);
13874       valArray[i] = $.splat(val.values);
13875       valuelabelArray[i] = $.splat(val.valuelabels);
13876       linkArray[i] = $.splat(val.links);
13877       titleArray[i] = $.splat(val.titles);
13878       percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13879       var acum = 0;
13880     }
13881     
13882
13883     nameArray.reverse();
13884     valArray.reverse();
13885     valuelabelArray.reverse();
13886     linkArray.reverse();
13887     titleArray.reverse();
13888     percentageArray.reverse();
13889     
13890       ch.push({
13891         'id': prefix + val.label,
13892         'name': val.label,
13893         
13894         'data': {
13895           'value': valArray,
13896           '$idArray': idArray,
13897           '$linkArray': linkArray,
13898           '$titleArray': titleArray,
13899           '$valueArray': valArray,
13900           '$valuelabelArray': valuelabelArray,
13901           '$colorArray': color,
13902           '$colorMono': $.splat(color[i % colorLength]),
13903           '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
13904           '$gradient': gradient,
13905           '$config': config,
13906           '$percentageArray' : percentageArray,
13907           '$canvas': canvas,
13908           '$st': st
13909         },
13910         'children': []
13911       });
13912     
13913     var root = {
13914       'id': prefix + '$root',
13915       'name': '',
13916       'data': {
13917         '$type': 'none',
13918         '$width': 1,
13919         '$height': 1
13920       },
13921       'children': ch
13922     };
13923     st.loadJSON(root);
13924     
13925     this.normalizeDims();
13926         
13927         if(renderBackground) {
13928                 this.renderBackground();        
13929         }
13930         if(!animate && title.text) {
13931                 this.renderTitle();
13932         }
13933         if(!animate && subtitle.text) {
13934                 this.renderSubtitle();
13935         }
13936         if(typeof FlashCanvas == "undefined") {
13937                 this.renderDropShadow();
13938         }
13939     st.compute();
13940     st.select(st.root);
13941     if(animate) {
13942       if(horz) {
13943         st.fx.animate({
13944           modes: ['node-property:width:dimArray'],
13945           duration:1500,
13946           onComplete: function() {
13947             that.busy = false;
13948           }
13949         });
13950       } else {
13951         st.fx.animate({
13952           modes: ['node-property:height:dimArray'],
13953           duration:1500,
13954           onComplete: function() {
13955             that.busy = false;
13956           }
13957         });
13958       }
13959     } else {
13960       this.busy = false;
13961     }
13962   },
13963   
13964   /*
13965     Method: updateJSON
13966    
13967     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.
13968     
13969     Parameters:
13970     
13971     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13972     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13973     
13974     Example:
13975     
13976     (start code js)
13977     barChart.updateJSON(json, {
13978       onComplete: function() {
13979         alert('update complete!');
13980       }
13981     });
13982     (end code)
13983  */  
13984   updateJSON: function(json, onComplete) {
13985     if(this.busy) return;
13986     this.busy = true;
13987     
13988     var st = this.st;
13989     var graph = st.graph;
13990     var values = json.values;
13991     var animate = this.config.animate;
13992     var that = this;
13993     var horz = this.config.orientation == 'horizontal';
13994     $.each(values, function(v) {
13995       var n = graph.getByName(v.label);
13996       if(n) {
13997         n.setData('valueArray', $.splat(v.values));
13998         if(json.label) {
13999           n.setData('stringArray', $.splat(json.label));
14000         }
14001       }
14002     });
14003     this.normalizeDims();
14004     st.compute();
14005     st.select(st.root);
14006     if(animate) {
14007       if(horz) {
14008         st.fx.animate({
14009           modes: ['node-property:width:dimArray'],
14010           duration:1500,
14011           onComplete: function() {
14012             that.busy = false;
14013             onComplete && onComplete.onComplete();
14014           }
14015         });
14016       } else {
14017         st.fx.animate({
14018           modes: ['node-property:height:dimArray'],
14019           duration:1500,
14020           onComplete: function() {
14021             that.busy = false;
14022             onComplete && onComplete.onComplete();
14023           }
14024         });
14025       }
14026     }
14027   },
14028   
14029   //adds the little brown bar when hovering the node
14030   select: function(id, name) {
14031
14032     if(!this.config.hoveredColor) return;
14033     var s = this.selected;
14034     if(s.id != id || s.name != name) {
14035       s.id = id;
14036       s.name = name;
14037       s.color = this.config.hoveredColor;
14038       this.st.graph.eachNode(function(n) {
14039         if(id == n.id) {
14040           n.setData('border', s);
14041         } else {
14042           n.setData('border', false);
14043         }
14044       });
14045       this.st.plot();
14046     }
14047   },
14048   
14049   /*
14050     Method: getLegend
14051    
14052     Returns an object containing as keys the legend names and as values hex strings with color values.
14053     
14054     Example:
14055     
14056     (start code js)
14057     var legend = barChart.getLegend();
14058     (end code)
14059   */  
14060   getLegend: function() {
14061     var legend = new Array();
14062     var name = new Array();
14063     var color = new Array();
14064     var n;
14065     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14066       n = adj.nodeTo;
14067     });
14068     var colors = n.getData('colorArray'),
14069         len = colors.length;
14070     $.each(n.getData('stringArray'), function(s, i) {
14071       color[i] = colors[i % len];
14072       name[i] = s;
14073     });
14074         legend['name'] = name;
14075         legend['color'] = color;
14076     return legend;
14077   },
14078   
14079   /*
14080     Method: getMaxValue
14081    
14082     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14083     
14084     Example:
14085     
14086     (start code js)
14087     var ans = barChart.getMaxValue();
14088     (end code)
14089     
14090     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14091     
14092     Example:
14093     
14094     (start code js)
14095     //will return 100 for all BarChart instances,
14096     //displaying all of them with the same scale
14097     $jit.BarChart.implement({
14098       'getMaxValue': function() {
14099         return 100;
14100       }
14101     });
14102     (end code)
14103     
14104   */  
14105   getMaxValue: function() {
14106     var maxValue = 0, stacked = true;
14107     this.st.graph.eachNode(function(n) {
14108       var valArray = n.getData('valueArray'),
14109           acum = 0;
14110       if(!valArray) return;
14111       if(stacked) {
14112         $.each(valArray, function(v) { 
14113           acum += +v;
14114         });
14115       } else {
14116         acum = Math.max.apply(null, valArray);
14117       }
14118       maxValue = maxValue>acum? maxValue:acum;
14119     });
14120     return maxValue;
14121   },
14122   
14123   setBarType: function(type) {
14124     this.config.type = type;
14125     this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14126   },
14127   
14128   normalizeDims: function() {
14129     //number of elements
14130     var root = this.st.graph.getNode(this.st.root), l=0;
14131     root.eachAdjacency(function() {
14132       l++;
14133     });
14134     var maxValue = this.getMaxValue() || 1,
14135         size = this.st.canvas.getSize(),
14136         config = this.config,
14137         margin = config.Margin,
14138         title = config.Title,
14139         subtitle = config.Subtitle,
14140         marginWidth = margin.left + margin.right,
14141         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14142         horz = config.orientation == 'horizontal',
14143         animate = config.animate,
14144         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14145
14146           - (config.showLabels && (config.Label.size + config.labelOffset)),
14147         dim1 = horz? 'height':'width',
14148         dim2 = horz? 'width':'height';
14149         
14150
14151         minWidth = size.width/8;
14152         
14153
14154
14155     this.st.graph.eachNode(function(n) {
14156       var acum = 0, animateValue = [];
14157       $.each(n.getData('valueArray'), function(v) {
14158         acum += +v;
14159         animateValue.push(0);
14160       });
14161       n.setData(dim1, minWidth);
14162             
14163       if(animate) {
14164         n.setData(dim2, acum * height / maxValue, 'end');
14165         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14166           return n * height / maxValue; 
14167         }), 'end');
14168         var dimArray = n.getData('dimArray');
14169         if(!dimArray) {
14170           n.setData('dimArray', animateValue);
14171         }
14172       } else {
14173                         n.setData(dim2, acum * height / maxValue);
14174                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14175                           return n * height / maxValue; 
14176                         }));
14177       }
14178
14179     });
14180   }
14181 });
14182
14183
14184
14185 /*
14186  * File: Options.PieChart.js
14187  *
14188 */
14189 /*
14190   Object: Options.PieChart
14191   
14192   <PieChart> options. 
14193   Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14194   
14195   Syntax:
14196   
14197   (start code js)
14198
14199   Options.PieChart = {
14200     animate: true,
14201     offset: 25,
14202     sliceOffset:0,
14203     labelOffset: 3,
14204     type: 'stacked',
14205     hoveredColor: '#9fd4ff',
14206     showLabels: true,
14207     resizeLabels: false,
14208     updateHeights: false
14209   };  
14210
14211   (end code)
14212   
14213   Example:
14214   
14215   (start code js)
14216
14217   var pie = new $jit.PieChart({
14218     animate: true,
14219     sliceOffset: 5,
14220     type: 'stacked:gradient'
14221   });  
14222
14223   (end code)
14224   
14225   Parameters:
14226   
14227   animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14228   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14229   sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14230   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14231   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14232   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14233   showLabels - (boolean) Default's *true*. Display the name of the slots.
14234   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.
14235   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.
14236
14237 */
14238 Options.PieChart = {
14239   $extend: true,
14240
14241   animate: true,
14242   offset: 25, // page offset
14243   sliceOffset:0,
14244   labelOffset: 3, // label offset
14245   type: 'stacked', // gradient
14246   labelType: 'name',
14247   hoveredColor: '#9fd4ff',
14248   Events: {
14249     enable: false,
14250     onClick: $.empty
14251   },
14252   Tips: {
14253     enable: false,
14254     onShow: $.empty,
14255     onHide: $.empty
14256   },
14257   showLabels: true,
14258   resizeLabels: false,
14259   
14260   //only valid for mono-valued datasets
14261   updateHeights: false
14262 };
14263
14264 /*
14265  * Class: Layouts.Radial
14266  * 
14267  * Implements a Radial Layout.
14268  * 
14269  * Implemented By:
14270  * 
14271  * <RGraph>, <Hypertree>
14272  * 
14273  */
14274 Layouts.Radial = new Class({
14275
14276   /*
14277    * Method: compute
14278    * 
14279    * Computes nodes' positions.
14280    * 
14281    * Parameters:
14282    * 
14283    * property - _optional_ A <Graph.Node> position property to store the new
14284    * positions. Possible values are 'pos', 'end' or 'start'.
14285    * 
14286    */
14287   compute : function(property) {
14288     var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14289     NodeDim.compute(this.graph, prop, this.config);
14290     this.graph.computeLevels(this.root, 0, "ignore");
14291     var lengthFunc = this.createLevelDistanceFunc(); 
14292     this.computeAngularWidths(prop);
14293     this.computePositions(prop, lengthFunc);
14294   },
14295
14296   /*
14297    * computePositions
14298    * 
14299    * Performs the main algorithm for computing node positions.
14300    */
14301   computePositions : function(property, getLength) {
14302     var propArray = property;
14303     var graph = this.graph;
14304     var root = graph.getNode(this.root);
14305     var parent = this.parent;
14306     var config = this.config;
14307
14308     for ( var i=0, l=propArray.length; i < l; i++) {
14309       var pi = propArray[i];
14310       root.setPos($P(0, 0), pi);
14311       root.setData('span', Math.PI * 2, pi);
14312     }
14313
14314     root.angleSpan = {
14315       begin : 0,
14316       end : 2 * Math.PI
14317     };
14318
14319     graph.eachBFS(this.root, function(elem) {
14320       var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14321       var angleInit = elem.angleSpan.begin;
14322       var len = getLength(elem);
14323       //Calculate the sum of all angular widths
14324       var totalAngularWidths = 0, subnodes = [], maxDim = {};
14325       elem.eachSubnode(function(sib) {
14326         totalAngularWidths += sib._treeAngularWidth;
14327         //get max dim
14328         for ( var i=0, l=propArray.length; i < l; i++) {
14329           var pi = propArray[i], dim = sib.getData('dim', pi);
14330           maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14331         }
14332         subnodes.push(sib);
14333       }, "ignore");
14334       //Maintain children order
14335       //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14336       if (parent && parent.id == elem.id && subnodes.length > 0
14337           && subnodes[0].dist) {
14338         subnodes.sort(function(a, b) {
14339           return (a.dist >= b.dist) - (a.dist <= b.dist);
14340         });
14341       }
14342       //Calculate nodes positions.
14343       for (var k = 0, ls=subnodes.length; k < ls; k++) {
14344         var child = subnodes[k];
14345         if (!child._flag) {
14346           var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14347           var theta = angleInit + angleProportion / 2;
14348
14349           for ( var i=0, l=propArray.length; i < l; i++) {
14350             var pi = propArray[i];
14351             child.setPos($P(theta, len), pi);
14352             child.setData('span', angleProportion, pi);
14353             child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14354           }
14355
14356           child.angleSpan = {
14357             begin : angleInit,
14358             end : angleInit + angleProportion
14359           };
14360           angleInit += angleProportion;
14361         }
14362       }
14363     }, "ignore");
14364   },
14365
14366   /*
14367    * Method: setAngularWidthForNodes
14368    * 
14369    * Sets nodes angular widths.
14370    */
14371   setAngularWidthForNodes : function(prop) {
14372     this.graph.eachBFS(this.root, function(elem, i) {
14373       var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14374       elem._angularWidth = diamValue / i;
14375     }, "ignore");
14376   },
14377
14378   /*
14379    * Method: setSubtreesAngularWidth
14380    * 
14381    * Sets subtrees angular widths.
14382    */
14383   setSubtreesAngularWidth : function() {
14384     var that = this;
14385     this.graph.eachNode(function(elem) {
14386       that.setSubtreeAngularWidth(elem);
14387     }, "ignore");
14388   },
14389
14390   /*
14391    * Method: setSubtreeAngularWidth
14392    * 
14393    * Sets the angular width for a subtree.
14394    */
14395   setSubtreeAngularWidth : function(elem) {
14396     var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14397     elem.eachSubnode(function(child) {
14398       that.setSubtreeAngularWidth(child);
14399       sumAW += child._treeAngularWidth;
14400     }, "ignore");
14401     elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14402   },
14403
14404   /*
14405    * Method: computeAngularWidths
14406    * 
14407    * Computes nodes and subtrees angular widths.
14408    */
14409   computeAngularWidths : function(prop) {
14410     this.setAngularWidthForNodes(prop);
14411     this.setSubtreesAngularWidth();
14412   }
14413
14414 });
14415
14416
14417 /*
14418  * File: Sunburst.js
14419  */
14420
14421 /*
14422    Class: Sunburst
14423       
14424    A radial space filling tree visualization.
14425    
14426    Inspired by:
14427  
14428    Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14429    
14430    Note:
14431    
14432    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.
14433    
14434   Implements:
14435   
14436   All <Loader> methods
14437   
14438    Constructor Options:
14439    
14440    Inherits options from
14441    
14442    - <Options.Canvas>
14443    - <Options.Controller>
14444    - <Options.Node>
14445    - <Options.Edge>
14446    - <Options.Label>
14447    - <Options.Events>
14448    - <Options.Tips>
14449    - <Options.NodeStyles>
14450    - <Options.Navigation>
14451    
14452    Additionally, there are other parameters and some default values changed
14453    
14454    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14455    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
14456    Node.type - Described in <Options.Node>. Default's to *multipie*.
14457    Node.height - Described in <Options.Node>. Default's *0*.
14458    Edge.type - Described in <Options.Edge>. Default's *none*.
14459    Label.textAlign - Described in <Options.Label>. Default's *start*.
14460    Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14461      
14462    Instance Properties:
14463
14464    canvas - Access a <Canvas> instance.
14465    graph - Access a <Graph> instance.
14466    op - Access a <Sunburst.Op> instance.
14467    fx - Access a <Sunburst.Plot> instance.
14468    labels - Access a <Sunburst.Label> interface implementation.   
14469
14470 */
14471
14472 $jit.Sunburst = new Class({
14473
14474   Implements: [ Loader, Extras, Layouts.Radial ],
14475
14476   initialize: function(controller) {
14477     var $Sunburst = $jit.Sunburst;
14478
14479     var config = {
14480       interpolation: 'linear',
14481       levelDistance: 100,
14482       Node: {
14483         'type': 'multipie',
14484         'height':0
14485       },
14486       Edge: {
14487         'type': 'none'
14488       },
14489       Label: {
14490         textAlign: 'start',
14491         textBaseline: 'middle'
14492       }
14493     };
14494
14495     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14496         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14497
14498     var canvasConfig = this.config;
14499     if(canvasConfig.useCanvas) {
14500       this.canvas = canvasConfig.useCanvas;
14501       this.config.labelContainer = this.canvas.id + '-label';
14502     } else {
14503       if(canvasConfig.background) {
14504         canvasConfig.background = $.merge({
14505           type: 'Fade',
14506           colorStop1: this.config.colorStop1,
14507           colorStop2: this.config.colorStop2
14508         }, canvasConfig.background);
14509       }
14510       this.canvas = new Canvas(this, canvasConfig);
14511       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14512     }
14513
14514     this.graphOptions = {
14515       'complex': false,
14516       'Node': {
14517         'selected': false,
14518         'exist': true,
14519         'drawn': true
14520       }
14521     };
14522     this.graph = new Graph(this.graphOptions, this.config.Node,
14523         this.config.Edge);
14524     this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14525     this.fx = new $Sunburst.Plot(this, $Sunburst);
14526     this.op = new $Sunburst.Op(this);
14527     this.json = null;
14528     this.root = null;
14529     this.rotated = null;
14530     this.busy = false;
14531     // initialize extras
14532     this.initializeExtras();
14533   },
14534
14535   /* 
14536   
14537     createLevelDistanceFunc 
14538   
14539     Returns the levelDistance function used for calculating a node distance 
14540     to its origin. This function returns a function that is computed 
14541     per level and not per node, such that all nodes with the same depth will have the 
14542     same distance to the origin. The resulting function gets the 
14543     parent node as parameter and returns a float.
14544
14545    */
14546   createLevelDistanceFunc: function() {
14547     var ld = this.config.levelDistance;
14548     return function(elem) {
14549       return (elem._depth + 1) * ld;
14550     };
14551   },
14552
14553   /* 
14554      Method: refresh 
14555      
14556      Computes positions and plots the tree.
14557
14558    */
14559   refresh: function() {
14560     this.compute();
14561     this.plot();
14562   },
14563
14564   /*
14565    reposition
14566   
14567    An alias for computing new positions to _endPos_
14568
14569    See also:
14570
14571    <Sunburst.compute>
14572    
14573   */
14574   reposition: function() {
14575     this.compute('end');
14576   },
14577
14578   /*
14579   Method: rotate
14580   
14581   Rotates the graph so that the selected node is horizontal on the right.
14582
14583   Parameters:
14584   
14585   node - (object) A <Graph.Node>.
14586   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14587   opt - (object) Configuration options merged with this visualization configuration options.
14588   
14589   See also:
14590
14591   <Sunburst.rotateAngle>
14592   
14593   */
14594   rotate: function(node, method, opt) {
14595     var theta = node.getPos(opt.property || 'current').getp(true).theta;
14596     this.rotated = node;
14597     this.rotateAngle(-theta, method, opt);
14598   },
14599
14600   /*
14601   Method: rotateAngle
14602   
14603   Rotates the graph of an angle theta.
14604   
14605    Parameters:
14606    
14607    node - (object) A <Graph.Node>.
14608    method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14609    opt - (object) Configuration options merged with this visualization configuration options.
14610    
14611    See also:
14612
14613    <Sunburst.rotate>
14614   
14615   */
14616   rotateAngle: function(theta, method, opt) {
14617     var that = this;
14618     var options = $.merge(this.config, opt || {}, {
14619       modes: [ 'polar' ]
14620     });
14621     var prop = opt.property || (method === "animate" ? 'end' : 'current');
14622     if(method === 'animate') {
14623       this.fx.animation.pause();
14624     }
14625     this.graph.eachNode(function(n) {
14626       var p = n.getPos(prop);
14627       p.theta += theta;
14628       if (p.theta < 0) {
14629         p.theta += Math.PI * 2;
14630       }
14631     });
14632     if (method == 'animate') {
14633       this.fx.animate(options);
14634     } else if (method == 'replot') {
14635       this.fx.plot();
14636       this.busy = false;
14637     }
14638   },
14639
14640   /*
14641    Method: plot
14642   
14643    Plots the Sunburst. This is a shortcut to *fx.plot*.
14644   */
14645   plot: function() {
14646     this.fx.plot();
14647   }
14648 });
14649
14650 $jit.Sunburst.$extend = true;
14651
14652 (function(Sunburst) {
14653
14654   /*
14655      Class: Sunburst.Op
14656
14657      Custom extension of <Graph.Op>.
14658
14659      Extends:
14660
14661      All <Graph.Op> methods
14662      
14663      See also:
14664      
14665      <Graph.Op>
14666
14667   */
14668   Sunburst.Op = new Class( {
14669
14670     Implements: Graph.Op
14671
14672   });
14673
14674   /*
14675      Class: Sunburst.Plot
14676
14677     Custom extension of <Graph.Plot>.
14678   
14679     Extends:
14680   
14681     All <Graph.Plot> methods
14682     
14683     See also:
14684     
14685     <Graph.Plot>
14686   
14687   */
14688   Sunburst.Plot = new Class( {
14689
14690     Implements: Graph.Plot
14691
14692   });
14693
14694   /*
14695     Class: Sunburst.Label
14696
14697     Custom extension of <Graph.Label>. 
14698     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14699   
14700     Extends:
14701   
14702     All <Graph.Label> methods and subclasses.
14703   
14704     See also:
14705   
14706     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14707   
14708    */
14709   Sunburst.Label = {};
14710
14711   /*
14712      Sunburst.Label.Native
14713
14714      Custom extension of <Graph.Label.Native>.
14715
14716      Extends:
14717
14718      All <Graph.Label.Native> methods
14719
14720      See also:
14721
14722      <Graph.Label.Native>
14723   */
14724   Sunburst.Label.Native = new Class( {
14725     Implements: Graph.Label.Native,
14726
14727     initialize: function(viz) {
14728       this.viz = viz;
14729       this.label = viz.config.Label;
14730       this.config = viz.config;
14731     },
14732
14733     renderLabel: function(canvas, node, controller) {
14734       var span = node.getData('span');
14735       if(span < Math.PI /2 && Math.tan(span) * 
14736           this.config.levelDistance * node._depth < 10) {
14737         return;
14738       }
14739       var ctx = canvas.getCtx();
14740       var measure = ctx.measureText(node.name);
14741       if (node.id == this.viz.root) {
14742         var x = -measure.width / 2, y = 0, thetap = 0;
14743         var ld = 0;
14744       } else {
14745         var indent = 5;
14746         var ld = controller.levelDistance - indent;
14747         var clone = node.pos.clone();
14748         clone.rho += indent;
14749         var p = clone.getp(true);
14750         var ct = clone.getc(true);
14751         var x = ct.x, y = ct.y;
14752         // get angle in degrees
14753         var pi = Math.PI;
14754         var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14755         var thetap = cond ? p.theta + pi : p.theta;
14756         if (cond) {
14757           x -= Math.abs(Math.cos(p.theta) * measure.width);
14758           y += Math.sin(p.theta) * measure.width;
14759         } else if (node.id == this.viz.root) {
14760           x -= measure.width / 2;
14761         }
14762       }
14763       ctx.save();
14764       ctx.translate(x, y);
14765       ctx.rotate(thetap);
14766       ctx.fillText(node.name, 0, 0);
14767       ctx.restore();
14768     }
14769   });
14770
14771   /*
14772      Sunburst.Label.SVG
14773
14774     Custom extension of <Graph.Label.SVG>.
14775   
14776     Extends:
14777   
14778     All <Graph.Label.SVG> methods
14779   
14780     See also:
14781   
14782     <Graph.Label.SVG>
14783   
14784   */
14785   Sunburst.Label.SVG = new Class( {
14786     Implements: Graph.Label.SVG,
14787
14788     initialize: function(viz) {
14789       this.viz = viz;
14790     },
14791
14792     /* 
14793        placeLabel
14794
14795        Overrides abstract method placeLabel in <Graph.Plot>.
14796
14797        Parameters:
14798
14799        tag - A DOM label element.
14800        node - A <Graph.Node>.
14801        controller - A configuration/controller object passed to the visualization.
14802       
14803      */
14804     placeLabel: function(tag, node, controller) {
14805       var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14806       var radius = canvas.getSize();
14807       var labelPos = {
14808         x: Math.round(pos.x + radius.width / 2),
14809         y: Math.round(pos.y + radius.height / 2)
14810       };
14811       tag.setAttribute('x', labelPos.x);
14812       tag.setAttribute('y', labelPos.y);
14813
14814       var bb = tag.getBBox();
14815       if (bb) {
14816         // center the label
14817     var x = tag.getAttribute('x');
14818     var y = tag.getAttribute('y');
14819     // get polar coordinates
14820     var p = node.pos.getp(true);
14821     // get angle in degrees
14822     var pi = Math.PI;
14823     var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14824     if (cond) {
14825       tag.setAttribute('x', x - bb.width);
14826       tag.setAttribute('y', y - bb.height);
14827     } else if (node.id == viz.root) {
14828       tag.setAttribute('x', x - bb.width / 2);
14829     }
14830
14831     var thetap = cond ? p.theta + pi : p.theta;
14832     if(node._depth)
14833       tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14834           + ' ' + y + ')');
14835   }
14836
14837   controller.onPlaceLabel(tag, node);
14838 }
14839   });
14840
14841   /*
14842      Sunburst.Label.HTML
14843
14844      Custom extension of <Graph.Label.HTML>.
14845
14846      Extends:
14847
14848      All <Graph.Label.HTML> methods.
14849
14850      See also:
14851
14852      <Graph.Label.HTML>
14853
14854   */
14855   Sunburst.Label.HTML = new Class( {
14856     Implements: Graph.Label.HTML,
14857
14858     initialize: function(viz) {
14859       this.viz = viz;
14860     },
14861     /* 
14862        placeLabel
14863
14864        Overrides abstract method placeLabel in <Graph.Plot>.
14865
14866        Parameters:
14867
14868        tag - A DOM label element.
14869        node - A <Graph.Node>.
14870        controller - A configuration/controller object passed to the visualization.
14871       
14872      */
14873     placeLabel: function(tag, node, controller) {
14874       var pos = node.pos.clone(), 
14875           canvas = this.viz.canvas,
14876           height = node.getData('height'),
14877           ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14878           radius = canvas.getSize();
14879       pos.rho += ldist;
14880       pos = pos.getc(true);
14881       
14882       var labelPos = {
14883         x: Math.round(pos.x + radius.width / 2),
14884         y: Math.round(pos.y + radius.height / 2)
14885       };
14886
14887       var style = tag.style;
14888       style.left = labelPos.x + 'px';
14889       style.top = labelPos.y + 'px';
14890       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14891
14892       controller.onPlaceLabel(tag, node);
14893     }
14894   });
14895
14896   /*
14897     Class: Sunburst.Plot.NodeTypes
14898
14899     This class contains a list of <Graph.Node> built-in types. 
14900     Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14901
14902     You can add your custom node types, customizing your visualization to the extreme.
14903
14904     Example:
14905
14906     (start code js)
14907       Sunburst.Plot.NodeTypes.implement({
14908         'mySpecialType': {
14909           'render': function(node, canvas) {
14910             //print your custom node to canvas
14911           },
14912           //optional
14913           'contains': function(node, pos) {
14914             //return true if pos is inside the node or false otherwise
14915           }
14916         }
14917       });
14918     (end code)
14919
14920   */
14921   Sunburst.Plot.NodeTypes = new Class( {
14922     'none': {
14923       'render': $.empty,
14924       'contains': $.lambda(false),
14925       'anglecontains': function(node, pos) {
14926         var span = node.getData('span') / 2, theta = node.pos.theta;
14927         var begin = theta - span, end = theta + span;
14928         if (begin < 0)
14929           begin += Math.PI * 2;
14930         var atan = Math.atan2(pos.y, pos.x);
14931         if (atan < 0)
14932           atan += Math.PI * 2;
14933         if (begin > end) {
14934           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14935         } else {
14936           return atan > begin && atan < end;
14937         }
14938       },
14939           'anglecontainsgauge': function(node, pos) {
14940         var span = node.getData('span') / 2, theta = node.pos.theta;
14941                 var config = node.getData('config');
14942         var ld = this.config.levelDistance;
14943                 var yOffset = pos.y-(ld/2);
14944                 var begin = ((theta - span)/2)+Math.PI,
14945         end = ((theta + span)/2)+Math.PI;
14946                 
14947         if (begin < 0)
14948           begin += Math.PI * 2;
14949         var atan = Math.atan2(yOffset, pos.x);
14950
14951                 
14952         if (atan < 0)
14953           atan += Math.PI * 2;
14954                   
14955                   
14956         if (begin > end) {
14957           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14958         } else {
14959           return atan > begin && atan < end;
14960         }
14961       }
14962     },
14963
14964     'pie': {
14965       'render': function(node, canvas) {
14966         var span = node.getData('span') / 2, theta = node.pos.theta;
14967         var begin = theta - span, end = theta + span;
14968         var polarNode = node.pos.getp(true);
14969         var polar = new Polar(polarNode.rho, begin);
14970         var p1coord = polar.getc(true);
14971         polar.theta = end;
14972         var p2coord = polar.getc(true);
14973
14974         var ctx = canvas.getCtx();
14975         ctx.beginPath();
14976         ctx.moveTo(0, 0);
14977         ctx.lineTo(p1coord.x, p1coord.y);
14978         ctx.moveTo(0, 0);
14979         ctx.lineTo(p2coord.x, p2coord.y);
14980         ctx.moveTo(0, 0);
14981         ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14982             false);
14983         ctx.fill();
14984       },
14985       'contains': function(node, pos) {
14986         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14987           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14988           var ld = this.config.levelDistance, d = node._depth;
14989           return (rho <= ld * d);
14990         }
14991         return false;
14992       }
14993     },
14994     'multipie': {
14995       'render': function(node, canvas) {
14996         var height = node.getData('height');
14997         var ldist = height? height : this.config.levelDistance;
14998         var span = node.getData('span') / 2, theta = node.pos.theta;
14999         var begin = theta - span, end = theta + span;
15000         var polarNode = node.pos.getp(true);
15001
15002         var polar = new Polar(polarNode.rho, begin);
15003         var p1coord = polar.getc(true);
15004
15005         polar.theta = end;
15006         var p2coord = polar.getc(true);
15007
15008         polar.rho += ldist;
15009         var p3coord = polar.getc(true);
15010
15011         polar.theta = begin;
15012         var p4coord = polar.getc(true);
15013
15014         var ctx = canvas.getCtx();
15015         ctx.moveTo(0, 0);
15016         ctx.beginPath();
15017         ctx.arc(0, 0, polarNode.rho, begin, end, false);
15018         ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15019         ctx.moveTo(p1coord.x, p1coord.y);
15020         ctx.lineTo(p4coord.x, p4coord.y);
15021         ctx.moveTo(p2coord.x, p2coord.y);
15022         ctx.lineTo(p3coord.x, p3coord.y);
15023         ctx.fill();
15024
15025         if (node.collapsed) {
15026           ctx.save();
15027           ctx.lineWidth = 2;
15028           ctx.moveTo(0, 0);
15029           ctx.beginPath();
15030           ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15031               true);
15032           ctx.stroke();
15033           ctx.restore();
15034         }
15035       },
15036       'contains': function(node, pos) {
15037         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15038           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15039           var height = node.getData('height');
15040           var ldist = height? height : this.config.levelDistance;
15041           var ld = this.config.levelDistance, d = node._depth;
15042           return (rho >= ld * d) && (rho <= (ld * d + ldist));
15043         }
15044         return false;
15045       }
15046     },
15047
15048     'gradient-multipie': {
15049       'render': function(node, canvas) {
15050         var ctx = canvas.getCtx();
15051         var height = node.getData('height');
15052         var ldist = height? height : this.config.levelDistance;
15053         var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15054             0, 0, node.getPos().rho + ldist);
15055
15056         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15057         $.each(colorArray, function(i) {
15058           ans.push(parseInt(i * 0.5, 10));
15059         });
15060         var endColor = $.rgbToHex(ans);
15061         radialGradient.addColorStop(0, endColor);
15062         radialGradient.addColorStop(1, node.getData('color'));
15063         ctx.fillStyle = radialGradient;
15064         this.nodeTypes['multipie'].render.call(this, node, canvas);
15065       },
15066       'contains': function(node, pos) {
15067         return this.nodeTypes['multipie'].contains.call(this, node, pos);
15068       }
15069     },
15070
15071     'gradient-pie': {
15072       'render': function(node, canvas) {
15073         var ctx = canvas.getCtx();
15074         var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15075             .getPos().rho);
15076
15077         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15078         $.each(colorArray, function(i) {
15079           ans.push(parseInt(i * 0.5, 10));
15080         });
15081         var endColor = $.rgbToHex(ans);
15082         radialGradient.addColorStop(1, endColor);
15083         radialGradient.addColorStop(0, node.getData('color'));
15084         ctx.fillStyle = radialGradient;
15085         this.nodeTypes['pie'].render.call(this, node, canvas);
15086       },
15087       'contains': function(node, pos) {
15088         return this.nodeTypes['pie'].contains.call(this, node, pos);
15089       }
15090     }
15091   });
15092
15093   /*
15094     Class: Sunburst.Plot.EdgeTypes
15095
15096     This class contains a list of <Graph.Adjacence> built-in types. 
15097     Edge types implemented are 'none', 'line' and 'arrow'.
15098   
15099     You can add your custom edge types, customizing your visualization to the extreme.
15100   
15101     Example:
15102   
15103     (start code js)
15104       Sunburst.Plot.EdgeTypes.implement({
15105         'mySpecialType': {
15106           'render': function(adj, canvas) {
15107             //print your custom edge to canvas
15108           },
15109           //optional
15110           'contains': function(adj, pos) {
15111             //return true if pos is inside the arc or false otherwise
15112           }
15113         }
15114       });
15115     (end code)
15116   
15117   */
15118   Sunburst.Plot.EdgeTypes = new Class({
15119     'none': $.empty,
15120     'line': {
15121       'render': function(adj, canvas) {
15122         var from = adj.nodeFrom.pos.getc(true),
15123             to = adj.nodeTo.pos.getc(true);
15124         this.edgeHelper.line.render(from, to, canvas);
15125       },
15126       'contains': function(adj, pos) {
15127         var from = adj.nodeFrom.pos.getc(true),
15128             to = adj.nodeTo.pos.getc(true);
15129         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15130       }
15131     },
15132     'arrow': {
15133       'render': function(adj, canvas) {
15134         var from = adj.nodeFrom.pos.getc(true),
15135             to = adj.nodeTo.pos.getc(true),
15136             dim = adj.getData('dim'),
15137             direction = adj.data.$direction,
15138             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15139         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15140       },
15141       'contains': function(adj, pos) {
15142         var from = adj.nodeFrom.pos.getc(true),
15143             to = adj.nodeTo.pos.getc(true);
15144         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15145       }
15146     },
15147     'hyperline': {
15148       'render': function(adj, canvas) {
15149         var from = adj.nodeFrom.pos.getc(),
15150             to = adj.nodeTo.pos.getc(),
15151             dim = Math.max(from.norm(), to.norm());
15152         this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15153       },
15154       'contains': $.lambda(false) //TODO(nico): Implement this!
15155     }
15156   });
15157
15158 })($jit.Sunburst);
15159
15160
15161 /*
15162  * File: PieChart.js
15163  *
15164 */
15165
15166 $jit.Sunburst.Plot.NodeTypes.implement({
15167   'piechart-stacked' : {
15168     'render' : function(node, canvas) {
15169       var pos = node.pos.getp(true),
15170           dimArray = node.getData('dimArray'),
15171           valueArray = node.getData('valueArray'),
15172           colorArray = node.getData('colorArray'),
15173           colorLength = colorArray.length,
15174           stringArray = node.getData('stringArray'),
15175           span = node.getData('span') / 2,
15176           theta = node.pos.theta,
15177           begin = theta - span,
15178           end = theta + span,
15179           polar = new Polar;
15180     
15181       var ctx = canvas.getCtx(), 
15182           opt = {},
15183           gradient = node.getData('gradient'),
15184           border = node.getData('border'),
15185           config = node.getData('config'),
15186           showLabels = config.showLabels,
15187           resizeLabels = config.resizeLabels,
15188           label = config.Label;
15189
15190       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15191       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15192
15193       if (colorArray && dimArray && stringArray) {
15194         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15195           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15196           if(dimi <= 0) continue;
15197           ctx.fillStyle = ctx.strokeStyle = colori;
15198           if(gradient && dimi) {
15199             var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15200                 xpos, ypos, acum + dimi + config.sliceOffset);
15201             var colorRgb = $.hexToRgb(colori), 
15202                 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15203                 endColor = $.rgbToHex(ans);
15204
15205             radialGradient.addColorStop(0, colori);
15206             radialGradient.addColorStop(0.5, colori);
15207             radialGradient.addColorStop(1, endColor);
15208             ctx.fillStyle = radialGradient;
15209           }
15210           
15211           polar.rho = acum + config.sliceOffset;
15212           polar.theta = begin;
15213           var p1coord = polar.getc(true);
15214           polar.theta = end;
15215           var p2coord = polar.getc(true);
15216           polar.rho += dimi;
15217           var p3coord = polar.getc(true);
15218           polar.theta = begin;
15219           var p4coord = polar.getc(true);
15220
15221           ctx.beginPath();
15222           //fixing FF arc method + fill
15223           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15224           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15225           ctx.fill();
15226           if(border && border.name == stringArray[i]) {
15227             opt.acum = acum;
15228             opt.dimValue = dimArray[i];
15229             opt.begin = begin;
15230             opt.end = end;
15231           }
15232           acum += (dimi || 0);
15233           valAcum += (valueArray[i] || 0);
15234         }
15235         if(border) {
15236           ctx.save();
15237           ctx.globalCompositeOperation = "source-over";
15238           ctx.lineWidth = 2;
15239           ctx.strokeStyle = border.color;
15240           var s = begin < end? 1 : -1;
15241           ctx.beginPath();
15242           //fixing FF arc method + fill
15243           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15244           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15245           ctx.closePath();
15246           ctx.stroke();
15247           ctx.restore();
15248         }
15249         if(showLabels && label.type == 'Native') {
15250           ctx.save();
15251           ctx.fillStyle = ctx.strokeStyle = label.color;
15252           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15253               fontSize = (label.size * scale) >> 0;
15254           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15255           
15256           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15257           ctx.textBaseline = 'middle';
15258           ctx.textAlign = 'center';
15259           
15260           polar.rho = acum + config.labelOffset + config.sliceOffset;
15261           polar.theta = node.pos.theta;
15262           var cart = polar.getc(true);
15263           
15264           ctx.fillText(node.name, cart.x, cart.y);
15265           ctx.restore();
15266         }
15267       }
15268     },
15269     'contains': function(node, pos) {
15270       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15271         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15272         var ld = this.config.levelDistance, d = node._depth;
15273         var config = node.getData('config');
15274         if(rho <=ld * d + config.sliceOffset) {
15275           var dimArray = node.getData('dimArray');
15276           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15277             var dimi = dimArray[i];
15278             if(rho >= acum && rho <= acum + dimi) {
15279               return {
15280                 name: node.getData('stringArray')[i],
15281                 color: node.getData('colorArray')[i],
15282                 value: node.getData('valueArray')[i],
15283                 label: node.name
15284               };
15285             }
15286             acum += dimi;
15287           }
15288         }
15289         return false;
15290         
15291       }
15292       return false;
15293     }
15294   },
15295     'piechart-basic' : {
15296     'render' : function(node, canvas) {
15297       var pos = node.pos.getp(true),
15298           dimArray = node.getData('dimArray'),
15299           valueArray = node.getData('valueArray'),
15300           colorArray = node.getData('colorMono'),
15301           colorLength = colorArray.length,
15302           stringArray = node.getData('stringArray'),
15303                   percentage = node.getData('percentage'),
15304                   iteration = node.getData('iteration'),
15305           span = node.getData('span') / 2,
15306           theta = node.pos.theta,
15307           begin = theta - span,
15308           end = theta + span,
15309           polar = new Polar;
15310     
15311       var ctx = canvas.getCtx(), 
15312           opt = {},
15313           gradient = node.getData('gradient'),
15314           border = node.getData('border'),
15315           config = node.getData('config'),
15316           renderSubtitle = node.getData('renderSubtitle'),
15317           renderBackground = config.renderBackground,
15318           showLabels = config.showLabels,
15319           resizeLabels = config.resizeLabels,
15320           label = config.Label;
15321
15322       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15323       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15324       //background rendering for IE
15325                 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15326                         backgroundColor = config.backgroundColor,
15327                         size = canvas.getSize();
15328                         ctx.save();
15329                     ctx.fillStyle = backgroundColor;
15330                     ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15331                     
15332                     //subtitle
15333
15334                         var margin = config.Margin,
15335                         title = config.Title,
15336                         subtitle = config.Subtitle;
15337                         ctx.fillStyle = title.color;
15338                         ctx.textAlign = 'left';
15339                         
15340                         if(title.text != "") {
15341                                 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15342                                 ctx.moveTo(0,0);
15343                                 if(label.type == 'Native') {
15344                                         ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15345                                 }
15346                         }       
15347         
15348                         if(subtitle.text != "") {
15349                                 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15350                                 if(label.type == 'Native') {
15351                                         ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15352                                 } 
15353                         }
15354                         ctx.restore();          
15355                 }
15356       if (colorArray && dimArray && stringArray) {
15357         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15358           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15359           if(dimi <= 0) continue;
15360           ctx.fillStyle = ctx.strokeStyle = colori;
15361           
15362           polar.rho = acum + config.sliceOffset;
15363           polar.theta = begin;
15364           var p1coord = polar.getc(true);
15365           polar.theta = end;
15366           var p2coord = polar.getc(true);
15367           polar.rho += dimi;
15368           var p3coord = polar.getc(true);
15369           polar.theta = begin;
15370           var p4coord = polar.getc(true);
15371           
15372           if(typeof FlashCanvas == "undefined") {
15373                   //drop shadow
15374                   ctx.beginPath();
15375                   ctx.fillStyle = "rgba(0,0,0,.2)";
15376                   ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15377                   ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);    
15378                   ctx.fill();
15379                   
15380                   if(gradient && dimi) {
15381                     var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15382                         xpos, ypos, acum + dimi + config.sliceOffset);
15383                     var colorRgb = $.hexToRgb(colori), 
15384                         endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15385                         endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15386         
15387                     radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15388                     radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15389                                 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15390                     radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15391                     ctx.fillStyle = radialGradient;
15392                   }
15393           }
15394
15395           
15396           //fixing FF arc method + fill
15397           ctx.beginPath();
15398           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15399           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15400           ctx.fill();
15401           if(border && border.name == stringArray[i]) {
15402             opt.acum = acum;
15403             opt.dimValue = dimArray[i];
15404             opt.begin = begin;
15405             opt.end = end;
15406             opt.sliceValue = valueArray[i];
15407           }
15408           acum += (dimi || 0);
15409           valAcum += (valueArray[i] || 0);
15410         }
15411         if(border) {
15412           ctx.save();
15413           ctx.globalCompositeOperation = "source-over";
15414           ctx.lineWidth = 2;
15415           ctx.strokeStyle = border.color;
15416           var s = begin < end? 1 : -1;
15417           ctx.beginPath();
15418           //fixing FF arc method + fill
15419           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15420           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15421           ctx.closePath();
15422           ctx.stroke();
15423           ctx.restore();
15424         }
15425         if(showLabels && label.type == 'Native') {
15426           ctx.save();
15427           ctx.fillStyle = ctx.strokeStyle = label.color;
15428           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15429               fontSize = (label.size * scale) >> 0;
15430           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15431           
15432           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15433           ctx.textBaseline = 'middle';
15434           ctx.textAlign = 'center';
15435           pi = Math.PI;
15436           angle = theta * 360 / (2 * pi);
15437           polar.rho = acum + config.labelOffset + config.sliceOffset;
15438           polar.theta = node.pos.theta;
15439           var cart = polar.getc(true);
15440           if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15441                 
15442                 } else {
15443                   if(config.labelType == 'name') {
15444                                 ctx.fillText(node.name, cart.x, cart.y);
15445                           } else {
15446                                 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15447                           }
15448           }
15449           ctx.restore();
15450         }
15451       }
15452     },
15453     'contains': function(node, pos) {
15454       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15455         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15456         var ld = this.config.levelDistance, d = node._depth;
15457         var config = node.getData('config');
15458
15459         if(rho <=ld * d + config.sliceOffset) {
15460           var dimArray = node.getData('dimArray');
15461           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15462             var dimi = dimArray[i];
15463             if(rho >= acum && rho <= acum + dimi) {
15464                           var url = Url.decode(node.getData('linkArray')[i]);
15465               return {
15466                 name: node.getData('stringArray')[i],
15467                 link: url,
15468                 color: node.getData('colorArray')[i],
15469                 value: node.getData('valueArray')[i],
15470                 percentage: node.getData('percentage'),
15471                 valuelabel: node.getData('valuelabelsArray')[i],
15472                 label: node.name
15473               };
15474             }
15475             acum += dimi;
15476           }
15477         }
15478         return false;
15479         
15480       }
15481       return false;
15482     }
15483   }
15484 });
15485
15486 /*
15487   Class: PieChart
15488   
15489   A visualization that displays stacked bar charts.
15490   
15491   Constructor Options:
15492   
15493   See <Options.PieChart>.
15494
15495 */
15496 $jit.PieChart = new Class({
15497   sb: null,
15498   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15499   selected: {},
15500   busy: false,
15501   
15502   initialize: function(opt) {
15503     this.controller = this.config = 
15504       $.merge(Options("Canvas", "PieChart", "Label"), {
15505         Label: { type: 'Native' }
15506       }, opt);
15507     this.initializeViz();
15508   },
15509   
15510   initializeViz: function() {
15511     var config = this.config, that = this;
15512     var nodeType = config.type.split(":")[0];
15513     var sb = new $jit.Sunburst({
15514       injectInto: config.injectInto,
15515       useCanvas: config.useCanvas,
15516       withLabels: config.Label.type != 'Native',
15517       background: config.background,
15518       renderBackground: config.renderBackground,
15519       backgroundColor: config.backgroundColor,
15520       colorStop1: config.colorStop1,
15521       colorStop2: config.colorStop2,
15522       Label: {
15523         type: config.Label.type
15524       },
15525       Node: {
15526         overridable: true,
15527         type: 'piechart-' + nodeType,
15528         width: 1,
15529         height: 1
15530       },
15531       Edge: {
15532         type: 'none'
15533       },
15534       Tips: {
15535         enable: config.Tips.enable,
15536         type: 'Native',
15537         force: true,
15538         onShow: function(tip, node, contains) {
15539           var elem = contains;
15540           config.Tips.onShow(tip, elem, node);
15541                           if(elem.link != 'undefined' && elem.link != '') {
15542                                 document.body.style.cursor = 'pointer';
15543                           }
15544         },
15545                 onHide: function() {
15546                                 document.body.style.cursor = 'default';
15547         }
15548       },
15549       Events: {
15550         enable: true,
15551         type: 'Native',
15552         onClick: function(node, eventInfo, evt) {
15553           if(!config.Events.enable) return;
15554           var elem = eventInfo.getContains();
15555           config.Events.onClick(elem, eventInfo, evt);
15556         },
15557         onMouseMove: function(node, eventInfo, evt) {
15558           if(!config.hoveredColor) return;
15559           if(node) {
15560             var elem = eventInfo.getContains();
15561             that.select(node.id, elem.name, elem.index);
15562           } else {
15563             that.select(false, false, false);
15564           }
15565         }
15566       },
15567       onCreateLabel: function(domElement, node) {
15568         var labelConf = config.Label;
15569         if(config.showLabels) {
15570           var style = domElement.style;
15571           style.fontSize = labelConf.size + 'px';
15572           style.fontFamily = labelConf.family;
15573           style.color = labelConf.color;
15574           style.textAlign = 'center';
15575           if(config.labelType == 'name') {
15576                 domElement.innerHTML = node.name;
15577           } else {
15578                 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15579           }
15580           domElement.style.width = '400px';
15581         }
15582       },
15583       onPlaceLabel: function(domElement, node) {
15584         if(!config.showLabels) return;
15585         var pos = node.pos.getp(true),
15586             dimArray = node.getData('dimArray'),
15587             span = node.getData('span') / 2,
15588             theta = node.pos.theta,
15589             begin = theta - span,
15590             end = theta + span,
15591             polar = new Polar;
15592
15593         var showLabels = config.showLabels,
15594             resizeLabels = config.resizeLabels,
15595             label = config.Label;
15596         
15597         if (dimArray) {
15598           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15599             acum += dimArray[i];
15600           }
15601           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15602               fontSize = (label.size * scale) >> 0;
15603           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15604           domElement.style.fontSize = fontSize + 'px';
15605           polar.rho = acum + config.labelOffset + config.sliceOffset;
15606           polar.theta = (begin + end) / 2;
15607           var pos = polar.getc(true);
15608           var radius = that.canvas.getSize();
15609           var labelPos = {
15610             x: Math.round(pos.x + radius.width / 2),
15611             y: Math.round(pos.y + radius.height / 2)
15612           };
15613           domElement.style.left = (labelPos.x - 200) + 'px';
15614           domElement.style.top = labelPos.y + 'px';
15615         }
15616       }
15617     });
15618     
15619     var size = sb.canvas.getSize(),
15620         min = Math.min;
15621     sb.config.levelDistance = min(size.width, size.height)/2 
15622       - config.offset - config.sliceOffset;
15623     this.sb = sb;
15624     this.canvas = this.sb.canvas;
15625     this.canvas.getCtx().globalCompositeOperation = 'lighter';
15626   },
15627     renderBackground: function() {
15628                 var canvas = this.canvas,
15629                 config = this.config,
15630                 backgroundColor = config.backgroundColor,
15631                 size = canvas.getSize(),
15632                 ctx = canvas.getCtx();
15633                 ctx.globalCompositeOperation = "destination-over";
15634             ctx.fillStyle = backgroundColor;
15635             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
15636   },
15637    renderTitle: function() {
15638         var canvas = this.canvas,
15639         size = canvas.getSize(),
15640         config = this.config,
15641         margin = config.Margin,
15642         radius = this.sb.config.levelDistance,
15643         title = config.Title,
15644         label = config.Label,
15645         subtitle = config.Subtitle;
15646         ctx = canvas.getCtx();
15647         ctx.fillStyle = title.color;
15648         ctx.textAlign = 'left';
15649         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15650         ctx.moveTo(0,0);
15651         if(label.type == 'Native') {
15652                 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15653         }       
15654   },
15655   renderSubtitle: function() {
15656         var canvas = this.canvas,
15657         size = canvas.getSize(),
15658         config = this.config,
15659         margin = config.Margin,
15660         radius = this.sb.config.levelDistance,
15661         title = config.Title,
15662         label = config.Label,
15663         subtitle = config.Subtitle;
15664         ctx = canvas.getCtx();
15665         ctx.fillStyle = title.color;
15666         ctx.textAlign = 'left';
15667         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15668         ctx.moveTo(0,0);
15669         if(label.type == 'Native') {
15670                 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15671         }       
15672   },
15673   clear: function() {
15674         var canvas = this.canvas;
15675         var ctx = canvas.getCtx(),
15676         size = canvas.getSize();
15677         ctx.fillStyle = "rgba(255,255,255,0)";
15678         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15679         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15680   },
15681   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
15682         var canvas = this.canvas,
15683         size = canvas.getSize(),
15684         config = this.config,
15685         orgHeight = size.height,
15686         margin = config.Margin,
15687         st = this.st,
15688         horz = config.orientation == 'horizontal';
15689         
15690
15691         var newWindowWidth = document.body.offsetWidth;
15692         var diff = newWindowWidth - orgWindowWidth;     
15693         var newWidth = orgContainerDivWidth + (diff/cols);
15694         var scale = newWidth/orgContainerDivWidth;
15695         canvas.resize(newWidth,orgHeight);
15696         if(typeof FlashCanvas == "undefined") {
15697                 canvas.clear();
15698         } else {
15699                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15700         }
15701         this.loadJSON(json);
15702
15703         },
15704   /*
15705     Method: loadJSON
15706    
15707     Loads JSON data into the visualization. 
15708     
15709     Parameters:
15710     
15711     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>.
15712     
15713     Example:
15714     (start code js)
15715     var pieChart = new $jit.PieChart(options);
15716     pieChart.loadJSON(json);
15717     (end code)
15718   */  
15719   loadJSON: function(json) {
15720     var prefix = $.time(), 
15721         ch = [], 
15722         sb = this.sb,
15723         name = $.splat(json.label),
15724         nameLength = name.length,
15725         color = $.splat(json.color || this.colors),
15726         colorLength = color.length,
15727         config = this.config,
15728         renderBackground = config.renderBackground,
15729         title = config.Title,
15730                 subtitle = config.Subtitle,
15731         gradient = !!config.type.split(":")[1],
15732         animate = config.animate,
15733         mono = nameLength == 1;
15734         totalValue = 0;
15735     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15736         var val = values[i];
15737         var valArray = $.splat(val.values);
15738         totalValue += parseFloat(valArray.sum());
15739     }
15740
15741     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15742       var val = values[i];
15743       var valArray = $.splat(val.values);
15744           var percentage = (valArray.sum()/totalValue) * 100;
15745
15746       var linkArray = $.splat(val.links);
15747       var valuelabelsArray = $.splat(val.valuelabels);
15748       
15749  
15750       ch.push({
15751         'id': prefix + val.label,
15752         'name': val.label,
15753         'data': {
15754           'value': valArray,
15755           'valuelabel': valuelabelsArray,
15756           '$linkArray': linkArray,
15757           '$valuelabelsArray': valuelabelsArray,
15758           '$valueArray': valArray,
15759           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15760           '$colorMono': $.splat(color[i % colorLength]),
15761           '$stringArray': name,
15762           '$gradient': gradient,
15763           '$config': config,
15764           '$iteration': i,
15765           '$percentage': percentage.toFixed(1),
15766           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15767         },
15768         'children': []
15769       });
15770     }
15771     var root = {
15772       'id': prefix + '$root',
15773       'name': '',
15774       'data': {
15775         '$type': 'none',
15776         '$width': 1,
15777         '$height': 1
15778       },
15779       'children': ch
15780     };
15781     sb.loadJSON(root);
15782     
15783     
15784     this.normalizeDims();
15785
15786     
15787     sb.refresh();
15788     if(title.text != "") {
15789         this.renderTitle();
15790     }
15791        
15792     if(subtitle.text != "") {
15793         this.renderSubtitle();
15794     }
15795      if(renderBackground && typeof FlashCanvas == "undefined") {
15796         this.renderBackground();        
15797     }
15798     
15799     if(animate) {
15800       sb.fx.animate({
15801         modes: ['node-property:dimArray'],
15802         duration:1500
15803       });
15804     }
15805   },
15806   
15807   /*
15808     Method: updateJSON
15809    
15810     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.
15811     
15812     Parameters:
15813     
15814     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15815     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15816     
15817     Example:
15818     
15819     (start code js)
15820     pieChart.updateJSON(json, {
15821       onComplete: function() {
15822         alert('update complete!');
15823       }
15824     });
15825     (end code)
15826   */  
15827   updateJSON: function(json, onComplete) {
15828     if(this.busy) return;
15829     this.busy = true;
15830     
15831     var sb = this.sb;
15832     var graph = sb.graph;
15833     var values = json.values;
15834     var animate = this.config.animate;
15835     var that = this;
15836     $.each(values, function(v) {
15837       var n = graph.getByName(v.label),
15838           vals = $.splat(v.values);
15839       if(n) {
15840         n.setData('valueArray', vals);
15841         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15842         if(json.label) {
15843           n.setData('stringArray', $.splat(json.label));
15844         }
15845       }
15846     });
15847     this.normalizeDims();
15848     if(animate) {
15849       sb.compute('end');
15850       sb.fx.animate({
15851         modes: ['node-property:dimArray:span', 'linear'],
15852         duration:1500,
15853         onComplete: function() {
15854           that.busy = false;
15855           onComplete && onComplete.onComplete();
15856         }
15857       });
15858     } else {
15859       sb.refresh();
15860     }
15861   },
15862     
15863   //adds the little brown bar when hovering the node
15864   select: function(id, name) {
15865     if(!this.config.hoveredColor) return;
15866     var s = this.selected;
15867     if(s.id != id || s.name != name) {
15868       s.id = id;
15869       s.name = name;
15870       s.color = this.config.hoveredColor;
15871       this.sb.graph.eachNode(function(n) {
15872         if(id == n.id) {
15873           n.setData('border', s);
15874         } else {
15875           n.setData('border', false);
15876         }
15877       });
15878       this.sb.plot();
15879     }
15880   },
15881   
15882   /*
15883     Method: getLegend
15884    
15885     Returns an object containing as keys the legend names and as values hex strings with color values.
15886     
15887     Example:
15888     
15889     (start code js)
15890     var legend = pieChart.getLegend();
15891     (end code)
15892   */  
15893   getLegend: function() {
15894     var legend = new Array();
15895     var name = new Array();
15896     var color = new Array();
15897     var n;
15898     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15899       n = adj.nodeTo;
15900     });
15901     var colors = n.getData('colorArray'),
15902         len = colors.length;
15903     $.each(n.getData('stringArray'), function(s, i) {
15904       color[i] = colors[i % len];
15905       name[i] = s;
15906     });
15907         legend['name'] = name;
15908         legend['color'] = color;
15909     return legend;
15910   },
15911   
15912   /*
15913     Method: getMaxValue
15914    
15915     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15916     
15917     Example:
15918     
15919     (start code js)
15920     var ans = pieChart.getMaxValue();
15921     (end code)
15922     
15923     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15924     
15925     Example:
15926     
15927     (start code js)
15928     //will return 100 for all PieChart instances,
15929     //displaying all of them with the same scale
15930     $jit.PieChart.implement({
15931       'getMaxValue': function() {
15932         return 100;
15933       }
15934     });
15935     (end code)
15936     
15937   */  
15938   getMaxValue: function() {
15939     var maxValue = 0;
15940     this.sb.graph.eachNode(function(n) {
15941       var valArray = n.getData('valueArray'),
15942           acum = 0;
15943       $.each(valArray, function(v) { 
15944         acum += +v;
15945       });
15946       maxValue = maxValue>acum? maxValue:acum;
15947     });
15948     return maxValue;
15949   },
15950   
15951   normalizeDims: function() {
15952     //number of elements
15953     var root = this.sb.graph.getNode(this.sb.root), l=0;
15954     root.eachAdjacency(function() {
15955       l++;
15956     });
15957     var maxValue = this.getMaxValue() || 1,
15958         config = this.config,
15959         animate = config.animate,
15960         rho = this.sb.config.levelDistance;
15961     this.sb.graph.eachNode(function(n) {
15962       var acum = 0, animateValue = [];
15963       $.each(n.getData('valueArray'), function(v) {
15964         acum += +v;
15965         animateValue.push(1);
15966       });
15967       var stat = (animateValue.length == 1) && !config.updateHeights;
15968       if(animate) {
15969         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15970           return stat? rho: (n * rho / maxValue); 
15971         }), 'end');
15972         var dimArray = n.getData('dimArray');
15973         if(!dimArray) {
15974           n.setData('dimArray', animateValue);
15975         }
15976       } else {
15977         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15978           return stat? rho : (n * rho / maxValue); 
15979         }));
15980       }
15981       n.setData('normalizedDim', acum / maxValue);
15982     });
15983   }
15984 });
15985
15986
15987 //Gauge Chart
15988
15989 Options.GaugeChart = {
15990   $extend: true,
15991
15992   animate: true,
15993   offset: 25, // page offset
15994   sliceOffset:0,
15995   labelOffset: 3, // label offset
15996   type: 'stacked', // gradient
15997   labelType: 'name',
15998   hoveredColor: '#9fd4ff',
15999   Events: {
16000     enable: false,
16001     onClick: $.empty
16002   },
16003   Tips: {
16004     enable: false,
16005     onShow: $.empty,
16006     onHide: $.empty
16007   },
16008   showLabels: true,
16009   resizeLabels: false,
16010   
16011   //only valid for mono-valued datasets
16012   updateHeights: false
16013 };
16014
16015
16016
16017 $jit.Sunburst.Plot.NodeTypes.implement({
16018     'gaugechart-basic' : {
16019     'render' : function(node, canvas) {
16020       var pos = node.pos.getp(true),
16021           dimArray = node.getData('dimArray'),
16022           valueArray = node.getData('valueArray'),
16023           valuelabelsArray = node.getData('valuelabelsArray'),
16024           gaugeTarget = node.getData('gaugeTarget'),
16025           nodeIteration = node.getData('nodeIteration'),
16026           nodeLength = node.getData('nodeLength'),
16027           colorArray = node.getData('colorMono'),
16028           colorLength = colorArray.length,
16029           stringArray = node.getData('stringArray'),
16030           span = node.getData('span') / 2,
16031           theta = node.pos.theta,
16032           begin = ((theta - span)/2)+Math.PI,
16033           end = ((theta + span)/2)+Math.PI,
16034           polar = new Polar;
16035
16036   
16037       var ctx = canvas.getCtx(), 
16038           opt = {},
16039           gradient = node.getData('gradient'),
16040           border = node.getData('border'),
16041           config = node.getData('config'),
16042           showLabels = config.showLabels,
16043           resizeLabels = config.resizeLabels,
16044           label = config.Label;
16045
16046       var xpos = Math.cos((begin + end) /2);
16047       var ypos = Math.sin((begin + end) /2);
16048
16049       if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16050         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16051           var dimi = dimArray[i], colori = colorArray[i % colorLength];
16052           if(dimi <= 0) continue;
16053           ctx.fillStyle = ctx.strokeStyle = colori;
16054           if(gradient && dimi) {
16055             var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16056                 xpos, (ypos + dimi/2), acum + dimi);
16057             var colorRgb = $.hexToRgb(colori), 
16058                 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16059                 endColor = $.rgbToHex(ans);
16060
16061             radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16062             radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16063             radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16064             radialGradient.addColorStop(1,  'rgba('+ans+',1)');
16065             ctx.fillStyle = radialGradient;
16066           }
16067           
16068           polar.rho = acum;
16069           polar.theta = begin;
16070           var p1coord = polar.getc(true);
16071           polar.theta = end;
16072           var p2coord = polar.getc(true);
16073           polar.rho += dimi;
16074           var p3coord = polar.getc(true);
16075           polar.theta = begin;
16076           var p4coord = polar.getc(true);
16077
16078                   
16079           ctx.beginPath();
16080           //fixing FF arc method + fill
16081           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16082           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16083           ctx.fill();
16084                   
16085
16086           acum += (dimi || 0);
16087           valAcum += (valueArray[i] || 0);                
16088         }
16089                 
16090                 if(showLabels && label.type == 'Native') {
16091                           ctx.save();
16092                           ctx.fillStyle = ctx.strokeStyle = label.color;
16093
16094                           
16095                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16096                           ctx.textBaseline = 'bottom';
16097                           ctx.textAlign = 'center';
16098
16099                           polar.rho = acum * .65;
16100                           polar.theta = begin;
16101                           var cart = polar.getc(true);
16102                           
16103                           //changes y pos of first label
16104                           if(nodeIteration == 1) {
16105                                 textY = cart.y - (label.size/2) + acum /2;
16106                           } else {
16107                                 textY = cart.y + acum/2;
16108                           }
16109                           
16110                           if(config.labelType == 'name') {
16111                                 ctx.fillText(node.name, cart.x, textY);
16112                           } else {
16113                                 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16114                           }
16115                           
16116                           //adds final label
16117                           if(nodeIteration == nodeLength) {
16118                                 polar.theta = end;
16119                                 var cart = polar.getc(true);
16120                                 if(config.labelType == 'name') {
16121                                         ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16122                                 } else {
16123                                         ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16124                                 }
16125                                 
16126                           }
16127                           ctx.restore();
16128                 }
16129
16130       }
16131       },
16132     'contains': function(node, pos) {
16133                 
16134                 
16135                 
16136       if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16137                 var config = node.getData('config');
16138         var ld = this.config.levelDistance , d = node._depth;
16139                 var yOffset = pos.y - (ld/2);
16140                 var xOffset = pos.x;
16141         var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16142         if(rho <=parseInt(ld * d)) {
16143           var dimArray = node.getData('dimArray');
16144           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16145                         var dimi = dimArray[i];
16146             if(rho >= ld * .8 && rho <= acum + dimi) {
16147                 
16148                           var url = Url.decode(node.getData('linkArray')[i]);
16149               return {
16150                 name: node.getData('stringArray')[i],
16151                 link: url,
16152                 color: node.getData('colorArray')[i],
16153                 value: node.getData('valueArray')[i],
16154                 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16155                 label: node.name
16156               };
16157             }
16158             acum += dimi;
16159                         
16160                         
16161           }
16162         }
16163         return false;
16164         
16165       }
16166       return false;
16167     }
16168   }
16169 });
16170
16171 /*
16172   Class: GaugeChart
16173   
16174   A visualization that displays gauge charts
16175   
16176   Constructor Options:
16177   
16178   See <Options.Gauge>.
16179
16180 */
16181 $jit.GaugeChart = new Class({
16182   sb: null,
16183   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16184   selected: {},
16185   busy: false,
16186   
16187   initialize: function(opt) {
16188     this.controller = this.config = 
16189       $.merge(Options("Canvas", "GaugeChart", "Label"), {
16190         Label: { type: 'Native' }
16191       }, opt);
16192     this.initializeViz();
16193   },
16194   
16195   initializeViz: function() {
16196     var config = this.config, that = this;
16197     var nodeType = config.type.split(":")[0];
16198     var sb = new $jit.Sunburst({
16199       injectInto: config.injectInto,
16200       useCanvas: config.useCanvas,
16201       withLabels: config.Label.type != 'Native',
16202       background: config.background,
16203       renderBackground: config.renderBackground,
16204       backgroundColor: config.backgroundColor,
16205       colorStop1: config.colorStop1,
16206       colorStop2: config.colorStop2,
16207       Label: {
16208         type: config.Label.type
16209       },
16210       Node: {
16211         overridable: true,
16212         type: 'gaugechart-' + nodeType,
16213         width: 1,
16214         height: 1
16215       },
16216       Edge: {
16217         type: 'none'
16218       },
16219       Tips: {
16220         enable: config.Tips.enable,
16221         type: 'Native',
16222         force: true,
16223         onShow: function(tip, node, contains) {
16224           var elem = contains;
16225           config.Tips.onShow(tip, elem, node);
16226                           if(elem.link != 'undefined' && elem.link != '') {
16227                                 document.body.style.cursor = 'pointer';
16228                           }
16229         },
16230                 onHide: function() {
16231                                 document.body.style.cursor = 'default';
16232         }
16233       },
16234       Events: {
16235         enable: true,
16236         type: 'Native',
16237         onClick: function(node, eventInfo, evt) {
16238           if(!config.Events.enable) return;
16239           var elem = eventInfo.getContains();
16240           config.Events.onClick(elem, eventInfo, evt);
16241         }
16242         },
16243       onCreateLabel: function(domElement, node) {
16244         var labelConf = config.Label;
16245         if(config.showLabels) {
16246           var style = domElement.style;
16247           style.fontSize = labelConf.size + 'px';
16248           style.fontFamily = labelConf.family;
16249           style.color = labelConf.color;
16250           style.textAlign = 'center';
16251           valuelabelsArray = node.getData('valuelabelsArray'),
16252           nodeIteration = node.getData('nodeIteration'),
16253           nodeLength = node.getData('nodeLength'),
16254           canvas = sb.canvas,
16255           prefix = $.time();
16256           
16257           if(config.labelType == 'name') {
16258                 domElement.innerHTML = node.name;
16259           } else {
16260                 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16261           }
16262           
16263           domElement.style.width = '400px';
16264           
16265           //adds final label
16266                   if(nodeIteration == nodeLength && nodeLength != 0) {
16267                   idLabel = canvas.id + "-label";
16268                   container = document.getElementById(idLabel);
16269                   finalLabel = document.createElement('div');
16270                   finalLabelStyle = finalLabel.style;
16271                   finalLabel.id = prefix + "finalLabel";
16272                   finalLabelStyle.position = "absolute";
16273                   finalLabelStyle.width = "400px";
16274                   finalLabelStyle.left = "0px";
16275                   container.appendChild(finalLabel);
16276                         if(config.labelType == 'name') {
16277                                 finalLabel.innerHTML = node.name;
16278                         } else {
16279                                 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16280                         }
16281                         
16282                   }
16283         }
16284       },
16285       onPlaceLabel: function(domElement, node) {
16286         if(!config.showLabels) return;
16287         var pos = node.pos.getp(true),
16288             dimArray = node.getData('dimArray'),
16289             nodeIteration = node.getData('nodeIteration'),
16290             nodeLength = node.getData('nodeLength'),
16291             span = node.getData('span') / 2,
16292             theta = node.pos.theta,
16293             begin = ((theta - span)/2)+Math.PI,
16294             end = ((theta + span)/2)+Math.PI,
16295             polar = new Polar;
16296
16297         var showLabels = config.showLabels,
16298             resizeLabels = config.resizeLabels,
16299             label = config.Label,
16300             radiusOffset = sb.config.levelDistance;
16301
16302         if (dimArray) {
16303           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16304             acum += dimArray[i];
16305           }
16306           var scale = resizeLabels? node.getData('normalizedDim') : 1,
16307               fontSize = (label.size * scale) >> 0;
16308           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16309           domElement.style.fontSize = fontSize + 'px';
16310           polar.rho = acum * .65;
16311           polar.theta = begin;
16312           var pos = polar.getc(true);
16313           var radius = that.canvas.getSize();
16314           var labelPos = {
16315             x: Math.round(pos.x + radius.width / 2),
16316             y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16317           };
16318           
16319
16320           
16321           domElement.style.left = (labelPos.x - 200) + 'px';
16322           domElement.style.top = labelPos.y + 'px';
16323           
16324           //reposition first label
16325           if(nodeIteration == 1) {
16326                  domElement.style.top = labelPos.y - label.size + 'px';
16327           }
16328           
16329           
16330           //position final label
16331                 if(nodeIteration == nodeLength && nodeLength != 0) {
16332                         polar.theta = end;
16333                         var final = polar.getc(true);
16334                         var finalPos = {
16335                                         x: Math.round(final.x + radius.width / 2),
16336                                 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16337                                 };
16338                         finalLabel.style.left = (finalPos.x - 200) + "px";
16339                         finalLabel.style.top = finalPos.y - label.size + "px";
16340                     }
16341           
16342         }
16343       }
16344    
16345     });
16346     this.sb = sb;
16347     this.canvas = this.sb.canvas;
16348     var size = sb.canvas.getSize(),
16349         min = Math.min;
16350         sb.config.levelDistance = min(size.width, size.height)/2 
16351       - config.offset - config.sliceOffset;
16352
16353
16354   },
16355         
16356   renderBackground: function() {
16357         var canvas = this.sb.canvas,
16358         config = this.config,
16359         style = config.gaugeStyle,
16360         ctx = canvas.getCtx(),
16361         size = canvas.getSize(),
16362         radius = this.sb.config.levelDistance,
16363         startAngle = (Math.PI/180)*1,
16364         endAngle = (Math.PI/180)*179;
16365         
16366
16367         //background border
16368         ctx.fillStyle = style.borderColor;      
16369         ctx.beginPath();
16370         ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true); 
16371         ctx.fill(); 
16372         
16373         
16374         var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16375         radialGradient.addColorStop(0, '#ffffff');  
16376         radialGradient.addColorStop(0.3, style.backgroundColor);  
16377         radialGradient.addColorStop(0.6, style.backgroundColor);  
16378         radialGradient.addColorStop(1, '#FFFFFF'); 
16379         ctx.fillStyle = radialGradient;
16380         
16381         //background
16382         startAngle = (Math.PI/180)*0;
16383         endAngle = (Math.PI/180)*180;
16384         ctx.beginPath();
16385         ctx.arc(0,radius/2,radius,startAngle,endAngle, true); 
16386         ctx.fill();     
16387         
16388         
16389  
16390   },
16391   
16392   
16393   renderNeedle: function(gaugePosition,target) {
16394         var canvas = this.sb.canvas,
16395         config = this.config,
16396         style = config.gaugeStyle,
16397         ctx = canvas.getCtx(),
16398         size = canvas.getSize(),
16399         radius = this.sb.config.levelDistance;
16400         gaugeCenter = (radius/2);
16401         startAngle = 0;
16402         endAngle = (Math.PI/180)*180;
16403         
16404         
16405         // needle
16406         ctx.fillStyle = style.needleColor;
16407         var segments = 180/target;
16408         needleAngle = gaugePosition * segments;
16409         ctx.translate(0, gaugeCenter);
16410         ctx.save();
16411         ctx.rotate(needleAngle * Math.PI / 180);  
16412         ctx.beginPath();
16413         ctx.moveTo(0,0); 
16414         ctx.lineTo(0,-4);  
16415         ctx.lineTo(-radius*.9,-1);  
16416         ctx.lineTo(-radius*.9,1);  
16417         ctx.lineTo(0,4);  
16418         ctx.lineTo(0,0);  
16419         ctx.closePath(); 
16420         ctx.fill();
16421         ctx.restore(); 
16422         
16423         
16424         // stroke needle
16425         ctx.lineWidth = 1;
16426         ctx.strokeStyle = '#aa0000';
16427         ctx.save();
16428         ctx.rotate(needleAngle * Math.PI / 180);  
16429         ctx.beginPath();
16430         ctx.moveTo(0,0); 
16431         ctx.lineTo(0,-4);  
16432         ctx.lineTo(-radius*.8,-1);  
16433         ctx.lineTo(-radius*.8,1);  
16434         ctx.lineTo(0,4);  
16435         ctx.lineTo(0,0);  
16436         ctx.closePath(); 
16437         ctx.stroke();
16438         ctx.restore(); 
16439
16440         //needle cap
16441         ctx.fillStyle = "#000000";
16442         ctx.lineWidth = style.borderSize;
16443     ctx.strokeStyle = style.borderColor;
16444         var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16445         radialGradient.addColorStop(0, '#666666');  
16446         radialGradient.addColorStop(0.8, '#444444');  
16447         radialGradient.addColorStop(1, 'rgba(0,0,0,0)'); 
16448         ctx.fillStyle = radialGradient;
16449         ctx.translate(0,5);
16450         ctx.save();
16451         ctx.beginPath();
16452         ctx.arc(0,0,radius*.2,startAngle,endAngle, true); 
16453         ctx.fill();     
16454         ctx.restore();
16455
16456         
16457   },
16458   
16459   renderTicks: function(values) {
16460         var canvas = this.sb.canvas,
16461         config = this.config,
16462         style = config.gaugeStyle,
16463         ctx = canvas.getCtx(),
16464         size = canvas.getSize(),
16465         radius = this.sb.config.levelDistance,
16466         gaugeCenter = (radius/2);
16467         
16468         
16469         ctx.strokeStyle = style.borderColor;
16470         ctx.lineWidth = 5;
16471         ctx.lineCap = "round";
16472                 for(var i=0, total = 0, l=values.length; i<l; i++) {
16473                         var val = values[i];
16474                         if(val.label != 'GaugePosition') {
16475                         total += (parseInt(val.values) || 0);
16476                         }
16477                 }
16478         
16479                 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16480                         var val = values[i];
16481                         if(val.label != 'GaugePosition') {
16482                         acum += (parseInt(val.values) || 0);
16483
16484                            var segments = 180/total;
16485                         angle = acum * segments;
16486
16487                           //alert(acum);
16488                                  ctx.save();
16489                                  ctx.translate(0, gaugeCenter);
16490                                  ctx.beginPath();
16491                                 ctx.rotate(angle * (Math.PI/180));
16492                                 ctx.moveTo(-radius,0);
16493                                 ctx.lineTo(-radius*.75,0);
16494                                 ctx.stroke();
16495                                  ctx.restore();
16496                         
16497                         }
16498                 }
16499         },
16500         
16501         renderPositionLabel: function(position) {
16502                 var canvas = this.sb.canvas,
16503                 config = this.config,
16504                 label = config.Label,
16505                 style = config.gaugeStyle,
16506                 ctx = canvas.getCtx(),
16507                 size = canvas.getSize(),
16508                 radius = this.sb.config.levelDistance,
16509                 gaugeCenter = (radius/2);
16510                 ctx.textBaseline = 'middle';
16511                 ctx.textAlign = 'center';
16512                 ctx.font = style.positionFontSize + 'px ' + label.family;
16513                 ctx.fillStyle = "#ffffff";
16514                 ctx.lineWidth = 2;
16515                 height = style.positionFontSize + 10,
16516                 cornerRadius = 8,
16517                 idLabel = canvas.id + "-label";
16518                 container = document.getElementById(idLabel);
16519                 if(label.type == 'Native') {
16520                         var m = ctx.measureText(position),
16521                         width = m.width + 40;
16522                 } else {
16523                         var width = 70;
16524                 }
16525                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16526                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16527                 if(label.type == 'Native') {
16528                         ctx.fillStyle = label.color;
16529                         ctx.fillText(position, 0, (height/2) + style.positionOffset);
16530                 } else {
16531                         var labelDiv =  document.createElement('div');
16532                         labelDivStyle = labelDiv.style;
16533                         labelDivStyle.color =  label.color;
16534                         labelDivStyle.fontSize =  style.positionFontSize + "px";
16535                         labelDivStyle.position = "absolute";
16536                         labelDivStyle.width = width + "px";
16537                         labelDivStyle.left = (size.width/2) - (width/2) + "px";
16538                         labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16539                         labelDiv.innerHTML = position;
16540                         container.appendChild(labelDiv);
16541                 }
16542         
16543     },
16544     
16545    renderSubtitle: function() {
16546         var canvas = this.canvas,
16547         size = canvas.getSize(),
16548         config = this.config,
16549         margin = config.Margin,
16550         radius = this.sb.config.levelDistance,
16551         title = config.Title,
16552         label = config.Label,
16553         subtitle = config.Subtitle;
16554         ctx = canvas.getCtx();
16555         ctx.fillStyle = title.color;
16556         ctx.textAlign = 'left';
16557         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16558         ctx.moveTo(0,0);
16559         if(label.type == 'Native') {
16560                 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2)); 
16561         }
16562   },
16563   
16564   renderChartBackground: function() {
16565                 var canvas = this.canvas,
16566                 config = this.config,
16567                 backgroundColor = config.backgroundColor,
16568                 size = canvas.getSize(),
16569                 ctx = canvas.getCtx();
16570                 //ctx.globalCompositeOperation = "destination-over";
16571             ctx.fillStyle = backgroundColor;
16572             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
16573   },
16574   clear: function() {
16575         var canvas = this.canvas;
16576         var ctx = canvas.getCtx(),
16577         size = canvas.getSize();
16578         ctx.fillStyle = "rgba(255,255,255,0)";
16579         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16580         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16581  },
16582   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
16583         var canvas = this.canvas,
16584         size = canvas.getSize(),
16585         config = this.config,
16586         orgHeight = size.height,
16587         margin = config.Margin,
16588         st = this.st,
16589         horz = config.orientation == 'horizontal';
16590         
16591
16592         var newWindowWidth = document.body.offsetWidth;
16593         var diff = newWindowWidth - orgWindowWidth;     
16594         var newWidth = orgContainerDivWidth + (diff/cols);
16595         canvas.resize(newWidth,orgHeight);
16596         if(typeof FlashCanvas == "undefined") {
16597                 canvas.clear();
16598         } else {
16599                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16600         }
16601         this.loadJSON(json);
16602
16603         },
16604   loadJSON: function(json) {
16605   
16606      var prefix = $.time(), 
16607         ch = [], 
16608         sb = this.sb,
16609         name = $.splat(json.label),
16610         nameLength = name.length,
16611         color = $.splat(json.color || this.colors),
16612         colorLength = color.length,
16613         config = this.config,
16614         renderBackground = config.renderBackground,
16615         gradient = !!config.type.split(":")[1],
16616         animate = config.animate,
16617         mono = nameLength == 1;
16618                 var props = $.splat(json.properties)[0];
16619
16620     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16621         
16622       var val = values[i];
16623           if(val.label != 'GaugePosition') {
16624                   var valArray = $.splat(val.values);
16625                   var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16626                   var valuelabelsArray = $.splat(val.valuelabels);
16627
16628                   ch.push({
16629                         'id': prefix + val.label,
16630                         'name': val.label,
16631                         'data': {
16632                           'value': valArray,
16633                           'valuelabel': valuelabelsArray,
16634                           '$linkArray': linkArray,
16635                           '$valuelabelsArray': valuelabelsArray,
16636                           '$valueArray': valArray,
16637                           '$nodeIteration': i,
16638                           '$nodeLength': l-1,
16639                           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16640                           '$colorMono': $.splat(color[i % colorLength]),
16641                           '$stringArray': name,
16642                           '$gradient': gradient,
16643                           '$config': config,
16644                           '$gaugeTarget': props['gaugeTarget'],
16645                           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16646                         },
16647                         'children': []
16648                   });
16649           } else {
16650                 var gaugePosition = val.gvalue;
16651                 var gaugePositionLabel = val.gvaluelabel;
16652           }
16653     }
16654     var root = {
16655       'id': prefix + '$root',
16656       'name': '',
16657       'data': {
16658         '$type': 'none',
16659         '$width': 1,
16660         '$height': 1
16661       },
16662       'children': ch
16663     };
16664         
16665         
16666     sb.loadJSON(root);
16667     
16668     if(renderBackground) {
16669         this.renderChartBackground();   
16670     }
16671     
16672     this.renderBackground();
16673     this.renderSubtitle();
16674     
16675     this.normalizeDims();
16676         
16677     sb.refresh();
16678     if(animate) {
16679       sb.fx.animate({
16680         modes: ['node-property:dimArray'],
16681         duration:1500
16682       });
16683     }
16684         
16685
16686         this.renderPositionLabel(gaugePositionLabel);
16687         if (props['gaugeTarget'] != 0) {
16688                 this.renderTicks(json.values);
16689                 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16690         }
16691         
16692         
16693
16694   },
16695   
16696   updateJSON: function(json, onComplete) {
16697     if(this.busy) return;
16698     this.busy = true;
16699     
16700     var sb = this.sb;
16701     var graph = sb.graph;
16702     var values = json.values;
16703     var animate = this.config.animate;
16704     var that = this;
16705     $.each(values, function(v) {
16706       var n = graph.getByName(v.label),
16707           vals = $.splat(v.values);
16708       if(n) {
16709         n.setData('valueArray', vals);
16710         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16711         if(json.label) {
16712           n.setData('stringArray', $.splat(json.label));
16713         }
16714       }
16715     });
16716     this.normalizeDims();
16717     if(animate) {
16718       sb.compute('end');
16719       sb.fx.animate({
16720         modes: ['node-property:dimArray:span', 'linear'],
16721         duration:1500,
16722         onComplete: function() {
16723           that.busy = false;
16724           onComplete && onComplete.onComplete();
16725         }
16726       });
16727     } else {
16728       sb.refresh();
16729     }
16730   },
16731     
16732   //adds the little brown bar when hovering the node
16733   select: function(id, name) {
16734     if(!this.config.hoveredColor) return;
16735     var s = this.selected;
16736     if(s.id != id || s.name != name) {
16737       s.id = id;
16738       s.name = name;
16739       s.color = this.config.hoveredColor;
16740       this.sb.graph.eachNode(function(n) {
16741         if(id == n.id) {
16742           n.setData('border', s);
16743         } else {
16744           n.setData('border', false);
16745         }
16746       });
16747       this.sb.plot();
16748     }
16749   },
16750   
16751   /*
16752     Method: getLegend
16753    
16754     Returns an object containing as keys the legend names and as values hex strings with color values.
16755     
16756     Example:
16757     
16758     (start code js)
16759     var legend = pieChart.getLegend();
16760     (end code)
16761   */  
16762   getLegend: function() {
16763     var legend = new Array();
16764     var name = new Array();
16765     var color = new Array();
16766     var n;
16767     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16768       n = adj.nodeTo;
16769     });
16770     var colors = n.getData('colorArray'),
16771         len = colors.length;
16772     $.each(n.getData('stringArray'), function(s, i) {
16773       color[i] = colors[i % len];
16774       name[i] = s;
16775     });
16776         legend['name'] = name;
16777         legend['color'] = color;
16778     return legend;
16779   },
16780   
16781   /*
16782     Method: getMaxValue
16783    
16784     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16785     
16786     Example:
16787     
16788     (start code js)
16789     var ans = pieChart.getMaxValue();
16790     (end code)
16791     
16792     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16793     
16794     Example:
16795     
16796     (start code js)
16797     //will return 100 for all PieChart instances,
16798     //displaying all of them with the same scale
16799     $jit.PieChart.implement({
16800       'getMaxValue': function() {
16801         return 100;
16802       }
16803     });
16804     (end code)
16805     
16806   */  
16807   getMaxValue: function() {
16808     var maxValue = 0;
16809     this.sb.graph.eachNode(function(n) {
16810       var valArray = n.getData('valueArray'),
16811           acum = 0;
16812       $.each(valArray, function(v) { 
16813         acum += +v;
16814       });
16815       maxValue = maxValue>acum? maxValue:acum;
16816     });
16817     return maxValue;
16818   },
16819   
16820   normalizeDims: function() {
16821     //number of elements
16822     var root = this.sb.graph.getNode(this.sb.root), l=0;
16823     root.eachAdjacency(function() {
16824       l++;
16825     });
16826     var maxValue = this.getMaxValue() || 1,
16827         config = this.config,
16828         animate = config.animate,
16829         rho = this.sb.config.levelDistance;
16830     this.sb.graph.eachNode(function(n) {
16831       var acum = 0, animateValue = [];
16832       $.each(n.getData('valueArray'), function(v) {
16833         acum += +v;
16834         animateValue.push(1);
16835       });
16836       var stat = (animateValue.length == 1) && !config.updateHeights;
16837       if(animate) {
16838         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16839           return stat? rho: (n * rho / maxValue); 
16840         }), 'end');
16841         var dimArray = n.getData('dimArray');
16842         if(!dimArray) {
16843           n.setData('dimArray', animateValue);
16844         }
16845       } else {
16846         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16847           return stat? rho : (n * rho / maxValue); 
16848         }));
16849       }
16850       n.setData('normalizedDim', acum / maxValue);
16851     });
16852   }
16853 });
16854
16855
16856 /*
16857  * Class: Layouts.TM
16858  * 
16859  * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16860  * 
16861  * Implemented By:
16862  * 
16863  * <TM>
16864  * 
16865  */
16866 Layouts.TM = {};
16867
16868 Layouts.TM.SliceAndDice = new Class({
16869   compute: function(prop) {
16870     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16871     this.controller.onBeforeCompute(root);
16872     var size = this.canvas.getSize(),
16873         config = this.config,
16874         width = size.width,
16875         height = size.height;
16876     this.graph.computeLevels(this.root, 0, "ignore");
16877     //set root position and dimensions
16878     root.getPos(prop).setc(-width/2, -height/2);
16879     root.setData('width', width, prop);
16880     root.setData('height', height + config.titleHeight, prop);
16881     this.computePositions(root, root, this.layout.orientation, prop);
16882     this.controller.onAfterCompute(root);
16883   },
16884   
16885   computePositions: function(par, ch, orn, prop) {
16886     //compute children areas
16887     var totalArea = 0;
16888     par.eachSubnode(function(n) {
16889       totalArea += n.getData('area', prop);
16890     });
16891     
16892     var config = this.config,
16893         offst = config.offset,
16894         width  = par.getData('width', prop),
16895         height = par.getData('height', prop) - config.titleHeight,
16896         fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16897     
16898     var otherSize, size, dim, pos, pos2, posth, pos2th;
16899     var horizontal = (orn == "h");
16900     if(horizontal) {
16901       orn = 'v';    
16902       otherSize = height;
16903       size = width * fact;
16904       dim = 'height';
16905       pos = 'y';
16906       pos2 = 'x';
16907       posth = config.titleHeight;
16908       pos2th = 0;
16909     } else {
16910       orn = 'h';    
16911       otherSize = height * fact;
16912       size = width;
16913       dim = 'width';
16914       pos = 'x';
16915       pos2 = 'y';
16916       posth = 0;
16917       pos2th = config.titleHeight;
16918     }
16919     var cpos = ch.getPos(prop);
16920     ch.setData('width', size, prop);
16921     ch.setData('height', otherSize, prop);
16922     var offsetSize = 0, tm = this;
16923     ch.eachSubnode(function(n) {
16924       var p = n.getPos(prop);
16925       p[pos] = offsetSize + cpos[pos] + posth;
16926       p[pos2] = cpos[pos2] + pos2th;
16927       tm.computePositions(ch, n, orn, prop);
16928       offsetSize += n.getData(dim, prop);
16929     });
16930   }
16931
16932 });
16933
16934 Layouts.TM.Area = {
16935  /*
16936     Method: compute
16937  
16938    Called by loadJSON to calculate recursively all node positions and lay out the tree.
16939  
16940     Parameters:
16941
16942        json - A JSON tree. See also <Loader.loadJSON>.
16943        coord - A coordinates object specifying width, height, left and top style properties.
16944  */
16945  compute: function(prop) {
16946     prop = prop || "current";
16947     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16948     this.controller.onBeforeCompute(root);
16949     var config = this.config,
16950         size = this.canvas.getSize(),
16951         width = size.width,
16952         height = size.height,
16953         offst = config.offset,
16954         offwdth = width - offst,
16955         offhght = height - offst;
16956     this.graph.computeLevels(this.root, 0, "ignore");
16957     //set root position and dimensions
16958     root.getPos(prop).setc(-width/2, -height/2);
16959     root.setData('width', width, prop);
16960     root.setData('height', height, prop);
16961     //create a coordinates object
16962     var coord = {
16963         'top': -height/2 + config.titleHeight,
16964         'left': -width/2,
16965         'width': offwdth,
16966         'height': offhght - config.titleHeight
16967     };
16968     this.computePositions(root, coord, prop);
16969     this.controller.onAfterCompute(root);
16970  }, 
16971  
16972  /*
16973     Method: computeDim
16974  
16975    Computes dimensions and positions of a group of nodes
16976    according to a custom layout row condition. 
16977  
16978     Parameters:
16979
16980        tail - An array of nodes.  
16981        initElem - An array of nodes (containing the initial node to be laid).
16982        w - A fixed dimension where nodes will be layed out.
16983        coord - A coordinates object specifying width, height, left and top style properties.
16984        comp - A custom comparison function
16985  */
16986  computeDim: function(tail, initElem, w, coord, comp, prop) {
16987    if(tail.length + initElem.length == 1) {
16988      var l = (tail.length == 1)? tail : initElem;
16989      this.layoutLast(l, w, coord, prop);
16990      return;
16991    }
16992    if(tail.length >= 2 && initElem.length == 0) {
16993      initElem = [tail.shift()];
16994    }
16995    if(tail.length == 0) {
16996      if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
16997      return;
16998    }
16999    var c = tail[0];
17000    if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17001      this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17002    } else {
17003      var newCoords = this.layoutRow(initElem, w, coord, prop);
17004      this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17005    }
17006  },
17007
17008  
17009  /*
17010     Method: worstAspectRatio
17011  
17012    Calculates the worst aspect ratio of a group of rectangles. 
17013        
17014     See also:
17015        
17016        <http://en.wikipedia.org/wiki/Aspect_ratio>
17017    
17018     Parameters:
17019
17020      ch - An array of nodes.  
17021      w  - The fixed dimension where rectangles are being laid out.
17022
17023     Returns:
17024  
17025         The worst aspect ratio.
17026
17027
17028  */
17029  worstAspectRatio: function(ch, w) {
17030    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17031    var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17032    for(var i=0, l=ch.length; i<l; i++) {
17033      var area = ch[i]._area;
17034      areaSum += area; 
17035      minArea = minArea < area? minArea : area;
17036      maxArea = maxArea > area? maxArea : area; 
17037    }
17038    var sqw = w * w, sqAreaSum = areaSum * areaSum;
17039    return Math.max(sqw * maxArea / sqAreaSum,
17040            sqAreaSum / (sqw * minArea));
17041  },
17042  
17043  /*
17044     Method: avgAspectRatio
17045  
17046    Calculates the average aspect ratio of a group of rectangles. 
17047        
17048        See also:
17049        
17050        <http://en.wikipedia.org/wiki/Aspect_ratio>
17051    
17052     Parameters:
17053
17054      ch - An array of nodes.  
17055        w - The fixed dimension where rectangles are being laid out.
17056
17057     Returns:
17058  
17059         The average aspect ratio.
17060
17061
17062  */
17063  avgAspectRatio: function(ch, w) {
17064    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17065    var arSum = 0;
17066    for(var i=0, l=ch.length; i<l; i++) {
17067      var area = ch[i]._area;
17068      var h = area / w;
17069      arSum += w > h? w / h : h / w;
17070    }
17071    return arSum / l;
17072  },
17073
17074  /*
17075     layoutLast
17076  
17077    Performs the layout of the last computed sibling.
17078  
17079     Parameters:
17080
17081        ch - An array of nodes.  
17082        w - A fixed dimension where nodes will be layed out.
17083      coord - A coordinates object specifying width, height, left and top style properties.
17084  */
17085  layoutLast: function(ch, w, coord, prop) {
17086    var child = ch[0];
17087    child.getPos(prop).setc(coord.left, coord.top);
17088    child.setData('width', coord.width, prop);
17089    child.setData('height', coord.height, prop);
17090  }
17091 };
17092
17093
17094 Layouts.TM.Squarified = new Class({
17095  Implements: Layouts.TM.Area,
17096  
17097  computePositions: function(node, coord, prop) {
17098    var config = this.config;
17099    
17100    if (coord.width >= coord.height) 
17101      this.layout.orientation = 'h';
17102    else
17103      this.layout.orientation = 'v';
17104    
17105    var ch = node.getSubnodes([1, 1], "ignore");
17106    if(ch.length > 0) {
17107      this.processChildrenLayout(node, ch, coord, prop);
17108      for(var i=0, l=ch.length; i<l; i++) {
17109        var chi = ch[i]; 
17110        var offst = config.offset,
17111            height = chi.getData('height', prop) - offst - config.titleHeight,
17112            width = chi.getData('width', prop) - offst;
17113        var chipos = chi.getPos(prop);
17114        coord = {
17115          'width': width,
17116          'height': height,
17117          'top': chipos.y + config.titleHeight,
17118          'left': chipos.x
17119        };
17120        this.computePositions(chi, coord, prop);
17121      }
17122    }
17123  },
17124
17125  /*
17126     Method: processChildrenLayout
17127  
17128    Computes children real areas and other useful parameters for performing the Squarified algorithm.
17129  
17130     Parameters:
17131
17132        par - The parent node of the json subtree.  
17133        ch - An Array of nodes
17134      coord - A coordinates object specifying width, height, left and top style properties.
17135  */
17136  processChildrenLayout: function(par, ch, coord, prop) {
17137    //compute children real areas
17138    var parentArea = coord.width * coord.height;
17139    var i, l=ch.length, totalChArea=0, chArea = [];
17140    for(i=0; i<l; i++) {
17141      chArea[i] = parseFloat(ch[i].getData('area', prop));
17142      totalChArea += chArea[i];
17143    }
17144    for(i=0; i<l; i++) {
17145      ch[i]._area = parentArea * chArea[i] / totalChArea;
17146    }
17147    var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17148    ch.sort(function(a, b) { 
17149      var diff = b._area - a._area; 
17150      return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1)); 
17151    });
17152    var initElem = [ch[0]];
17153    var tail = ch.slice(1);
17154    this.squarify(tail, initElem, minimumSideValue, coord, prop);
17155  },
17156
17157  /*
17158    Method: squarify
17159  
17160    Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17161  
17162     Parameters:
17163
17164        tail - An array of nodes.  
17165        initElem - An array of nodes, containing the initial node to be laid out.
17166        w - A fixed dimension where nodes will be laid out.
17167        coord - A coordinates object specifying width, height, left and top style properties.
17168  */
17169  squarify: function(tail, initElem, w, coord, prop) {
17170    this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17171  },
17172  
17173  /*
17174     Method: layoutRow
17175  
17176    Performs the layout of an array of nodes.
17177  
17178     Parameters:
17179
17180        ch - An array of nodes.  
17181        w - A fixed dimension where nodes will be laid out.
17182        coord - A coordinates object specifying width, height, left and top style properties.
17183  */
17184  layoutRow: function(ch, w, coord, prop) {
17185    if(this.layout.horizontal()) {
17186      return this.layoutV(ch, w, coord, prop);
17187    } else {
17188      return this.layoutH(ch, w, coord, prop);
17189    }
17190  },
17191  
17192  layoutV: function(ch, w, coord, prop) {
17193    var totalArea = 0, rnd = function(x) { return x; }; 
17194    $.each(ch, function(elem) { totalArea += elem._area; });
17195    var width = rnd(totalArea / w), top =  0; 
17196    for(var i=0, l=ch.length; i<l; i++) {
17197      var h = rnd(ch[i]._area / width);
17198      var chi = ch[i];
17199      chi.getPos(prop).setc(coord.left, coord.top + top);
17200      chi.setData('width', width, prop);
17201      chi.setData('height', h, prop);
17202      top += h;
17203    }
17204    var ans = {
17205      'height': coord.height,
17206      'width': coord.width - width,
17207      'top': coord.top,
17208      'left': coord.left + width
17209    };
17210    //take minimum side value.
17211    ans.dim = Math.min(ans.width, ans.height);
17212    if(ans.dim != ans.height) this.layout.change();
17213    return ans;
17214  },
17215  
17216  layoutH: function(ch, w, coord, prop) {
17217    var totalArea = 0; 
17218    $.each(ch, function(elem) { totalArea += elem._area; });
17219    var height = totalArea / w,
17220        top = coord.top, 
17221        left = 0;
17222    
17223    for(var i=0, l=ch.length; i<l; i++) {
17224      var chi = ch[i];
17225      var w = chi._area / height;
17226      chi.getPos(prop).setc(coord.left + left, top);
17227      chi.setData('width', w, prop);
17228      chi.setData('height', height, prop);
17229      left += w;
17230    }
17231    var ans = {
17232      'height': coord.height - height,
17233      'width': coord.width,
17234      'top': coord.top + height,
17235      'left': coord.left
17236    };
17237    ans.dim = Math.min(ans.width, ans.height);
17238    if(ans.dim != ans.width) this.layout.change();
17239    return ans;
17240  }
17241 });
17242
17243 Layouts.TM.Strip = new Class({
17244   Implements: Layouts.TM.Area,
17245
17246     /*
17247       Method: compute
17248     
17249      Called by loadJSON to calculate recursively all node positions and lay out the tree.
17250     
17251       Parameters:
17252     
17253          json - A JSON subtree. See also <Loader.loadJSON>. 
17254        coord - A coordinates object specifying width, height, left and top style properties.
17255     */
17256     computePositions: function(node, coord, prop) {
17257      var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17258      if(ch.length > 0) {
17259        this.processChildrenLayout(node, ch, coord, prop);
17260        for(var i=0, l=ch.length; i<l; i++) {
17261          var chi = ch[i];
17262          var offst = config.offset,
17263              height = chi.getData('height', prop) - offst - config.titleHeight,
17264              width  = chi.getData('width', prop)  - offst;
17265          var chipos = chi.getPos(prop);
17266          coord = {
17267            'width': width,
17268            'height': height,
17269            'top': chipos.y + config.titleHeight,
17270            'left': chipos.x
17271          };
17272          this.computePositions(chi, coord, prop);
17273        }
17274      }
17275     },
17276     
17277     /*
17278       Method: processChildrenLayout
17279     
17280      Computes children real areas and other useful parameters for performing the Strip algorithm.
17281     
17282       Parameters:
17283     
17284          par - The parent node of the json subtree.  
17285          ch - An Array of nodes
17286          coord - A coordinates object specifying width, height, left and top style properties.
17287     */
17288     processChildrenLayout: function(par, ch, coord, prop) {
17289      //compute children real areas
17290       var parentArea = coord.width * coord.height;
17291       var i, l=ch.length, totalChArea=0, chArea = [];
17292       for(i=0; i<l; i++) {
17293         chArea[i] = +ch[i].getData('area', prop);
17294         totalChArea += chArea[i];
17295       }
17296       for(i=0; i<l; i++) {
17297         ch[i]._area = parentArea * chArea[i] / totalChArea;
17298       }
17299      var side = this.layout.horizontal()? coord.width : coord.height;
17300      var initElem = [ch[0]];
17301      var tail = ch.slice(1);
17302      this.stripify(tail, initElem, side, coord, prop);
17303     },
17304     
17305     /*
17306       Method: stripify
17307     
17308      Performs an heuristic method to calculate div elements sizes in order to have 
17309      a good compromise between aspect ratio and order.
17310     
17311       Parameters:
17312     
17313          tail - An array of nodes.  
17314          initElem - An array of nodes.
17315          w - A fixed dimension where nodes will be layed out.
17316        coord - A coordinates object specifying width, height, left and top style properties.
17317     */
17318     stripify: function(tail, initElem, w, coord, prop) {
17319      this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17320     },
17321     
17322     /*
17323       Method: layoutRow
17324     
17325      Performs the layout of an array of nodes.
17326     
17327       Parameters:
17328     
17329          ch - An array of nodes.  
17330          w - A fixed dimension where nodes will be laid out.
17331          coord - A coordinates object specifying width, height, left and top style properties.
17332     */
17333     layoutRow: function(ch, w, coord, prop) {
17334      if(this.layout.horizontal()) {
17335        return this.layoutH(ch, w, coord, prop);
17336      } else {
17337        return this.layoutV(ch, w, coord, prop);
17338      }
17339     },
17340     
17341     layoutV: function(ch, w, coord, prop) {
17342      var totalArea = 0; 
17343      $.each(ch, function(elem) { totalArea += elem._area; });
17344      var width = totalArea / w, top =  0; 
17345      for(var i=0, l=ch.length; i<l; i++) {
17346        var chi = ch[i];
17347        var h = chi._area / width;
17348        chi.getPos(prop).setc(coord.left, 
17349            coord.top + (w - h - top));
17350        chi.setData('width', width, prop);
17351        chi.setData('height', h, prop);
17352        top += h;
17353      }
17354     
17355      return {
17356        'height': coord.height,
17357        'width': coord.width - width,
17358        'top': coord.top,
17359        'left': coord.left + width,
17360        'dim': w
17361      };
17362     },
17363     
17364     layoutH: function(ch, w, coord, prop) {
17365      var totalArea = 0; 
17366      $.each(ch, function(elem) { totalArea += elem._area; });
17367      var height = totalArea / w,
17368          top = coord.height - height, 
17369          left = 0;
17370      
17371      for(var i=0, l=ch.length; i<l; i++) {
17372        var chi = ch[i];
17373        var s = chi._area / height;
17374        chi.getPos(prop).setc(coord.left + left, coord.top + top);
17375        chi.setData('width', s, prop);
17376        chi.setData('height', height, prop);
17377        left += s;
17378      }
17379      return {
17380        'height': coord.height - height,
17381        'width': coord.width,
17382        'top': coord.top,
17383        'left': coord.left,
17384        'dim': w
17385      };
17386     }
17387  });
17388
17389 /*
17390  * Class: Layouts.Icicle
17391  *
17392  * Implements the icicle tree layout.
17393  *
17394  * Implemented By:
17395  *
17396  * <Icicle>
17397  *
17398  */
17399
17400 Layouts.Icicle = new Class({
17401  /*
17402   * Method: compute
17403   *
17404   * Called by loadJSON to calculate all node positions.
17405   *
17406   * Parameters:
17407   *
17408   * posType - The nodes' position to compute. Either "start", "end" or
17409   *            "current". Defaults to "current".
17410   */
17411   compute: function(posType) {
17412     posType = posType || "current";
17413     var root = this.graph.getNode(this.root),
17414         config = this.config,
17415         size = this.canvas.getSize(),
17416         width = size.width,
17417         height = size.height,
17418         offset = config.offset,
17419         levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17420
17421     this.controller.onBeforeCompute(root);
17422
17423     Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17424
17425     var treeDepth = 0;
17426
17427     Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17428
17429     var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17430     var maxDepth = Math.min(treeDepth, levelsToShow-1);
17431     var initialDepth = startNode._depth;
17432     if(this.layout.horizontal()) {
17433       this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17434     } else {
17435       this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17436     }
17437   },
17438
17439   computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17440     root.getPos(posType).setc(x, y);
17441     root.setData('width', width, posType);
17442     root.setData('height', height, posType);
17443
17444     var nodeLength, prevNodeLength = 0, totalDim = 0;
17445     var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17446
17447     if(!children.length)
17448       return;
17449
17450     $.each(children, function(e) { totalDim += e.getData('dim'); });
17451
17452     for(var i=0, l=children.length; i < l; i++) {
17453       if(this.layout.horizontal()) {
17454         nodeLength = height * children[i].getData('dim') / totalDim;
17455         this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17456         y += nodeLength;
17457       } else {
17458         nodeLength = width * children[i].getData('dim') / totalDim;
17459         this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17460         x += nodeLength;
17461       }
17462     }
17463   }
17464 });
17465
17466
17467
17468 /*
17469  * File: Icicle.js
17470  *
17471 */
17472
17473 /*
17474   Class: Icicle
17475   
17476   Icicle space filling visualization.
17477   
17478   Implements:
17479   
17480   All <Loader> methods
17481   
17482   Constructor Options:
17483   
17484   Inherits options from
17485   
17486   - <Options.Canvas>
17487   - <Options.Controller>
17488   - <Options.Node>
17489   - <Options.Edge>
17490   - <Options.Label>
17491   - <Options.Events>
17492   - <Options.Tips>
17493   - <Options.NodeStyles>
17494   - <Options.Navigation>
17495   
17496   Additionally, there are other parameters and some default values changed
17497
17498   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17499   offset - (number) Default's *2*. Boxes offset.
17500   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17501   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17502   animate - (boolean) Default's *false*. Whether to animate transitions.
17503   Node.type - Described in <Options.Node>. Default's *rectangle*.
17504   Label.type - Described in <Options.Label>. Default's *Native*.
17505   duration - Described in <Options.Fx>. Default's *700*.
17506   fps - Described in <Options.Fx>. Default's *45*.
17507   
17508   Instance Properties:
17509   
17510   canvas - Access a <Canvas> instance.
17511   graph - Access a <Graph> instance.
17512   op - Access a <Icicle.Op> instance.
17513   fx - Access a <Icicle.Plot> instance.
17514   labels - Access a <Icicle.Label> interface implementation.
17515
17516 */
17517
17518 $jit.Icicle = new Class({
17519   Implements: [ Loader, Extras, Layouts.Icicle ],
17520
17521   layout: {
17522     orientation: "h",
17523     vertical: function(){
17524       return this.orientation == "v";
17525     },
17526     horizontal: function(){
17527       return this.orientation == "h";
17528     },
17529     change: function(){
17530       this.orientation = this.vertical()? "h" : "v";
17531     }
17532   },
17533
17534   initialize: function(controller) {
17535     var config = {
17536       animate: false,
17537       orientation: "h",
17538       offset: 2,
17539       levelsToShow: Number.MAX_VALUE,
17540       constrained: false,
17541       Node: {
17542         type: 'rectangle',
17543         overridable: true
17544       },
17545       Edge: {
17546         type: 'none'
17547       },
17548       Label: {
17549         type: 'Native'
17550       },
17551       duration: 700,
17552       fps: 45
17553     };
17554
17555     var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17556                        "Events", "Navigation", "Controller", "Label");
17557     this.controller = this.config = $.merge(opts, config, controller);
17558     this.layout.orientation = this.config.orientation;
17559
17560     var canvasConfig = this.config;
17561     if (canvasConfig.useCanvas) {
17562       this.canvas = canvasConfig.useCanvas;
17563       this.config.labelContainer = this.canvas.id + '-label';
17564     } else {
17565       this.canvas = new Canvas(this, canvasConfig);
17566       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17567     }
17568
17569     this.graphOptions = {
17570       'complex': true,
17571       'Node': {
17572         'selected': false,
17573         'exist': true,
17574         'drawn': true
17575       }
17576     };
17577
17578     this.graph = new Graph(
17579       this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17580
17581     this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17582     this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17583     this.op = new $jit.Icicle.Op(this);
17584     this.group = new $jit.Icicle.Group(this);
17585     this.clickedNode = null;
17586
17587     this.initializeExtras();
17588   },
17589
17590   /* 
17591     Method: refresh 
17592     
17593     Computes positions and plots the tree.
17594   */
17595   refresh: function(){
17596     var labelType = this.config.Label.type;
17597     if(labelType != 'Native') {
17598       var that = this;
17599       this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17600     }
17601     this.compute();
17602     this.plot();
17603   },
17604
17605   /* 
17606     Method: plot 
17607     
17608     Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
17609   
17610    */
17611   plot: function(){
17612     this.fx.plot(this.config);
17613   },
17614
17615   /* 
17616     Method: enter 
17617     
17618     Sets the node as root.
17619     
17620      Parameters:
17621      
17622      node - (object) A <Graph.Node>.
17623   
17624    */
17625   enter: function (node) {
17626     if (this.busy)
17627       return;
17628     this.busy = true;
17629
17630     var that = this,
17631         config = this.config;
17632
17633     var callback = {
17634       onComplete: function() {
17635         //compute positions of newly inserted nodes
17636         if(config.request)
17637           that.compute();
17638
17639         if(config.animate) {
17640           that.graph.nodeList.setDataset(['current', 'end'], {
17641             'alpha': [1, 0] //fade nodes
17642           });
17643
17644           Graph.Util.eachSubgraph(node, function(n) {
17645             n.setData('alpha', 1, 'end');
17646           }, "ignore");
17647
17648           that.fx.animate({
17649             duration: 500,
17650             modes:['node-property:alpha'],
17651             onComplete: function() {
17652               that.clickedNode = node;
17653               that.compute('end');
17654
17655               that.fx.animate({
17656                 modes:['linear', 'node-property:width:height'],
17657                 duration: 1000,
17658                 onComplete: function() {
17659                   that.busy = false;
17660                   that.clickedNode = node;
17661                 }
17662               });
17663             }
17664           });
17665         } else {
17666           that.clickedNode = node;
17667           that.busy = false;
17668           that.refresh();
17669         }
17670       }
17671     };
17672
17673     if(config.request) {
17674       this.requestNodes(clickedNode, callback);
17675     } else {
17676       callback.onComplete();
17677     }
17678   },
17679
17680   /* 
17681     Method: out 
17682     
17683     Sets the parent node of the current selected node as root.
17684   
17685    */
17686   out: function(){
17687     if(this.busy)
17688       return;
17689
17690     var that = this,
17691         GUtil = Graph.Util,
17692         config = this.config,
17693         graph = this.graph,
17694         parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17695         parent = parents[0],
17696         clickedNode = parent,
17697         previousClickedNode = this.clickedNode;
17698
17699     this.busy = true;
17700     this.events.hoveredNode = false;
17701
17702     if(!parent) {
17703       this.busy = false;
17704       return;
17705     }
17706
17707     //final plot callback
17708     callback = {
17709       onComplete: function() {
17710         that.clickedNode = parent;
17711         if(config.request) {
17712           that.requestNodes(parent, {
17713             onComplete: function() {
17714               that.compute();
17715               that.plot();
17716               that.busy = false;
17717             }
17718           });
17719         } else {
17720           that.compute();
17721           that.plot();
17722           that.busy = false;
17723         }
17724       }
17725     };
17726
17727     //animate node positions
17728     if(config.animate) {
17729       this.clickedNode = clickedNode;
17730       this.compute('end');
17731       //animate the visible subtree only
17732       this.clickedNode = previousClickedNode;
17733       this.fx.animate({
17734         modes:['linear', 'node-property:width:height'],
17735         duration: 1000,
17736         onComplete: function() {
17737           //animate the parent subtree
17738           that.clickedNode = clickedNode;
17739           //change nodes alpha
17740           graph.nodeList.setDataset(['current', 'end'], {
17741             'alpha': [0, 1]
17742           });
17743           GUtil.eachSubgraph(previousClickedNode, function(node) {
17744             node.setData('alpha', 1);
17745           }, "ignore");
17746           that.fx.animate({
17747             duration: 500,
17748             modes:['node-property:alpha'],
17749             onComplete: function() {
17750               callback.onComplete();
17751             }
17752           });
17753         }
17754       });
17755     } else {
17756       callback.onComplete();
17757     }
17758   },
17759   requestNodes: function(node, onComplete){
17760     var handler = $.merge(this.controller, onComplete),
17761         levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17762
17763     if (handler.request) {
17764       var leaves = [], d = node._depth;
17765       Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17766         if (n.drawn && !Graph.Util.anySubnode(n)) {
17767           leaves.push(n);
17768           n._level = n._depth - d;
17769           if (this.config.constrained)
17770             n._level = levelsToShow - n._level;
17771
17772         }
17773       });
17774       this.group.requestNodes(leaves, handler);
17775     } else {
17776       handler.onComplete();
17777     }
17778   }
17779 });
17780
17781 /*
17782   Class: Icicle.Op
17783   
17784   Custom extension of <Graph.Op>.
17785   
17786   Extends:
17787   
17788   All <Graph.Op> methods
17789   
17790   See also:
17791   
17792   <Graph.Op>
17793   
17794   */
17795 $jit.Icicle.Op = new Class({
17796
17797   Implements: Graph.Op
17798
17799 });
17800
17801 /*
17802  * Performs operations on group of nodes.
17803  */
17804 $jit.Icicle.Group = new Class({
17805
17806   initialize: function(viz){
17807     this.viz = viz;
17808     this.canvas = viz.canvas;
17809     this.config = viz.config;
17810   },
17811
17812   /*
17813    * Calls the request method on the controller to request a subtree for each node.
17814    */
17815   requestNodes: function(nodes, controller){
17816     var counter = 0, len = nodes.length, nodeSelected = {};
17817     var complete = function(){
17818       controller.onComplete();
17819     };
17820     var viz = this.viz;
17821     if (len == 0)
17822       complete();
17823     for(var i = 0; i < len; i++) {
17824       nodeSelected[nodes[i].id] = nodes[i];
17825       controller.request(nodes[i].id, nodes[i]._level, {
17826         onComplete: function(nodeId, data){
17827           if (data && data.children) {
17828             data.id = nodeId;
17829             viz.op.sum(data, {
17830               type: 'nothing'
17831             });
17832           }
17833           if (++counter == len) {
17834             Graph.Util.computeLevels(viz.graph, viz.root, 0);
17835             complete();
17836           }
17837         }
17838       });
17839     }
17840   }
17841 });
17842
17843 /*
17844   Class: Icicle.Plot
17845   
17846   Custom extension of <Graph.Plot>.
17847   
17848   Extends:
17849   
17850   All <Graph.Plot> methods
17851   
17852   See also:
17853   
17854   <Graph.Plot>
17855   
17856   */
17857 $jit.Icicle.Plot = new Class({
17858   Implements: Graph.Plot,
17859
17860   plot: function(opt, animating){
17861     opt = opt || this.viz.controller;
17862     var viz = this.viz,
17863         graph = viz.graph,
17864         root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17865         initialDepth = root._depth;
17866
17867     viz.canvas.clear();
17868     this.plotTree(root, $.merge(opt, {
17869       'withLabels': true,
17870       'hideLabels': false,
17871       'plotSubtree': function(root, node) {
17872         return !viz.config.constrained ||
17873                (node._depth - initialDepth < viz.config.levelsToShow);
17874       }
17875     }), animating);
17876   }
17877 });
17878
17879 /*
17880   Class: Icicle.Label
17881   
17882   Custom extension of <Graph.Label>. 
17883   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17884   
17885   Extends:
17886   
17887   All <Graph.Label> methods and subclasses.
17888   
17889   See also:
17890   
17891   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17892   
17893   */
17894 $jit.Icicle.Label = {};
17895
17896 /*
17897   Icicle.Label.Native
17898   
17899   Custom extension of <Graph.Label.Native>.
17900   
17901   Extends:
17902   
17903   All <Graph.Label.Native> methods
17904   
17905   See also:
17906   
17907   <Graph.Label.Native>
17908
17909   */
17910 $jit.Icicle.Label.Native = new Class({
17911   Implements: Graph.Label.Native,
17912
17913   renderLabel: function(canvas, node, controller) {
17914     var ctx = canvas.getCtx(),
17915         width = node.getData('width'),
17916         height = node.getData('height'),
17917         size = node.getLabelData('size'),
17918         m = ctx.measureText(node.name);
17919
17920     // Guess as much as possible if the label will fit in the node
17921     if(height < (size * 1.5) || width < m.width)
17922       return;
17923
17924     var pos = node.pos.getc(true);
17925     ctx.fillText(node.name,
17926                  pos.x + width / 2,
17927                  pos.y + height / 2);
17928   }
17929 });
17930
17931 /*
17932   Icicle.Label.SVG
17933   
17934   Custom extension of <Graph.Label.SVG>.
17935   
17936   Extends:
17937   
17938   All <Graph.Label.SVG> methods
17939   
17940   See also:
17941   
17942   <Graph.Label.SVG>
17943 */
17944 $jit.Icicle.Label.SVG = new Class( {
17945   Implements: Graph.Label.SVG,
17946
17947   initialize: function(viz){
17948     this.viz = viz;
17949   },
17950
17951   /*
17952     placeLabel
17953    
17954     Overrides abstract method placeLabel in <Graph.Plot>.
17955    
17956     Parameters:
17957    
17958     tag - A DOM label element.
17959     node - A <Graph.Node>.
17960     controller - A configuration/controller object passed to the visualization.
17961    */
17962   placeLabel: function(tag, node, controller){
17963     var pos = node.pos.getc(true), canvas = this.viz.canvas;
17964     var radius = canvas.getSize();
17965     var labelPos = {
17966       x: Math.round(pos.x + radius.width / 2),
17967       y: Math.round(pos.y + radius.height / 2)
17968     };
17969     tag.setAttribute('x', labelPos.x);
17970     tag.setAttribute('y', labelPos.y);
17971
17972     controller.onPlaceLabel(tag, node);
17973   }
17974 });
17975
17976 /*
17977   Icicle.Label.HTML
17978   
17979   Custom extension of <Graph.Label.HTML>.
17980   
17981   Extends:
17982   
17983   All <Graph.Label.HTML> methods.
17984   
17985   See also:
17986   
17987   <Graph.Label.HTML>
17988   
17989   */
17990 $jit.Icicle.Label.HTML = new Class( {
17991   Implements: Graph.Label.HTML,
17992
17993   initialize: function(viz){
17994     this.viz = viz;
17995   },
17996
17997   /*
17998     placeLabel
17999    
18000     Overrides abstract method placeLabel in <Graph.Plot>.
18001    
18002     Parameters:
18003    
18004     tag - A DOM label element.
18005     node - A <Graph.Node>.
18006     controller - A configuration/controller object passed to the visualization.
18007    */
18008   placeLabel: function(tag, node, controller){
18009     var pos = node.pos.getc(true), canvas = this.viz.canvas;
18010     var radius = canvas.getSize();
18011     var labelPos = {
18012       x: Math.round(pos.x + radius.width / 2),
18013       y: Math.round(pos.y + radius.height / 2)
18014     };
18015
18016     var style = tag.style;
18017     style.left = labelPos.x + 'px';
18018     style.top = labelPos.y + 'px';
18019     style.display = '';
18020
18021     controller.onPlaceLabel(tag, node);
18022   }
18023 });
18024
18025 /*
18026   Class: Icicle.Plot.NodeTypes
18027   
18028   This class contains a list of <Graph.Node> built-in types. 
18029   Node types implemented are 'none', 'rectangle'.
18030   
18031   You can add your custom node types, customizing your visualization to the extreme.
18032   
18033   Example:
18034   
18035   (start code js)
18036     Icicle.Plot.NodeTypes.implement({
18037       'mySpecialType': {
18038         'render': function(node, canvas) {
18039           //print your custom node to canvas
18040         },
18041         //optional
18042         'contains': function(node, pos) {
18043           //return true if pos is inside the node or false otherwise
18044         }
18045       }
18046     });
18047   (end code)
18048   
18049   */
18050 $jit.Icicle.Plot.NodeTypes = new Class( {
18051   'none': {
18052     'render': $.empty
18053   },
18054
18055   'rectangle': {
18056     'render': function(node, canvas, animating) {
18057       var config = this.viz.config;
18058       var offset = config.offset;
18059       var width = node.getData('width');
18060       var height = node.getData('height');
18061       var border = node.getData('border');
18062       var pos = node.pos.getc(true);
18063       var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18064       var ctx = canvas.getCtx();
18065       
18066       if(width - offset < 2 || height - offset < 2) return;
18067       
18068       if(config.cushion) {
18069         var color = node.getData('color');
18070         var lg = ctx.createRadialGradient(posx + (width - offset)/2, 
18071                                           posy + (height - offset)/2, 1, 
18072                                           posx + (width-offset)/2, posy + (height-offset)/2, 
18073                                           width < height? height : width);
18074         var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
18075             function(r) { return r * 0.3 >> 0; }));
18076         lg.addColorStop(0, color);
18077         lg.addColorStop(1, colorGrad);
18078         ctx.fillStyle = lg;
18079       }
18080
18081       if (border) {
18082         ctx.strokeStyle = border;
18083         ctx.lineWidth = 3;
18084       }
18085
18086       ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18087       border && ctx.strokeRect(pos.x, pos.y, width, height);
18088     },
18089
18090     'contains': function(node, pos) {
18091       if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18092       var npos = node.pos.getc(true),
18093           width = node.getData('width'),
18094           height = node.getData('height');
18095       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18096     }
18097   }
18098 });
18099
18100 $jit.Icicle.Plot.EdgeTypes = new Class( {
18101   'none': $.empty
18102 });
18103
18104
18105
18106 /*
18107  * File: Layouts.ForceDirected.js
18108  *
18109 */
18110
18111 /*
18112  * Class: Layouts.ForceDirected
18113  * 
18114  * Implements a Force Directed Layout.
18115  * 
18116  * Implemented By:
18117  * 
18118  * <ForceDirected>
18119  * 
18120  * Credits:
18121  * 
18122  * Marcus Cobden <http://marcuscobden.co.uk>
18123  * 
18124  */
18125 Layouts.ForceDirected = new Class({
18126
18127   getOptions: function(random) {
18128     var s = this.canvas.getSize();
18129     var w = s.width, h = s.height;
18130     //count nodes
18131     var count = 0;
18132     this.graph.eachNode(function(n) { 
18133       count++;
18134     });
18135     var k2 = w * h / count, k = Math.sqrt(k2);
18136     var l = this.config.levelDistance;
18137     
18138     return {
18139       width: w,
18140       height: h,
18141       tstart: w * 0.1,
18142       nodef: function(x) { return k2 / (x || 1); },
18143       edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18144     };
18145   },
18146   
18147   compute: function(property, incremental) {
18148     var prop = $.splat(property || ['current', 'start', 'end']);
18149     var opt = this.getOptions();
18150     NodeDim.compute(this.graph, prop, this.config);
18151     this.graph.computeLevels(this.root, 0, "ignore");
18152     this.graph.eachNode(function(n) {
18153       $.each(prop, function(p) {
18154         var pos = n.getPos(p);
18155         if(pos.equals(Complex.KER)) {
18156           pos.x = opt.width/5 * (Math.random() - 0.5);
18157           pos.y = opt.height/5 * (Math.random() - 0.5);
18158         }
18159         //initialize disp vector
18160         n.disp = {};
18161         $.each(prop, function(p) {
18162           n.disp[p] = $C(0, 0);
18163         });
18164       });
18165     });
18166     this.computePositions(prop, opt, incremental);
18167   },
18168   
18169   computePositions: function(property, opt, incremental) {
18170     var times = this.config.iterations, i = 0, that = this;
18171     if(incremental) {
18172       (function iter() {
18173         for(var total=incremental.iter, j=0; j<total; j++) {
18174           opt.t = opt.tstart * (1 - i++/(times -1));
18175           that.computePositionStep(property, opt);
18176           if(i >= times) {
18177             incremental.onComplete();
18178             return;
18179           }
18180         }
18181         incremental.onStep(Math.round(i / (times -1) * 100));
18182         setTimeout(iter, 1);
18183       })();
18184     } else {
18185       for(; i < times; i++) {
18186         opt.t = opt.tstart * (1 - i/(times -1));
18187         this.computePositionStep(property, opt);
18188       }
18189     }
18190   },
18191   
18192   computePositionStep: function(property, opt) {
18193     var graph = this.graph;
18194     var min = Math.min, max = Math.max;
18195     var dpos = $C(0, 0);
18196     //calculate repulsive forces
18197     graph.eachNode(function(v) {
18198       //initialize disp
18199       $.each(property, function(p) {
18200         v.disp[p].x = 0; v.disp[p].y = 0;
18201       });
18202       graph.eachNode(function(u) {
18203         if(u.id != v.id) {
18204           $.each(property, function(p) {
18205             var vp = v.getPos(p), up = u.getPos(p);
18206             dpos.x = vp.x - up.x;
18207             dpos.y = vp.y - up.y;
18208             var norm = dpos.norm() || 1;
18209             v.disp[p].$add(dpos
18210                 .$scale(opt.nodef(norm) / norm));
18211           });
18212         }
18213       });
18214     });
18215     //calculate attractive forces
18216     var T = !!graph.getNode(this.root).visited;
18217     graph.eachNode(function(node) {
18218       node.eachAdjacency(function(adj) {
18219         var nodeTo = adj.nodeTo;
18220         if(!!nodeTo.visited === T) {
18221           $.each(property, function(p) {
18222             var vp = node.getPos(p), up = nodeTo.getPos(p);
18223             dpos.x = vp.x - up.x;
18224             dpos.y = vp.y - up.y;
18225             var norm = dpos.norm() || 1;
18226             node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18227             nodeTo.disp[p].$add(dpos.$scale(-1));
18228           });
18229         }
18230       });
18231       node.visited = !T;
18232     });
18233     //arrange positions to fit the canvas
18234     var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18235     graph.eachNode(function(u) {
18236       $.each(property, function(p) {
18237         var disp = u.disp[p];
18238         var norm = disp.norm() || 1;
18239         var p = u.getPos(p);
18240         p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
18241             disp.y * min(Math.abs(disp.y), t) / norm));
18242         p.x = min(w2, max(-w2, p.x));
18243         p.y = min(h2, max(-h2, p.y));
18244       });
18245     });
18246   }
18247 });
18248
18249 /*
18250  * File: ForceDirected.js
18251  */
18252
18253 /*
18254    Class: ForceDirected
18255       
18256    A visualization that lays graphs using a Force-Directed layout algorithm.
18257    
18258    Inspired by:
18259   
18260    Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18261    
18262   Implements:
18263   
18264   All <Loader> methods
18265   
18266    Constructor Options:
18267    
18268    Inherits options from
18269    
18270    - <Options.Canvas>
18271    - <Options.Controller>
18272    - <Options.Node>
18273    - <Options.Edge>
18274    - <Options.Label>
18275    - <Options.Events>
18276    - <Options.Tips>
18277    - <Options.NodeStyles>
18278    - <Options.Navigation>
18279    
18280    Additionally, there are two parameters
18281    
18282    levelDistance - (number) Default's *50*. The natural length desired for the edges.
18283    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*. 
18284      
18285    Instance Properties:
18286
18287    canvas - Access a <Canvas> instance.
18288    graph - Access a <Graph> instance.
18289    op - Access a <ForceDirected.Op> instance.
18290    fx - Access a <ForceDirected.Plot> instance.
18291    labels - Access a <ForceDirected.Label> interface implementation.
18292
18293 */
18294
18295 $jit.ForceDirected = new Class( {
18296
18297   Implements: [ Loader, Extras, Layouts.ForceDirected ],
18298
18299   initialize: function(controller) {
18300     var $ForceDirected = $jit.ForceDirected;
18301
18302     var config = {
18303       iterations: 50,
18304       levelDistance: 50
18305     };
18306
18307     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18308         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18309
18310     var canvasConfig = this.config;
18311     if(canvasConfig.useCanvas) {
18312       this.canvas = canvasConfig.useCanvas;
18313       this.config.labelContainer = this.canvas.id + '-label';
18314     } else {
18315       if(canvasConfig.background) {
18316         canvasConfig.background = $.merge({
18317           type: 'Circles'
18318         }, canvasConfig.background);
18319       }
18320       this.canvas = new Canvas(this, canvasConfig);
18321       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18322     }
18323
18324     this.graphOptions = {
18325       'complex': true,
18326       'Node': {
18327         'selected': false,
18328         'exist': true,
18329         'drawn': true
18330       }
18331     };
18332     this.graph = new Graph(this.graphOptions, this.config.Node,
18333         this.config.Edge);
18334     this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18335     this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18336     this.op = new $ForceDirected.Op(this);
18337     this.json = null;
18338     this.busy = false;
18339     // initialize extras
18340     this.initializeExtras();
18341   },
18342
18343   /* 
18344     Method: refresh 
18345     
18346     Computes positions and plots the tree.
18347   */
18348   refresh: function() {
18349     this.compute();
18350     this.plot();
18351   },
18352
18353   reposition: function() {
18354     this.compute('end');
18355   },
18356
18357 /*
18358   Method: computeIncremental
18359   
18360   Performs the Force Directed algorithm incrementally.
18361   
18362   Description:
18363   
18364   ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
18365   This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
18366   avoiding browser messages such as "This script is taking too long to complete".
18367   
18368   Parameters:
18369   
18370   opt - (object) The object properties are described below
18371   
18372   iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
18373   of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18374   
18375   property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
18376   You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
18377   computations for final animation positions then you can just choose 'end'.
18378   
18379   onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
18380   parameter a percentage value.
18381   
18382   onComplete - A callback function called when the algorithm completed.
18383   
18384   Example:
18385   
18386   In this example I calculate the end positions and then animate the graph to those positions
18387   
18388   (start code js)
18389   var fd = new $jit.ForceDirected(...);
18390   fd.computeIncremental({
18391     iter: 20,
18392     property: 'end',
18393     onStep: function(perc) {
18394       Log.write("loading " + perc + "%");
18395     },
18396     onComplete: function() {
18397       Log.write("done");
18398       fd.animate();
18399     }
18400   });
18401   (end code)
18402   
18403   In this example I calculate all positions and (re)plot the graph
18404   
18405   (start code js)
18406   var fd = new ForceDirected(...);
18407   fd.computeIncremental({
18408     iter: 20,
18409     property: ['end', 'start', 'current'],
18410     onStep: function(perc) {
18411       Log.write("loading " + perc + "%");
18412     },
18413     onComplete: function() {
18414       Log.write("done");
18415       fd.plot();
18416     }
18417   });
18418   (end code)
18419   
18420   */
18421   computeIncremental: function(opt) {
18422     opt = $.merge( {
18423       iter: 20,
18424       property: 'end',
18425       onStep: $.empty,
18426       onComplete: $.empty
18427     }, opt || {});
18428
18429     this.config.onBeforeCompute(this.graph.getNode(this.root));
18430     this.compute(opt.property, opt);
18431   },
18432
18433   /*
18434     Method: plot
18435    
18436     Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18437    */
18438   plot: function() {
18439     this.fx.plot();
18440   },
18441
18442   /*
18443      Method: animate
18444     
18445      Animates the graph from the current positions to the 'end' node positions.
18446   */
18447   animate: function(opt) {
18448     this.fx.animate($.merge( {
18449       modes: [ 'linear' ]
18450     }, opt || {}));
18451   }
18452 });
18453
18454 $jit.ForceDirected.$extend = true;
18455
18456 (function(ForceDirected) {
18457
18458   /*
18459      Class: ForceDirected.Op
18460      
18461      Custom extension of <Graph.Op>.
18462
18463      Extends:
18464
18465      All <Graph.Op> methods
18466      
18467      See also:
18468      
18469      <Graph.Op>
18470
18471   */
18472   ForceDirected.Op = new Class( {
18473
18474     Implements: Graph.Op
18475
18476   });
18477
18478   /*
18479     Class: ForceDirected.Plot
18480     
18481     Custom extension of <Graph.Plot>.
18482   
18483     Extends:
18484   
18485     All <Graph.Plot> methods
18486     
18487     See also:
18488     
18489     <Graph.Plot>
18490   
18491   */
18492   ForceDirected.Plot = new Class( {
18493
18494     Implements: Graph.Plot
18495
18496   });
18497
18498   /*
18499     Class: ForceDirected.Label
18500     
18501     Custom extension of <Graph.Label>. 
18502     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18503   
18504     Extends:
18505   
18506     All <Graph.Label> methods and subclasses.
18507   
18508     See also:
18509   
18510     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18511   
18512   */
18513   ForceDirected.Label = {};
18514
18515   /*
18516      ForceDirected.Label.Native
18517      
18518      Custom extension of <Graph.Label.Native>.
18519
18520      Extends:
18521
18522      All <Graph.Label.Native> methods
18523
18524      See also:
18525
18526      <Graph.Label.Native>
18527
18528   */
18529   ForceDirected.Label.Native = new Class( {
18530     Implements: Graph.Label.Native
18531   });
18532
18533   /*
18534     ForceDirected.Label.SVG
18535     
18536     Custom extension of <Graph.Label.SVG>.
18537   
18538     Extends:
18539   
18540     All <Graph.Label.SVG> methods
18541   
18542     See also:
18543   
18544     <Graph.Label.SVG>
18545   
18546   */
18547   ForceDirected.Label.SVG = new Class( {
18548     Implements: Graph.Label.SVG,
18549
18550     initialize: function(viz) {
18551       this.viz = viz;
18552     },
18553
18554     /* 
18555        placeLabel
18556
18557        Overrides abstract method placeLabel in <Graph.Label>.
18558
18559        Parameters:
18560
18561        tag - A DOM label element.
18562        node - A <Graph.Node>.
18563        controller - A configuration/controller object passed to the visualization.
18564       
18565      */
18566     placeLabel: function(tag, node, controller) {
18567       var pos = node.pos.getc(true), 
18568           canvas = this.viz.canvas,
18569           ox = canvas.translateOffsetX,
18570           oy = canvas.translateOffsetY,
18571           sx = canvas.scaleOffsetX,
18572           sy = canvas.scaleOffsetY,
18573           radius = canvas.getSize();
18574       var labelPos = {
18575         x: Math.round(pos.x * sx + ox + radius.width / 2),
18576         y: Math.round(pos.y * sy + oy + radius.height / 2)
18577       };
18578       tag.setAttribute('x', labelPos.x);
18579       tag.setAttribute('y', labelPos.y);
18580
18581       controller.onPlaceLabel(tag, node);
18582     }
18583   });
18584
18585   /*
18586      ForceDirected.Label.HTML
18587      
18588      Custom extension of <Graph.Label.HTML>.
18589
18590      Extends:
18591
18592      All <Graph.Label.HTML> methods.
18593
18594      See also:
18595
18596      <Graph.Label.HTML>
18597
18598   */
18599   ForceDirected.Label.HTML = new Class( {
18600     Implements: Graph.Label.HTML,
18601
18602     initialize: function(viz) {
18603       this.viz = viz;
18604     },
18605     /* 
18606        placeLabel
18607
18608        Overrides abstract method placeLabel in <Graph.Plot>.
18609
18610        Parameters:
18611
18612        tag - A DOM label element.
18613        node - A <Graph.Node>.
18614        controller - A configuration/controller object passed to the visualization.
18615       
18616      */
18617     placeLabel: function(tag, node, controller) {
18618       var pos = node.pos.getc(true), 
18619           canvas = this.viz.canvas,
18620           ox = canvas.translateOffsetX,
18621           oy = canvas.translateOffsetY,
18622           sx = canvas.scaleOffsetX,
18623           sy = canvas.scaleOffsetY,
18624           radius = canvas.getSize();
18625       var labelPos = {
18626         x: Math.round(pos.x * sx + ox + radius.width / 2),
18627         y: Math.round(pos.y * sy + oy + radius.height / 2)
18628       };
18629       var style = tag.style;
18630       style.left = labelPos.x + 'px';
18631       style.top = labelPos.y + 'px';
18632       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18633
18634       controller.onPlaceLabel(tag, node);
18635     }
18636   });
18637
18638   /*
18639     Class: ForceDirected.Plot.NodeTypes
18640
18641     This class contains a list of <Graph.Node> built-in types. 
18642     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18643
18644     You can add your custom node types, customizing your visualization to the extreme.
18645
18646     Example:
18647
18648     (start code js)
18649       ForceDirected.Plot.NodeTypes.implement({
18650         'mySpecialType': {
18651           'render': function(node, canvas) {
18652             //print your custom node to canvas
18653           },
18654           //optional
18655           'contains': function(node, pos) {
18656             //return true if pos is inside the node or false otherwise
18657           }
18658         }
18659       });
18660     (end code)
18661
18662   */
18663   ForceDirected.Plot.NodeTypes = new Class({
18664     'none': {
18665       'render': $.empty,
18666       'contains': $.lambda(false)
18667     },
18668     'circle': {
18669       'render': function(node, canvas){
18670         var pos = node.pos.getc(true), 
18671             dim = node.getData('dim');
18672         this.nodeHelper.circle.render('fill', pos, dim, canvas);
18673       },
18674       'contains': function(node, pos){
18675         var npos = node.pos.getc(true), 
18676             dim = node.getData('dim');
18677         return this.nodeHelper.circle.contains(npos, pos, dim);
18678       }
18679     },
18680     'ellipse': {
18681       'render': function(node, canvas){
18682         var pos = node.pos.getc(true), 
18683             width = node.getData('width'), 
18684             height = node.getData('height');
18685         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18686         },
18687       // TODO(nico): be more precise...
18688       'contains': function(node, pos){
18689         var npos = node.pos.getc(true), 
18690             width = node.getData('width'), 
18691             height = node.getData('height');
18692         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18693       }
18694     },
18695     'square': {
18696       'render': function(node, canvas){
18697         var pos = node.pos.getc(true), 
18698             dim = node.getData('dim');
18699         this.nodeHelper.square.render('fill', pos, dim, canvas);
18700       },
18701       'contains': function(node, pos){
18702         var npos = node.pos.getc(true), 
18703             dim = node.getData('dim');
18704         return this.nodeHelper.square.contains(npos, pos, dim);
18705       }
18706     },
18707     'rectangle': {
18708       'render': function(node, canvas){
18709         var pos = node.pos.getc(true), 
18710             width = node.getData('width'), 
18711             height = node.getData('height');
18712         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18713       },
18714       'contains': function(node, pos){
18715         var npos = node.pos.getc(true), 
18716             width = node.getData('width'), 
18717             height = node.getData('height');
18718         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18719       }
18720     },
18721     'triangle': {
18722       'render': function(node, canvas){
18723         var pos = node.pos.getc(true), 
18724             dim = node.getData('dim');
18725         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18726       },
18727       'contains': function(node, pos) {
18728         var npos = node.pos.getc(true), 
18729             dim = node.getData('dim');
18730         return this.nodeHelper.triangle.contains(npos, pos, dim);
18731       }
18732     },
18733     'star': {
18734       'render': function(node, canvas){
18735         var pos = node.pos.getc(true),
18736             dim = node.getData('dim');
18737         this.nodeHelper.star.render('fill', pos, dim, canvas);
18738       },
18739       'contains': function(node, pos) {
18740         var npos = node.pos.getc(true),
18741             dim = node.getData('dim');
18742         return this.nodeHelper.star.contains(npos, pos, dim);
18743       }
18744     }
18745   });
18746
18747   /*
18748     Class: ForceDirected.Plot.EdgeTypes
18749   
18750     This class contains a list of <Graph.Adjacence> built-in types. 
18751     Edge types implemented are 'none', 'line' and 'arrow'.
18752   
18753     You can add your custom edge types, customizing your visualization to the extreme.
18754   
18755     Example:
18756   
18757     (start code js)
18758       ForceDirected.Plot.EdgeTypes.implement({
18759         'mySpecialType': {
18760           'render': function(adj, canvas) {
18761             //print your custom edge to canvas
18762           },
18763           //optional
18764           'contains': function(adj, pos) {
18765             //return true if pos is inside the arc or false otherwise
18766           }
18767         }
18768       });
18769     (end code)
18770   
18771   */
18772   ForceDirected.Plot.EdgeTypes = new Class({
18773     'none': $.empty,
18774     'line': {
18775       'render': function(adj, canvas) {
18776         var from = adj.nodeFrom.pos.getc(true),
18777             to = adj.nodeTo.pos.getc(true);
18778         this.edgeHelper.line.render(from, to, canvas);
18779       },
18780       'contains': function(adj, pos) {
18781         var from = adj.nodeFrom.pos.getc(true),
18782             to = adj.nodeTo.pos.getc(true);
18783         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18784       }
18785     },
18786     'arrow': {
18787       'render': function(adj, canvas) {
18788         var from = adj.nodeFrom.pos.getc(true),
18789             to = adj.nodeTo.pos.getc(true),
18790             dim = adj.getData('dim'),
18791             direction = adj.data.$direction,
18792             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18793         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18794       },
18795       'contains': function(adj, pos) {
18796         var from = adj.nodeFrom.pos.getc(true),
18797             to = adj.nodeTo.pos.getc(true);
18798         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18799       }
18800     }
18801   });
18802
18803 })($jit.ForceDirected);
18804
18805
18806 /*
18807  * File: Treemap.js
18808  *
18809 */
18810
18811 $jit.TM = {};
18812
18813 var TM = $jit.TM;
18814
18815 $jit.TM.$extend = true;
18816
18817 /*
18818   Class: TM.Base
18819   
18820   Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18821   
18822   Implements:
18823   
18824   All <Loader> methods
18825   
18826   Constructor Options:
18827   
18828   Inherits options from
18829   
18830   - <Options.Canvas>
18831   - <Options.Controller>
18832   - <Options.Node>
18833   - <Options.Edge>
18834   - <Options.Label>
18835   - <Options.Events>
18836   - <Options.Tips>
18837   - <Options.NodeStyles>
18838   - <Options.Navigation>
18839   
18840   Additionally, there are other parameters and some default values changed
18841
18842   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18843   titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18844   offset - (number) Default's *2*. Boxes offset.
18845   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18846   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18847   animate - (boolean) Default's *false*. Whether to animate transitions.
18848   Node.type - Described in <Options.Node>. Default's *rectangle*.
18849   duration - Described in <Options.Fx>. Default's *700*.
18850   fps - Described in <Options.Fx>. Default's *45*.
18851   
18852   Instance Properties:
18853   
18854   canvas - Access a <Canvas> instance.
18855   graph - Access a <Graph> instance.
18856   op - Access a <TM.Op> instance.
18857   fx - Access a <TM.Plot> instance.
18858   labels - Access a <TM.Label> interface implementation.
18859
18860   Inspired by:
18861   
18862   Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18863   
18864   Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18865   
18866    Note:
18867    
18868    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.
18869
18870 */
18871 TM.Base = {
18872   layout: {
18873     orientation: "h",
18874     vertical: function(){
18875       return this.orientation == "v";
18876     },
18877     horizontal: function(){
18878       return this.orientation == "h";
18879     },
18880     change: function(){
18881       this.orientation = this.vertical()? "h" : "v";
18882     }
18883   },
18884
18885   initialize: function(controller){
18886     var config = {
18887       orientation: "h",
18888       titleHeight: 13,
18889       offset: 2,
18890       levelsToShow: 0,
18891       constrained: false,
18892       animate: false,
18893       Node: {
18894         type: 'rectangle',
18895         overridable: true,
18896         //we all know why this is not zero,
18897         //right, Firefox?
18898         width: 3,
18899         height: 3,
18900         color: '#444'
18901       },
18902       Label: {
18903         textAlign: 'center',
18904         textBaseline: 'top'
18905       },
18906       Edge: {
18907         type: 'none'
18908       },
18909       duration: 700,
18910       fps: 45
18911     };
18912
18913     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18914         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18915     this.layout.orientation = this.config.orientation;
18916
18917     var canvasConfig = this.config;
18918     if (canvasConfig.useCanvas) {
18919       this.canvas = canvasConfig.useCanvas;
18920       this.config.labelContainer = this.canvas.id + '-label';
18921     } else {
18922       if(canvasConfig.background) {
18923         canvasConfig.background = $.merge({
18924           type: 'Circles'
18925         }, canvasConfig.background);
18926       }
18927       this.canvas = new Canvas(this, canvasConfig);
18928       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18929     }
18930
18931     this.graphOptions = {
18932       'complex': true,
18933       'Node': {
18934         'selected': false,
18935         'exist': true,
18936         'drawn': true
18937       }
18938     };
18939     this.graph = new Graph(this.graphOptions, this.config.Node,
18940         this.config.Edge);
18941     this.labels = new TM.Label[canvasConfig.Label.type](this);
18942     this.fx = new TM.Plot(this);
18943     this.op = new TM.Op(this);
18944     this.group = new TM.Group(this);
18945     this.geom = new TM.Geom(this);
18946     this.clickedNode = null;
18947     this.busy = false;
18948     // initialize extras
18949     this.initializeExtras();
18950   },
18951
18952   /* 
18953     Method: refresh 
18954     
18955     Computes positions and plots the tree.
18956   */
18957   refresh: function(){
18958     if(this.busy) return;
18959     this.busy = true;
18960     var that = this;
18961     if(this.config.animate) {
18962       this.compute('end');
18963       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18964           && this.clickedNode.id || this.root));
18965       this.fx.animate($.merge(this.config, {
18966         modes: ['linear', 'node-property:width:height'],
18967         onComplete: function() {
18968           that.busy = false;
18969         }
18970       }));
18971     } else {
18972       var labelType = this.config.Label.type;
18973       if(labelType != 'Native') {
18974         var that = this;
18975         this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18976       }
18977       this.busy = false;
18978       this.compute();
18979       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18980           && this.clickedNode.id || this.root));
18981       this.plot();
18982     }
18983   },
18984
18985   /* 
18986     Method: plot 
18987     
18988     Plots the TreeMap. This is a shortcut to *fx.plot*. 
18989   
18990    */
18991   plot: function(){
18992     this.fx.plot();
18993   },
18994
18995   /* 
18996   Method: leaf 
18997   
18998   Returns whether the node is a leaf.
18999   
19000    Parameters:
19001    
19002    n - (object) A <Graph.Node>.
19003
19004  */
19005   leaf: function(n){
19006     return n.getSubnodes([
19007         1, 1
19008     ], "ignore").length == 0;
19009   },
19010   
19011   /* 
19012   Method: enter 
19013   
19014   Sets the node as root.
19015   
19016    Parameters:
19017    
19018    n - (object) A <Graph.Node>.
19019
19020  */
19021   enter: function(n){
19022     if(this.busy) return;
19023     this.busy = true;
19024     
19025     var that = this,
19026         config = this.config,
19027         graph = this.graph,
19028         clickedNode = n,
19029         previousClickedNode = this.clickedNode;
19030
19031     var callback = {
19032       onComplete: function() {
19033         //ensure that nodes are shown for that level
19034         if(config.levelsToShow > 0) {
19035           that.geom.setRightLevelToShow(n);
19036         }
19037         //compute positions of newly inserted nodes
19038         if(config.levelsToShow > 0 || config.request) that.compute();
19039         if(config.animate) {
19040           //fade nodes
19041           graph.nodeList.setData('alpha', 0, 'end');
19042           n.eachSubgraph(function(n) {
19043             n.setData('alpha', 1, 'end');
19044           }, "ignore");
19045           that.fx.animate({
19046             duration: 500,
19047             modes:['node-property:alpha'],
19048             onComplete: function() {
19049               //compute end positions
19050               that.clickedNode = clickedNode;
19051               that.compute('end');
19052               //animate positions
19053               //TODO(nico) commenting this line didn't seem to throw errors...
19054               that.clickedNode = previousClickedNode;
19055               that.fx.animate({
19056                 modes:['linear', 'node-property:width:height'],
19057                 duration: 1000,
19058                 onComplete: function() { 
19059                   that.busy = false;
19060                   //TODO(nico) check comment above
19061                   that.clickedNode = clickedNode;
19062                 }
19063               });
19064             }
19065           });
19066         } else {
19067           that.busy = false;
19068           that.clickedNode = n;
19069           that.refresh();
19070         }
19071       }
19072     };
19073     if(config.request) {
19074       this.requestNodes(clickedNode, callback);
19075     } else {
19076       callback.onComplete();
19077     }
19078   },
19079
19080   /* 
19081   Method: out 
19082   
19083   Sets the parent node of the current selected node as root.
19084
19085  */
19086   out: function(){
19087     if(this.busy) return;
19088     this.busy = true;
19089     this.events.hoveredNode = false;
19090     var that = this,
19091         config = this.config,
19092         graph = this.graph,
19093         parents = graph.getNode(this.clickedNode 
19094             && this.clickedNode.id || this.root).getParents(),
19095         parent = parents[0],
19096         clickedNode = parent,
19097         previousClickedNode = this.clickedNode;
19098     
19099     //if no parents return
19100     if(!parent) {
19101       this.busy = false;
19102       return;
19103     }
19104     //final plot callback
19105     callback = {
19106       onComplete: function() {
19107         that.clickedNode = parent;
19108         if(config.request) {
19109           that.requestNodes(parent, {
19110             onComplete: function() {
19111               that.compute();
19112               that.plot();
19113               that.busy = false;
19114             }
19115           });
19116         } else {
19117           that.compute();
19118           that.plot();
19119           that.busy = false;
19120         }
19121       }
19122     };
19123     //prune tree
19124     if (config.levelsToShow > 0)
19125       this.geom.setRightLevelToShow(parent);
19126     //animate node positions
19127     if(config.animate) {
19128       this.clickedNode = clickedNode;
19129       this.compute('end');
19130       //animate the visible subtree only
19131       this.clickedNode = previousClickedNode;
19132       this.fx.animate({
19133         modes:['linear', 'node-property:width:height'],
19134         duration: 1000,
19135         onComplete: function() {
19136           //animate the parent subtree
19137           that.clickedNode = clickedNode;
19138           //change nodes alpha
19139           graph.eachNode(function(n) {
19140             n.setDataset(['current', 'end'], {
19141               'alpha': [0, 1]
19142             });
19143           }, "ignore");
19144           previousClickedNode.eachSubgraph(function(node) {
19145             node.setData('alpha', 1);
19146           }, "ignore");
19147           that.fx.animate({
19148             duration: 500,
19149             modes:['node-property:alpha'],
19150             onComplete: function() {
19151               callback.onComplete();
19152             }
19153           });
19154         }
19155       });
19156     } else {
19157       callback.onComplete();
19158     }
19159   },
19160
19161   requestNodes: function(node, onComplete){
19162     var handler = $.merge(this.controller, onComplete), 
19163         lev = this.config.levelsToShow;
19164     if (handler.request) {
19165       var leaves = [], d = node._depth;
19166       node.eachLevel(0, lev, function(n){
19167         var nodeLevel = lev - (n._depth - d);
19168         if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19169           leaves.push(n);
19170           n._level = nodeLevel;
19171         }
19172       });
19173       this.group.requestNodes(leaves, handler);
19174     } else {
19175       handler.onComplete();
19176     }
19177   }
19178 };
19179
19180 /*
19181   Class: TM.Op
19182   
19183   Custom extension of <Graph.Op>.
19184   
19185   Extends:
19186   
19187   All <Graph.Op> methods
19188   
19189   See also:
19190   
19191   <Graph.Op>
19192   
19193   */
19194 TM.Op = new Class({
19195   Implements: Graph.Op,
19196
19197   initialize: function(viz){
19198     this.viz = viz;
19199   }
19200 });
19201
19202 //extend level methods of Graph.Geom
19203 TM.Geom = new Class({
19204   Implements: Graph.Geom,
19205   
19206   getRightLevelToShow: function() {
19207     return this.viz.config.levelsToShow;
19208   },
19209   
19210   setRightLevelToShow: function(node) {
19211     var level = this.getRightLevelToShow(), 
19212         fx = this.viz.labels;
19213     node.eachLevel(0, level+1, function(n) {
19214       var d = n._depth - node._depth;
19215       if(d > level) {
19216         n.drawn = false; 
19217         n.exist = false;
19218         n.ignore = true;
19219         fx.hideLabel(n, false);
19220       } else {
19221         n.drawn = true;
19222         n.exist = true;
19223         delete n.ignore;
19224       }
19225     });
19226     node.drawn = true;
19227     delete node.ignore;
19228   }
19229 });
19230
19231 /*
19232
19233 Performs operations on group of nodes.
19234
19235 */
19236 TM.Group = new Class( {
19237
19238   initialize: function(viz){
19239     this.viz = viz;
19240     this.canvas = viz.canvas;
19241     this.config = viz.config;
19242   },
19243
19244   /*
19245   
19246     Calls the request method on the controller to request a subtree for each node. 
19247   */
19248   requestNodes: function(nodes, controller){
19249     var counter = 0, len = nodes.length, nodeSelected = {};
19250     var complete = function(){
19251       controller.onComplete();
19252     };
19253     var viz = this.viz;
19254     if (len == 0)
19255       complete();
19256     for ( var i = 0; i < len; i++) {
19257       nodeSelected[nodes[i].id] = nodes[i];
19258       controller.request(nodes[i].id, nodes[i]._level, {
19259         onComplete: function(nodeId, data){
19260           if (data && data.children) {
19261             data.id = nodeId;
19262             viz.op.sum(data, {
19263               type: 'nothing'
19264             });
19265           }
19266           if (++counter == len) {
19267             viz.graph.computeLevels(viz.root, 0);
19268             complete();
19269           }
19270         }
19271       });
19272     }
19273   }
19274 });
19275
19276 /*
19277   Class: TM.Plot
19278   
19279   Custom extension of <Graph.Plot>.
19280   
19281   Extends:
19282   
19283   All <Graph.Plot> methods
19284   
19285   See also:
19286   
19287   <Graph.Plot>
19288   
19289   */
19290 TM.Plot = new Class({
19291
19292   Implements: Graph.Plot,
19293
19294   initialize: function(viz){
19295     this.viz = viz;
19296     this.config = viz.config;
19297     this.node = this.config.Node;
19298     this.edge = this.config.Edge;
19299     this.animation = new Animation;
19300     this.nodeTypes = new TM.Plot.NodeTypes;
19301     this.edgeTypes = new TM.Plot.EdgeTypes;
19302     this.labels = viz.labels;
19303   },
19304
19305   plot: function(opt, animating){
19306     var viz = this.viz, 
19307         graph = viz.graph;
19308     viz.canvas.clear();
19309     this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19310       'withLabels': true,
19311       'hideLabels': false,
19312       'plotSubtree': function(n, ch){
19313         return n.anySubnode("exist");
19314       }
19315     }), animating);
19316   }
19317 });
19318
19319 /*
19320   Class: TM.Label
19321   
19322   Custom extension of <Graph.Label>. 
19323   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19324
19325   Extends:
19326
19327   All <Graph.Label> methods and subclasses.
19328
19329   See also:
19330
19331   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19332   
19333 */
19334 TM.Label = {};
19335
19336 /*
19337  TM.Label.Native
19338
19339  Custom extension of <Graph.Label.Native>.
19340
19341  Extends:
19342
19343  All <Graph.Label.Native> methods
19344
19345  See also:
19346
19347  <Graph.Label.Native>
19348 */
19349 TM.Label.Native = new Class({
19350   Implements: Graph.Label.Native,
19351
19352   initialize: function(viz) {
19353     this.config = viz.config;
19354     this.leaf = viz.leaf;
19355   },
19356   
19357   renderLabel: function(canvas, node, controller){
19358     if(!this.leaf(node) && !this.config.titleHeight) return;
19359     var pos = node.pos.getc(true), 
19360         ctx = canvas.getCtx(),
19361         width = node.getData('width'),
19362         height = node.getData('height'),
19363         x = pos.x + width/2,
19364         y = pos.y;
19365         
19366     ctx.fillText(node.name, x, y, width);
19367   }
19368 });
19369
19370 /*
19371  TM.Label.SVG
19372
19373   Custom extension of <Graph.Label.SVG>.
19374
19375   Extends:
19376
19377   All <Graph.Label.SVG> methods
19378
19379   See also:
19380
19381   <Graph.Label.SVG>
19382 */
19383 TM.Label.SVG = new Class( {
19384   Implements: Graph.Label.SVG,
19385
19386   initialize: function(viz){
19387     this.viz = viz;
19388     this.leaf = viz.leaf;
19389     this.config = viz.config;
19390   },
19391
19392   /* 
19393   placeLabel
19394
19395   Overrides abstract method placeLabel in <Graph.Plot>.
19396
19397   Parameters:
19398
19399   tag - A DOM label element.
19400   node - A <Graph.Node>.
19401   controller - A configuration/controller object passed to the visualization.
19402   
19403   */
19404   placeLabel: function(tag, node, controller){
19405     var pos = node.pos.getc(true), 
19406         canvas = this.viz.canvas,
19407         ox = canvas.translateOffsetX,
19408         oy = canvas.translateOffsetY,
19409         sx = canvas.scaleOffsetX,
19410         sy = canvas.scaleOffsetY,
19411         radius = canvas.getSize();
19412     var labelPos = {
19413       x: Math.round(pos.x * sx + ox + radius.width / 2),
19414       y: Math.round(pos.y * sy + oy + radius.height / 2)
19415     };
19416     tag.setAttribute('x', labelPos.x);
19417     tag.setAttribute('y', labelPos.y);
19418
19419     if(!this.leaf(node) && !this.config.titleHeight) {
19420       tag.style.display = 'none';
19421     }
19422     controller.onPlaceLabel(tag, node);
19423   }
19424 });
19425
19426 /*
19427  TM.Label.HTML
19428
19429  Custom extension of <Graph.Label.HTML>.
19430
19431  Extends:
19432
19433  All <Graph.Label.HTML> methods.
19434
19435  See also:
19436
19437  <Graph.Label.HTML>
19438
19439 */
19440 TM.Label.HTML = new Class( {
19441   Implements: Graph.Label.HTML,
19442
19443   initialize: function(viz){
19444     this.viz = viz;
19445     this.leaf = viz.leaf;
19446     this.config = viz.config;
19447   },
19448
19449   /* 
19450     placeLabel
19451   
19452     Overrides abstract method placeLabel in <Graph.Plot>.
19453   
19454     Parameters:
19455   
19456     tag - A DOM label element.
19457     node - A <Graph.Node>.
19458     controller - A configuration/controller object passed to the visualization.
19459   
19460   */
19461   placeLabel: function(tag, node, controller){
19462     var pos = node.pos.getc(true), 
19463         canvas = this.viz.canvas,
19464         ox = canvas.translateOffsetX,
19465         oy = canvas.translateOffsetY,
19466         sx = canvas.scaleOffsetX,
19467         sy = canvas.scaleOffsetY,
19468         radius = canvas.getSize();
19469     var labelPos = {
19470       x: Math.round(pos.x * sx + ox + radius.width / 2),
19471       y: Math.round(pos.y * sy + oy + radius.height / 2)
19472     };
19473
19474     var style = tag.style;
19475     style.left = labelPos.x + 'px';
19476     style.top = labelPos.y + 'px';
19477     style.width = node.getData('width') * sx + 'px';
19478     style.height = node.getData('height') * sy + 'px';
19479     style.zIndex = node._depth * 100;
19480     style.display = '';
19481
19482     if(!this.leaf(node) && !this.config.titleHeight) {
19483       tag.style.display = 'none';
19484     }
19485     controller.onPlaceLabel(tag, node);
19486   }
19487 });
19488
19489 /*
19490   Class: TM.Plot.NodeTypes
19491
19492   This class contains a list of <Graph.Node> built-in types. 
19493   Node types implemented are 'none', 'rectangle'.
19494
19495   You can add your custom node types, customizing your visualization to the extreme.
19496
19497   Example:
19498
19499   (start code js)
19500     TM.Plot.NodeTypes.implement({
19501       'mySpecialType': {
19502         'render': function(node, canvas) {
19503           //print your custom node to canvas
19504         },
19505         //optional
19506         'contains': function(node, pos) {
19507           //return true if pos is inside the node or false otherwise
19508         }
19509       }
19510     });
19511   (end code)
19512
19513 */
19514 TM.Plot.NodeTypes = new Class( {
19515   'none': {
19516     'render': $.empty
19517   },
19518
19519   'rectangle': {
19520     'render': function(node, canvas, animating){
19521       var leaf = this.viz.leaf(node),
19522           config = this.config,
19523           offst = config.offset,
19524           titleHeight = config.titleHeight,
19525           pos = node.pos.getc(true),
19526           width = node.getData('width'),
19527           height = node.getData('height'),
19528           border = node.getData('border'),
19529           ctx = canvas.getCtx(),
19530           posx = pos.x + offst / 2, 
19531           posy = pos.y + offst / 2;
19532       if(width <= offst || height <= offst) return;
19533       if (leaf) {
19534         if(config.cushion) {
19535           var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1, 
19536               posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19537           var color = node.getData('color');
19538           var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
19539               function(r) { return r * 0.2 >> 0; }));
19540           lg.addColorStop(0, color);
19541           lg.addColorStop(1, colorGrad);
19542           ctx.fillStyle = lg;
19543         }
19544         ctx.fillRect(posx, posy, width - offst, height - offst);
19545         if(border) {
19546           ctx.save();
19547           ctx.strokeStyle = border;
19548           ctx.strokeRect(posx, posy, width - offst, height - offst);
19549           ctx.restore();
19550         }
19551       } else if(titleHeight > 0){
19552         ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19553             titleHeight - offst);
19554         if(border) {
19555           ctx.save();
19556           ctx.strokeStyle = border;
19557           ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19558               height - offst);
19559           ctx.restore();
19560         }
19561       }
19562     },
19563     'contains': function(node, pos) {
19564       if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19565       var npos = node.pos.getc(true),
19566           width = node.getData('width'), 
19567           leaf = this.viz.leaf(node),
19568           height = leaf? node.getData('height') : this.config.titleHeight;
19569       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19570     }
19571   }
19572 });
19573
19574 TM.Plot.EdgeTypes = new Class( {
19575   'none': $.empty
19576 });
19577
19578 /*
19579   Class: TM.SliceAndDice
19580   
19581   A slice and dice TreeMap visualization.
19582   
19583   Implements:
19584   
19585   All <TM.Base> methods and properties.
19586 */
19587 TM.SliceAndDice = new Class( {
19588   Implements: [
19589       Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19590   ]
19591 });
19592
19593 /*
19594   Class: TM.Squarified
19595   
19596   A squarified TreeMap visualization.
19597
19598   Implements:
19599   
19600   All <TM.Base> methods and properties.
19601 */
19602 TM.Squarified = new Class( {
19603   Implements: [
19604       Loader, Extras, TM.Base, Layouts.TM.Squarified
19605   ]
19606 });
19607
19608 /*
19609   Class: TM.Strip
19610   
19611   A strip TreeMap visualization.
19612
19613   Implements:
19614   
19615   All <TM.Base> methods and properties.
19616 */
19617 TM.Strip = new Class( {
19618   Implements: [
19619       Loader, Extras, TM.Base, Layouts.TM.Strip
19620   ]
19621 });
19622
19623
19624 /*
19625  * File: RGraph.js
19626  *
19627  */
19628
19629 /*
19630    Class: RGraph
19631    
19632    A radial graph visualization with advanced animations.
19633    
19634    Inspired by:
19635  
19636    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>
19637    
19638    Note:
19639    
19640    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.
19641    
19642   Implements:
19643   
19644   All <Loader> methods
19645   
19646    Constructor Options:
19647    
19648    Inherits options from
19649    
19650    - <Options.Canvas>
19651    - <Options.Controller>
19652    - <Options.Node>
19653    - <Options.Edge>
19654    - <Options.Label>
19655    - <Options.Events>
19656    - <Options.Tips>
19657    - <Options.NodeStyles>
19658    - <Options.Navigation>
19659    
19660    Additionally, there are other parameters and some default values changed
19661    
19662    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19663    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
19664      
19665    Instance Properties:
19666
19667    canvas - Access a <Canvas> instance.
19668    graph - Access a <Graph> instance.
19669    op - Access a <RGraph.Op> instance.
19670    fx - Access a <RGraph.Plot> instance.
19671    labels - Access a <RGraph.Label> interface implementation.   
19672 */
19673
19674 $jit.RGraph = new Class( {
19675
19676   Implements: [
19677       Loader, Extras, Layouts.Radial
19678   ],
19679
19680   initialize: function(controller){
19681     var $RGraph = $jit.RGraph;
19682
19683     var config = {
19684       interpolation: 'linear',
19685       levelDistance: 100
19686     };
19687
19688     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19689         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19690
19691     var canvasConfig = this.config;
19692     if(canvasConfig.useCanvas) {
19693       this.canvas = canvasConfig.useCanvas;
19694       this.config.labelContainer = this.canvas.id + '-label';
19695     } else {
19696       if(canvasConfig.background) {
19697         canvasConfig.background = $.merge({
19698           type: 'Circles'
19699         }, canvasConfig.background);
19700       }
19701       this.canvas = new Canvas(this, canvasConfig);
19702       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19703     }
19704
19705     this.graphOptions = {
19706       'complex': false,
19707       'Node': {
19708         'selected': false,
19709         'exist': true,
19710         'drawn': true
19711       }
19712     };
19713     this.graph = new Graph(this.graphOptions, this.config.Node,
19714         this.config.Edge);
19715     this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19716     this.fx = new $RGraph.Plot(this, $RGraph);
19717     this.op = new $RGraph.Op(this);
19718     this.json = null;
19719     this.root = null;
19720     this.busy = false;
19721     this.parent = false;
19722     // initialize extras
19723     this.initializeExtras();
19724   },
19725
19726   /* 
19727   
19728     createLevelDistanceFunc 
19729   
19730     Returns the levelDistance function used for calculating a node distance 
19731     to its origin. This function returns a function that is computed 
19732     per level and not per node, such that all nodes with the same depth will have the 
19733     same distance to the origin. The resulting function gets the 
19734     parent node as parameter and returns a float.
19735
19736    */
19737   createLevelDistanceFunc: function(){
19738     var ld = this.config.levelDistance;
19739     return function(elem){
19740       return (elem._depth + 1) * ld;
19741     };
19742   },
19743
19744   /* 
19745      Method: refresh 
19746      
19747      Computes positions and plots the tree.
19748
19749    */
19750   refresh: function(){
19751     this.compute();
19752     this.plot();
19753   },
19754
19755   reposition: function(){
19756     this.compute('end');
19757   },
19758
19759   /*
19760    Method: plot
19761   
19762    Plots the RGraph. This is a shortcut to *fx.plot*.
19763   */
19764   plot: function(){
19765     this.fx.plot();
19766   },
19767   /*
19768    getNodeAndParentAngle
19769   
19770    Returns the _parent_ of the given node, also calculating its angle span.
19771   */
19772   getNodeAndParentAngle: function(id){
19773     var theta = false;
19774     var n = this.graph.getNode(id);
19775     var ps = n.getParents();
19776     var p = (ps.length > 0)? ps[0] : false;
19777     if (p) {
19778       var posParent = p.pos.getc(), posChild = n.pos.getc();
19779       var newPos = posParent.add(posChild.scale(-1));
19780       theta = Math.atan2(newPos.y, newPos.x);
19781       if (theta < 0)
19782         theta += 2 * Math.PI;
19783     }
19784     return {
19785       parent: p,
19786       theta: theta
19787     };
19788   },
19789   /*
19790    tagChildren
19791   
19792    Enumerates the children in order to maintain child ordering (second constraint of the paper).
19793   */
19794   tagChildren: function(par, id){
19795     if (par.angleSpan) {
19796       var adjs = [];
19797       par.eachAdjacency(function(elem){
19798         adjs.push(elem.nodeTo);
19799       }, "ignore");
19800       var len = adjs.length;
19801       for ( var i = 0; i < len && id != adjs[i].id; i++)
19802         ;
19803       for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19804         adjs[j].dist = k++;
19805       }
19806     }
19807   },
19808   /* 
19809   Method: onClick 
19810   
19811   Animates the <RGraph> to center the node specified by *id*.
19812
19813    Parameters:
19814
19815    id - A <Graph.Node> id.
19816    opt - (optional|object) An object containing some extra properties described below
19817    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19818
19819    Example:
19820
19821    (start code js)
19822      rgraph.onClick('someid');
19823      //or also...
19824      rgraph.onClick('someid', {
19825       hideLabels: false
19826      });
19827     (end code)
19828     
19829   */
19830   onClick: function(id, opt){
19831     if (this.root != id && !this.busy) {
19832       this.busy = true;
19833       this.root = id;
19834       that = this;
19835       this.controller.onBeforeCompute(this.graph.getNode(id));
19836       var obj = this.getNodeAndParentAngle(id);
19837
19838       // second constraint
19839       this.tagChildren(obj.parent, id);
19840       this.parent = obj.parent;
19841       this.compute('end');
19842
19843       // first constraint
19844       var thetaDiff = obj.theta - obj.parent.endPos.theta;
19845       this.graph.eachNode(function(elem){
19846         elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19847       });
19848
19849       var mode = this.config.interpolation;
19850       opt = $.merge( {
19851         onComplete: $.empty
19852       }, opt || {});
19853
19854       this.fx.animate($.merge( {
19855         hideLabels: true,
19856         modes: [
19857           mode
19858         ]
19859       }, opt, {
19860         onComplete: function(){
19861           that.busy = false;
19862           opt.onComplete();
19863         }
19864       }));
19865     }
19866   }
19867 });
19868
19869 $jit.RGraph.$extend = true;
19870
19871 (function(RGraph){
19872
19873   /*
19874      Class: RGraph.Op
19875      
19876      Custom extension of <Graph.Op>.
19877
19878      Extends:
19879
19880      All <Graph.Op> methods
19881      
19882      See also:
19883      
19884      <Graph.Op>
19885
19886   */
19887   RGraph.Op = new Class( {
19888
19889     Implements: Graph.Op
19890
19891   });
19892
19893   /*
19894      Class: RGraph.Plot
19895     
19896     Custom extension of <Graph.Plot>.
19897   
19898     Extends:
19899   
19900     All <Graph.Plot> methods
19901     
19902     See also:
19903     
19904     <Graph.Plot>
19905   
19906   */
19907   RGraph.Plot = new Class( {
19908
19909     Implements: Graph.Plot
19910
19911   });
19912
19913   /*
19914     Object: RGraph.Label
19915
19916     Custom extension of <Graph.Label>. 
19917     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19918   
19919     Extends:
19920   
19921     All <Graph.Label> methods and subclasses.
19922   
19923     See also:
19924   
19925     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19926   
19927    */
19928   RGraph.Label = {};
19929
19930   /*
19931      RGraph.Label.Native
19932
19933      Custom extension of <Graph.Label.Native>.
19934
19935      Extends:
19936
19937      All <Graph.Label.Native> methods
19938
19939      See also:
19940
19941      <Graph.Label.Native>
19942
19943   */
19944   RGraph.Label.Native = new Class( {
19945     Implements: Graph.Label.Native
19946   });
19947
19948   /*
19949      RGraph.Label.SVG
19950     
19951     Custom extension of <Graph.Label.SVG>.
19952   
19953     Extends:
19954   
19955     All <Graph.Label.SVG> methods
19956   
19957     See also:
19958   
19959     <Graph.Label.SVG>
19960   
19961   */
19962   RGraph.Label.SVG = new Class( {
19963     Implements: Graph.Label.SVG,
19964
19965     initialize: function(viz){
19966       this.viz = viz;
19967     },
19968
19969     /* 
19970        placeLabel
19971
19972        Overrides abstract method placeLabel in <Graph.Plot>.
19973
19974        Parameters:
19975
19976        tag - A DOM label element.
19977        node - A <Graph.Node>.
19978        controller - A configuration/controller object passed to the visualization.
19979       
19980      */
19981     placeLabel: function(tag, node, controller){
19982       var pos = node.pos.getc(true), 
19983           canvas = this.viz.canvas,
19984           ox = canvas.translateOffsetX,
19985           oy = canvas.translateOffsetY,
19986           sx = canvas.scaleOffsetX,
19987           sy = canvas.scaleOffsetY,
19988           radius = canvas.getSize();
19989       var labelPos = {
19990         x: Math.round(pos.x * sx + ox + radius.width / 2),
19991         y: Math.round(pos.y * sy + oy + radius.height / 2)
19992       };
19993       tag.setAttribute('x', labelPos.x);
19994       tag.setAttribute('y', labelPos.y);
19995
19996       controller.onPlaceLabel(tag, node);
19997     }
19998   });
19999
20000   /*
20001      RGraph.Label.HTML
20002
20003      Custom extension of <Graph.Label.HTML>.
20004
20005      Extends:
20006
20007      All <Graph.Label.HTML> methods.
20008
20009      See also:
20010
20011      <Graph.Label.HTML>
20012
20013   */
20014   RGraph.Label.HTML = new Class( {
20015     Implements: Graph.Label.HTML,
20016
20017     initialize: function(viz){
20018       this.viz = viz;
20019     },
20020     /* 
20021        placeLabel
20022
20023        Overrides abstract method placeLabel in <Graph.Plot>.
20024
20025        Parameters:
20026
20027        tag - A DOM label element.
20028        node - A <Graph.Node>.
20029        controller - A configuration/controller object passed to the visualization.
20030       
20031      */
20032     placeLabel: function(tag, node, controller){
20033       var pos = node.pos.getc(true), 
20034           canvas = this.viz.canvas,
20035           ox = canvas.translateOffsetX,
20036           oy = canvas.translateOffsetY,
20037           sx = canvas.scaleOffsetX,
20038           sy = canvas.scaleOffsetY,
20039           radius = canvas.getSize();
20040       var labelPos = {
20041         x: Math.round(pos.x * sx + ox + radius.width / 2),
20042         y: Math.round(pos.y * sy + oy + radius.height / 2)
20043       };
20044
20045       var style = tag.style;
20046       style.left = labelPos.x + 'px';
20047       style.top = labelPos.y + 'px';
20048       style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20049
20050       controller.onPlaceLabel(tag, node);
20051     }
20052   });
20053
20054   /*
20055     Class: RGraph.Plot.NodeTypes
20056
20057     This class contains a list of <Graph.Node> built-in types. 
20058     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20059
20060     You can add your custom node types, customizing your visualization to the extreme.
20061
20062     Example:
20063
20064     (start code js)
20065       RGraph.Plot.NodeTypes.implement({
20066         'mySpecialType': {
20067           'render': function(node, canvas) {
20068             //print your custom node to canvas
20069           },
20070           //optional
20071           'contains': function(node, pos) {
20072             //return true if pos is inside the node or false otherwise
20073           }
20074         }
20075       });
20076     (end code)
20077
20078   */
20079   RGraph.Plot.NodeTypes = new Class({
20080     'none': {
20081       'render': $.empty,
20082       'contains': $.lambda(false)
20083     },
20084     'circle': {
20085       'render': function(node, canvas){
20086         var pos = node.pos.getc(true), 
20087             dim = node.getData('dim');
20088         this.nodeHelper.circle.render('fill', pos, dim, canvas);
20089       },
20090       'contains': function(node, pos){
20091         var npos = node.pos.getc(true), 
20092             dim = node.getData('dim');
20093         return this.nodeHelper.circle.contains(npos, pos, dim);
20094       }
20095     },
20096     'ellipse': {
20097       'render': function(node, canvas){
20098         var pos = node.pos.getc(true), 
20099             width = node.getData('width'), 
20100             height = node.getData('height');
20101         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20102         },
20103       // TODO(nico): be more precise...
20104       'contains': function(node, pos){
20105         var npos = node.pos.getc(true), 
20106             width = node.getData('width'), 
20107             height = node.getData('height');
20108         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20109       }
20110     },
20111     'square': {
20112       'render': function(node, canvas){
20113         var pos = node.pos.getc(true), 
20114             dim = node.getData('dim');
20115         this.nodeHelper.square.render('fill', pos, dim, canvas);
20116       },
20117       'contains': function(node, pos){
20118         var npos = node.pos.getc(true), 
20119             dim = node.getData('dim');
20120         return this.nodeHelper.square.contains(npos, pos, dim);
20121       }
20122     },
20123     'rectangle': {
20124       'render': function(node, canvas){
20125         var pos = node.pos.getc(true), 
20126             width = node.getData('width'), 
20127             height = node.getData('height');
20128         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20129       },
20130       'contains': function(node, pos){
20131         var npos = node.pos.getc(true), 
20132             width = node.getData('width'), 
20133             height = node.getData('height');
20134         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20135       }
20136     },
20137     'triangle': {
20138       'render': function(node, canvas){
20139         var pos = node.pos.getc(true), 
20140             dim = node.getData('dim');
20141         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20142       },
20143       'contains': function(node, pos) {
20144         var npos = node.pos.getc(true), 
20145             dim = node.getData('dim');
20146         return this.nodeHelper.triangle.contains(npos, pos, dim);
20147       }
20148     },
20149     'star': {
20150       'render': function(node, canvas){
20151         var pos = node.pos.getc(true),
20152             dim = node.getData('dim');
20153         this.nodeHelper.star.render('fill', pos, dim, canvas);
20154       },
20155       'contains': function(node, pos) {
20156         var npos = node.pos.getc(true),
20157             dim = node.getData('dim');
20158         return this.nodeHelper.star.contains(npos, pos, dim);
20159       }
20160     }
20161   });
20162
20163   /*
20164     Class: RGraph.Plot.EdgeTypes
20165
20166     This class contains a list of <Graph.Adjacence> built-in types. 
20167     Edge types implemented are 'none', 'line' and 'arrow'.
20168   
20169     You can add your custom edge types, customizing your visualization to the extreme.
20170   
20171     Example:
20172   
20173     (start code js)
20174       RGraph.Plot.EdgeTypes.implement({
20175         'mySpecialType': {
20176           'render': function(adj, canvas) {
20177             //print your custom edge to canvas
20178           },
20179           //optional
20180           'contains': function(adj, pos) {
20181             //return true if pos is inside the arc or false otherwise
20182           }
20183         }
20184       });
20185     (end code)
20186   
20187   */
20188   RGraph.Plot.EdgeTypes = new Class({
20189     'none': $.empty,
20190     'line': {
20191       'render': function(adj, canvas) {
20192         var from = adj.nodeFrom.pos.getc(true),
20193             to = adj.nodeTo.pos.getc(true);
20194         this.edgeHelper.line.render(from, to, canvas);
20195       },
20196       'contains': function(adj, pos) {
20197         var from = adj.nodeFrom.pos.getc(true),
20198             to = adj.nodeTo.pos.getc(true);
20199         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20200       }
20201     },
20202     'arrow': {
20203       'render': function(adj, canvas) {
20204         var from = adj.nodeFrom.pos.getc(true),
20205             to = adj.nodeTo.pos.getc(true),
20206             dim = adj.getData('dim'),
20207             direction = adj.data.$direction,
20208             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20209         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20210       },
20211       'contains': function(adj, pos) {
20212         var from = adj.nodeFrom.pos.getc(true),
20213             to = adj.nodeTo.pos.getc(true);
20214         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20215       }
20216     }
20217   });
20218
20219 })($jit.RGraph);
20220
20221
20222 /*
20223  * File: Hypertree.js
20224  * 
20225 */
20226
20227 /* 
20228      Complex 
20229      
20230      A multi-purpose Complex Class with common methods. Extended for the Hypertree. 
20231  
20232 */
20233 /* 
20234    moebiusTransformation 
20235  
20236    Calculates a moebius transformation for this point / complex. 
20237     For more information go to: 
20238         http://en.wikipedia.org/wiki/Moebius_transformation. 
20239  
20240    Parameters: 
20241  
20242       c - An initialized Complex instance representing a translation Vector. 
20243 */
20244
20245 Complex.prototype.moebiusTransformation = function(c) {
20246   var num = this.add(c);
20247   var den = c.$conjugate().$prod(this);
20248   den.x++;
20249   return num.$div(den);
20250 };
20251
20252 /* 
20253     moebiusTransformation 
20254      
20255     Calculates a moebius transformation for the hyperbolic tree. 
20256      
20257     <http://en.wikipedia.org/wiki/Moebius_transformation> 
20258       
20259      Parameters: 
20260      
20261         graph - A <Graph> instance.
20262         pos - A <Complex>.
20263         prop - A property array.
20264         theta - Rotation angle. 
20265         startPos - _optional_ start position. 
20266 */
20267 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20268   this.eachNode(graph, function(elem) {
20269     for ( var i = 0; i < prop.length; i++) {
20270       var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20271       elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20272     }
20273   }, flags);
20274 };
20275
20276 /* 
20277    Class: Hypertree 
20278    
20279    A Hyperbolic Tree/Graph visualization.
20280    
20281    Inspired by:
20282  
20283    A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli). 
20284    <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20285  
20286   Note:
20287  
20288   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.
20289
20290   Implements:
20291   
20292   All <Loader> methods
20293   
20294   Constructor Options:
20295   
20296   Inherits options from
20297   
20298   - <Options.Canvas>
20299   - <Options.Controller>
20300   - <Options.Node>
20301   - <Options.Edge>
20302   - <Options.Label>
20303   - <Options.Events>
20304   - <Options.Tips>
20305   - <Options.NodeStyles>
20306   - <Options.Navigation>
20307   
20308   Additionally, there are other parameters and some default values changed
20309   
20310   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*.
20311   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.
20312   fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20313   duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20314   Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
20315   
20316   Instance Properties:
20317   
20318   canvas - Access a <Canvas> instance.
20319   graph - Access a <Graph> instance.
20320   op - Access a <Hypertree.Op> instance.
20321   fx - Access a <Hypertree.Plot> instance.
20322   labels - Access a <Hypertree.Label> interface implementation.
20323
20324 */
20325
20326 $jit.Hypertree = new Class( {
20327
20328   Implements: [ Loader, Extras, Layouts.Radial ],
20329
20330   initialize: function(controller) {
20331     var $Hypertree = $jit.Hypertree;
20332
20333     var config = {
20334       radius: "auto",
20335       offset: 0,
20336       Edge: {
20337         type: 'hyperline'
20338       },
20339       duration: 1500,
20340       fps: 35
20341     };
20342     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20343         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20344
20345     var canvasConfig = this.config;
20346     if(canvasConfig.useCanvas) {
20347       this.canvas = canvasConfig.useCanvas;
20348       this.config.labelContainer = this.canvas.id + '-label';
20349     } else {
20350       if(canvasConfig.background) {
20351         canvasConfig.background = $.merge({
20352           type: 'Circles'
20353         }, canvasConfig.background);
20354       }
20355       this.canvas = new Canvas(this, canvasConfig);
20356       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20357     }
20358
20359     this.graphOptions = {
20360       'complex': false,
20361       'Node': {
20362         'selected': false,
20363         'exist': true,
20364         'drawn': true
20365       }
20366     };
20367     this.graph = new Graph(this.graphOptions, this.config.Node,
20368         this.config.Edge);
20369     this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20370     this.fx = new $Hypertree.Plot(this, $Hypertree);
20371     this.op = new $Hypertree.Op(this);
20372     this.json = null;
20373     this.root = null;
20374     this.busy = false;
20375     // initialize extras
20376     this.initializeExtras();
20377   },
20378
20379   /* 
20380   
20381   createLevelDistanceFunc 
20382
20383   Returns the levelDistance function used for calculating a node distance 
20384   to its origin. This function returns a function that is computed 
20385   per level and not per node, such that all nodes with the same depth will have the 
20386   same distance to the origin. The resulting function gets the 
20387   parent node as parameter and returns a float.
20388
20389   */
20390   createLevelDistanceFunc: function() {
20391     // get max viz. length.
20392     var r = this.getRadius();
20393     // get max depth.
20394     var depth = 0, max = Math.max, config = this.config;
20395     this.graph.eachNode(function(node) {
20396       depth = max(node._depth, depth);
20397     }, "ignore");
20398     depth++;
20399     // node distance generator
20400     var genDistFunc = function(a) {
20401       return function(node) {
20402         node.scale = r;
20403         var d = node._depth + 1;
20404         var acum = 0, pow = Math.pow;
20405         while (d) {
20406           acum += pow(a, d--);
20407         }
20408         return acum - config.offset;
20409       };
20410     };
20411     // estimate better edge length.
20412     for ( var i = 0.51; i <= 1; i += 0.01) {
20413       var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20414       if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20415     }
20416     return genDistFunc(0.75);
20417   },
20418
20419   /* 
20420     Method: getRadius 
20421     
20422     Returns the current radius of the visualization. If *config.radius* is *auto* then it 
20423     calculates the radius by taking the smaller size of the <Canvas> widget.
20424     
20425     See also:
20426     
20427     <Canvas.getSize>
20428    
20429   */
20430   getRadius: function() {
20431     var rad = this.config.radius;
20432     if (rad !== "auto") { return rad; }
20433     var s = this.canvas.getSize();
20434     return Math.min(s.width, s.height) / 2;
20435   },
20436
20437   /* 
20438     Method: refresh 
20439     
20440     Computes positions and plots the tree.
20441
20442     Parameters:
20443
20444     reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20445
20446    */
20447   refresh: function(reposition) {
20448     if (reposition) {
20449       this.reposition();
20450       this.graph.eachNode(function(node) {
20451         node.startPos.rho = node.pos.rho = node.endPos.rho;
20452         node.startPos.theta = node.pos.theta = node.endPos.theta;
20453       });
20454     } else {
20455       this.compute();
20456     }
20457     this.plot();
20458   },
20459
20460   /* 
20461    reposition 
20462    
20463    Computes nodes' positions and restores the tree to its previous position.
20464
20465    For calculating nodes' positions the root must be placed on its origin. This method does this 
20466      and then attemps to restore the hypertree to its previous position.
20467     
20468   */
20469   reposition: function() {
20470     this.compute('end');
20471     var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20472     Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20473         'end', "ignore");
20474     this.graph.eachNode(function(node) {
20475       if (node.ignore) {
20476         node.endPos.rho = node.pos.rho;
20477         node.endPos.theta = node.pos.theta;
20478       }
20479     });
20480   },
20481
20482   /* 
20483    Method: plot 
20484    
20485    Plots the <Hypertree>. This is a shortcut to *fx.plot*. 
20486
20487   */
20488   plot: function() {
20489     this.fx.plot();
20490   },
20491
20492   /* 
20493    Method: onClick 
20494    
20495    Animates the <Hypertree> to center the node specified by *id*.
20496
20497    Parameters:
20498
20499    id - A <Graph.Node> id.
20500    opt - (optional|object) An object containing some extra properties described below
20501    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20502
20503    Example:
20504
20505    (start code js)
20506      ht.onClick('someid');
20507      //or also...
20508      ht.onClick('someid', {
20509       hideLabels: false
20510      });
20511     (end code)
20512     
20513   */
20514   onClick: function(id, opt) {
20515     var pos = this.graph.getNode(id).pos.getc(true);
20516     this.move(pos, opt);
20517   },
20518
20519   /* 
20520    Method: move 
20521
20522    Translates the tree to the given position. 
20523
20524    Parameters:
20525
20526    pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20527    opt - This object has been defined in <Hypertree.onClick>
20528    
20529    Example:
20530    
20531    (start code js)
20532      ht.move({ x: 0, y: 0.7 }, {
20533        hideLabels: false
20534      });
20535    (end code)
20536
20537   */
20538   move: function(pos, opt) {
20539     var versor = $C(pos.x, pos.y);
20540     if (this.busy === false && versor.norm() < 1) {
20541       this.busy = true;
20542       var root = this.graph.getClosestNodeToPos(versor), that = this;
20543       this.graph.computeLevels(root.id, 0);
20544       this.controller.onBeforeCompute(root);
20545       opt = $.merge( {
20546         onComplete: $.empty
20547       }, opt || {});
20548       this.fx.animate($.merge( {
20549         modes: [ 'moebius' ],
20550         hideLabels: true
20551       }, opt, {
20552         onComplete: function() {
20553           that.busy = false;
20554           opt.onComplete();
20555         }
20556       }), versor);
20557     }
20558   }
20559 });
20560
20561 $jit.Hypertree.$extend = true;
20562
20563 (function(Hypertree) {
20564
20565   /* 
20566      Class: Hypertree.Op 
20567    
20568      Custom extension of <Graph.Op>.
20569
20570      Extends:
20571
20572      All <Graph.Op> methods
20573      
20574      See also:
20575      
20576      <Graph.Op>
20577
20578   */
20579   Hypertree.Op = new Class( {
20580
20581     Implements: Graph.Op
20582
20583   });
20584
20585   /* 
20586      Class: Hypertree.Plot 
20587    
20588     Custom extension of <Graph.Plot>.
20589   
20590     Extends:
20591   
20592     All <Graph.Plot> methods
20593     
20594     See also:
20595     
20596     <Graph.Plot>
20597   
20598   */
20599   Hypertree.Plot = new Class( {
20600
20601     Implements: Graph.Plot
20602
20603   });
20604
20605   /*
20606     Object: Hypertree.Label
20607
20608     Custom extension of <Graph.Label>. 
20609     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20610   
20611     Extends:
20612   
20613     All <Graph.Label> methods and subclasses.
20614   
20615     See also:
20616   
20617     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20618
20619    */
20620   Hypertree.Label = {};
20621
20622   /*
20623      Hypertree.Label.Native
20624
20625      Custom extension of <Graph.Label.Native>.
20626
20627      Extends:
20628
20629      All <Graph.Label.Native> methods
20630
20631      See also:
20632
20633      <Graph.Label.Native>
20634
20635   */
20636   Hypertree.Label.Native = new Class( {
20637     Implements: Graph.Label.Native,
20638
20639     initialize: function(viz) {
20640       this.viz = viz;
20641     },
20642
20643     renderLabel: function(canvas, node, controller) {
20644       var ctx = canvas.getCtx();
20645       var coord = node.pos.getc(true);
20646       var s = this.viz.getRadius();
20647       ctx.fillText(node.name, coord.x * s, coord.y * s);
20648     }
20649   });
20650
20651   /*
20652      Hypertree.Label.SVG
20653
20654     Custom extension of <Graph.Label.SVG>.
20655   
20656     Extends:
20657   
20658     All <Graph.Label.SVG> methods
20659   
20660     See also:
20661   
20662     <Graph.Label.SVG>
20663   
20664   */
20665   Hypertree.Label.SVG = new Class( {
20666     Implements: Graph.Label.SVG,
20667
20668     initialize: function(viz) {
20669       this.viz = viz;
20670     },
20671
20672     /* 
20673        placeLabel
20674
20675        Overrides abstract method placeLabel in <Graph.Plot>.
20676
20677        Parameters:
20678
20679        tag - A DOM label element.
20680        node - A <Graph.Node>.
20681        controller - A configuration/controller object passed to the visualization.
20682       
20683      */
20684     placeLabel: function(tag, node, controller) {
20685       var pos = node.pos.getc(true), 
20686           canvas = this.viz.canvas,
20687           ox = canvas.translateOffsetX,
20688           oy = canvas.translateOffsetY,
20689           sx = canvas.scaleOffsetX,
20690           sy = canvas.scaleOffsetY,
20691           radius = canvas.getSize(),
20692           r = this.viz.getRadius();
20693       var labelPos = {
20694         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20695         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20696       };
20697       tag.setAttribute('x', labelPos.x);
20698       tag.setAttribute('y', labelPos.y);
20699       controller.onPlaceLabel(tag, node);
20700     }
20701   });
20702
20703   /*
20704      Hypertree.Label.HTML
20705
20706      Custom extension of <Graph.Label.HTML>.
20707
20708      Extends:
20709
20710      All <Graph.Label.HTML> methods.
20711
20712      See also:
20713
20714      <Graph.Label.HTML>
20715
20716   */
20717   Hypertree.Label.HTML = new Class( {
20718     Implements: Graph.Label.HTML,
20719
20720     initialize: function(viz) {
20721       this.viz = viz;
20722     },
20723     /* 
20724        placeLabel
20725
20726        Overrides abstract method placeLabel in <Graph.Plot>.
20727
20728        Parameters:
20729
20730        tag - A DOM label element.
20731        node - A <Graph.Node>.
20732        controller - A configuration/controller object passed to the visualization.
20733       
20734      */
20735     placeLabel: function(tag, node, controller) {
20736       var pos = node.pos.getc(true), 
20737           canvas = this.viz.canvas,
20738           ox = canvas.translateOffsetX,
20739           oy = canvas.translateOffsetY,
20740           sx = canvas.scaleOffsetX,
20741           sy = canvas.scaleOffsetY,
20742           radius = canvas.getSize(),
20743           r = this.viz.getRadius();
20744       var labelPos = {
20745         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20746         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20747       };
20748       var style = tag.style;
20749       style.left = labelPos.x + 'px';
20750       style.top = labelPos.y + 'px';
20751       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20752
20753       controller.onPlaceLabel(tag, node);
20754     }
20755   });
20756
20757   /*
20758     Class: Hypertree.Plot.NodeTypes
20759
20760     This class contains a list of <Graph.Node> built-in types. 
20761     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20762
20763     You can add your custom node types, customizing your visualization to the extreme.
20764
20765     Example:
20766
20767     (start code js)
20768       Hypertree.Plot.NodeTypes.implement({
20769         'mySpecialType': {
20770           'render': function(node, canvas) {
20771             //print your custom node to canvas
20772           },
20773           //optional
20774           'contains': function(node, pos) {
20775             //return true if pos is inside the node or false otherwise
20776           }
20777         }
20778       });
20779     (end code)
20780
20781   */
20782   Hypertree.Plot.NodeTypes = new Class({
20783     'none': {
20784       'render': $.empty,
20785       'contains': $.lambda(false)
20786     },
20787     'circle': {
20788       'render': function(node, canvas) {
20789         var nconfig = this.node,
20790             dim = node.getData('dim'),
20791             p = node.pos.getc();
20792         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20793         p.$scale(node.scale);
20794         if (dim > 0.2) {
20795           this.nodeHelper.circle.render('fill', p, dim, canvas);
20796         }
20797       },
20798       'contains': function(node, pos) {
20799         var dim = node.getData('dim'),
20800             npos = node.pos.getc().$scale(node.scale);
20801         return this.nodeHelper.circle.contains(npos, pos, dim);
20802       }
20803     },
20804     'ellipse': {
20805       'render': function(node, canvas) {
20806         var pos = node.pos.getc().$scale(node.scale),
20807             width = node.getData('width'),
20808             height = node.getData('height');
20809         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20810       },
20811       'contains': function(node, pos) {
20812         var width = node.getData('width'),
20813             height = node.getData('height'),
20814             npos = node.pos.getc().$scale(node.scale);
20815         return this.nodeHelper.circle.contains(npos, pos, width, height);
20816       }
20817     },
20818     'square': {
20819       'render': function(node, canvas) {
20820         var nconfig = this.node,
20821             dim = node.getData('dim'),
20822             p = node.pos.getc();
20823         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20824         p.$scale(node.scale);
20825         if (dim > 0.2) {
20826           this.nodeHelper.square.render('fill', p, dim, canvas);
20827         }
20828       },
20829       'contains': function(node, pos) {
20830         var dim = node.getData('dim'),
20831             npos = node.pos.getc().$scale(node.scale);
20832         return this.nodeHelper.square.contains(npos, pos, dim);
20833       }
20834     },
20835     'rectangle': {
20836       'render': function(node, canvas) {
20837         var nconfig = this.node,
20838             width = node.getData('width'),
20839             height = node.getData('height'),
20840             pos = node.pos.getc();
20841         width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20842         height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20843         pos.$scale(node.scale);
20844         if (width > 0.2 && height > 0.2) {
20845           this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20846         }
20847       },
20848       'contains': function(node, pos) {
20849         var width = node.getData('width'),
20850             height = node.getData('height'),
20851             npos = node.pos.getc().$scale(node.scale);
20852         return this.nodeHelper.square.contains(npos, pos, width, height);
20853       }
20854     },
20855     'triangle': {
20856       'render': function(node, canvas) {
20857         var nconfig = this.node,
20858             dim = node.getData('dim'),
20859             p = node.pos.getc();
20860         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20861         p.$scale(node.scale);
20862         if (dim > 0.2) {
20863           this.nodeHelper.triangle.render('fill', p, dim, canvas);
20864         }
20865       },
20866       'contains': function(node, pos) {
20867         var dim = node.getData('dim'),
20868             npos = node.pos.getc().$scale(node.scale);
20869         return this.nodeHelper.triangle.contains(npos, pos, dim);
20870       }
20871     },
20872     'star': {
20873       'render': function(node, canvas) {
20874         var nconfig = this.node,
20875             dim = node.getData('dim'),
20876             p = node.pos.getc();
20877         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20878         p.$scale(node.scale);
20879         if (dim > 0.2) {
20880           this.nodeHelper.star.render('fill', p, dim, canvas);
20881         }
20882       },
20883       'contains': function(node, pos) {
20884         var dim = node.getData('dim'),
20885             npos = node.pos.getc().$scale(node.scale);
20886         return this.nodeHelper.star.contains(npos, pos, dim);
20887       }
20888     }
20889   });
20890
20891   /*
20892    Class: Hypertree.Plot.EdgeTypes
20893
20894     This class contains a list of <Graph.Adjacence> built-in types. 
20895     Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20896   
20897     You can add your custom edge types, customizing your visualization to the extreme.
20898   
20899     Example:
20900   
20901     (start code js)
20902       Hypertree.Plot.EdgeTypes.implement({
20903         'mySpecialType': {
20904           'render': function(adj, canvas) {
20905             //print your custom edge to canvas
20906           },
20907           //optional
20908           'contains': function(adj, pos) {
20909             //return true if pos is inside the arc or false otherwise
20910           }
20911         }
20912       });
20913     (end code)
20914   
20915   */
20916   Hypertree.Plot.EdgeTypes = new Class({
20917     'none': $.empty,
20918     'line': {
20919       'render': function(adj, canvas) {
20920         var from = adj.nodeFrom.pos.getc(true),
20921           to = adj.nodeTo.pos.getc(true),
20922           r = adj.nodeFrom.scale;
20923           this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20924       },
20925       'contains': function(adj, pos) {
20926         var from = adj.nodeFrom.pos.getc(true),
20927             to = adj.nodeTo.pos.getc(true),
20928             r = adj.nodeFrom.scale;
20929             this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20930       }
20931     },
20932     'arrow': {
20933       'render': function(adj, canvas) {
20934         var from = adj.nodeFrom.pos.getc(true),
20935             to = adj.nodeTo.pos.getc(true),
20936             r = adj.nodeFrom.scale,
20937             dim = adj.getData('dim'),
20938             direction = adj.data.$direction,
20939             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20940         this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20941       },
20942       'contains': function(adj, pos) {
20943         var from = adj.nodeFrom.pos.getc(true),
20944             to = adj.nodeTo.pos.getc(true),
20945             r = adj.nodeFrom.scale;
20946         this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20947       }
20948     },
20949     'hyperline': {
20950       'render': function(adj, canvas) {
20951         var from = adj.nodeFrom.pos.getc(),
20952             to = adj.nodeTo.pos.getc(),
20953             dim = this.viz.getRadius();
20954         this.edgeHelper.hyperline.render(from, to, dim, canvas);
20955       },
20956       'contains': $.lambda(false)
20957     }
20958   });
20959
20960 })($jit.Hypertree);
20961
20962
20963
20964
20965  })();