]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/SugarCharts/Jit/js/Jit/jit.js
Release 6.4.0
[Github/sugarcrm.git] / jssource / src_files / include / SugarCharts / Jit / js / Jit / jit.js
1 /*
2   Copyright (c) 2010, Nicolas Garcia Belmonte
3   All rights reserved
4
5   > Redistribution and use in source and binary forms, with or without
6   > modification, are permitted provided that the following conditions are met:
7   >      * Redistributions of source code must retain the above copyright
8   >        notice, this list of conditions and the following disclaimer.
9   >      * Redistributions in binary form must reproduce the above copyright
10   >        notice, this list of conditions and the following disclaimer in the
11   >        documentation and/or other materials provided with the distribution.
12   >      * Neither the name of the organization nor the
13   >        names of its contributors may be used to endorse or promote products
14   >        derived from this software without specific prior written permission.
15   >
16   >  THIS SOFTWARE IS PROVIDED BY NICOLAS GARCIA BELMONTE ``AS IS'' AND ANY
17   >  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   >  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   >  DISCLAIMED. IN NO EVENT SHALL NICOLAS GARCIA BELMONTE BE LIABLE FOR ANY
20   >  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   >  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   >  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   >  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   >  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   >  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27  
28  /** Lam Huynh on 10/10/2010 added funnel,guage charts **/
29  /** Lam Huynh on 02/27/2011 added image exporting **/
30  /** Lam Huynh on 02/23/2011 added line charts **/
31  
32  (function () { 
33
34 /*
35   File: Core.js
36
37  */
38
39 /*
40  Object: $jit
41  
42  Defines the namespace for all library Classes and Objects. 
43  This variable is the *only* global variable defined in the Toolkit. 
44  There are also other interesting properties attached to this variable described below.
45  */
46 window.$jit = function(w) {
47   w = w || window;
48   for(var k in $jit) {
49     if($jit[k].$extend) {
50       w[k] = $jit[k];
51     }
52   }
53 };
54
55 $jit.version = '2.0.0b';
56 /*
57   Object: $jit.id
58   
59   Works just like *document.getElementById*
60   
61   Example:
62   (start code js)
63   var element = $jit.id('elementId');
64   (end code)
65
66 */
67
68 /*
69  Object: $jit.util
70  
71  Contains utility functions.
72  
73  Some of the utility functions and the Class system were based in the MooTools Framework 
74  <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. 
75  MIT license <http://mootools.net/license.txt>.
76  
77  These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
78  I'd suggest you to use the functions from those libraries instead of using these, since their functions 
79  are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
80  
81  */
82 var $ = function(d) {
83   return document.getElementById(d);
84 };
85
86 $.empty = function() {
87 };
88
89 function pad(number, length) {
90    
91     var str = '' + number;
92     while (str.length < length) {
93         str =  str + '0';
94     }
95    
96     return str;
97
98 };
99
100 var Url = {
101  
102         // public method for url encoding
103         encode : function (string) {
104                 return escape(this._utf8_encode(string));
105         },
106  
107         // public method for url decoding
108         decode : function (string) {
109                 return this._utf8_decode(unescape(string));
110         },
111  
112         // private method for UTF-8 encoding
113         _utf8_encode : function (string) {
114                 string = string.replace(/\r\n/g,"\n");
115                 var utftext = "";
116  
117                 for (var n = 0; n < string.length; n++) {
118  
119                         var c = string.charCodeAt(n);
120  
121                         if (c < 128) {
122                                 utftext += String.fromCharCode(c);
123                         }
124                         else if((c > 127) && (c < 2048)) {
125                                 utftext += String.fromCharCode((c >> 6) | 192);
126                                 utftext += String.fromCharCode((c & 63) | 128);
127                         }
128                         else {
129                                 utftext += String.fromCharCode((c >> 12) | 224);
130                                 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
131                                 utftext += String.fromCharCode((c & 63) | 128);
132                         }
133  
134                 }
135  
136                 return utftext;
137         },
138  
139         // private method for UTF-8 decoding
140         _utf8_decode : function (utftext) {
141                 var string = "";
142                 var i = 0;
143                 var c = c1 = c2 = 0;
144  
145                 while ( i < utftext.length ) {
146  
147                         c = utftext.charCodeAt(i);
148  
149                         if (c < 128) {
150                                 string += String.fromCharCode(c);
151                                 i++;
152                         }
153                         else if((c > 191) && (c < 224)) {
154                                 c2 = utftext.charCodeAt(i+1);
155                                 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
156                                 i += 2;
157                         }
158                         else {
159                                 c2 = utftext.charCodeAt(i+1);
160                                 c3 = utftext.charCodeAt(i+2);
161                                 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
162                                 i += 3;
163                         }
164  
165                 }
166  
167                 return string;
168         }
169  
170 };
171
172 Array.prototype.sum = function() {
173   return (! this.length) ? 0 : this.slice(1).sum() +
174       ((typeof this[0] == 'number') ? this[0] : 0);
175 };
176
177 function array_match(needle, haystack) {
178     var length = haystack.length;
179     var indexValue = new Array();
180     for(var i = 0, count = 0; i < length; i++) {
181         if(haystack[i] == needle) {
182                 indexValue[count] = i;
183                 count++;
184         }
185     }
186     return new Array(count,indexValue);
187 };
188
189 $.roundedRect = function (ctx,x,y,width,height,radius,fillType){
190   ctx.beginPath();
191   ctx.moveTo(x,y+radius);
192   ctx.lineTo(x,y+height-radius);
193   ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
194   ctx.lineTo(x+width-radius,y+height);
195   ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
196   ctx.lineTo(x+width,y+radius);
197   ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
198   ctx.lineTo(x+radius,y);
199   ctx.quadraticCurveTo(x,y,x,y+radius);
200   if(fillType=="fill") {
201         ctx.fill();
202   } else {
203         ctx.stroke();
204         }
205 };
206
207 $.saveImageFile = function (id,jsonfilename,imageExt) {
208         var parts = jsonfilename.split("/");
209         var filename = parts[2].replace(".js","."+imageExt);
210         var oCanvas = document.getElementById(id+"-canvas");
211         
212         if(oCanvas) {
213                 if(imageExt == "jpg") {
214                         var strDataURI = oCanvas.toDataURL("image/jpeg"); 
215                 } else {
216                         var strDataURI = oCanvas.toDataURL("image/png");
217                 }
218                 var handleFailure = function(o){
219                         //alert('failed to write image' + filename);
220                         //remove alert since chrome triggers this function when user navigates away from page before image gets written.
221                 }       
222                 var handleSuccess = function(o){
223                 }                       
224                 var callback =
225                 {
226                   success:handleSuccess,
227                   failure:handleFailure,
228                   argument: { foo:'foo', bar:''}
229                 };
230                 var path = "index.php?action=DynamicAction&DynamicAction=saveImage&module=Charts&to_pdf=1";
231                 var postData = "imageStr=" + strDataURI + "&filename=" + filename;
232                 var request = YAHOO.util.Connect.asyncRequest('POST', path, callback, postData);
233         }
234 };
235
236 $.saveImageTest = function (id,jsonfilename,imageExt) {
237                 if(typeof FlashCanvas != "undefined") {
238                         setTimeout(function(){$.saveImageFile(id,jsonfilename,imageExt)},10000);
239                 } else {
240                         $.saveImageFile(id,jsonfilename,imageExt);
241                 }
242         };
243 /*
244   Method: extend
245   
246   Augment an object by appending another object's properties.
247   
248   Parameters:
249   
250   original - (object) The object to be extended.
251   extended - (object) An object which properties are going to be appended to the original object.
252   
253   Example:
254   (start code js)
255   $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
256   (end code)
257 */
258 $.extend = function(original, extended) {
259   for ( var key in (extended || {}))
260     original[key] = extended[key];
261   return original;
262 };
263
264 $.lambda = function(value) {
265   return (typeof value == 'function') ? value : function() {
266     return value;
267   };
268 };
269
270 $.time = Date.now || function() {
271   return +new Date;
272 };
273
274 /*
275   Method: splat
276   
277   Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
278   
279   Parameters:
280   
281   obj - (mixed) The object to be wrapped in an array.
282   
283   Example:
284   (start code js)
285   $jit.util.splat(3);   //[3]
286   $jit.util.splat([3]); //[3]
287   (end code)
288 */
289 $.splat = function(obj) {
290   var type = $.type(obj);
291   return type ? ((type != 'array') ? [ obj ] : obj) : [];
292 };
293
294 $.type = function(elem) {
295   var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
296   if(type != 'object') return type;
297   if(elem && elem.$$family) return elem.$$family;
298   return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
299 };
300 $.type.s = Object.prototype.toString;
301
302 /*
303   Method: each
304   
305   Iterates through an iterable applying *f*.
306   
307   Parameters:
308   
309   iterable - (array) The original array.
310   fn - (function) The function to apply to the array elements.
311   
312   Example:
313   (start code js)
314   $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
315   (end code)
316 */
317 $.each = function(iterable, fn) {
318   var type = $.type(iterable);
319   if (type == 'object') {
320     for ( var key in iterable)
321       fn(iterable[key], key);
322   } else {
323     for ( var i = 0, l = iterable.length; i < l; i++)
324       fn(iterable[i], i);
325   }
326 };
327
328 $.indexOf = function(array, item) {
329   if(Array.indexOf) return array.indexOf(item);
330   for(var i=0,l=array.length; i<l; i++) {
331     if(array[i] === item) return i;
332   }
333   return -1;
334 };
335
336 /*
337   Method: map
338   
339   Maps or collects an array by applying *f*.
340   
341   Parameters:
342   
343   array - (array) The original array.
344   f - (function) The function to apply to the array elements.
345   
346   Example:
347   (start code js)
348   $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
349   (end code)
350 */
351 $.map = function(array, f) {
352   var ans = [];
353   $.each(array, function(elem, i) {
354     ans.push(f(elem, i));
355   });
356   return ans;
357 };
358
359 /*
360   Method: reduce
361   
362   Iteratively applies the binary function *f* storing the result in an accumulator.
363   
364   Parameters:
365   
366   array - (array) The original array.
367   f - (function) The function to apply to the array elements.
368   opt - (optional|mixed) The starting value for the acumulator.
369   
370   Example:
371   (start code js)
372   $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
373   (end code)
374 */
375 $.reduce = function(array, f, opt) {
376   var l = array.length;
377   if(l==0) return opt;
378   var acum = arguments.length == 3? opt : array[--l];
379   while(l--) {
380     acum = f(acum, array[l]);
381   }
382   return acum;
383 };
384
385 /*
386   Method: merge
387   
388   Merges n-objects and their sub-objects creating a new, fresh object.
389   
390   Parameters:
391   
392   An arbitrary number of objects.
393   
394   Example:
395   (start code js)
396   $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
397   (end code)
398 */
399 $.merge = function() {
400   var mix = {};
401   for ( var i = 0, l = arguments.length; i < l; i++) {
402     var object = arguments[i];
403     if ($.type(object) != 'object')
404       continue;
405     for ( var key in object) {
406       var op = object[key], mp = mix[key];
407       mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
408           .merge(mp, op) : $.unlink(op);
409     }
410   }
411   return mix;
412 };
413
414 $.unlink = function(object) {
415   var unlinked;
416   switch ($.type(object)) {
417   case 'object':
418     unlinked = {};
419     for ( var p in object)
420       unlinked[p] = $.unlink(object[p]);
421     break;
422   case 'array':
423     unlinked = [];
424     for ( var i = 0, l = object.length; i < l; i++)
425       unlinked[i] = $.unlink(object[i]);
426     break;
427   default:
428     return object;
429   }
430   return unlinked;
431 };
432
433 $.zip = function() {
434   if(arguments.length === 0) return [];
435   for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
436     for(var i=0, row=[]; i<l; i++) {
437       row.push(arguments[i][j]);
438     }
439     ans.push(row);
440   }
441   return ans;
442 };
443
444 /*
445   Method: rgbToHex
446   
447   Converts an RGB array into a Hex string.
448   
449   Parameters:
450   
451   srcArray - (array) An array with R, G and B values
452   
453   Example:
454   (start code js)
455   $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
456   (end code)
457 */
458 $.rgbToHex = function(srcArray, array) {
459   if (srcArray.length < 3)
460     return null;
461   if (srcArray.length == 4 && srcArray[3] == 0 && !array)
462     return 'transparent';
463   var hex = [];
464   for ( var i = 0; i < 3; i++) {
465     var bit = (srcArray[i] - 0).toString(16);
466     hex.push(bit.length == 1 ? '0' + bit : bit);
467   }
468   return array ? hex : '#' + hex.join('');
469 };
470
471 /*
472   Method: hexToRgb
473   
474   Converts an Hex color string into an RGB array.
475   
476   Parameters:
477   
478   hex - (string) A color hex string.
479   
480   Example:
481   (start code js)
482   $jit.util.hexToRgb('#fff'); //[255, 255, 255]
483   (end code)
484 */
485 $.hexToRgb = function(hex) {
486   if (hex.length != 7) {
487     hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
488     hex.shift();
489     if (hex.length != 3)
490       return null;
491     var rgb = [];
492     for ( var i = 0; i < 3; i++) {
493       var value = hex[i];
494       if (value.length == 1)
495         value += value;
496       rgb.push(parseInt(value, 16));
497     }
498     return rgb;
499   } else {
500     hex = parseInt(hex.slice(1), 16);
501     return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
502   }
503 };
504
505 $.destroy = function(elem) {
506   $.clean(elem);
507   if (elem.parentNode)
508     elem.parentNode.removeChild(elem);
509   if (elem.clearAttributes)
510     elem.clearAttributes();
511 };
512
513 $.clean = function(elem) {
514   for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
515     $.destroy(ch[i]);
516   }
517 };
518
519 /*
520   Method: addEvent
521   
522   Cross-browser add event listener.
523   
524   Parameters:
525   
526   obj - (obj) The Element to attach the listener to.
527   type - (string) The listener type. For example 'click', or 'mousemove'.
528   fn - (function) The callback function to be used when the event is fired.
529   
530   Example:
531   (start code js)
532   $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
533   (end code)
534 */
535 $.addEvent = function(obj, type, fn) {
536   if (obj.addEventListener)
537     obj.addEventListener(type, fn, false);
538   else
539     obj.attachEvent('on' + type, fn);
540 };
541
542 $.addEvents = function(obj, typeObj) {
543   for(var type in typeObj) {
544     $.addEvent(obj, type, typeObj[type]);
545   }
546 };
547
548 $.hasClass = function(obj, klass) {
549   return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
550 };
551
552 $.addClass = function(obj, klass) {
553   if (!$.hasClass(obj, klass))
554     obj.className = (obj.className + " " + klass);
555 };
556
557 $.removeClass = function(obj, klass) {
558   obj.className = obj.className.replace(new RegExp(
559       '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
560 };
561
562 $.getPos = function(elem) {
563   var offset = getOffsets(elem);
564   var scroll = getScrolls(elem);
565   return {
566     x: offset.x - scroll.x,
567     y: offset.y - scroll.y
568   };
569
570   function getOffsets(elem) {
571     var position = {
572       x: 0,
573       y: 0
574     };
575     while (elem && !isBody(elem)) {
576       position.x += elem.offsetLeft;
577       position.y += elem.offsetTop;
578       try{elem=elem.offsetParent;}catch(e){elem=document.body;}
579     }
580     return position;
581   }
582
583   function getScrolls(elem) {
584     var position = {
585       x: 0,
586       y: 0
587     };
588     while (elem && !isBody(elem)) {
589       position.x += elem.scrollLeft;
590       position.y += elem.scrollTop;
591       elem = elem.parentNode;
592     }
593     return position;
594   }
595
596   function isBody(element) {
597     return (/^(?:body|html)$/i).test(element.tagName);
598   }
599 };
600
601 $.event = {
602   get: function(e, win) {
603     win = win || window;
604     return e || win.event;
605   },
606   getWheel: function(e) {
607     return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
608   },
609   isRightClick: function(e) {
610     return (e.which == 3 || e.button == 2);
611   },
612   getPos: function(e, win) {
613     // get mouse position
614     win = win || window;
615     e = e || win.event;
616     var doc = win.document;
617     doc = doc.documentElement || doc.body;
618     //TODO(nico): make touch event handling better
619     if(e.touches && e.touches.length) {
620       e = e.touches[0];
621     }
622     var page = {
623       x: e.pageX || (e.clientX + doc.scrollLeft),
624       y: e.pageY || (e.clientY + doc.scrollTop)
625     };
626     return page;
627   },
628   stop: function(e) {
629     if (e.stopPropagation) e.stopPropagation();
630     e.cancelBubble = true;
631     if (e.preventDefault) e.preventDefault();
632     else e.returnValue = false;
633   }
634 };
635
636 $jit.util = $jit.id = $;
637
638 var Class = function(properties) {
639   properties = properties || {};
640   var klass = function() {
641     for ( var key in this) {
642       if (typeof this[key] != 'function')
643         this[key] = $.unlink(this[key]);
644     }
645     this.constructor = klass;
646     if (Class.prototyping)
647       return this;
648     var instance = this.initialize ? this.initialize.apply(this, arguments)
649         : this;
650     //typize
651     this.$$family = 'class';
652     return instance;
653   };
654
655   for ( var mutator in Class.Mutators) {
656     if (!properties[mutator])
657       continue;
658     properties = Class.Mutators[mutator](properties, properties[mutator]);
659     delete properties[mutator];
660   }
661
662   $.extend(klass, this);
663   klass.constructor = Class;
664   klass.prototype = properties;
665   return klass;
666 };
667
668 Class.Mutators = {
669
670   Implements: function(self, klasses) {
671     $.each($.splat(klasses), function(klass) {
672       Class.prototyping = klass;
673       var instance = (typeof klass == 'function') ? new klass : klass;
674       for ( var prop in instance) {
675         if (!(prop in self)) {
676           self[prop] = instance[prop];
677         }
678       }
679       delete Class.prototyping;
680     });
681     return self;
682   }
683
684 };
685
686 $.extend(Class, {
687
688   inherit: function(object, properties) {
689     for ( var key in properties) {
690       var override = properties[key];
691       var previous = object[key];
692       var type = $.type(override);
693       if (previous && type == 'function') {
694         if (override != previous) {
695           Class.override(object, key, override);
696         }
697       } else if (type == 'object') {
698         object[key] = $.merge(previous, override);
699       } else {
700         object[key] = override;
701       }
702     }
703     return object;
704   },
705
706   override: function(object, name, method) {
707     var parent = Class.prototyping;
708     if (parent && object[name] != parent[name])
709       parent = null;
710     var override = function() {
711       var previous = this.parent;
712       this.parent = parent ? parent[name] : object[name];
713       var value = method.apply(this, arguments);
714       this.parent = previous;
715       return value;
716     };
717     object[name] = override;
718   }
719
720 });
721
722 Class.prototype.implement = function() {
723   var proto = this.prototype;
724   $.each(Array.prototype.slice.call(arguments || []), function(properties) {
725     Class.inherit(proto, properties);
726   });
727   return this;
728 };
729
730 $jit.Class = Class;
731
732 /*
733   Object: $jit.json
734   
735   Provides JSON utility functions.
736   
737   Most of these functions are JSON-tree traversal and manipulation functions.
738 */
739 $jit.json = {
740   /*
741      Method: prune
742   
743      Clears all tree nodes having depth greater than maxLevel.
744   
745      Parameters:
746   
747         tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
748         maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
749
750   */
751   prune: function(tree, maxLevel) {
752     this.each(tree, function(elem, i) {
753       if (i == maxLevel && elem.children) {
754         delete elem.children;
755         elem.children = [];
756       }
757     });
758   },
759   /*
760      Method: getParent
761   
762      Returns the parent node of the node having _id_ as id.
763   
764      Parameters:
765   
766         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
767         id - (string) The _id_ of the child node whose parent will be returned.
768
769     Returns:
770
771         A tree JSON node if any, or false otherwise.
772   
773   */
774   getParent: function(tree, id) {
775     if (tree.id == id)
776       return false;
777     var ch = tree.children;
778     if (ch && ch.length > 0) {
779       for ( var i = 0; i < ch.length; i++) {
780         if (ch[i].id == id)
781           return tree;
782         else {
783           var ans = this.getParent(ch[i], id);
784           if (ans)
785             return ans;
786         }
787       }
788     }
789     return false;
790   },
791   /*
792      Method: getSubtree
793   
794      Returns the subtree that matches the given id.
795   
796      Parameters:
797   
798         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
799         id - (string) A node *unique* identifier.
800   
801      Returns:
802   
803         A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
804
805   */
806   getSubtree: function(tree, id) {
807     if (tree.id == id)
808       return tree;
809     for ( var i = 0, ch = tree.children; i < ch.length; i++) {
810       var t = this.getSubtree(ch[i], id);
811       if (t != null)
812         return t;
813     }
814     return null;
815   },
816   /*
817      Method: eachLevel
818   
819       Iterates on tree nodes with relative depth less or equal than a specified level.
820   
821      Parameters:
822   
823         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
824         initLevel - (number) An integer specifying the initial relative level. Usually zero.
825         toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
826         action - (function) A function that receives a node and an integer specifying the actual level of the node.
827           
828     Example:
829    (start code js)
830      $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
831         alert(node.name + ' ' + depth);
832      });
833    (end code)
834   */
835   eachLevel: function(tree, initLevel, toLevel, action) {
836     if (initLevel <= toLevel) {
837       action(tree, initLevel);
838       if(!tree.children) return;
839       for ( var i = 0, ch = tree.children; i < ch.length; i++) {
840         this.eachLevel(ch[i], initLevel + 1, toLevel, action);
841       }
842     }
843   },
844   /*
845      Method: each
846   
847       A JSON tree iterator.
848   
849      Parameters:
850   
851         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
852         action - (function) A function that receives a node.
853
854     Example:
855     (start code js)
856       $jit.json.each(tree, function(node) {
857         alert(node.name);
858       });
859     (end code)
860           
861   */
862   each: function(tree, action) {
863     this.eachLevel(tree, 0, Number.MAX_VALUE, action);
864   }
865 };
866
867
868 /*
869      An object containing multiple type of transformations. 
870 */
871
872 $jit.Trans = {
873   $extend: true,
874   
875   linear: function(p){
876     return p;
877   }
878 };
879
880 var Trans = $jit.Trans;
881
882 (function(){
883
884   var makeTrans = function(transition, params){
885     params = $.splat(params);
886     return $.extend(transition, {
887       easeIn: function(pos){
888         return transition(pos, params);
889       },
890       easeOut: function(pos){
891         return 1 - transition(1 - pos, params);
892       },
893       easeInOut: function(pos){
894         return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
895             2 * (1 - pos), params)) / 2;
896       }
897     });
898   };
899
900   var transitions = {
901
902     Pow: function(p, x){
903       return Math.pow(p, x[0] || 6);
904     },
905
906     Expo: function(p){
907       return Math.pow(2, 8 * (p - 1));
908     },
909
910     Circ: function(p){
911       return 1 - Math.sin(Math.acos(p));
912     },
913
914     Sine: function(p){
915       return 1 - Math.sin((1 - p) * Math.PI / 2);
916     },
917
918     Back: function(p, x){
919       x = x[0] || 1.618;
920       return Math.pow(p, 2) * ((x + 1) * p - x);
921     },
922
923     Bounce: function(p){
924       var value;
925       for ( var a = 0, b = 1; 1; a += b, b /= 2) {
926         if (p >= (7 - 4 * a) / 11) {
927           value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
928           break;
929         }
930       }
931       return value;
932     },
933
934     Elastic: function(p, x){
935       return Math.pow(2, 10 * --p)
936           * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
937     }
938
939   };
940
941   $.each(transitions, function(val, key){
942     Trans[key] = makeTrans(val);
943   });
944
945   $.each( [
946       'Quad', 'Cubic', 'Quart', 'Quint'
947   ], function(elem, i){
948     Trans[elem] = makeTrans(function(p){
949       return Math.pow(p, [
950         i + 2
951       ]);
952     });
953   });
954
955 })();
956
957 /*
958    A Class that can perform animations for generic objects.
959
960    If you are looking for animation transitions please take a look at the <Trans> object.
961
962    Used by:
963
964    <Graph.Plot>
965    
966    Based on:
967    
968    The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
969
970 */
971
972 var Animation = new Class( {
973
974   initialize: function(options){
975     this.setOptions(options);
976   },
977
978   setOptions: function(options){
979     var opt = {
980       duration: 2500,
981       fps: 40,
982       transition: Trans.Quart.easeInOut,
983       compute: $.empty,
984       complete: $.empty,
985       link: 'ignore'
986     };
987     this.opt = $.merge(opt, options || {});
988     return this;
989   },
990
991   step: function(){
992     var time = $.time(), opt = this.opt;
993     if (time < this.time + opt.duration) {
994       var delta = opt.transition((time - this.time) / opt.duration);
995       opt.compute(delta);
996     } else {
997       this.timer = clearInterval(this.timer);
998       opt.compute(1);
999       opt.complete();
1000     }
1001   },
1002
1003   start: function(){
1004     if (!this.check())
1005       return this;
1006     this.time = 0;
1007     this.startTimer();
1008     return this;
1009   },
1010
1011   startTimer: function(){
1012     var that = this, fps = this.opt.fps;
1013     if (this.timer)
1014       return false;
1015     this.time = $.time() - this.time;
1016     this.timer = setInterval((function(){
1017       that.step();
1018     }), Math.round(1000 / fps));
1019     return true;
1020   },
1021
1022   pause: function(){
1023     this.stopTimer();
1024     return this;
1025   },
1026
1027   resume: function(){
1028     this.startTimer();
1029     return this;
1030   },
1031
1032   stopTimer: function(){
1033     if (!this.timer)
1034       return false;
1035     this.time = $.time() - this.time;
1036     this.timer = clearInterval(this.timer);
1037     return true;
1038   },
1039
1040   check: function(){
1041     if (!this.timer)
1042       return true;
1043     if (this.opt.link == 'cancel') {
1044       this.stopTimer();
1045       return true;
1046     }
1047     return false;
1048   }
1049 });
1050
1051
1052 var Options = function() {
1053   var args = arguments;
1054   for(var i=0, l=args.length, ans={}; i<l; i++) {
1055     var opt = Options[args[i]];
1056     if(opt.$extend) {
1057       $.extend(ans, opt);
1058     } else {
1059       ans[args[i]] = opt;  
1060     }
1061   }
1062   return ans;
1063 };
1064
1065 /*
1066  * File: Options.AreaChart.js
1067  *
1068 */
1069
1070 /*
1071   Object: Options.AreaChart
1072   
1073   <AreaChart> options. 
1074   Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1075   
1076   Syntax:
1077   
1078   (start code js)
1079
1080   Options.AreaChart = {
1081     animate: true,
1082     labelOffset: 3,
1083     type: 'stacked',
1084     selectOnHover: true,
1085     showAggregates: true,
1086     showLabels: true,
1087     filterOnClick: false,
1088     restoreOnRightClick: false
1089   };
1090   
1091   (end code)
1092   
1093   Example:
1094   
1095   (start code js)
1096
1097   var areaChart = new $jit.AreaChart({
1098     animate: true,
1099     type: 'stacked:gradient',
1100     selectOnHover: true,
1101     filterOnClick: true,
1102     restoreOnRightClick: true
1103   });
1104   
1105   (end code)
1106
1107   Parameters:
1108   
1109   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1110   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1111   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1112   selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1113   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1114   showLabels - (boolean) Default's *true*. Display the name of the slots.
1115   filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1116   restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1117   
1118 */
1119   
1120 Options.AreaChart = {
1121   $extend: true,
1122
1123   animate: true,
1124   labelOffset: 3, // label offset
1125   type: 'stacked', // gradient
1126   Tips: {
1127     enable: false,
1128     onShow: $.empty,
1129     onHide: $.empty
1130   },
1131   Events: {
1132     enable: false,
1133     onClick: $.empty
1134   },
1135   selectOnHover: true,
1136   showAggregates: true,
1137   showLabels: true,
1138   filterOnClick: false,
1139   restoreOnRightClick: false
1140 };
1141
1142 /*
1143  * File: Options.Margin.js
1144  *
1145 */
1146
1147 /*
1148   Object: Options.Margin
1149   
1150   Canvas drawing margins. 
1151   
1152   Syntax:
1153   
1154   (start code js)
1155
1156   Options.Margin = {
1157     top: 0,
1158     left: 0,
1159     right: 0,
1160     bottom: 0
1161   };
1162   
1163   (end code)
1164   
1165   Example:
1166   
1167   (start code js)
1168
1169   var viz = new $jit.Viz({
1170     Margin: {
1171       right: 10,
1172       bottom: 20
1173     }
1174   });
1175   
1176   (end code)
1177
1178   Parameters:
1179   
1180   top - (number) Default's *0*. Top margin.
1181   left - (number) Default's *0*. Left margin.
1182   right - (number) Default's *0*. Right margin.
1183   bottom - (number) Default's *0*. Bottom margin.
1184   
1185 */
1186
1187 Options.Margin = {
1188   $extend: false,
1189   
1190   top: 0,
1191   left: 0,
1192   right: 0,
1193   bottom: 0
1194 };
1195
1196 /*
1197  * File: Options.Canvas.js
1198  *
1199 */
1200
1201 /*
1202   Object: Options.Canvas
1203   
1204   These are Canvas general options, like where to append it in the DOM, its dimensions, background, 
1205   and other more advanced options.
1206   
1207   Syntax:
1208   
1209   (start code js)
1210
1211   Options.Canvas = {
1212     injectInto: 'id',
1213     width: false,
1214     height: false,
1215     useCanvas: false,
1216     withLabels: true,
1217     background: false
1218   };  
1219   (end code)
1220   
1221   Example:
1222   
1223   (start code js)
1224   var viz = new $jit.Viz({
1225     injectInto: 'someContainerId',
1226     width: 500,
1227     height: 700
1228   });
1229   (end code)
1230   
1231   Parameters:
1232   
1233   injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
1234   width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1235   height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1236   useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1237   withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1238   background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1239 */
1240
1241 Options.Canvas = {
1242     $extend: true,
1243     
1244     injectInto: 'id',
1245     width: false,
1246     height: false,
1247     useCanvas: false,
1248     withLabels: true,
1249     background: false,
1250     colorStop1: 'rgba(255,255,255,1)',
1251     colorStop2: 'rgba(255,255,255,0)'
1252 };
1253
1254 /*
1255  * File: Options.Tree.js
1256  *
1257 */
1258
1259 /*
1260   Object: Options.Tree
1261   
1262   Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1263   
1264   Syntax:
1265   
1266   (start code js)
1267   Options.Tree = {
1268     orientation: "left",
1269     subtreeOffset: 8,
1270     siblingOffset: 5,
1271     indent:10,
1272     multitree: false,
1273     align:"center"
1274   };
1275   (end code)
1276   
1277   Example:
1278   
1279   (start code js)
1280   var st = new $jit.ST({
1281     orientation: 'left',
1282     subtreeOffset: 1,
1283     siblingOFfset: 5,
1284     multitree: true
1285   });
1286   (end code)
1287
1288   Parameters:
1289     
1290   subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1291   siblingOffset - (number) Default's 5. Separation offset between siblings.
1292   orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1293   align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1294   indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1295   multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1296      
1297 */
1298 Options.Tree = {
1299     $extend: true,
1300     
1301     orientation: "left",
1302     subtreeOffset: 8,
1303     siblingOffset: 5,
1304     indent:10,
1305     multitree: false,
1306     align:"center"
1307 };
1308
1309
1310 /*
1311  * File: Options.Node.js
1312  *
1313 */
1314
1315 /*
1316   Object: Options.Node
1317
1318   Provides Node rendering options for Tree and Graph based visualizations.
1319
1320   Syntax:
1321     
1322   (start code js)
1323   Options.Node = {
1324     overridable: false,
1325     type: 'circle',
1326     color: '#ccb',
1327     alpha: 1,
1328     dim: 3,
1329     height: 20,
1330     width: 90,
1331     autoHeight: false,
1332     autoWidth: false,
1333     lineWidth: 1,
1334     transform: true,
1335     align: "center",
1336     angularWidth:1,
1337     span:1,
1338     CanvasStyles: {}
1339   };
1340   (end code)
1341   
1342   Example:
1343   
1344   (start code js)
1345   var viz = new $jit.Viz({
1346     Node: {
1347       overridable: true,
1348       width: 30,
1349       autoHeight: true,
1350       type: 'rectangle'
1351     }
1352   });
1353   (end code)
1354   
1355   Parameters:
1356
1357   overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1358   type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
1359   color - (string) Default's *#ccb*. Node color.
1360   alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1361   dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1362   height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1363   width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1364   autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1365   autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1366   lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1367   transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1368   align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
1369   angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1370   span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1371   CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
1372
1373 */
1374 Options.Node = {
1375   $extend: false,
1376   
1377   overridable: false,
1378   type: 'circle',
1379   color: '#ccb',
1380   alpha: 1,
1381   dim: 3,
1382   height: 20,
1383   width: 90,
1384   autoHeight: false,
1385   autoWidth: false,
1386   lineWidth: 1,
1387   transform: true,
1388   align: "center",
1389   angularWidth:1,
1390   span:1,
1391   //Raw canvas styles to be
1392   //applied to the context instance
1393   //before plotting a node
1394   CanvasStyles: {}
1395 };
1396
1397
1398 /*
1399  * File: Options.Edge.js
1400  *
1401 */
1402
1403 /*
1404   Object: Options.Edge
1405
1406   Provides Edge rendering options for Tree and Graph based visualizations.
1407
1408   Syntax:
1409     
1410   (start code js)
1411   Options.Edge = {
1412     overridable: false,
1413     type: 'line',
1414     color: '#ccb',
1415     lineWidth: 1,
1416     dim:15,
1417     alpha: 1,
1418     CanvasStyles: {}
1419   };
1420   (end code)
1421   
1422   Example:
1423   
1424   (start code js)
1425   var viz = new $jit.Viz({
1426     Edge: {
1427       overridable: true,
1428       type: 'line',
1429       color: '#fff',
1430       CanvasStyles: {
1431         shadowColor: '#ccc',
1432         shadowBlur: 10
1433       }
1434     }
1435   });
1436   (end code)
1437   
1438   Parameters:
1439     
1440    overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1441    type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
1442    color - (string) Default's '#ccb'. Edge color.
1443    lineWidth - (number) Default's *1*. Line/Edge width.
1444    alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1445    dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
1446    epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
1447    CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
1448
1449   See also:
1450    
1451    If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
1452 */
1453 Options.Edge = {
1454   $extend: false,
1455   
1456   overridable: false,
1457   type: 'line',
1458   color: '#ccb',
1459   lineWidth: 1,
1460   dim:15,
1461   alpha: 1,
1462   epsilon: 7,
1463
1464   //Raw canvas styles to be
1465   //applied to the context instance
1466   //before plotting an edge
1467   CanvasStyles: {}
1468 };
1469
1470
1471 /*
1472  * File: Options.Fx.js
1473  *
1474 */
1475
1476 /*
1477   Object: Options.Fx
1478
1479   Provides animation options like duration of the animations, frames per second and animation transitions.  
1480
1481   Syntax:
1482   
1483   (start code js)
1484     Options.Fx = {
1485       fps:40,
1486       duration: 2500,
1487       transition: $jit.Trans.Quart.easeInOut,
1488       clearCanvas: true
1489     };
1490   (end code)
1491   
1492   Example:
1493   
1494   (start code js)
1495   var viz = new $jit.Viz({
1496     duration: 1000,
1497     fps: 35,
1498     transition: $jit.Trans.linear
1499   });
1500   (end code)
1501   
1502   Parameters:
1503   
1504   clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1505   duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1506   fps - (number) Default's *40*. Frames per second.
1507   transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1508   
1509   Object: $jit.Trans
1510   
1511   This object is used for specifying different animation transitions in all visualizations.
1512
1513   There are many different type of animation transitions.
1514
1515   linear:
1516
1517   Displays a linear transition
1518
1519   >Trans.linear
1520   
1521   (see Linear.png)
1522
1523   Quad:
1524
1525   Displays a Quadratic transition.
1526
1527   >Trans.Quad.easeIn
1528   >Trans.Quad.easeOut
1529   >Trans.Quad.easeInOut
1530   
1531  (see Quad.png)
1532
1533  Cubic:
1534
1535  Displays a Cubic transition.
1536
1537  >Trans.Cubic.easeIn
1538  >Trans.Cubic.easeOut
1539  >Trans.Cubic.easeInOut
1540
1541  (see Cubic.png)
1542
1543  Quart:
1544
1545  Displays a Quartetic transition.
1546
1547  >Trans.Quart.easeIn
1548  >Trans.Quart.easeOut
1549  >Trans.Quart.easeInOut
1550
1551  (see Quart.png)
1552
1553  Quint:
1554
1555  Displays a Quintic transition.
1556
1557  >Trans.Quint.easeIn
1558  >Trans.Quint.easeOut
1559  >Trans.Quint.easeInOut
1560
1561  (see Quint.png)
1562
1563  Expo:
1564
1565  Displays an Exponential transition.
1566
1567  >Trans.Expo.easeIn
1568  >Trans.Expo.easeOut
1569  >Trans.Expo.easeInOut
1570
1571  (see Expo.png)
1572
1573  Circ:
1574
1575  Displays a Circular transition.
1576
1577  >Trans.Circ.easeIn
1578  >Trans.Circ.easeOut
1579  >Trans.Circ.easeInOut
1580
1581  (see Circ.png)
1582
1583  Sine:
1584
1585  Displays a Sineousidal transition.
1586
1587  >Trans.Sine.easeIn
1588  >Trans.Sine.easeOut
1589  >Trans.Sine.easeInOut
1590
1591  (see Sine.png)
1592
1593  Back:
1594
1595  >Trans.Back.easeIn
1596  >Trans.Back.easeOut
1597  >Trans.Back.easeInOut
1598
1599  (see Back.png)
1600
1601  Bounce:
1602
1603  Bouncy transition.
1604
1605  >Trans.Bounce.easeIn
1606  >Trans.Bounce.easeOut
1607  >Trans.Bounce.easeInOut
1608
1609  (see Bounce.png)
1610
1611  Elastic:
1612
1613  Elastic curve.
1614
1615  >Trans.Elastic.easeIn
1616  >Trans.Elastic.easeOut
1617  >Trans.Elastic.easeInOut
1618
1619  (see Elastic.png)
1620  
1621  Based on:
1622      
1623  Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
1624
1625
1626 */
1627 Options.Fx = {
1628   $extend: true,
1629   
1630   fps:40,
1631   duration: 2500,
1632   transition: $jit.Trans.Quart.easeInOut,
1633   clearCanvas: true
1634 };
1635
1636 /*
1637  * File: Options.Label.js
1638  *
1639 */
1640 /*
1641   Object: Options.Label
1642
1643   Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.  
1644
1645   Syntax:
1646   
1647   (start code js)
1648     Options.Label = {
1649       overridable: false,
1650       type: 'HTML', //'SVG', 'Native'
1651       style: ' ',
1652       size: 10,
1653       family: 'sans-serif',
1654       textAlign: 'center',
1655       textBaseline: 'alphabetic',
1656       color: '#fff'
1657     };
1658   (end code)
1659   
1660   Example:
1661   
1662   (start code js)
1663   var viz = new $jit.Viz({
1664     Label: {
1665       type: 'Native',
1666       size: 11,
1667       color: '#ccc'
1668     }
1669   });
1670   (end code)
1671   
1672   Parameters:
1673     
1674   overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1675   type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1676   style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1677   size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1678   family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1679   color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1680 */
1681 Options.Label = {
1682   $extend: false,
1683   
1684   overridable: false,
1685   type: 'HTML', //'SVG', 'Native'
1686   style: ' ',
1687   size: 10,
1688   family: 'sans-serif',
1689   textAlign: 'center',
1690   textBaseline: 'alphabetic',
1691   color: '#fff'
1692 };
1693
1694
1695 /*
1696  * File: Options.Tips.js
1697  *
1698  */
1699
1700 /*
1701   Object: Options.Tips
1702   
1703   Tips options
1704   
1705   Syntax:
1706     
1707   (start code js)
1708   Options.Tips = {
1709     enable: false,
1710     type: 'auto',
1711     offsetX: 20,
1712     offsetY: 20,
1713     onShow: $.empty,
1714     onHide: $.empty
1715   };
1716   (end code)
1717   
1718   Example:
1719   
1720   (start code js)
1721   var viz = new $jit.Viz({
1722     Tips: {
1723       enable: true,
1724       type: 'Native',
1725       offsetX: 10,
1726       offsetY: 10,
1727       onShow: function(tip, node) {
1728         tip.innerHTML = node.name;
1729       }
1730     }
1731   });
1732   (end code)
1733
1734   Parameters:
1735
1736   enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class. 
1737   type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
1738   offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
1739   offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
1740   onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
1741   onHide() - This callack is used when hiding a tooltip.
1742
1743 */
1744 Options.Tips = {
1745   $extend: false,
1746   
1747   enable: false,
1748   type: 'auto',
1749   offsetX: 20,
1750   offsetY: 20,
1751   force: false,
1752   onShow: $.empty,
1753   onHide: $.empty
1754 };
1755
1756
1757 /*
1758  * File: Options.NodeStyles.js
1759  *
1760  */
1761
1762 /*
1763   Object: Options.NodeStyles
1764   
1765   Apply different styles when a node is hovered or selected.
1766   
1767   Syntax:
1768     
1769   (start code js)
1770   Options.NodeStyles = {
1771     enable: false,
1772     type: 'auto',
1773     stylesHover: false,
1774     stylesClick: false
1775   };
1776   (end code)
1777   
1778   Example:
1779   
1780   (start code js)
1781   var viz = new $jit.Viz({
1782     NodeStyles: {
1783       enable: true,
1784       type: 'Native',
1785       stylesHover: {
1786         dim: 30,
1787         color: '#fcc'
1788       },
1789       duration: 600
1790     }
1791   });
1792   (end code)
1793
1794   Parameters:
1795   
1796   enable - (boolean) Default's *false*. Whether to enable this option.
1797   type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
1798   stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799   stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1800 */
1801
1802 Options.NodeStyles = {
1803   $extend: false,
1804   
1805   enable: false,
1806   type: 'auto',
1807   stylesHover: false,
1808   stylesClick: false
1809 };
1810
1811
1812 /*
1813  * File: Options.Events.js
1814  *
1815 */
1816
1817 /*
1818   Object: Options.Events
1819   
1820   Configuration for adding mouse/touch event handlers to Nodes.
1821   
1822   Syntax:
1823   
1824   (start code js)
1825   Options.Events = {
1826     enable: false,
1827     enableForEdges: false,
1828     type: 'auto',
1829     onClick: $.empty,
1830     onRightClick: $.empty,
1831     onMouseMove: $.empty,
1832     onMouseEnter: $.empty,
1833     onMouseLeave: $.empty,
1834     onDragStart: $.empty,
1835     onDragMove: $.empty,
1836     onDragCancel: $.empty,
1837     onDragEnd: $.empty,
1838     onTouchStart: $.empty,
1839     onTouchMove: $.empty,
1840     onTouchEnd: $.empty,
1841     onTouchCancel: $.empty,
1842     onMouseWheel: $.empty
1843   };
1844   (end code)
1845   
1846   Example:
1847   
1848   (start code js)
1849   var viz = new $jit.Viz({
1850     Events: {
1851       enable: true,
1852       onClick: function(node, eventInfo, e) {
1853         viz.doSomething();
1854       },
1855       onMouseEnter: function(node, eventInfo, e) {
1856         viz.canvas.getElement().style.cursor = 'pointer';
1857       },
1858       onMouseLeave: function(node, eventInfo, e) {
1859         viz.canvas.getElement().style.cursor = '';
1860       }
1861     }
1862   });
1863   (end code)
1864   
1865   Parameters:
1866   
1867   enable - (boolean) Default's *false*. Whether to enable the Event system.
1868   enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
1869   type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
1870   onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1871   onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1872   onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner).  *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1873   onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1874   onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1875   onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1876   onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1877   onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1878   onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1879   onTouchStart(node, eventInfo, e) - Behaves just like onDragStart. 
1880   onTouchMove(node, eventInfo, e) - Behaves just like onDragMove. 
1881   onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd. 
1882   onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1883   onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
1884 */
1885
1886 Options.Events = {
1887   $extend: false,
1888   
1889   enable: false,
1890   enableForEdges: false,
1891   type: 'auto',
1892   onClick: $.empty,
1893   onRightClick: $.empty,
1894   onMouseMove: $.empty,
1895   onMouseEnter: $.empty,
1896   onMouseLeave: $.empty,
1897   onDragStart: $.empty,
1898   onDragMove: $.empty,
1899   onDragCancel: $.empty,
1900   onDragEnd: $.empty,
1901   onTouchStart: $.empty,
1902   onTouchMove: $.empty,
1903   onTouchEnd: $.empty,
1904   onMouseWheel: $.empty
1905 };
1906
1907 /*
1908  * File: Options.Navigation.js
1909  *
1910 */
1911
1912 /*
1913   Object: Options.Navigation
1914   
1915   Panning and zooming options for Graph/Tree based visualizations. These options are implemented 
1916   by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1917   
1918   Syntax:
1919   
1920   (start code js)
1921
1922   Options.Navigation = {
1923     enable: false,
1924     type: 'auto',
1925     panning: false, //true, 'avoid nodes'
1926     zooming: false
1927   };
1928   
1929   (end code)
1930   
1931   Example:
1932     
1933   (start code js)
1934   var viz = new $jit.Viz({
1935     Navigation: {
1936       enable: true,
1937       panning: 'avoid nodes',
1938       zooming: 20
1939     }
1940   });
1941   (end code)
1942   
1943   Parameters:
1944   
1945   enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1946   panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
1947   zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
1948   
1949 */
1950
1951 Options.Navigation = {
1952   $extend: false,
1953   
1954   enable: false,
1955   type: 'auto',
1956   panning: false, //true | 'avoid nodes'
1957   zooming: false
1958 };
1959
1960 /*
1961  * File: Options.Controller.js
1962  *
1963 */
1964
1965 /*
1966   Object: Options.Controller
1967   
1968   Provides controller methods. Controller methods are callback functions that get called at different stages 
1969   of the animation, computing or plotting of the visualization.
1970   
1971   Implemented by:
1972     
1973   All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1974   
1975   Syntax:
1976   
1977   (start code js)
1978
1979   Options.Controller = {
1980     onBeforeCompute: $.empty,
1981     onAfterCompute:  $.empty,
1982     onCreateLabel:   $.empty,
1983     onPlaceLabel:    $.empty,
1984     onComplete:      $.empty,
1985     onBeforePlotLine:$.empty,
1986     onAfterPlotLine: $.empty,
1987     onBeforePlotNode:$.empty,
1988     onAfterPlotNode: $.empty,
1989     request:         false
1990   };
1991   
1992   (end code)
1993   
1994   Example:
1995     
1996   (start code js)
1997   var viz = new $jit.Viz({
1998     onBeforePlotNode: function(node) {
1999       if(node.selected) {
2000         node.setData('color', '#ffc');
2001       } else {
2002         node.removeData('color');
2003       }
2004     },
2005     onBeforePlotLine: function(adj) {
2006       if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2007         adj.setData('color', '#ffc');
2008       } else {
2009         adj.removeData('color');
2010       }
2011     },
2012     onAfterCompute: function() {
2013       alert("computed!");
2014     }
2015   });
2016   (end code)
2017   
2018   Parameters:
2019
2020    onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2021    onAfterCompute() - This method is triggered after all animations or computations ended.
2022    onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
2023    onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
2024    onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
2025    onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2026    onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
2027    onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2028
2029     *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2030     
2031     request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.  
2032  
2033  */
2034 Options.Controller = {
2035   $extend: true,
2036   
2037   onBeforeCompute: $.empty,
2038   onAfterCompute:  $.empty,
2039   onCreateLabel:   $.empty,
2040   onPlaceLabel:    $.empty,
2041   onComplete:      $.empty,
2042   onBeforePlotLine:$.empty,
2043   onAfterPlotLine: $.empty,
2044   onBeforePlotNode:$.empty,
2045   onAfterPlotNode: $.empty,
2046   request:         false
2047 };
2048
2049
2050 /*
2051  * File: Extras.js
2052  * 
2053  * Provides Extras such as Tips and Style Effects.
2054  * 
2055  * Description:
2056  * 
2057  * Provides the <Tips> and <NodeStyles> classes and functions.
2058  *
2059  */
2060
2061 /*
2062  * Manager for mouse events (clicking and mouse moving).
2063  * 
2064  * This class is used for registering objects implementing onClick
2065  * and onMousemove methods. These methods are called when clicking or
2066  * moving the mouse around  the Canvas.
2067  * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2068  * 
2069  */
2070 var ExtrasInitializer = {
2071   initialize: function(className, viz) {
2072     this.viz = viz;
2073     this.canvas = viz.canvas;
2074     this.config = viz.config[className];
2075     this.nodeTypes = viz.fx.nodeTypes;
2076     var type = this.config.type;
2077     this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2078     this.labelContainer = this.dom && viz.labels.getLabelContainer();
2079     this.isEnabled() && this.initializePost();
2080   },
2081   initializePost: $.empty,
2082   setAsProperty: $.lambda(false),
2083   isEnabled: function() {
2084     return this.config.enable;
2085   },
2086   isLabel: function(e, win) {
2087     e = $.event.get(e, win);
2088     var labelContainer = this.labelContainer,
2089         target = e.target || e.srcElement;
2090     if(target && target.parentNode == labelContainer)
2091       return target;
2092     return false;
2093   }
2094 };
2095
2096 var EventsInterface = {
2097   onMouseUp: $.empty,
2098   onMouseDown: $.empty,
2099   onMouseMove: $.empty,
2100   onMouseOver: $.empty,
2101   onMouseOut: $.empty,
2102   onMouseWheel: $.empty,
2103   onTouchStart: $.empty,
2104   onTouchMove: $.empty,
2105   onTouchEnd: $.empty,
2106   onTouchCancel: $.empty
2107 };
2108
2109 var MouseEventsManager = new Class({
2110   initialize: function(viz) {
2111     this.viz = viz;
2112     this.canvas = viz.canvas;
2113     this.node = false;
2114     this.edge = false;
2115     this.registeredObjects = [];
2116     this.attachEvents();
2117   },
2118   
2119   attachEvents: function() {
2120     var htmlCanvas = this.canvas.getElement(), 
2121         that = this;
2122     htmlCanvas.oncontextmenu = $.lambda(false);
2123     $.addEvents(htmlCanvas, {
2124       'mouseup': function(e, win) {
2125         var event = $.event.get(e, win);
2126         that.handleEvent('MouseUp', e, win, 
2127             that.makeEventObject(e, win), 
2128             $.event.isRightClick(event));
2129       },
2130       'mousedown': function(e, win) {
2131         var event = $.event.get(e, win);
2132         that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win), 
2133             $.event.isRightClick(event));
2134       },
2135       'mousemove': function(e, win) {
2136         that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2137       },
2138       'mouseover': function(e, win) {
2139         that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2140       },
2141       'mouseout': function(e, win) {
2142         that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2143       },
2144       'touchstart': function(e, win) {
2145         that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2146       },
2147       'touchmove': function(e, win) {
2148         that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2149       },
2150       'touchend': function(e, win) {
2151         that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2152       }
2153     });
2154     //attach mousewheel event
2155     var handleMouseWheel = function(e, win) {
2156       var event = $.event.get(e, win);
2157       var wheel = $.event.getWheel(event);
2158       that.handleEvent('MouseWheel', e, win, wheel);
2159     };
2160     //TODO(nico): this is a horrible check for non-gecko browsers!
2161     if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2162       $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2163     } else {
2164       htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2165     }
2166   },
2167   
2168   register: function(obj) {
2169     this.registeredObjects.push(obj);
2170   },
2171   
2172   handleEvent: function() {
2173     var args = Array.prototype.slice.call(arguments),
2174         type = args.shift();
2175     for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2176       regs[i]['on' + type].apply(regs[i], args);
2177     }
2178   },
2179   
2180   makeEventObject: function(e, win) {
2181     var that = this,
2182         graph = this.viz.graph,
2183         fx = this.viz.fx,
2184         ntypes = fx.nodeTypes,
2185         etypes = fx.edgeTypes;
2186     return {
2187       pos: false,
2188       node: false,
2189       edge: false,
2190       contains: false,
2191       getNodeCalled: false,
2192       getEdgeCalled: false,
2193       getPos: function() {
2194         //TODO(nico): check why this can't be cache anymore when using edge detection
2195         //if(this.pos) return this.pos;
2196         var canvas = that.viz.canvas,
2197             s = canvas.getSize(),
2198             p = canvas.getPos(),
2199             ox = canvas.translateOffsetX,
2200             oy = canvas.translateOffsetY,
2201             sx = canvas.scaleOffsetX,
2202             sy = canvas.scaleOffsetY,
2203             pos = $.event.getPos(e, win);
2204         this.pos = {
2205           x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2206           y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2207         };
2208         return this.pos;
2209       },
2210       getNode: function() {
2211         if(this.getNodeCalled) return this.node;
2212         this.getNodeCalled = true;
2213         for(var id in graph.nodes) {
2214           var n = graph.nodes[id],
2215               geom = n && ntypes[n.getData('type')],
2216               contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2217           if(contains) {
2218             this.contains = contains;
2219             return that.node = this.node = n;
2220           }
2221         }
2222         return that.node = this.node = false;
2223       },
2224       getEdge: function() {
2225         if(this.getEdgeCalled) return this.edge;
2226         this.getEdgeCalled = true;
2227         var hashset = {};
2228         for(var id in graph.edges) {
2229           var edgeFrom = graph.edges[id];
2230           hashset[id] = true;
2231           for(var edgeId in edgeFrom) {
2232             if(edgeId in hashset) continue;
2233             var e = edgeFrom[edgeId],
2234                 geom = e && etypes[e.getData('type')],
2235                 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2236             if(contains) {
2237               this.contains = contains;
2238               return that.edge = this.edge = e;
2239             }
2240           }
2241         }
2242         return that.edge = this.edge = false;
2243       },
2244       getContains: function() {
2245         if(this.getNodeCalled) return this.contains;
2246         this.getNode();
2247         return this.contains;
2248       }
2249     };
2250   }
2251 });
2252
2253 /* 
2254  * Provides the initialization function for <NodeStyles> and <Tips> implemented 
2255  * by all main visualizations.
2256  *
2257  */
2258 var Extras = {
2259   initializeExtras: function() {
2260     var mem = new MouseEventsManager(this), that = this;
2261     $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2262       var obj = new Extras.Classes[k](k, that);
2263       if(obj.isEnabled()) {
2264         mem.register(obj);
2265       }
2266       if(obj.setAsProperty()) {
2267         that[k.toLowerCase()] = obj;
2268       }
2269     });
2270   }   
2271 };
2272
2273 Extras.Classes = {};
2274 /*
2275   Class: Events
2276    
2277   This class defines an Event API to be accessed by the user.
2278   The methods implemented are the ones defined in the <Options.Events> object.
2279 */
2280
2281 Extras.Classes.Events = new Class({
2282   Implements: [ExtrasInitializer, EventsInterface],
2283   
2284   initializePost: function() {
2285     this.fx = this.viz.fx;
2286     this.ntypes = this.viz.fx.nodeTypes;
2287     this.etypes = this.viz.fx.edgeTypes;
2288     
2289     this.hovered = false;
2290     this.pressed = false;
2291     this.touched = false;
2292
2293     this.touchMoved = false;
2294     this.moved = false;
2295     
2296   },
2297   
2298   setAsProperty: $.lambda(true),
2299   
2300   onMouseUp: function(e, win, event, isRightClick) {
2301     var evt = $.event.get(e, win);
2302     if(!this.moved) {
2303       if(isRightClick) {
2304         this.config.onRightClick(this.hovered, event, evt);
2305       } else {
2306         this.config.onClick(this.pressed, event, evt);
2307       }
2308     }
2309     if(this.pressed) {
2310       if(this.moved) {
2311         this.config.onDragEnd(this.pressed, event, evt);
2312       } else {
2313         this.config.onDragCancel(this.pressed, event, evt);
2314       }
2315       this.pressed = this.moved = false;
2316     }
2317   },
2318
2319   onMouseOut: function(e, win, event) {
2320    //mouseout a label
2321    var evt = $.event.get(e, win), label;
2322    if(this.dom && (label = this.isLabel(e, win))) {
2323      this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2324                               event, evt);
2325      this.hovered = false;
2326      return;
2327    }
2328    //mouseout canvas
2329    var rt = evt.relatedTarget,
2330        canvasWidget = this.canvas.getElement();
2331    while(rt && rt.parentNode) {
2332      if(canvasWidget == rt.parentNode) return;
2333      rt = rt.parentNode;
2334    }
2335    if(this.hovered) {
2336      this.config.onMouseLeave(this.hovered,
2337          event, evt);
2338      this.hovered = false;
2339    }
2340   },
2341   
2342   onMouseOver: function(e, win, event) {
2343     //mouseover a label
2344     var evt = $.event.get(e, win), label;
2345     if(this.dom && (label = this.isLabel(e, win))) {
2346       this.hovered = this.viz.graph.getNode(label.id);
2347       this.config.onMouseEnter(this.hovered,
2348                                event, evt);
2349     }
2350   },
2351   
2352   onMouseMove: function(e, win, event) {
2353    var label, evt = $.event.get(e, win);
2354    if(this.pressed) {
2355      this.moved = true;
2356      this.config.onDragMove(this.pressed, event, evt);
2357      return;
2358    }
2359    if(this.dom) {
2360      this.config.onMouseMove(this.hovered,
2361          event, evt);
2362    } else {
2363      if(this.hovered) {
2364        var hn = this.hovered;
2365        var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2366        var contains = geom && geom.contains 
2367          && geom.contains.call(this.fx, hn, event.getPos());
2368        if(contains) {
2369          this.config.onMouseMove(hn, event, evt);
2370          return;
2371        } else {
2372          this.config.onMouseLeave(hn, event, evt);
2373          this.hovered = false;
2374        }
2375      }
2376      if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2377        this.config.onMouseEnter(this.hovered, event, evt);
2378      } else {
2379        this.config.onMouseMove(false, event, evt);
2380      }
2381    }
2382   },
2383   
2384   onMouseWheel: function(e, win, delta) {
2385     this.config.onMouseWheel(delta, $.event.get(e, win));
2386   },
2387   
2388   onMouseDown: function(e, win, event) {
2389     var evt = $.event.get(e, win);
2390     this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2391     this.config.onDragStart(this.pressed, event, evt);
2392   },
2393   
2394   onTouchStart: function(e, win, event) {
2395     var evt = $.event.get(e, win);
2396     this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2397     this.config.onTouchStart(this.touched, event, evt);
2398   },
2399   
2400   onTouchMove: function(e, win, event) {
2401     var evt = $.event.get(e, win);
2402     if(this.touched) {
2403       this.touchMoved = true;
2404       this.config.onTouchMove(this.touched, event, evt);
2405     }
2406   },
2407   
2408   onTouchEnd: function(e, win, event) {
2409     var evt = $.event.get(e, win);
2410     if(this.touched) {
2411       if(this.touchMoved) {
2412         this.config.onTouchEnd(this.touched, event, evt);
2413       } else {
2414         this.config.onTouchCancel(this.touched, event, evt);
2415       }
2416       this.touched = this.touchMoved = false;
2417     }
2418   }
2419 });
2420
2421 /*
2422    Class: Tips
2423     
2424    A class containing tip related functions. This class is used internally.
2425    
2426    Used by:
2427    
2428    <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2429    
2430    See also:
2431    
2432    <Options.Tips>
2433 */
2434
2435 Extras.Classes.Tips = new Class({
2436   Implements: [ExtrasInitializer, EventsInterface],
2437   
2438   initializePost: function() {
2439     //add DOM tooltip
2440     if(document.body) {
2441       var tip = $('_tooltip') || document.createElement('div');
2442       tip.id = '_tooltip';
2443       tip.className = 'tip';
2444       $.extend(tip.style, {
2445         position: 'absolute',
2446         display: 'none',
2447         zIndex: 13000
2448       });
2449       document.body.appendChild(tip);
2450       this.tip = tip;
2451       this.node = false;
2452     }
2453   },
2454   
2455   setAsProperty: $.lambda(true),
2456   
2457   onMouseOut: function(e, win) {
2458     //mouseout a label
2459     if(this.dom && this.isLabel(e, win)) {
2460       this.hide(true);
2461       return;
2462     }
2463     //mouseout canvas
2464     var rt = e.relatedTarget,
2465         canvasWidget = this.canvas.getElement();
2466     while(rt && rt.parentNode) {
2467       if(canvasWidget == rt.parentNode) return;
2468       rt = rt.parentNode;
2469     }
2470     this.hide(false);
2471   },
2472   
2473   onMouseOver: function(e, win) {
2474     //mouseover a label
2475     var label;
2476     if(this.dom && (label = this.isLabel(e, win))) {
2477       this.node = this.viz.graph.getNode(label.id);
2478       this.config.onShow(this.tip, this.node, label);
2479     }
2480   },
2481   
2482   onMouseMove: function(e, win, opt) {
2483     if(this.dom && this.isLabel(e, win)) {
2484       this.setTooltipPosition($.event.getPos(e, win));
2485     }
2486     if(!this.dom) {
2487       var node = opt.getNode();
2488       if(!node) {
2489         this.hide(true);
2490         return;
2491       }
2492       if(this.config.force || !this.node || this.node.id != node.id) {
2493         this.node = node;
2494         this.config.onShow(this.tip, node, opt.getContains());
2495       }
2496       this.setTooltipPosition($.event.getPos(e, win));
2497     }
2498   },
2499   
2500   setTooltipPosition: function(pos) {
2501     var tip = this.tip, 
2502         style = tip.style, 
2503         cont = this.config;
2504     style.display = '';
2505     //get window dimensions
2506     var win = {
2507       'height': document.body.clientHeight,
2508       'width': document.body.clientWidth
2509     };
2510     //get tooltip dimensions
2511     var obj = {
2512       'width': tip.offsetWidth,
2513       'height': tip.offsetHeight  
2514     };
2515     //set tooltip position
2516     var x = cont.offsetX, y = cont.offsetY;
2517     style.top = ((pos.y + y + obj.height > win.height)?  
2518         (pos.y - obj.height - y) : pos.y + y) + 'px';
2519     style.left = ((pos.x + obj.width + x > win.width)? 
2520         (pos.x - obj.width - x) : pos.x + x) + 'px';
2521   },
2522   
2523   hide: function(triggerCallback) {
2524     if(!SUGAR.util.isTouchScreen()) {
2525         this.tip.style.display = 'none';
2526     }
2527     triggerCallback && this.config.onHide();
2528   }
2529 });
2530
2531 /*
2532   Class: NodeStyles
2533    
2534   Change node styles when clicking or hovering a node. This class is used internally.
2535   
2536   Used by:
2537   
2538   <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2539   
2540   See also:
2541   
2542   <Options.NodeStyles>
2543 */
2544 Extras.Classes.NodeStyles = new Class({
2545   Implements: [ExtrasInitializer, EventsInterface],
2546   
2547   initializePost: function() {
2548     this.fx = this.viz.fx;
2549     this.types = this.viz.fx.nodeTypes;
2550     this.nStyles = this.config;
2551     this.nodeStylesOnHover = this.nStyles.stylesHover;
2552     this.nodeStylesOnClick = this.nStyles.stylesClick;
2553     this.hoveredNode = false;
2554     this.fx.nodeFxAnimation = new Animation();
2555     
2556     this.down = false;
2557     this.move = false;
2558   },
2559   
2560   onMouseOut: function(e, win) {
2561     this.down = this.move = false;
2562     if(!this.hoveredNode) return;
2563     //mouseout a label
2564     if(this.dom && this.isLabel(e, win)) {
2565       this.toggleStylesOnHover(this.hoveredNode, false);
2566     }
2567     //mouseout canvas
2568     var rt = e.relatedTarget,
2569         canvasWidget = this.canvas.getElement();
2570     while(rt && rt.parentNode) {
2571       if(canvasWidget == rt.parentNode) return;
2572       rt = rt.parentNode;
2573     }
2574     this.toggleStylesOnHover(this.hoveredNode, false);
2575     this.hoveredNode = false;
2576   },
2577   
2578   onMouseOver: function(e, win) {
2579     //mouseover a label
2580     var label;
2581     if(this.dom && (label = this.isLabel(e, win))) {
2582       var node = this.viz.graph.getNode(label.id);
2583       if(node.selected) return;
2584       this.hoveredNode = node;
2585       this.toggleStylesOnHover(this.hoveredNode, true);
2586     }
2587   },
2588   
2589   onMouseDown: function(e, win, event, isRightClick) {
2590     if(isRightClick) return;
2591     var label;
2592     if(this.dom && (label = this.isLabel(e, win))) {
2593       this.down = this.viz.graph.getNode(label.id);
2594     } else if(!this.dom) {
2595       this.down = event.getNode();
2596     }
2597     this.move = false;
2598   },
2599   
2600   onMouseUp: function(e, win, event, isRightClick) {
2601     if(isRightClick) return;
2602     if(!this.move) {
2603       this.onClick(event.getNode());
2604     }
2605     this.down = this.move = false;
2606   },
2607   
2608   getRestoredStyles: function(node, type) {
2609     var restoredStyles = {}, 
2610         nStyles = this['nodeStylesOn' + type];
2611     for(var prop in nStyles) {
2612       restoredStyles[prop] = node.styles['$' + prop];
2613     }
2614     return restoredStyles;
2615   },
2616   
2617   toggleStylesOnHover: function(node, set) {
2618     if(this.nodeStylesOnHover) {
2619       this.toggleStylesOn('Hover', node, set);
2620     }
2621   },
2622
2623   toggleStylesOnClick: function(node, set) {
2624     if(this.nodeStylesOnClick) {
2625       this.toggleStylesOn('Click', node, set);
2626     }
2627   },
2628   
2629   toggleStylesOn: function(type, node, set) {
2630     var viz = this.viz;
2631     var nStyles = this.nStyles;
2632     if(set) {
2633       var that = this;
2634       if(!node.styles) {
2635         node.styles = $.merge(node.data, {});
2636       }
2637       for(var s in this['nodeStylesOn' + type]) {
2638         var $s = '$' + s;
2639         if(!($s in node.styles)) {
2640             node.styles[$s] = node.getData(s); 
2641         }
2642       }
2643       viz.fx.nodeFx($.extend({
2644         'elements': {
2645           'id': node.id,
2646           'properties': that['nodeStylesOn' + type]
2647          },
2648          transition: Trans.Quart.easeOut,
2649          duration:300,
2650          fps:40
2651       }, this.config));
2652     } else {
2653       var restoredStyles = this.getRestoredStyles(node, type);
2654       viz.fx.nodeFx($.extend({
2655         'elements': {
2656           'id': node.id,
2657           'properties': restoredStyles
2658          },
2659          transition: Trans.Quart.easeOut,
2660          duration:300,
2661          fps:40
2662       }, this.config));
2663     }
2664   },
2665
2666   onClick: function(node) {
2667     if(!node) return;
2668     var nStyles = this.nodeStylesOnClick;
2669     if(!nStyles) return;
2670     //if the node is selected then unselect it
2671     if(node.selected) {
2672       this.toggleStylesOnClick(node, false);
2673       delete node.selected;
2674     } else {
2675       //unselect all selected nodes...
2676       this.viz.graph.eachNode(function(n) {
2677         if(n.selected) {
2678           for(var s in nStyles) {
2679             n.setData(s, n.styles['$' + s], 'end');
2680           }
2681           delete n.selected;
2682         }
2683       });
2684       //select clicked node
2685       this.toggleStylesOnClick(node, true);
2686       node.selected = true;
2687       delete node.hovered;
2688       this.hoveredNode = false;
2689     }
2690   },
2691   
2692   onMouseMove: function(e, win, event) {
2693     //if mouse button is down and moving set move=true
2694     if(this.down) this.move = true;
2695     //already handled by mouseover/out
2696     if(this.dom && this.isLabel(e, win)) return;
2697     var nStyles = this.nodeStylesOnHover;
2698     if(!nStyles) return;
2699     
2700     if(!this.dom) {
2701       if(this.hoveredNode) {
2702         var geom = this.types[this.hoveredNode.getData('type')];
2703         var contains = geom && geom.contains && geom.contains.call(this.fx, 
2704             this.hoveredNode, event.getPos());
2705         if(contains) return;
2706       }
2707       var node = event.getNode();
2708       //if no node is being hovered then just exit
2709       if(!this.hoveredNode && !node) return;
2710       //if the node is hovered then exit
2711       if(node.hovered) return;
2712       //select hovered node
2713       if(node && !node.selected) {
2714         //check if an animation is running and exit it
2715         this.fx.nodeFxAnimation.stopTimer();
2716         //unselect all hovered nodes...
2717         this.viz.graph.eachNode(function(n) {
2718           if(n.hovered && !n.selected) {
2719             for(var s in nStyles) {
2720               n.setData(s, n.styles['$' + s], 'end');
2721             }
2722             delete n.hovered;
2723           }
2724         });
2725         //select hovered node
2726         node.hovered = true;
2727         this.hoveredNode = node;
2728         this.toggleStylesOnHover(node, true);
2729       } else if(this.hoveredNode && !this.hoveredNode.selected) {
2730         //check if an animation is running and exit it
2731         this.fx.nodeFxAnimation.stopTimer();
2732         //unselect hovered node
2733         this.toggleStylesOnHover(this.hoveredNode, false);
2734         delete this.hoveredNode.hovered;
2735         this.hoveredNode = false;
2736       }
2737     }
2738   }
2739 });
2740
2741 Extras.Classes.Navigation = new Class({
2742   Implements: [ExtrasInitializer, EventsInterface],
2743   
2744   initializePost: function() {
2745     this.pos = false;
2746     this.pressed = false;
2747   },
2748   
2749   onMouseWheel: function(e, win, scroll) {
2750     if(!this.config.zooming) return;
2751     $.event.stop($.event.get(e, win));
2752     var val = this.config.zooming / 1000,
2753         ans = 1 + scroll * val;
2754     this.canvas.scale(ans, ans);
2755   },
2756   
2757   onMouseDown: function(e, win, eventInfo) {
2758     if(!this.config.panning) return;
2759     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2760     this.pressed = true;
2761     this.pos = eventInfo.getPos();
2762     var canvas = this.canvas,
2763         ox = canvas.translateOffsetX,
2764         oy = canvas.translateOffsetY,
2765         sx = canvas.scaleOffsetX,
2766         sy = canvas.scaleOffsetY;
2767     this.pos.x *= sx;
2768     this.pos.x += ox;
2769     this.pos.y *= sy;
2770     this.pos.y += oy;
2771   },
2772   
2773   onMouseMove: function(e, win, eventInfo) {
2774     if(!this.config.panning) return;
2775     if(!this.pressed) return;
2776     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2777     var thispos = this.pos, 
2778         currentPos = eventInfo.getPos(),
2779         canvas = this.canvas,
2780         ox = canvas.translateOffsetX,
2781         oy = canvas.translateOffsetY,
2782         sx = canvas.scaleOffsetX,
2783         sy = canvas.scaleOffsetY;
2784     currentPos.x *= sx;
2785     currentPos.y *= sy;
2786     currentPos.x += ox;
2787     currentPos.y += oy;
2788     var x = currentPos.x - thispos.x,
2789         y = currentPos.y - thispos.y;
2790     this.pos = currentPos;
2791     this.canvas.translate(x * 1/sx, y * 1/sy);
2792   },
2793   
2794   onMouseUp: function(e, win, eventInfo, isRightClick) {
2795     if(!this.config.panning) return;
2796     this.pressed = false;
2797   }
2798 });
2799
2800
2801 /*
2802  * File: Canvas.js
2803  *
2804  */
2805
2806 /*
2807  Class: Canvas
2808  
2809         A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to 
2810         know more about <Canvas> options take a look at <Options.Canvas>.
2811  
2812  A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior 
2813  across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2814  
2815  Example:
2816  
2817  Suppose we have this HTML
2818  
2819  (start code xml)
2820         <div id="infovis"></div>
2821  (end code)
2822  
2823  Now we create a new Visualization
2824  
2825  (start code js)
2826         var viz = new $jit.Viz({
2827                 //Where to inject the canvas. Any div container will do.
2828                 'injectInto':'infovis',
2829                  //width and height for canvas. 
2830                  //Default's to the container offsetWidth and Height.
2831                  'width': 900,
2832                  'height':500
2833          });
2834  (end code)
2835
2836  The generated HTML will look like this
2837  
2838  (start code xml)
2839  <div id="infovis">
2840         <div id="infovis-canvaswidget" style="position:relative;">
2841         <canvas id="infovis-canvas" width=900 height=500
2842         style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2843         <div id="infovis-label"
2844         style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2845         </div>
2846         </div>
2847  </div>
2848  (end code)
2849  
2850  As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2851  of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2852  */
2853
2854 var Canvas;
2855 (function() {
2856   //check for native canvas support
2857   var canvasType = typeof HTMLCanvasElement,
2858       supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2859   //create element function
2860   function $E(tag, props) {
2861     var elem = document.createElement(tag);
2862     for(var p in props) {
2863       if(typeof props[p] == "object") {
2864         $.extend(elem[p], props[p]);
2865       } else {
2866         elem[p] = props[p];
2867       }
2868     }
2869     if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2870       elem = G_vmlCanvasManager.initElement(elem);
2871     }
2872     return elem;
2873   }
2874   //canvas widget which we will call just Canvas
2875   $jit.Canvas = Canvas = new Class({
2876     canvases: [],
2877     pos: false,
2878     element: false,
2879     labelContainer: false,
2880     translateOffsetX: 0,
2881     translateOffsetY: 0,
2882     scaleOffsetX: 1,
2883     scaleOffsetY: 1,
2884     
2885     initialize: function(viz, opt) {
2886       this.viz = viz;
2887       this.opt = opt;
2888       var id = $.type(opt.injectInto) == 'string'? 
2889           opt.injectInto:opt.injectInto.id,
2890           idLabel = id + "-label", 
2891           wrapper = $(id),
2892           width = opt.width || wrapper.offsetWidth,
2893           height = opt.height || wrapper.offsetHeight;
2894       this.id = id;
2895       //canvas options
2896       var canvasOptions = {
2897         injectInto: id,
2898         width: width,
2899         height: height
2900       };
2901       //create main wrapper
2902       this.element = $E('div', {
2903         'id': id + '-canvaswidget',
2904         'style': {
2905           'position': 'relative',
2906           'width': width + 'px',
2907           'height': height + 'px'
2908         }
2909       });
2910       //create label container
2911       this.labelContainer = this.createLabelContainer(opt.Label.type, 
2912           idLabel, canvasOptions);
2913       //create primary canvas
2914       this.canvases.push(new Canvas.Base({
2915         config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2916         plot: function(base) {
2917           viz.fx.plot();
2918         },
2919         resize: function() {
2920           viz.refresh();
2921         }
2922       }));
2923       //create secondary canvas
2924       var back = opt.background;
2925       if(back) {
2926         var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2927         this.canvases.push(new Canvas.Base(backCanvas));
2928       }
2929       //insert canvases
2930       var len = this.canvases.length;
2931       while(len--) {
2932         this.element.appendChild(this.canvases[len].canvas);
2933         if(len > 0) {
2934           this.canvases[len].plot();
2935         }
2936       }
2937       this.element.appendChild(this.labelContainer);
2938       wrapper.appendChild(this.element);
2939       //Update canvas position when the page is scrolled.
2940       var timer = null, that = this;
2941       $.addEvent(window, 'scroll', function() {
2942         clearTimeout(timer);
2943         timer = setTimeout(function() {
2944           that.getPos(true); //update canvas position
2945         }, 500);
2946       });
2947       $.addEvent(window, 'click', function() {
2948         clearTimeout(timer);
2949         timer = setTimeout(function() {
2950           that.getPos(true); //update canvas position
2951         }, 500);
2952       });
2953       sb = document.getElementById('sb'+id);
2954       $.addEvent(sb, 'scroll', function() {
2955         clearTimeout(timer);
2956         timer = setTimeout(function() {
2957           that.getPos(true); //update canvas position
2958         }, 500);
2959       });
2960     },
2961     /*
2962       Method: getCtx
2963       
2964       Returns the main canvas context object
2965       
2966       Example:
2967       
2968       (start code js)
2969        var ctx = canvas.getCtx();
2970        //Now I can use the native canvas context
2971        //and for example change some canvas styles
2972        ctx.globalAlpha = 1;
2973       (end code)
2974     */
2975     getCtx: function(i) {
2976       return this.canvases[i || 0].getCtx();
2977     },
2978     /*
2979       Method: getConfig
2980       
2981       Returns the current Configuration for this Canvas Widget.
2982       
2983       Example:
2984       
2985       (start code js)
2986        var config = canvas.getConfig();
2987       (end code)
2988     */
2989     getConfig: function() {
2990       return this.opt;
2991     },
2992     /*
2993       Method: getElement
2994
2995       Returns the main Canvas DOM wrapper
2996       
2997       Example:
2998       
2999       (start code js)
3000        var wrapper = canvas.getElement();
3001        //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3002       (end code)
3003     */
3004     getElement: function() {
3005       return this.element;
3006     },
3007     /*
3008       Method: getSize
3009       
3010       Returns canvas dimensions.
3011       
3012       Returns:
3013       
3014       An object with *width* and *height* properties.
3015       
3016       Example:
3017       (start code js)
3018       canvas.getSize(); //returns { width: 900, height: 500 }
3019       (end code)
3020     */
3021     getSize: function(i) {
3022       return this.canvases[i || 0].getSize();
3023     },
3024     /*
3025       Method: resize
3026       
3027       Resizes the canvas.
3028       
3029       Parameters:
3030       
3031       width - New canvas width.
3032       height - New canvas height.
3033       
3034       Example:
3035       
3036       (start code js)
3037        canvas.resize(width, height);
3038       (end code)
3039     
3040     */
3041     resize: function(width, height) {
3042       this.getPos(true);
3043       this.translateOffsetX = this.translateOffsetY = 0;
3044       this.scaleOffsetX = this.scaleOffsetY = 1;
3045       for(var i=0, l=this.canvases.length; i<l; i++) {
3046         this.canvases[i].resize(width, height);
3047       }
3048       var style = this.element.style;
3049       style.width = width + 'px';
3050       style.height = height + 'px';
3051       if(this.labelContainer)
3052         this.labelContainer.style.width = width + 'px';
3053     },
3054     /*
3055       Method: translate
3056       
3057       Applies a translation to the canvas.
3058       
3059       Parameters:
3060       
3061       x - (number) x offset.
3062       y - (number) y offset.
3063       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3064       
3065       Example:
3066       
3067       (start code js)
3068        canvas.translate(30, 30);
3069       (end code)
3070     
3071     */
3072     translate: function(x, y, disablePlot) {
3073       this.translateOffsetX += x*this.scaleOffsetX;
3074       this.translateOffsetY += y*this.scaleOffsetY;
3075       for(var i=0, l=this.canvases.length; i<l; i++) {
3076         this.canvases[i].translate(x, y, disablePlot);
3077       }
3078     },
3079     /*
3080       Method: scale
3081       
3082       Scales the canvas.
3083       
3084       Parameters:
3085       
3086       x - (number) scale value.
3087       y - (number) scale value.
3088       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3089       
3090       Example:
3091       
3092       (start code js)
3093        canvas.scale(0.5, 0.5);
3094       (end code)
3095     
3096     */
3097     scale: function(x, y, disablePlot) {
3098         if(!disablePlot) {
3099                 disablePlot = false;
3100                 }
3101       var px = this.scaleOffsetX * x,
3102           py = this.scaleOffsetY * y;
3103       var dx = this.translateOffsetX * (x -1) / px,
3104           dy = this.translateOffsetY * (y -1) / py;
3105       this.scaleOffsetX = px;
3106       this.scaleOffsetY = py;
3107       for(var i=0, l=this.canvases.length; i<l; i++) {
3108         this.canvases[i].scale(x, y, true);
3109       }
3110       this.translate(dx, dy, disablePlot);
3111     },
3112     /*
3113       Method: getPos
3114       
3115       Returns the canvas position as an *x, y* object.
3116       
3117       Parameters:
3118       
3119       force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3120       
3121       Returns:
3122       
3123       An object with *x* and *y* properties.
3124       
3125       Example:
3126       (start code js)
3127       canvas.getPos(true); //returns { x: 900, y: 500 }
3128       (end code)
3129     */
3130     getPos: function(force){
3131       if(force || !this.pos) {
3132         return this.pos = $.getPos(this.getElement());
3133       }
3134       return this.pos;
3135     },
3136     /*
3137        Method: clear
3138        
3139        Clears the canvas.
3140     */
3141     clear: function(i){
3142       this.canvases[i||0].clear();
3143     },
3144     
3145     path: function(type, action){
3146       var ctx = this.canvases[0].getCtx();
3147       ctx.beginPath();
3148       action(ctx);
3149       ctx[type]();
3150       ctx.closePath();
3151     },
3152     
3153     createLabelContainer: function(type, idLabel, dim) {
3154       var NS = 'http://www.w3.org/2000/svg';
3155       if(type == 'HTML' || type == 'Native') {
3156         return $E('div', {
3157           'id': idLabel,
3158           'style': {
3159             'overflow': 'visible',
3160             'position': 'absolute',
3161             'top': 0,
3162             'left': 0,
3163             'width': dim.width + 'px',
3164             'height': 0
3165           }
3166         });
3167       } else if(type == 'SVG') {
3168         var svgContainer = document.createElementNS(NS, 'svg:svg');
3169         svgContainer.setAttribute("width", dim.width);
3170         svgContainer.setAttribute('height', dim.height);
3171         var style = svgContainer.style;
3172         style.position = 'absolute';
3173         style.left = style.top = '0px';
3174         var labelContainer = document.createElementNS(NS, 'svg:g');
3175         labelContainer.setAttribute('width', dim.width);
3176         labelContainer.setAttribute('height', dim.height);
3177         labelContainer.setAttribute('x', 0);
3178         labelContainer.setAttribute('y', 0);
3179         labelContainer.setAttribute('id', idLabel);
3180         svgContainer.appendChild(labelContainer);
3181         return svgContainer;
3182       }
3183     }
3184   });
3185   //base canvas wrapper
3186   Canvas.Base = new Class({
3187     translateOffsetX: 0,
3188     translateOffsetY: 0,
3189     scaleOffsetX: 1,
3190     scaleOffsetY: 1,
3191
3192     initialize: function(viz) {
3193       this.viz = viz;
3194       this.opt = viz.config;
3195       this.size = false;
3196       this.createCanvas();
3197       this.translateToCenter();
3198     },
3199     createCanvas: function() {
3200       var opt = this.opt,
3201           width = opt.width,
3202           height = opt.height;
3203       this.canvas = $E('canvas', {
3204         'id': opt.injectInto + opt.idSuffix,
3205         'width': width,
3206         'height': height,
3207         'style': {
3208           'position': 'absolute',
3209           'top': 0,
3210           'left': 0,
3211           'width': width + 'px',
3212           'height': height + 'px'
3213         }
3214       });
3215     },
3216     getCtx: function() {
3217       if(!this.ctx) 
3218         return this.ctx = this.canvas.getContext('2d');
3219       return this.ctx;
3220     },
3221     getSize: function() {
3222       if(this.size) return this.size;
3223       var canvas = this.canvas;
3224       return this.size = {
3225         width: canvas.width,
3226         height: canvas.height
3227       };
3228     },
3229     translateToCenter: function(ps) {
3230       var size = this.getSize(),
3231           width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3232           height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3233       var ctx = this.getCtx();
3234       ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3235       ctx.translate(width/2, height/2);
3236     },
3237     resize: function(width, height) {
3238       var size = this.getSize(),
3239           canvas = this.canvas,
3240           styles = canvas.style;
3241       this.size = false;
3242       canvas.width = width;
3243       canvas.height = height;
3244       styles.width = width + "px";
3245       styles.height = height + "px";
3246       //small ExCanvas fix
3247       //if(!supportsCanvas) {
3248         //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           {
11674               yCoord = y;
11675               xCoord = x + acum;
11676               chartBarWidth = dimArray[i];
11677               chartBarHeight = height;
11678           }
11679           else
11680           {
11681               xCoord = x;
11682               yCoord = y - acum - dimArray[i];
11683               chartBarWidth = width;
11684               chartBarHeight = dimArray[i];
11685           }
11686           ctx.fillRect(xCoord, yCoord, chartBarWidth, chartBarHeight);
11687
11688           // add label
11689           if (chartBarHeight > 0)
11690           {
11691               ctx.font = label.style + ' ' + (label.size - 2) + 'px ' + label.family;
11692               labelText = valueArray[i].toString();
11693               mtxt = ctx.measureText(labelText);
11694
11695               labelTextPaddingX = 10;
11696               labelTextPaddingY = 6;
11697
11698               labelBoxWidth = mtxt.width + labelTextPaddingX;
11699               labelBoxHeight = label.size + labelTextPaddingY;
11700
11701               // do NOT draw label if label box is smaller than chartBarHeight
11702               if ((horz && (labelBoxWidth < chartBarWidth)) || (!horz && (labelBoxHeight < chartBarHeight)))
11703               {
11704                   labelBoxX = xCoord + chartBarWidth/2 - mtxt.width/2 - labelTextPaddingX/2;
11705                   labelBoxY = yCoord + chartBarHeight/2 - labelBoxHeight/2;
11706
11707                   ctx.fillStyle = "rgba(255,255,255,.2)";
11708                   $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "fill");
11709                   ctx.fillStyle = "rgba(0,0,0,.8)";
11710                   $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "stroke");
11711                   ctx.textAlign = 'center';
11712                   ctx.fillStyle = "rgba(255,255,255,.6)";
11713                   ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2, labelBoxY + labelBoxHeight/2);
11714                   ctx.fillStyle = "rgba(0,0,0,.6)";
11715                   ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2 + 1, labelBoxY + labelBoxHeight/2 + 1);
11716               }
11717           }
11718
11719           if(border && border.name == stringArray[i]) {
11720             opt.acum = acum;
11721             opt.dimValue = dimArray[i];
11722           }
11723           acum += (dimArray[i] || 0);
11724           valAcum += (valueArray[i] || 0);
11725         }
11726         if(border) {
11727           ctx.save();
11728           ctx.lineWidth = 2;
11729           ctx.strokeStyle = border.color;
11730           if(horz) {
11731             ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11732           } else {
11733             ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11734           }
11735           ctx.restore();
11736         }
11737         if(label.type == 'Native') {
11738           ctx.save();
11739           ctx.fillStyle = ctx.strokeStyle = label.color;
11740           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11741           ctx.textBaseline = 'middle';
11742                         if(gvl) {
11743                                 acumValueLabel = gvl;
11744                         } else {
11745                                 acumValueLabel = valAcum;
11746                         }
11747           if(aggregates(node.name, valAcum)) {
11748             if(!horz) {
11749                           ctx.textAlign = 'center';
11750                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11751                           //background box
11752                           ctx.save();
11753                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11754                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11755                                  (label ? label.size + config.labelOffset : 0));
11756                           mtxt = ctx.measureText(acumValueLabel);
11757                           boxWidth = mtxt.width+10;
11758                           inset = 10;
11759                           boxHeight = label.size+6;
11760                           
11761                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11762                                 bottomPadding = acum - config.labelOffset - boxHeight;
11763                           } else {
11764                                 bottomPadding = acum + config.labelOffset + inset;
11765                           }
11766                         
11767                         
11768                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11769                           cornerRadius = 4;     
11770                           boxX = -inset/2;
11771                           boxY = -boxHeight/2;
11772                           
11773                           ctx.rotate(0 * Math.PI / 180);
11774                           ctx.fillStyle = "rgba(255,255,255,.8)";
11775                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11776                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11777                           }
11778                           //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11779                           ctx.fillStyle = ctx.strokeStyle = label.color;
11780                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11781                           ctx.restore();
11782
11783             }
11784           }
11785           if(showLabels(node.name, valAcum, node)) {
11786             if(horz) {
11787
11788
11789                 //background box
11790                 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11791                                 inset = 10;
11792                                 
11793                                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11794                 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11795                 boxWidth = mtxt.width+10;
11796                 inset = 10;
11797                 
11798                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11799                         leftPadding = acum - config.labelOffset - boxWidth - inset;
11800                 } else {
11801                         leftPadding = acum + config.labelOffset;
11802                 }
11803                 
11804                 
11805                                 ctx.textAlign = 'left';
11806                                 ctx.translate(x + inset + leftPadding, y + height/2);
11807                                 boxHeight = label.size+6;
11808                                 boxX = -inset/2;
11809                                 boxY = -boxHeight/2;
11810                                 ctx.fillStyle = "rgba(255,255,255,.8)";
11811                                 cornerRadius = 4;
11812                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {  
11813                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11814                                 }
11815                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11816                                 
11817                           ctx.fillStyle = label.color;
11818               ctx.rotate(0 * Math.PI / 180);
11819               ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
11820
11821
11822             } else {
11823               //if the number of nodes greater than 8 rotate labels 45 degrees
11824               if(nodeCount > 8) {
11825                                 ctx.textAlign = 'left';
11826                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
11827                                 ctx.rotate(45* Math.PI / 180);
11828                                 ctx.fillText(node.name, 0, 0);
11829                           } else {
11830                                 ctx.textAlign = 'center';
11831                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
11832                           }
11833             }
11834           }
11835           ctx.restore();
11836         }
11837       }
11838     },
11839     'contains': function(node, mpos) {
11840       var pos = node.pos.getc(true), 
11841           width = node.getData('width'),
11842           height = node.getData('height'),
11843           algnPos = this.getAlignedPos(pos, width, height),
11844           x = algnPos.x, y = algnPos.y,
11845           dimArray = node.getData('dimArray'),
11846           config = node.getData('config'),
11847           rx = mpos.x - x,
11848           horz = config.orientation == 'horizontal';
11849       //bounding box check
11850       if(horz) {
11851         if(mpos.x < x || mpos.x > x + width
11852             || mpos.y > y + height || mpos.y < y) {
11853             return false;
11854           }
11855       } else {
11856         if(mpos.x < x || mpos.x > x + width
11857             || mpos.y > y || mpos.y < y - height) {
11858             return false;
11859           }
11860       }
11861       //deep check
11862       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
11863         var dimi = dimArray[i];
11864                 var url = Url.decode(node.getData('linkArray')[i]);
11865         if(horz) {
11866           acum += dimi;
11867           var intersec = acum;
11868           if(mpos.x <= intersec) {
11869             return {
11870               'name': node.getData('stringArray')[i],
11871               'color': node.getData('colorArray')[i],
11872               'value': node.getData('valueArray')[i],
11873               'valuelabel': node.getData('valuelabelArray')[i],
11874                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11875                           'link': url,
11876               'label': node.name
11877             };
11878           }
11879         } else {
11880           acum -= dimi;
11881           var intersec = acum;
11882           if(mpos.y >= intersec) {
11883             return {
11884               'name': node.getData('stringArray')[i],
11885               'color': node.getData('colorArray')[i],
11886               'value': node.getData('valueArray')[i],
11887                           'valuelabel': node.getData('valuelabelArray')[i],
11888                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
11889               'link': url,
11890               'label': node.name
11891             };
11892           }
11893         }
11894       }
11895       return false;
11896     }
11897   },
11898   'barchart-grouped' : {
11899     'render' : function(node, canvas) {
11900       var pos = node.pos.getc(true), 
11901           width = node.getData('width'),
11902           height = node.getData('height'),
11903           algnPos = this.getAlignedPos(pos, width, height),
11904           x = algnPos.x, y = algnPos.y,
11905           dimArray = node.getData('dimArray'),
11906           valueArray = node.getData('valueArray'),
11907           valuelabelArray = node.getData('valuelabelArray'),
11908           linkArray = node.getData('linkArray'),
11909           valueLength = valueArray.length,
11910           colorArray = node.getData('colorArray'),
11911           colorLength = colorArray.length,
11912           stringArray = node.getData('stringArray'); 
11913
11914       var ctx = canvas.getCtx(),
11915           canvasSize = canvas.getSize(),
11916           opt = {},
11917           border = node.getData('border'),
11918           gradient = node.getData('gradient'),
11919           config = node.getData('config'),
11920           horz = config.orientation == 'horizontal',
11921           aggregates = config.showAggregates,
11922           showLabels = config.showLabels,
11923           label = config.Label,
11924           shadow = config.shadow,
11925           margin = config.Margin,
11926           fixedDim = (horz? height : width) / valueLength;
11927       
11928       //drop shadow
11929       
11930        maxValue = Math.max.apply(null, dimArray);
11931        
11932        
11933           
11934            ctx.fillStyle = "rgba(0,0,0,.2)";
11935       if (colorArray && dimArray && stringArray && shadow.enable) {
11936                  shadowThickness = shadow.size;
11937
11938         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11939                 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
11940                 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
11941                 if(horz) {
11942                                     
11943                         ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
11944                                         
11945                 } else {
11946                         
11947                         if(i == 0) {
11948                                 if(nextBar && nextBar > dimArray[i]) {
11949                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11950                                 } else if (nextBar && nextBar < dimArray[i]){
11951                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11952                                 } else {
11953                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11954                                 }
11955                         } else if (i> 0 && i<l-1) {
11956                                 if(nextBar && nextBar > dimArray[i]) {
11957                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
11958                                 } else if (nextBar && nextBar < dimArray[i]){
11959                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11960                                 } else {
11961                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
11962                                 }
11963                         } else if (i == l-1) {
11964                                 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
11965                         }
11966                         
11967                         
11968                 }
11969         }
11970
11971       } 
11972                         
11973       
11974       if (colorArray && dimArray && stringArray) {
11975         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
11976           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11977           if(gradient) {
11978             var linear;
11979             if(horz) {
11980               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
11981                   x + dimArray[i]/2, y + fixedDim * (i + 1));
11982             } else {
11983               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
11984                   x + fixedDim * (i + 1), y - dimArray[i]/2);
11985             }
11986             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11987                 function(v) { return (v * 0.8) >> 0; }));
11988             linear.addColorStop(0, color);
11989             linear.addColorStop(0.3, colorArray[i % colorLength]);
11990             linear.addColorStop(0.7, colorArray[i % colorLength]);
11991             linear.addColorStop(1, color);
11992             ctx.fillStyle = linear;
11993           }
11994           if(horz) {
11995             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
11996           } else {
11997             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
11998           }
11999           if(border && border.name == stringArray[i]) {
12000             opt.acum = fixedDim * i;
12001             opt.dimValue = dimArray[i];
12002           }
12003           acum += (dimArray[i] || 0);
12004           valAcum += (valueArray[i] || 0);
12005                   ctx.fillStyle = ctx.strokeStyle = label.color;
12006           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12007           
12008           inset = 10;
12009                   if(aggregates(node.name, valAcum) && label.type == 'Native') {
12010                                 if(valuelabelArray[i]) {
12011                                         acumValueLabel = valuelabelArray[i];
12012                                 } else {
12013                                         acumValueLabel = valueArray[i];
12014                                 }
12015                            if(horz) {
12016                                   ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12017                                   ctx.textAlign = 'left';
12018                                   ctx.textBaseline = 'top';
12019                                   ctx.fillStyle = "rgba(255,255,255,.8)";
12020                                   //background box
12021                                   gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
12022                                   mtxt = ctx.measureText(acumValueLabel);
12023                                   boxWidth = mtxt.width+10;
12024                                   
12025                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12026                                         leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
12027                                   } else {
12028                                         leftPadding = dimArray[i] + config.labelOffset + inset;
12029                                   }
12030                               boxHeight = label.size+6;
12031                                   boxX = x + leftPadding;
12032                                   boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
12033                                   cornerRadius = 4;     
12034                                   
12035                                   
12036                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12037                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12038                                   }
12039                                 //  $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12040                                   
12041                                   ctx.fillStyle = ctx.strokeStyle = label.color;
12042                                   ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12043                                   
12044
12045                                         
12046                                         
12047                                 } else {
12048                                   
12049                                         ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12050                                         ctx.save();
12051                                         ctx.textAlign = 'center';
12052                                         
12053                                         //background box
12054                                         gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12055                                          (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12056                                          (label ? label.size + config.labelOffset : 0));
12057                                         
12058                                         mtxt = ctx.measureText(acumValueLabel);
12059                                         boxWidth = mtxt.width+10;
12060                                         boxHeight = label.size+6;
12061                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12062                                                 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12063                                         } else {
12064                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12065                                         }
12066                                                                                                 
12067                                         
12068                                         ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12069                                         
12070                                         boxX = -boxWidth/2;
12071                                         boxY = -boxHeight/2;
12072                                         ctx.fillStyle = "rgba(255,255,255,.8)";
12073                                         
12074                                         cornerRadius = 4;       
12075
12076                                         //ctx.rotate(270* Math.PI / 180);
12077                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12078                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12079                                         }
12080                                         //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12081                                         
12082                                         ctx.fillStyle = ctx.strokeStyle = label.color;
12083                                         ctx.fillText(acumValueLabel, 0,0);
12084                                         ctx.restore();
12085
12086                                 }
12087                         }
12088         }
12089         if(border) {
12090           ctx.save();
12091           ctx.lineWidth = 2;
12092           ctx.strokeStyle = border.color;
12093           if(horz) {
12094             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12095           } else {
12096             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12097           }
12098           ctx.restore();
12099         }
12100         if(label.type == 'Native') {
12101           ctx.save();
12102           ctx.fillStyle = ctx.strokeStyle = label.color;
12103           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12104           ctx.textBaseline = 'middle';
12105
12106           if(showLabels(node.name, valAcum, node)) {
12107             if(horz) {
12108               ctx.textAlign = 'center';
12109               ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12110               ctx.rotate(Math.PI / 2);
12111               ctx.fillText(node.name, 0, 0);
12112             } else {
12113               ctx.textAlign = 'center';
12114               ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12115             }
12116           }
12117           ctx.restore();
12118         }
12119       }
12120     },
12121     'contains': function(node, mpos) {
12122       var pos = node.pos.getc(true), 
12123           width = node.getData('width'),
12124           height = node.getData('height'),
12125           algnPos = this.getAlignedPos(pos, width, height),
12126           x = algnPos.x, y = algnPos.y,
12127           dimArray = node.getData('dimArray'),
12128           len = dimArray.length,
12129           config = node.getData('config'),
12130           rx = mpos.x - x,
12131           horz = config.orientation == 'horizontal',
12132           fixedDim = (horz? height : width) / len;
12133       //bounding box check
12134       if(horz) {
12135         if(mpos.x < x || mpos.x > x + width
12136             || mpos.y > y + height || mpos.y < y) {
12137             return false;
12138           }
12139       } else {
12140         if(mpos.x < x || mpos.x > x + width
12141             || mpos.y > y || mpos.y < y - height) {
12142             return false;
12143           }
12144       }
12145       //deep check
12146       for(var i=0, l=dimArray.length; i<l; i++) {
12147         var dimi = dimArray[i];
12148                 var url = Url.decode(node.getData('linkArray')[i]);
12149         if(horz) {
12150           var limit = y + fixedDim * i;
12151           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12152             return {
12153               'name': node.getData('stringArray')[i],
12154               'color': node.getData('colorArray')[i],
12155               'value': node.getData('valueArray')[i],
12156                           'valuelabel': node.getData('valuelabelArray')[i],
12157               'title': node.getData('titleArray')[i],
12158                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12159               'link': url,
12160               'label': node.name
12161             };
12162           }
12163         } else {
12164           var limit = x + fixedDim * i;
12165           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12166             return {
12167               'name': node.getData('stringArray')[i],
12168               'color': node.getData('colorArray')[i],
12169               'value': node.getData('valueArray')[i],
12170                           'valuelabel': node.getData('valuelabelArray')[i],
12171               'title': node.getData('titleArray')[i],
12172                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12173               'link': url,
12174               'label': node.name
12175             };
12176           }
12177         }
12178       }
12179       return false;
12180     }
12181   },
12182   'barchart-basic' : {
12183     'render' : function(node, canvas) {
12184       var pos = node.pos.getc(true), 
12185           width = node.getData('width'),
12186           height = node.getData('height'),
12187           algnPos = this.getAlignedPos(pos, width, height),
12188           x = algnPos.x, y = algnPos.y,
12189           dimArray = node.getData('dimArray'),
12190           valueArray = node.getData('valueArray'),
12191                   valuelabelArray = node.getData('valuelabelArray'),
12192           linkArray = node.getData('linkArray'),
12193           valueLength = valueArray.length,
12194           colorArray = node.getData('colorMono'),
12195           colorLength = colorArray.length,
12196           stringArray = node.getData('stringArray'); 
12197
12198       var ctx = canvas.getCtx(),
12199           canvasSize = canvas.getSize(),
12200           opt = {},
12201           border = node.getData('border'),
12202           gradient = node.getData('gradient'),
12203           config = node.getData('config'),
12204           horz = config.orientation == 'horizontal',
12205           aggregates = config.showAggregates,
12206           showLabels = config.showLabels,
12207           label = config.Label,
12208           fixedDim = (horz? height : width) / valueLength,
12209           margin = config.Margin;
12210       
12211       if (colorArray && dimArray && stringArray) {
12212         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12213           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12214
12215           if(gradient) {
12216             var linear;
12217             if(horz) {
12218               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
12219                   x + dimArray[i]/2, y + fixedDim * (i + 1));
12220             } else {
12221               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
12222                   x + fixedDim * (i + 1), y - dimArray[i]/2);
12223             }
12224             //drop shadow 
12225            if(config.shadow.size) {
12226                   shadowThickness = config.shadow.size;
12227                   ctx.fillStyle = "rgba(0,0,0,.2)";
12228                   if(horz) {
12229                     ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12230                   } else {
12231                     ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12232                   }
12233           }
12234           
12235             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
12236                 function(v) { return (v * 0.8) >> 0; }));
12237             linear.addColorStop(0, color);
12238             linear.addColorStop(0.3, colorArray[i % colorLength]);
12239             linear.addColorStop(0.7, colorArray[i % colorLength]);
12240             linear.addColorStop(1, color);
12241             ctx.fillStyle = linear;
12242           }
12243           if(horz) {
12244             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12245           } else {
12246             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12247           }
12248           if(border && border.name == stringArray[i]) {
12249             opt.acum = fixedDim * i;
12250             opt.dimValue = dimArray[i];
12251           }
12252           acum += (dimArray[i] || 0);
12253           valAcum += (valueArray[i] || 0);
12254                   
12255               if(label.type == 'Native') {
12256                                  ctx.fillStyle = ctx.strokeStyle = label.color;
12257                                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12258                                  if(aggregates(node.name, valAcum)) {
12259                                         if(valuelabelArray[i]) {
12260                                                 acumValueLabel = valuelabelArray[i];
12261                                           } else {
12262                                                 acumValueLabel = valueArray[i];
12263                                           }
12264                                          if(!horz) {
12265                                           ctx.textAlign = 'center';
12266                                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12267                                           //background box
12268                                           ctx.save();
12269                                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12270                                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12271                                                  (label ? label.size + config.labelOffset : 0));
12272                           mtxt = ctx.measureText(acumValueLabel);
12273                                           boxWidth = mtxt.width+10;
12274                                           inset = 10;
12275                                           boxHeight = label.size+6;
12276                                           
12277                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12278                                                 bottomPadding = dimArray[i] - config.labelOffset  - inset;
12279                                           } else {
12280                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12281                                           }
12282                                         
12283                                         
12284                                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12285                                           cornerRadius = 4;     
12286                                           boxX = -inset/2;
12287                                           boxY = -boxHeight/2;
12288                                           
12289                                           //ctx.rotate(270* Math.PI / 180);
12290                                           ctx.fillStyle = "rgba(255,255,255,.6)";
12291                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12292                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12293                                           }
12294                                          // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12295                                           ctx.fillStyle = ctx.strokeStyle = label.color;
12296                                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12297                                           ctx.restore();
12298                                         }
12299                                 }
12300                 }
12301         }
12302         if(border) {
12303           ctx.save();
12304           ctx.lineWidth = 2;
12305           ctx.strokeStyle = border.color;
12306           if(horz) {
12307             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12308           } else {
12309             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12310           }
12311           ctx.restore();
12312         }
12313         if(label.type == 'Native') {
12314           ctx.save();
12315           ctx.fillStyle = ctx.strokeStyle = label.color;
12316           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12317           ctx.textBaseline = 'middle';
12318           if(showLabels(node.name, valAcum, node)) {
12319             if(horz) {
12320                 
12321                 //background box
12322                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12323                 mtxt = ctx.measureText(node.name + ": " + valAcum);
12324                 boxWidth = mtxt.width+10;
12325                 inset = 10;
12326                 
12327                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12328                         leftPadding = acum - config.labelOffset - boxWidth - inset;
12329                 } else {
12330                         leftPadding = acum + config.labelOffset;
12331                 }
12332                 
12333                                 
12334                                 ctx.textAlign = 'left';
12335                                 ctx.translate(x + inset + leftPadding, y + height/2);
12336                                 boxHeight = label.size+6;
12337                                 boxX = -inset/2;
12338                                 boxY = -boxHeight/2;
12339                                 ctx.fillStyle = "rgba(255,255,255,.8)";
12340                                 
12341                                 cornerRadius = 4;       
12342                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12343                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12344                                 }
12345                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12346                 
12347                                 
12348                                 ctx.fillStyle = label.color;
12349                                 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12350
12351             } else {
12352               
12353                           if(stringArray.length > 8) {
12354                                 ctx.textAlign = 'left';
12355                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12356                                 ctx.rotate(45* Math.PI / 180);
12357                                 ctx.fillText(node.name, 0, 0);
12358                           } else {
12359                                 ctx.textAlign = 'center';
12360                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12361                           }
12362               
12363             }
12364           }
12365           ctx.restore();
12366         }
12367       }
12368     },
12369     'contains': function(node, mpos) {
12370       var pos = node.pos.getc(true), 
12371           width = node.getData('width'),
12372           height = node.getData('height'),
12373           config = node.getData('config'),
12374           algnPos = this.getAlignedPos(pos, width, height),
12375           x = algnPos.x, y = algnPos.y ,
12376           dimArray = node.getData('dimArray'),
12377           len = dimArray.length,
12378           rx = mpos.x - x,
12379           horz = config.orientation == 'horizontal',
12380           fixedDim = (horz? height : width) / len;
12381
12382       //bounding box check
12383       if(horz) {
12384         if(mpos.x < x || mpos.x > x + width
12385             || mpos.y > y + height || mpos.y < y) {
12386             return false;
12387           }
12388       } else {
12389         if(mpos.x < x || mpos.x > x + width
12390             || mpos.y > y || mpos.y < y - height) {
12391             return false;
12392           }
12393       }
12394       //deep check
12395       for(var i=0, l=dimArray.length; i<l; i++) {
12396         var dimi = dimArray[i];
12397                 var url = Url.decode(node.getData('linkArray')[i]);
12398         if(horz) {
12399           var limit = y + fixedDim * i;
12400           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12401             return {
12402               'name': node.getData('stringArray')[i],
12403               'color': node.getData('colorArray')[i],
12404               'value': node.getData('valueArray')[i],
12405                           'valuelabel': node.getData('valuelabelArray')[i],
12406                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12407               'link': url,
12408               'label': node.name
12409             };
12410           }
12411         } else {
12412           var limit = x + fixedDim * i;
12413           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12414             return {
12415               'name': node.getData('stringArray')[i],
12416               'color': node.getData('colorArray')[i],
12417               'value': node.getData('valueArray')[i],
12418                           'valuelabel': node.getData('valuelabelArray')[i],
12419                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12420               'link': url,
12421               'label': node.name
12422             };
12423           }
12424         }
12425       }
12426       return false;
12427     }
12428   }
12429 });
12430
12431 /*
12432   Class: BarChart
12433   
12434   A visualization that displays stacked bar charts.
12435   
12436   Constructor Options:
12437   
12438   See <Options.BarChart>.
12439
12440 */
12441 $jit.BarChart = new Class({
12442   st: null,
12443   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12444   selected: {},
12445   busy: false,
12446   
12447   initialize: function(opt) {
12448     this.controller = this.config = 
12449       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12450         Label: { type: 'Native' }
12451       }, opt);
12452     //set functions for showLabels and showAggregates
12453     var showLabels = this.config.showLabels,
12454         typeLabels = $.type(showLabels),
12455         showAggregates = this.config.showAggregates,
12456         typeAggregates = $.type(showAggregates);
12457     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12458     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12459     Options.Fx.clearCanvas = false;
12460     this.initializeViz();
12461   },
12462   
12463   initializeViz: function() {
12464     var config = this.config, that = this;
12465     var nodeType = config.type.split(":")[0],
12466         horz = config.orientation == 'horizontal',
12467         nodeLabels = {};
12468     var st = new $jit.ST({
12469       injectInto: config.injectInto,
12470       orientation: horz? 'left' : 'bottom',
12471       background: config.background,
12472       renderBackground: config.renderBackground,
12473       backgroundColor: config.backgroundColor,
12474       colorStop1: config.colorStop1,
12475       colorStop2: config.colorStop2,
12476       levelDistance: 0,
12477       nodeCount: config.nodeCount,
12478       siblingOffset: config.barsOffset,
12479       subtreeOffset: 0,
12480       withLabels: config.Label.type != 'Native',      
12481       useCanvas: config.useCanvas,
12482       Label: {
12483         type: config.Label.type
12484       },
12485       Node: {
12486         overridable: true,
12487         type: 'barchart-' + nodeType,
12488         align: 'left',
12489         width: 1,
12490         height: 1
12491       },
12492       Edge: {
12493         type: 'none'
12494       },
12495       Tips: {
12496         enable: config.Tips.enable,
12497         type: 'Native',
12498         force: true,
12499         onShow: function(tip, node, contains) {
12500           var elem = contains;
12501           config.Tips.onShow(tip, elem, node);
12502                           if(elem.link != 'undefined' && elem.link != '') {
12503                                 document.body.style.cursor = 'pointer';
12504                           }
12505         },
12506                 onHide: function(call) {
12507                         document.body.style.cursor = 'default';
12508
12509         }
12510       },
12511       Events: {
12512         enable: true,
12513         type: 'Native',
12514         onClick: function(node, eventInfo, evt) {
12515           if(!config.Events.enable) return;
12516           var elem = eventInfo.getContains();
12517           config.Events.onClick(elem, eventInfo, evt);
12518         },
12519         onMouseMove: function(node, eventInfo, evt) {
12520           if(!config.hoveredColor) return;
12521           if(node) {
12522             var elem = eventInfo.getContains();
12523             that.select(node.id, elem.name, elem.index);
12524           } else {
12525             that.select(false, false, false);
12526           }
12527         }
12528       },
12529       onCreateLabel: function(domElement, node) {
12530         var labelConf = config.Label,
12531             valueArray = node.getData('valueArray'),
12532             acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12533             grouped = config.type.split(':')[0] == 'grouped',
12534             horz = config.orientation == 'horizontal';
12535         var nlbs = {
12536           wrapper: document.createElement('div'),
12537           aggregate: document.createElement('div'),
12538           label: document.createElement('div')
12539         };
12540         
12541         var wrapper = nlbs.wrapper,
12542             label = nlbs.label,
12543             aggregate = nlbs.aggregate,
12544             wrapperStyle = wrapper.style,
12545             labelStyle = label.style,
12546             aggregateStyle = aggregate.style;
12547         //store node labels
12548         nodeLabels[node.id] = nlbs;
12549         //append labels
12550         wrapper.appendChild(label);
12551         wrapper.appendChild(aggregate);
12552         if(!config.showLabels(node.name, acum, node)) {
12553           labelStyle.display = 'none';
12554         }
12555         if(!config.showAggregates(node.name, acum, node)) {
12556           aggregateStyle.display = 'none';
12557         }
12558         wrapperStyle.position = 'relative';
12559         wrapperStyle.overflow = 'visible';
12560         wrapperStyle.fontSize = labelConf.size + 'px';
12561         wrapperStyle.fontFamily = labelConf.family;
12562         wrapperStyle.color = labelConf.color;
12563         wrapperStyle.textAlign = 'center';
12564         aggregateStyle.position = labelStyle.position = 'absolute';
12565         
12566         domElement.style.width = node.getData('width') + 'px';
12567         domElement.style.height = node.getData('height') + 'px';
12568         aggregateStyle.left = "0px";
12569         labelStyle.left =  config.labelOffset + 'px';
12570         labelStyle.whiteSpace =  "nowrap";
12571                 label.innerHTML = node.name;       
12572         
12573         domElement.appendChild(wrapper);
12574       },
12575       onPlaceLabel: function(domElement, node) {
12576         if(!nodeLabels[node.id]) return;
12577         var labels = nodeLabels[node.id],
12578             wrapperStyle = labels.wrapper.style,
12579             labelStyle = labels.label.style,
12580             aggregateStyle = labels.aggregate.style,
12581             grouped = config.type.split(':')[0] == 'grouped',
12582             horz = config.orientation == 'horizontal',
12583             dimArray = node.getData('dimArray'),
12584             valArray = node.getData('valueArray'),
12585             nodeCount = node.getData('nodeCount'),
12586             valueLength = valArray.length;
12587             valuelabelArray = node.getData('valuelabelArray'),
12588             stringArray = node.getData('stringArray'),
12589             width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12590             height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12591             font = parseInt(wrapperStyle.fontSize, 10),
12592             domStyle = domElement.style,
12593             fixedDim = (horz? height : width) / valueLength;
12594             
12595         
12596         if(dimArray && valArray) {
12597           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12598           
12599           aggregateStyle.width = width  - config.labelOffset + "px";
12600           for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12601             if(dimArray[i] > 0) {
12602               acum+= valArray[i];
12603             }
12604           }
12605           if(config.showLabels(node.name, acum, node)) {
12606             labelStyle.display = '';
12607           } else {
12608             labelStyle.display = 'none';
12609           }
12610           if(config.showAggregates(node.name, acum, node)) {
12611             aggregateStyle.display = '';
12612           } else {
12613             aggregateStyle.display = 'none';
12614           }
12615           if(config.orientation == 'horizontal') {
12616             aggregateStyle.textAlign = 'right';
12617             labelStyle.textAlign = 'left';
12618             labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12619             aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12620             domElement.style.height = wrapperStyle.height = height + 'px';
12621           } else {
12622             aggregateStyle.top = (-font - config.labelOffset) + 'px';
12623             labelStyle.top = (config.labelOffset + height) + 'px';
12624             domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12625             domElement.style.height = wrapperStyle.height = height + 'px';
12626             if(stringArray.length > 8) {
12627                 labels.label.className = "rotatedLabelReverse";
12628                 labelStyle.textAlign = "left";
12629                 labelStyle.top = config.labelOffset + height + width/2 + "px";
12630             }
12631           }
12632           
12633           if(horz) {
12634
12635                         labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12636                         labels.aggregate.innerHTML = "";
12637
12638           } else {
12639                 
12640                         if(grouped) {
12641                                 maxValue = Math.max.apply(null,dimArray);
12642                                 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12643                                         valueLabelDim = 50;
12644                                         valueLabel = document.createElement('div');
12645                                         valueLabel.innerHTML =  valuelabelArray[i];
12646 //                                      valueLabel.class = "rotatedLabel";
12647                                         valueLabel.className = "rotatedLabel";
12648                                         valueLabel.style.position = "absolute";
12649                                                 valueLabel.style.textAlign = "left";
12650                                                 valueLabel.style.verticalAlign = "middle";
12651                                         valueLabel.style.height = valueLabelDim + "px";
12652                                         valueLabel.style.width = valueLabelDim + "px";
12653                                         valueLabel.style.top =  (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12654                                         valueLabel.style.left = (fixedDim * i) + "px";
12655                                         labels.wrapper.appendChild(valueLabel);
12656                                 }
12657                         } else {
12658                                 labels.aggregate.innerHTML = acum;
12659                         }
12660           }
12661         }
12662       }
12663     });
12664
12665     var size = st.canvas.getSize(),
12666         l = config.nodeCount,
12667         margin = config.Margin;
12668         title = config.Title;
12669         subtitle = config.Subtitle,
12670         grouped = config.type.split(':')[0] == 'grouped',
12671         margin = config.Margin,
12672         ticks = config.Ticks,
12673         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12674         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12675         horz = config.orientation == 'horizontal',
12676         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12677         fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12678         whiteSpace = size.width - (marginWidth + (fixedDim * l));
12679         //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
12680         if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12681         location.reload();
12682         //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12683         if(!grouped && !horz) {
12684                 st.config.siblingOffset = whiteSpace/(l+1);
12685         }
12686         
12687         
12688         
12689         //Bars offset
12690     if(horz) {
12691       st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);    
12692           if(config.Ticks.enable)       {
12693                 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;
12694           } else {
12695                 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12696           }
12697     } else {
12698       st.config.offsetY = -size.height/2 + margin.bottom 
12699         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12700           if(config.Ticks.enable)       {
12701                 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12702           } else {
12703                 st.config.offsetX = (margin.right - margin.left)/2;
12704           }
12705     }
12706     this.st = st;
12707     this.canvas = this.st.canvas;
12708   },
12709   
12710  
12711   
12712   renderTitle: function() {
12713         var canvas = this.canvas,
12714         size = canvas.getSize(),
12715         config = this.config,
12716         margin = config.Margin,
12717         label = config.Label,
12718         title = config.Title;
12719         ctx = canvas.getCtx();
12720         ctx.fillStyle = title.color;
12721         ctx.textAlign = 'left';
12722         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12723         if(label.type == 'Native') {
12724                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12725         }
12726   },  
12727   
12728   renderSubtitle: function() {
12729         var canvas = this.canvas,
12730         size = canvas.getSize(),
12731         config = this.config,
12732         margin = config.Margin,
12733         label = config.Label,
12734         subtitle = config.Subtitle,
12735         nodeCount = config.nodeCount,
12736         horz = config.orientation == 'horizontal' ? true : false,
12737         ctx = canvas.getCtx();
12738         ctx.fillStyle = title.color;
12739         ctx.textAlign = 'left';
12740         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12741         if(label.type == 'Native') {
12742                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12743         }
12744   },
12745   
12746   renderScrollNote: function() {
12747         var canvas = this.canvas,
12748         size = canvas.getSize(),
12749         config = this.config,
12750         margin = config.Margin,
12751         label = config.Label,
12752         note = config.ScrollNote;
12753         ctx = canvas.getCtx();
12754         ctx.fillStyle = title.color;
12755         title = config.Title;
12756         ctx.textAlign = 'center';
12757         ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12758         if(label.type == 'Native') {
12759                 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12760         }
12761   },  
12762   
12763   renderTicks: function() {
12764
12765         var canvas = this.canvas,
12766         size = canvas.getSize(),
12767         config = this.config,
12768         margin = config.Margin,
12769         ticks = config.Ticks,
12770         title = config.Title,
12771         subtitle = config.Subtitle,
12772         label = config.Label,
12773         shadow = config.shadow;
12774         horz = config.orientation == 'horizontal',
12775         maxValue = this.getMaxValue(),
12776         maxTickValue = Math.ceil(maxValue*.1)*10;
12777         if(maxTickValue == maxValue) {
12778                 var length = maxTickValue.toString().length;
12779                 maxTickValue = maxTickValue + parseInt(pad(1,length));
12780         }
12781         grouped = config.type.split(':')[0] == 'grouped',
12782         labelValue = 0,
12783         labelIncrement = maxTickValue/ticks.segments,
12784         ctx = canvas.getCtx();
12785         ctx.strokeStyle = ticks.color;
12786     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12787
12788         ctx.textAlign = 'center';
12789         ctx.textBaseline = 'middle';
12790         
12791         idLabel = canvas.id + "-label";
12792         labelDim = 100;
12793         container = document.getElementById(idLabel);
12794                   
12795                   
12796         if(horz) {
12797                 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12798                 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12799                 segmentLength = grid/ticks.segments;
12800                 ctx.fillStyle = ticks.color;
12801                 ctx.fillRect(axis,
12802                  (size.height/2)-margin.bottom-config.labelOffset-label.size - (subtitle.text? subtitle.size+subtitle.offset:0) + (shadow.enable ? shadow.size : 0),
12803                  size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0),
12804                  1);
12805                 while(axis<=grid) {
12806                         ctx.fillStyle = ticks.color;
12807                         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);
12808                         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));
12809                         ctx.fillStyle = label.color;
12810                         
12811                         if(label.type == 'Native' && config.showLabels) {            
12812                  ctx.fillText(labelValue, Math.round(axis), -(size.height/2)+margin.top+(title.text? title.size+title.offset:0)+config.labelOffset+lineHeight+label.size);
12813                         }
12814                         axis += segmentLength;
12815                         labelValue += labelIncrement;
12816                 }
12817         
12818         } else {
12819         
12820                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12821                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
12822                 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)),
12823                 segmentLength = grid/ticks.segments;
12824                 ctx.fillStyle = ticks.color;
12825                 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));
12826
12827                 while(axis>=grid) {
12828                         ctx.save();
12829                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
12830                         ctx.rotate(0 * Math.PI / 180 );
12831                         ctx.fillStyle = label.color;
12832                         if(config.showLabels) {
12833                                 if(label.type == 'Native') { 
12834                                         ctx.fillText(labelValue, 0, 0);
12835                                 } else {
12836                                         //html labels on y axis
12837                                         labelDiv = document.createElement('div');
12838                                         labelDiv.innerHTML = labelValue;
12839                                         labelDiv.className = "rotatedLabel";
12840 //                                      labelDiv.class = "rotatedLabel";
12841                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
12842                                         labelDiv.style.left = margin.left + "px";
12843                                         labelDiv.style.width = labelDim + "px";
12844                                         labelDiv.style.height = labelDim + "px";
12845                                         labelDiv.style.textAlign = "center";
12846                                         labelDiv.style.verticalAlign = "middle";
12847                                         labelDiv.style.position = "absolute";
12848                                         container.appendChild(labelDiv);
12849                                 }
12850                         }
12851                         ctx.restore();
12852                         ctx.fillStyle = ticks.color;
12853                         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 );
12854                         htmlOrigin += segmentLength;
12855                         axis += segmentLength;
12856                         labelValue += labelIncrement;
12857                 }
12858         }
12859         
12860         
12861         
12862
12863   },
12864   
12865   renderBackground: function() {
12866                 var canvas = this.canvas,
12867                 config = this.config,
12868                 backgroundColor = config.backgroundColor,
12869                 size = canvas.getSize(),
12870                 ctx = canvas.getCtx();
12871             ctx.fillStyle = backgroundColor;
12872             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
12873   },
12874   
12875   clear: function() {
12876         var canvas = this.canvas;
12877         var ctx = canvas.getCtx(),
12878         size = canvas.getSize();
12879         ctx.fillStyle = "rgba(255,255,255,0)";
12880         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
12881         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
12882  },
12883   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
12884         var canvas = this.canvas,
12885         size = canvas.getSize(),
12886         config = this.config,
12887         orgHeight = size.height,
12888         margin = config.Margin,
12889         st = this.st,
12890         grouped = config.type.split(':')[0] == 'grouped',
12891         horz = config.orientation == 'horizontal',
12892                 ctx = canvas.getCtx();
12893         
12894         var newWindowWidth = document.body.offsetWidth;
12895         var diff = newWindowWidth - orgWindowWidth;     
12896         var newWidth = orgContainerDivWidth + (diff/cols);
12897         var scale = newWidth/orgContainerDivWidth;
12898         canvas.resize(newWidth,orgHeight);
12899         if(typeof FlashCanvas == "undefined") {
12900                 canvas.clear();
12901         } else {
12902                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
12903         }
12904         if(horz) {
12905                 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
12906         }
12907         
12908         this.loadJSON(json);
12909
12910         
12911         },
12912   /*
12913     Method: loadJSON
12914    
12915     Loads JSON data into the visualization. 
12916     
12917     Parameters:
12918     
12919     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>.
12920     
12921     Example:
12922     (start code js)
12923     var barChart = new $jit.BarChart(options);
12924     barChart.loadJSON(json);
12925     (end code)
12926  */  
12927   loadJSON: function(json) {
12928     if(this.busy) return;
12929     this.busy = true;
12930     
12931     var prefix = $.time(), 
12932         ch = [], 
12933         st = this.st,
12934         name = $.splat(json.label), 
12935         color = $.splat(json.color || this.colors),
12936         config = this.config,
12937         gradient = !!config.type.split(":")[1],
12938         renderBackground = config.renderBackground,
12939         animate = config.animate,
12940         ticks = config.Ticks,
12941         title = config.Title,
12942         note = config.ScrollNote,
12943         subtitle = config.Subtitle,
12944         horz = config.orientation == 'horizontal',
12945         that = this,
12946                 colorLength = color.length,
12947                 nameLength = name.length;
12948         groupTotalValue = 0;
12949     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12950         var val = values[i];
12951         var valArray = $.splat(val.values);
12952         groupTotalValue += parseFloat(valArray.sum());
12953     }
12954
12955     for(var i=0, values=json.values, l=values.length; i<l; i++) {
12956       var val = values[i];
12957       var valArray = $.splat(values[i].values);
12958       var valuelabelArray = $.splat(values[i].valuelabels);
12959       var linkArray = $.splat(values[i].links);
12960       var titleArray = $.splat(values[i].titles);
12961       var barTotalValue = valArray.sum();
12962       var acum = 0;
12963       ch.push({
12964         'id': prefix + val.label,
12965         'name': val.label,
12966         
12967         'data': {
12968           'value': valArray,
12969           '$linkArray': linkArray,
12970                   '$gvl': val.gvaluelabel,
12971           '$titleArray': titleArray,
12972           '$valueArray': valArray,
12973           '$valuelabelArray': valuelabelArray,
12974           '$colorArray': color,
12975           '$colorMono': $.splat(color[i % colorLength]),
12976           '$stringArray': name,
12977           '$barTotalValue': barTotalValue,
12978           '$groupTotalValue': groupTotalValue,
12979           '$nodeCount': values.length,
12980           '$gradient': gradient,
12981           '$config': config
12982         },
12983         'children': []
12984       });
12985     }
12986     var root = {
12987       'id': prefix + '$root',
12988       'name': '',
12989       'data': {
12990         '$type': 'none',
12991         '$width': 1,
12992         '$height': 1
12993       },
12994       'children': ch
12995     };
12996     st.loadJSON(root);
12997     
12998     this.normalizeDims();
12999     
13000     if(renderBackground) {
13001                 this.renderBackground();
13002     }
13003         
13004         if(!animate && ticks.enable) {
13005                 this.renderTicks();
13006         }
13007         if(!animate && note.text) {
13008                 this.renderScrollNote();
13009         }
13010         if(!animate && title.text) {
13011                 this.renderTitle();
13012         }
13013         if(!animate && subtitle.text) {
13014                 this.renderSubtitle();
13015         }
13016
13017     st.compute();
13018     st.select(st.root);
13019     if(animate) {
13020       if(horz) {
13021         st.fx.animate({
13022           modes: ['node-property:width:dimArray'],
13023           duration:1500,
13024           onComplete: function() {
13025             that.busy = false;
13026           }
13027         });
13028       } else {
13029         st.fx.animate({
13030           modes: ['node-property:height:dimArray'],
13031           duration:1500,
13032           onComplete: function() {
13033             that.busy = false;
13034           }
13035         });
13036       }
13037     } else {
13038       this.busy = false;
13039     }
13040   },
13041   
13042   /*
13043     Method: updateJSON
13044    
13045     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.
13046     
13047     Parameters:
13048     
13049     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13050     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13051     
13052     Example:
13053     
13054     (start code js)
13055     barChart.updateJSON(json, {
13056       onComplete: function() {
13057         alert('update complete!');
13058       }
13059     });
13060     (end code)
13061  */  
13062   updateJSON: function(json, onComplete) {
13063     if(this.busy) return;
13064     this.busy = true;
13065     
13066     var st = this.st;
13067     var graph = st.graph;
13068     var values = json.values;
13069     var animate = this.config.animate;
13070     var that = this;
13071     var horz = this.config.orientation == 'horizontal';
13072     $.each(values, function(v) {
13073       var n = graph.getByName(v.label);
13074       if(n) {
13075         n.setData('valueArray', $.splat(v.values));
13076         if(json.label) {
13077           n.setData('stringArray', $.splat(json.label));
13078         }
13079       }
13080     });
13081     this.normalizeDims();
13082     st.compute();
13083     st.select(st.root);
13084     if(animate) {
13085       if(horz) {
13086         st.fx.animate({
13087           modes: ['node-property:width:dimArray'],
13088           duration:1500,
13089           onComplete: function() {
13090             that.busy = false;
13091             onComplete && onComplete.onComplete();
13092           }
13093         });
13094       } else {
13095         st.fx.animate({
13096           modes: ['node-property:height:dimArray'],
13097           duration:1500,
13098           onComplete: function() {
13099             that.busy = false;
13100             onComplete && onComplete.onComplete();
13101           }
13102         });
13103       }
13104     }
13105   },
13106   
13107   //adds the little brown bar when hovering the node
13108   select: function(id, name) {
13109
13110     if(!this.config.hoveredColor) return;
13111     var s = this.selected;
13112     if(s.id != id || s.name != name) {
13113       s.id = id;
13114       s.name = name;
13115       s.color = this.config.hoveredColor;
13116       this.st.graph.eachNode(function(n) {
13117         if(id == n.id) {
13118           n.setData('border', s);
13119         } else {
13120           n.setData('border', false);
13121         }
13122       });
13123       this.st.plot();
13124     }
13125   },
13126   
13127   /*
13128     Method: getLegend
13129    
13130     Returns an object containing as keys the legend names and as values hex strings with color values.
13131     
13132     Example:
13133     
13134     (start code js)
13135     var legend = barChart.getLegend();
13136     (end code)
13137   */  
13138   getLegend: function() {
13139     var legend = new Array();
13140     var name = new Array();
13141     var color = new Array();
13142     var n;
13143     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13144       n = adj.nodeTo;
13145     });
13146     var colors = n.getData('colorArray'),
13147         len = colors.length;
13148     $.each(n.getData('stringArray'), function(s, i) {
13149       color[i] = colors[i % len];
13150       name[i] = s;
13151     });
13152         legend['name'] = name;
13153         legend['color'] = color;
13154     return legend;
13155   },
13156   
13157   /*
13158     Method: getMaxValue
13159    
13160     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13161     
13162     Example:
13163     
13164     (start code js)
13165     var ans = barChart.getMaxValue();
13166     (end code)
13167     
13168     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13169     
13170     Example:
13171     
13172     (start code js)
13173     //will return 100 for all BarChart instances,
13174     //displaying all of them with the same scale
13175     $jit.BarChart.implement({
13176       'getMaxValue': function() {
13177         return 100;
13178       }
13179     });
13180     (end code)
13181     
13182   */  
13183   getMaxValue: function() {
13184     var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13185     this.st.graph.eachNode(function(n) {
13186       var valArray = n.getData('valueArray'),
13187           acum = 0;
13188       if(!valArray) return;
13189       if(stacked) {
13190         $.each(valArray, function(v) { 
13191           acum += +v;
13192         });
13193       } else {
13194         acum = Math.max.apply(null, valArray);
13195       }
13196       maxValue = maxValue>acum? maxValue:acum;
13197     });
13198     return maxValue;
13199   },
13200   
13201   setBarType: function(type) {
13202     this.config.type = type;
13203     this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13204   },
13205   
13206   normalizeDims: function() {
13207     //number of elements
13208     var root = this.st.graph.getNode(this.st.root), l=0;
13209     root.eachAdjacency(function() {
13210       l++;
13211     });
13212     var maxValue = this.getMaxValue() || 1,
13213         size = this.st.canvas.getSize(),
13214         config = this.config,
13215         margin = config.Margin,
13216         ticks = config.Ticks,
13217         title = config.Title,
13218         subtitle = config.Subtitle,
13219         grouped = config.type.split(':')[0] == 'grouped',
13220         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13221         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13222         horz = config.orientation == 'horizontal',
13223         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13224         animate = config.animate,
13225         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13226
13227           - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13228         dim1 = horz? 'height':'width',
13229         dim2 = horz? 'width':'height',
13230         basic = config.type.split(':')[0] == 'basic';
13231         
13232         
13233                 var maxTickValue = Math.ceil(maxValue*.1)*10;
13234                 if(maxTickValue == maxValue) {
13235                         var length = maxTickValue.toString().length;
13236                         maxTickValue = maxTickValue + parseInt(pad(1,length));
13237                 }
13238
13239                 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13240
13241                 
13242     this.st.graph.eachNode(function(n) {
13243       var acum = 0, animateValue = [];
13244       $.each(n.getData('valueArray'), function(v) {
13245         acum += +v;
13246         animateValue.push(0);
13247       });
13248       
13249       if(grouped) {
13250         fixedDim = animateValue.length * 40;
13251       }
13252       n.setData(dim1, fixedDim);
13253       
13254       
13255       if(animate) {
13256         n.setData(dim2, acum * height / maxValue, 'end');
13257         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13258           return n * height / maxValue; 
13259         }), 'end');
13260         var dimArray = n.getData('dimArray');
13261         if(!dimArray) {
13262           n.setData('dimArray', animateValue);
13263         }
13264       } else {
13265         
13266
13267                 if(ticks.enable) {
13268                         n.setData(dim2, acum * height / maxTickValue);
13269                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13270                           return n * height / maxTickValue; 
13271                         }));
13272                 } else {
13273                         n.setData(dim2, acum * height / maxValue);
13274                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13275                           return n * height / maxValue; 
13276                         }));
13277                 }
13278       }
13279     });
13280   }
13281 });
13282
13283 //funnel chart options
13284
13285
13286 Options.FunnelChart = {
13287   $extend: true,
13288   
13289   animate: true,
13290   type: 'stacked', //stacked, grouped, : gradient
13291   labelOffset: 3, //label offset
13292   barsOffset: 0, //distance between bars
13293   hoveredColor: '#9fd4ff',
13294   orientation: 'vertical',
13295   showAggregates: true,
13296   showLabels: true,
13297   Tips: {
13298     enable: false,
13299     onShow: $.empty,
13300     onHide: $.empty
13301   },
13302   Events: {
13303     enable: false,
13304     onClick: $.empty
13305   }
13306 };
13307
13308 $jit.ST.Plot.NodeTypes.implement({
13309   'funnelchart-basic' : {
13310     'render' : function(node, canvas) {
13311       var pos = node.pos.getc(true), 
13312           width  = node.getData('width'),
13313           height = node.getData('height'),
13314           algnPos = this.getAlignedPos(pos, width, height),
13315           x = algnPos.x, y = algnPos.y,
13316           dimArray = node.getData('dimArray'),
13317           valueArray = node.getData('valueArray'),
13318           valuelabelArray = node.getData('valuelabelArray'),
13319           linkArray = node.getData('linkArray'),
13320           colorArray = node.getData('colorArray'),
13321           colorLength = colorArray.length,
13322           stringArray = node.getData('stringArray');
13323       var ctx = canvas.getCtx(),
13324           opt = {},
13325           border = node.getData('border'),
13326           gradient = node.getData('gradient'),
13327           config = node.getData('config'),
13328           horz = config.orientation == 'horizontal',
13329           aggregates = config.showAggregates,
13330           showLabels = config.showLabels,
13331           label = config.Label,
13332           size = canvas.getSize(),
13333           labelOffset = config.labelOffset + 10;
13334           minWidth =  width * .25;
13335           ratio = .65;
13336
13337       if (colorArray && dimArray && stringArray) {
13338         
13339         
13340         // horizontal lines
13341         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13342         ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13343
13344         if(label.type == 'Native') {      
13345        if(showLabels(node.name, valAcum, node)) {
13346                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13347                  var stringValue = stringArray[i];
13348                  var valueLabel = String(valuelabelArray[i]);
13349              var mV = ctx.measureText(stringValue);
13350              var mVL = ctx.measureText(valueLabel);
13351                  var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13352                          var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13353                          var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13354                  var bottomWidth = minWidth + ((acum) * ratio);  
13355                  var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);  
13356                          var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13357                          var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13358 //             ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13359
13360                         //right lines
13361                         ctx.beginPath();
13362                         ctx.moveTo(bottomWidth/2,y - acum); //
13363                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13364                         ctx.lineTo(bottomWidthLabel/2 + (labelOffset) + labelOffsetRight + mV.width,y - acum - labelOffsetHeight);  // bottom right
13365                         ctx.stroke();
13366                         //left lines
13367                         ctx.beginPath();
13368                         ctx.moveTo(-bottomWidth/2,y - acum); //
13369                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset-10),y - acum - labelOffsetHeight);  // top right
13370                         ctx.lineTo(-bottomWidthLabel/2 - (labelOffset) - labelOffsetLeft -mVL.width,y - acum - labelOffsetHeight);  // bottom right
13371                         ctx.stroke();
13372        }
13373         }
13374
13375                 acum += (dimArray[i] || 0);
13376           valAcum += (valueArray[i] || 0);
13377           
13378           
13379                 }
13380                 
13381  
13382   
13383         //funnel segments and labels
13384         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13385           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13386                           var colori = colorArray[i % colorLength];
13387                           if(label.type == 'Native') { 
13388                                   var stringValue = stringArray[i];
13389                           var valueLabel = String(valuelabelArray[i]);
13390                               var mV = ctx.measureText(stringValue);
13391                       var mVL = ctx.measureText(valueLabel);
13392                           } else {
13393                                   var mV = 10;
13394                       var mVL = 10;     
13395                           }
13396                       var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13397                       var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13398                       var labelOffsetRight = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mV.width + 20 : 0) : 0;
13399                       var labelOffsetLeft = (previousElementHeight < label.size && i > 0) ? ((i%2!=0 && dimArray[i] < label.size) ? mVL.width + 20 : 0) : 0;
13400                       
13401           var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13402           var bottomWidth = minWidth + ((acum) * ratio);
13403           var bottomWidthLabel = minWidth + ((acum + labelOffsetHeight) * ratio);
13404           
13405
13406           if(gradient) {
13407             var linear;
13408               linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13409                         var colorRgb = $.hexToRgb(colori);
13410             var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
13411                 function(v) { return (v * .5) >> 0; });
13412             linear.addColorStop(0, 'rgba('+color+',1)');
13413             linear.addColorStop(0.5,  'rgba('+colorRgb+',1)');
13414             linear.addColorStop(1, 'rgba('+color+',1)');
13415             ctx.fillStyle = linear;
13416           }
13417           
13418                         ctx.beginPath();
13419                         ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13420                         ctx.lineTo(topWidth/2,y - acum - dimArray[i]);  // top right
13421                         ctx.lineTo(bottomWidth/2,y - acum);  // bottom right
13422                         ctx.lineTo(-bottomWidth/2,y - acum);  // bottom left
13423                         ctx.closePath(); 
13424                         ctx.fill();
13425                 
13426           
13427           if(border && border.name == stringArray[i]) {
13428             opt.acum = acum;
13429             opt.dimValue = dimArray[i];
13430           }
13431           
13432           
13433         if(border) {
13434           ctx.save();
13435           ctx.lineWidth = 2;
13436           ctx.strokeStyle = border.color;
13437
13438             //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13439          
13440           ctx.restore();
13441         }
13442         if(label.type == 'Native') {
13443           ctx.save();
13444           ctx.fillStyle = ctx.strokeStyle = label.color;
13445           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13446           ctx.textBaseline = 'middle';
13447
13448                                 acumValueLabel = valAcum;
13449
13450           if(showLabels(node.name, valAcum, node)) {
13451
13452                       
13453               ctx.textAlign = 'left';
13454               ctx.fillText(stringArray[i],(bottomWidthLabel/2) + labelOffset + labelOffsetRight, y - acum - labelOffsetHeight - label.size/2);
13455               ctx.textAlign = 'right';
13456               ctx.fillText(valuelabelArray[i],(-bottomWidthLabel/2) - labelOffset - labelOffsetLeft, y - acum - labelOffsetHeight - label.size/2);
13457               }
13458           ctx.restore();
13459         }
13460
13461           acum += (dimArray[i] || 0);
13462           valAcum += (valueArray[i] || 0);
13463           
13464         }
13465
13466       }
13467     },
13468     'contains': function(node, mpos) {
13469       var pos = node.pos.getc(true), 
13470           width = node.getData('width'),
13471           height = node.getData('height'),
13472           algnPos = this.getAlignedPos(pos, width, height),
13473           x = algnPos.x, y = algnPos.y,
13474           dimArray = node.getData('dimArray'),
13475           config = node.getData('config'),
13476           st = node.getData('st'),
13477           rx = mpos.x - x,
13478           horz = config.orientation == 'horizontal',
13479            minWidth =  width * .25;
13480           ratio = .65,
13481           canvas = node.getData('canvas'),
13482           size = canvas.getSize(),
13483           offsetY = st.config.offsetY;
13484       //bounding box check
13485
13486         if(mpos.y > y || mpos.y < y - height) {
13487             return false;
13488           }
13489           
13490          var newY = Math.abs(mpos.y + offsetY);
13491         var bound = minWidth + (newY * ratio);
13492         var boundLeft = -bound/2;
13493         var boundRight = bound/2;
13494          if(mpos.x < boundLeft || mpos.x > boundRight ) {
13495             return false;
13496           }
13497
13498       
13499       //deep check
13500       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13501         var dimi = dimArray[i];
13502
13503           
13504           
13505                 var url = Url.decode(node.getData('linkArray')[i]);
13506           acum -= dimi;  
13507           var intersec = acum;
13508           if(mpos.y >= intersec) {
13509             return {
13510               'name': node.getData('stringArray')[i],
13511               'color': node.getData('colorArray')[i],
13512               'value': node.getData('valueArray')[i],
13513               'percentage': node.getData('percentageArray')[i],
13514                           'valuelabel': node.getData('valuelabelArray')[i],
13515               'link': url,
13516               'label': node.name
13517             };
13518           }
13519         
13520       }
13521       return false;
13522     }
13523   }
13524 });
13525
13526 /*
13527   Class: FunnelChart
13528   
13529   A visualization that displays funnel charts.
13530   
13531   Constructor Options:
13532   
13533   See <Options.FunnelChart>.
13534
13535 */
13536 $jit.FunnelChart = new Class({
13537   st: null,
13538   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13539   selected: {},
13540   busy: false,
13541   
13542   initialize: function(opt) {
13543     this.controller = this.config = 
13544       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13545         Label: { type: 'Native' }
13546       }, opt);
13547     //set functions for showLabels and showAggregates
13548     var showLabels = this.config.showLabels,
13549         typeLabels = $.type(showLabels),
13550         showAggregates = this.config.showAggregates,
13551         typeAggregates = $.type(showAggregates);
13552     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13553     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13554     Options.Fx.clearCanvas = false;
13555     this.initializeViz();
13556   },
13557   
13558   initializeViz: function() {
13559     var config = this.config, that = this;
13560     var nodeType = config.type.split(":")[0],
13561         horz = config.orientation == 'horizontal',
13562         nodeLabels = {};
13563     var st = new $jit.ST({
13564       injectInto: config.injectInto,
13565       orientation: horz? 'left' : 'bottom',
13566       levelDistance: 0,
13567       background: config.background,
13568       renderBackground: config.renderBackground,
13569       backgroundColor: config.backgroundColor,
13570       colorStop1: config.colorStop1,
13571       colorStop2: config.colorStop2,
13572       siblingOffset: config.segmentOffset,
13573       subtreeOffset: 0,
13574       withLabels: config.Label.type != 'Native',      
13575       useCanvas: config.useCanvas,
13576       Label: {
13577         type: config.Label.type
13578       },
13579       Node: {
13580         overridable: true,
13581         type: 'funnelchart-' + nodeType,
13582         align: 'left',
13583         width: 1,
13584         height: 1
13585       },
13586       Edge: {
13587         type: 'none'
13588       },
13589       Tips: {
13590         enable: config.Tips.enable,
13591         type: 'Native',
13592         force: true,
13593         onShow: function(tip, node, contains) {
13594           var elem = contains;
13595           config.Tips.onShow(tip, elem, node);
13596                           if(elem.link != 'undefined' && elem.link != '') {
13597                                 document.body.style.cursor = 'pointer';
13598                           }
13599         },
13600                 onHide: function(call) {
13601                         document.body.style.cursor = 'default';
13602
13603         }
13604       },
13605       Events: {
13606         enable: true,
13607         type: 'Native',
13608         onClick: function(node, eventInfo, evt) {
13609           if(!config.Events.enable) return;
13610           var elem = eventInfo.getContains();
13611           config.Events.onClick(elem, eventInfo, evt);
13612         },
13613         onMouseMove: function(node, eventInfo, evt) {
13614           if(!config.hoveredColor) return;
13615           if(node) {
13616             var elem = eventInfo.getContains();
13617             that.select(node.id, elem.name, elem.index);
13618           } else {
13619             that.select(false, false, false);
13620           }
13621         }
13622       },
13623       onCreateLabel: function(domElement, node) {
13624         var labelConf = config.Label,
13625             valueArray = node.getData('valueArray'),
13626             idArray = node.getData('idArray'),
13627             valuelabelArray = node.getData('valuelabelArray'),
13628             stringArray = node.getData('stringArray');
13629             size = st.canvas.getSize()
13630             prefix = $.time();
13631                 
13632                 for(var i=0, l=valueArray.length; i<l; i++) {
13633         var nlbs = {
13634           wrapper: document.createElement('div'),
13635           valueLabel: document.createElement('div'),
13636           label: document.createElement('div')
13637         };
13638         var wrapper = nlbs.wrapper,
13639             label = nlbs.label,
13640             valueLabel = nlbs.valueLabel,
13641             wrapperStyle = wrapper.style,
13642             labelStyle = label.style,
13643             valueLabelStyle = valueLabel.style;
13644         //store node labels
13645         nodeLabels[idArray[i]] = nlbs;
13646         //append labels
13647         wrapper.appendChild(label);
13648         wrapper.appendChild(valueLabel);
13649
13650         wrapperStyle.position = 'relative';
13651         wrapperStyle.overflow = 'visible';
13652         wrapperStyle.fontSize = labelConf.size + 'px';
13653         wrapperStyle.fontFamily = labelConf.family;
13654         wrapperStyle.color = labelConf.color;
13655         wrapperStyle.textAlign = 'center';
13656         wrapperStyle.width = size.width + 'px';
13657         valueLabelStyle.position = labelStyle.position = 'absolute';
13658         valueLabelStyle.left = labelStyle.left =  '0px';
13659                 valueLabelStyle.width = (size.width/3) + 'px';
13660                 valueLabelStyle.textAlign = 'right';
13661         label.innerHTML = stringArray[i];
13662         valueLabel.innerHTML = valuelabelArray[i];
13663         domElement.id = prefix+'funnel';
13664         domElement.style.width = size.width + 'px';
13665         
13666                 domElement.appendChild(wrapper);
13667                 }
13668
13669       },
13670       onPlaceLabel: function(domElement, node) {
13671
13672             var dimArray = node.getData('dimArray'),
13673             idArray = node.getData('idArray'),
13674             valueArray = node.getData('valueArray'),
13675             valuelabelArray = node.getData('valuelabelArray'),
13676             stringArray = node.getData('stringArray');
13677             size = st.canvas.getSize(),
13678             pos = node.pos.getc(true),
13679              domElement.style.left = "0px",
13680              domElement.style.top = "0px",
13681              minWidth = node.getData('width') * .25,
13682              ratio = .65,
13683              pos = node.pos.getc(true),
13684              labelConf = config.Label;
13685              
13686              
13687                 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13688
13689         var labels = nodeLabels[idArray[i]],
13690             wrapperStyle = labels.wrapper.style,
13691             labelStyle = labels.label.style,
13692             valueLabelStyle = labels.valueLabel.style;
13693                 var bottomWidth = minWidth + (acum * ratio); 
13694                 
13695             font = parseInt(wrapperStyle.fontSize, 10),
13696             domStyle = domElement.style;
13697            
13698                 
13699        
13700                         wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
13701             valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
13702             labelStyle.left =  (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
13703
13704                         acum += (dimArray[i] || 0);
13705
13706                 }
13707
13708       }
13709
13710     });
13711
13712     var size = st.canvas.getSize(),
13713         margin = config.Margin;
13714         title = config.Title;
13715         subtitle = config.Subtitle;
13716         //y offset
13717
13718       st.config.offsetY = -size.height/2 + margin.bottom 
13719         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
13720
13721                 st.config.offsetX = (margin.right - margin.left)/2;
13722           
13723     
13724     this.st = st;
13725     this.canvas = this.st.canvas;
13726   },
13727   
13728   renderTitle: function() {
13729         var canvas = this.canvas,
13730         size = canvas.getSize(),
13731         config = this.config,
13732         margin = config.Margin,
13733         label = config.Label,
13734         title = config.Title;
13735         ctx = canvas.getCtx();
13736         ctx.fillStyle = title.color;
13737         ctx.textAlign = 'left';
13738         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
13739         if(label.type == 'Native') {
13740                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
13741         }
13742   },  
13743   
13744   renderSubtitle: function() {
13745         var canvas = this.canvas,
13746         size = canvas.getSize(),
13747         config = this.config,
13748         margin = config.Margin,
13749         label = config.Label,
13750         subtitle = config.Subtitle;
13751         ctx = canvas.getCtx();
13752         ctx.fillStyle = title.color;
13753         ctx.textAlign = 'left';
13754         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
13755         if(label.type == 'Native') {
13756                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
13757         }
13758   },
13759   
13760   
13761   renderDropShadow: function() {
13762         var canvas = this.canvas,
13763         size = canvas.getSize(),
13764         config = this.config,
13765         margin = config.Margin,
13766         horz = config.orientation == 'horizontal',
13767         label = config.Label,
13768         title = config.Title,
13769         shadowThickness = 4,
13770         subtitle = config.Subtitle,
13771         ctx = canvas.getCtx(),
13772         minwidth = (size.width/8) * .25,
13773         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13774         topMargin = (title.text? title.size + title.offset : 0)  + margin.top,
13775     height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13776           - (config.showLabels && (config.Label.size + config.labelOffset)),
13777     ratio = .65,
13778         topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
13779         topY = (-size.height/2) + topMargin - shadowThickness;
13780         bottomY = (-size.height/2) + topMargin + height + shadowThickness;
13781         bottomWidth = minwidth + shadowThickness;
13782         ctx.beginPath();
13783         ctx.fillStyle = "rgba(0,0,0,.2)";
13784         ctx.moveTo(0,topY);
13785         ctx.lineTo(-topWidth/2,topY); //top left
13786         ctx.lineTo(-bottomWidth/2,bottomY);  // bottom left
13787         ctx.lineTo(bottomWidth/2,bottomY);  // bottom right
13788         ctx.lineTo(topWidth/2,topY);  // top right
13789         ctx.closePath(); 
13790         ctx.fill();
13791                         
13792                         
13793   },
13794
13795    renderBackground: function() {
13796                 var canvas = this.canvas,
13797                 config = this.config,
13798                 backgroundColor = config.backgroundColor,
13799                 size = canvas.getSize(),
13800                 ctx = canvas.getCtx();
13801                 //ctx.globalCompositeOperation = "destination-over";
13802             ctx.fillStyle = backgroundColor;
13803             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
13804   },
13805   clear: function() {
13806         var canvas = this.canvas;
13807         var ctx = canvas.getCtx(),
13808         size = canvas.getSize();
13809         ctx.fillStyle = "rgba(255,255,255,0)";
13810         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13811         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13812   },
13813    resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
13814         var canvas = this.canvas,
13815         size = canvas.getSize(),
13816         config = this.config,
13817         orgHeight = size.height,
13818         margin = config.Margin,
13819         st = this.st,
13820         label = config.Label,
13821         horz = config.orientation == 'horizontal',
13822         ctx = canvas.getCtx();
13823         
13824
13825         var newWindowWidth = document.body.offsetWidth;
13826         var diff = newWindowWidth - orgWindowWidth;     
13827         var newWidth = orgContainerDivWidth + (diff/cols);
13828         canvas.resize(newWidth,orgHeight);
13829
13830         if(typeof FlashCanvas == "undefined") {
13831                 canvas.clear();
13832         } else {
13833                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13834         }
13835         this.loadJSON(json);
13836
13837         },
13838         
13839   loadJSON: function(json) {
13840     if(this.busy) return;
13841     this.busy = true;
13842     var prefix = $.time(), 
13843         ch = [], 
13844         st = this.st,
13845         name = $.splat(json.label), 
13846         color = $.splat(json.color || this.colors),
13847         config = this.config,
13848         canvas = this.canvas,
13849         gradient = !!config.type.split(":")[1],
13850         animate = config.animate,
13851         title = config.Title,
13852         subtitle = config.Subtitle,
13853         renderBackground = config.renderBackground,
13854         horz = config.orientation == 'horizontal',
13855         that = this,
13856                 colorLength = color.length,
13857                 nameLength = name.length,
13858                 totalValue = 0;
13859     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13860         var val = values[i];
13861         var valArray = $.splat(val.values);
13862         totalValue += parseFloat(valArray.sum());
13863     }
13864     
13865     
13866     var nameArray = new Array();
13867     var idArray = new Array();
13868     var valArray = new Array();
13869     var valuelabelArray = new Array();
13870     var linkArray = new Array();
13871     var titleArray = new Array();
13872     var percentageArray = new Array();
13873     
13874     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13875       var val = values[i];
13876       nameArray[i] = $.splat(val.label);
13877       idArray[i] = $.splat(prefix + val.label);
13878       valArray[i] = $.splat(val.values);
13879       valuelabelArray[i] = $.splat(val.valuelabels);
13880       linkArray[i] = $.splat(val.links);
13881       titleArray[i] = $.splat(val.titles);
13882       percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
13883       var acum = 0;
13884     }
13885     
13886
13887     nameArray.reverse();
13888     valArray.reverse();
13889     valuelabelArray.reverse();
13890     linkArray.reverse();
13891     titleArray.reverse();
13892     percentageArray.reverse();
13893     
13894       ch.push({
13895         'id': prefix + val.label,
13896         'name': val.label,
13897         
13898         'data': {
13899           'value': valArray,
13900           '$idArray': idArray,
13901           '$linkArray': linkArray,
13902           '$titleArray': titleArray,
13903           '$valueArray': valArray,
13904           '$valuelabelArray': valuelabelArray,
13905           '$colorArray': color,
13906           '$colorMono': $.splat(color[i % colorLength]),
13907           '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
13908           '$gradient': gradient,
13909           '$config': config,
13910           '$percentageArray' : percentageArray,
13911           '$canvas': canvas,
13912           '$st': st
13913         },
13914         'children': []
13915       });
13916     
13917     var root = {
13918       'id': prefix + '$root',
13919       'name': '',
13920       'data': {
13921         '$type': 'none',
13922         '$width': 1,
13923         '$height': 1
13924       },
13925       'children': ch
13926     };
13927     st.loadJSON(root);
13928     
13929     this.normalizeDims();
13930         
13931         if(renderBackground) {
13932                 this.renderBackground();        
13933         }
13934         if(!animate && title.text) {
13935                 this.renderTitle();
13936         }
13937         if(!animate && subtitle.text) {
13938                 this.renderSubtitle();
13939         }
13940         if(typeof FlashCanvas == "undefined") {
13941                 this.renderDropShadow();
13942         }
13943     st.compute();
13944     st.select(st.root);
13945     if(animate) {
13946       if(horz) {
13947         st.fx.animate({
13948           modes: ['node-property:width:dimArray'],
13949           duration:1500,
13950           onComplete: function() {
13951             that.busy = false;
13952           }
13953         });
13954       } else {
13955         st.fx.animate({
13956           modes: ['node-property:height:dimArray'],
13957           duration:1500,
13958           onComplete: function() {
13959             that.busy = false;
13960           }
13961         });
13962       }
13963     } else {
13964       this.busy = false;
13965     }
13966   },
13967   
13968   /*
13969     Method: updateJSON
13970    
13971     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.
13972     
13973     Parameters:
13974     
13975     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13976     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13977     
13978     Example:
13979     
13980     (start code js)
13981     barChart.updateJSON(json, {
13982       onComplete: function() {
13983         alert('update complete!');
13984       }
13985     });
13986     (end code)
13987  */  
13988   updateJSON: function(json, onComplete) {
13989     if(this.busy) return;
13990     this.busy = true;
13991     
13992     var st = this.st;
13993     var graph = st.graph;
13994     var values = json.values;
13995     var animate = this.config.animate;
13996     var that = this;
13997     var horz = this.config.orientation == 'horizontal';
13998     $.each(values, function(v) {
13999       var n = graph.getByName(v.label);
14000       if(n) {
14001         n.setData('valueArray', $.splat(v.values));
14002         if(json.label) {
14003           n.setData('stringArray', $.splat(json.label));
14004         }
14005       }
14006     });
14007     this.normalizeDims();
14008     st.compute();
14009     st.select(st.root);
14010     if(animate) {
14011       if(horz) {
14012         st.fx.animate({
14013           modes: ['node-property:width:dimArray'],
14014           duration:1500,
14015           onComplete: function() {
14016             that.busy = false;
14017             onComplete && onComplete.onComplete();
14018           }
14019         });
14020       } else {
14021         st.fx.animate({
14022           modes: ['node-property:height:dimArray'],
14023           duration:1500,
14024           onComplete: function() {
14025             that.busy = false;
14026             onComplete && onComplete.onComplete();
14027           }
14028         });
14029       }
14030     }
14031   },
14032   
14033   //adds the little brown bar when hovering the node
14034   select: function(id, name) {
14035
14036     if(!this.config.hoveredColor) return;
14037     var s = this.selected;
14038     if(s.id != id || s.name != name) {
14039       s.id = id;
14040       s.name = name;
14041       s.color = this.config.hoveredColor;
14042       this.st.graph.eachNode(function(n) {
14043         if(id == n.id) {
14044           n.setData('border', s);
14045         } else {
14046           n.setData('border', false);
14047         }
14048       });
14049       this.st.plot();
14050     }
14051   },
14052   
14053   /*
14054     Method: getLegend
14055    
14056     Returns an object containing as keys the legend names and as values hex strings with color values.
14057     
14058     Example:
14059     
14060     (start code js)
14061     var legend = barChart.getLegend();
14062     (end code)
14063   */  
14064   getLegend: function() {
14065     var legend = new Array();
14066     var name = new Array();
14067     var color = new Array();
14068     var n;
14069     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14070       n = adj.nodeTo;
14071     });
14072     var colors = n.getData('colorArray'),
14073         len = colors.length;
14074     $.each(n.getData('stringArray'), function(s, i) {
14075       color[i] = colors[i % len];
14076       name[i] = s;
14077     });
14078         legend['name'] = name;
14079         legend['color'] = color;
14080     return legend;
14081   },
14082   
14083   /*
14084     Method: getMaxValue
14085    
14086     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14087     
14088     Example:
14089     
14090     (start code js)
14091     var ans = barChart.getMaxValue();
14092     (end code)
14093     
14094     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14095     
14096     Example:
14097     
14098     (start code js)
14099     //will return 100 for all BarChart instances,
14100     //displaying all of them with the same scale
14101     $jit.BarChart.implement({
14102       'getMaxValue': function() {
14103         return 100;
14104       }
14105     });
14106     (end code)
14107     
14108   */  
14109   getMaxValue: function() {
14110     var maxValue = 0, stacked = true;
14111     this.st.graph.eachNode(function(n) {
14112       var valArray = n.getData('valueArray'),
14113           acum = 0;
14114       if(!valArray) return;
14115       if(stacked) {
14116         $.each(valArray, function(v) { 
14117           acum += +v;
14118         });
14119       } else {
14120         acum = Math.max.apply(null, valArray);
14121       }
14122       maxValue = maxValue>acum? maxValue:acum;
14123     });
14124     return maxValue;
14125   },
14126   
14127   setBarType: function(type) {
14128     this.config.type = type;
14129     this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14130   },
14131   
14132   normalizeDims: function() {
14133     //number of elements
14134     var root = this.st.graph.getNode(this.st.root), l=0;
14135     root.eachAdjacency(function() {
14136       l++;
14137     });
14138     var maxValue = this.getMaxValue() || 1,
14139         size = this.st.canvas.getSize(),
14140         config = this.config,
14141         margin = config.Margin,
14142         title = config.Title,
14143         subtitle = config.Subtitle,
14144         marginWidth = margin.left + margin.right,
14145         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14146         horz = config.orientation == 'horizontal',
14147         animate = config.animate,
14148         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14149
14150           - (config.showLabels && (config.Label.size + config.labelOffset)),
14151         dim1 = horz? 'height':'width',
14152         dim2 = horz? 'width':'height';
14153         
14154
14155         minWidth = size.width/8;
14156         
14157
14158
14159     this.st.graph.eachNode(function(n) {
14160       var acum = 0, animateValue = [];
14161       $.each(n.getData('valueArray'), function(v) {
14162         acum += +v;
14163         animateValue.push(0);
14164       });
14165       n.setData(dim1, minWidth);
14166             
14167       if(animate) {
14168         n.setData(dim2, acum * height / maxValue, 'end');
14169         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14170           return n * height / maxValue; 
14171         }), 'end');
14172         var dimArray = n.getData('dimArray');
14173         if(!dimArray) {
14174           n.setData('dimArray', animateValue);
14175         }
14176       } else {
14177                         n.setData(dim2, acum * height / maxValue);
14178                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14179                           return n * height / maxValue; 
14180                         }));
14181       }
14182
14183     });
14184   }
14185 });
14186
14187
14188
14189 /*
14190  * File: Options.PieChart.js
14191  *
14192 */
14193 /*
14194   Object: Options.PieChart
14195   
14196   <PieChart> options. 
14197   Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14198   
14199   Syntax:
14200   
14201   (start code js)
14202
14203   Options.PieChart = {
14204     animate: true,
14205     offset: 25,
14206     sliceOffset:0,
14207     labelOffset: 3,
14208     type: 'stacked',
14209     hoveredColor: '#9fd4ff',
14210     showLabels: true,
14211     resizeLabels: false,
14212     updateHeights: false
14213   };  
14214
14215   (end code)
14216   
14217   Example:
14218   
14219   (start code js)
14220
14221   var pie = new $jit.PieChart({
14222     animate: true,
14223     sliceOffset: 5,
14224     type: 'stacked:gradient'
14225   });  
14226
14227   (end code)
14228   
14229   Parameters:
14230   
14231   animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14232   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14233   sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14234   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14235   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14236   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14237   showLabels - (boolean) Default's *true*. Display the name of the slots.
14238   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.
14239   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.
14240
14241 */
14242 Options.PieChart = {
14243   $extend: true,
14244
14245   animate: true,
14246   offset: 25, // page offset
14247   sliceOffset:0,
14248   labelOffset: 3, // label offset
14249   type: 'stacked', // gradient
14250   labelType: 'name',
14251   hoveredColor: '#9fd4ff',
14252   Events: {
14253     enable: false,
14254     onClick: $.empty
14255   },
14256   Tips: {
14257     enable: false,
14258     onShow: $.empty,
14259     onHide: $.empty
14260   },
14261   showLabels: true,
14262   resizeLabels: false,
14263   
14264   //only valid for mono-valued datasets
14265   updateHeights: false
14266 };
14267
14268 /*
14269  * Class: Layouts.Radial
14270  * 
14271  * Implements a Radial Layout.
14272  * 
14273  * Implemented By:
14274  * 
14275  * <RGraph>, <Hypertree>
14276  * 
14277  */
14278 Layouts.Radial = new Class({
14279
14280   /*
14281    * Method: compute
14282    * 
14283    * Computes nodes' positions.
14284    * 
14285    * Parameters:
14286    * 
14287    * property - _optional_ A <Graph.Node> position property to store the new
14288    * positions. Possible values are 'pos', 'end' or 'start'.
14289    * 
14290    */
14291   compute : function(property) {
14292     var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14293     NodeDim.compute(this.graph, prop, this.config);
14294     this.graph.computeLevels(this.root, 0, "ignore");
14295     var lengthFunc = this.createLevelDistanceFunc(); 
14296     this.computeAngularWidths(prop);
14297     this.computePositions(prop, lengthFunc);
14298   },
14299
14300   /*
14301    * computePositions
14302    * 
14303    * Performs the main algorithm for computing node positions.
14304    */
14305   computePositions : function(property, getLength) {
14306     var propArray = property;
14307     var graph = this.graph;
14308     var root = graph.getNode(this.root);
14309     var parent = this.parent;
14310     var config = this.config;
14311
14312     for ( var i=0, l=propArray.length; i < l; i++) {
14313       var pi = propArray[i];
14314       root.setPos($P(0, 0), pi);
14315       root.setData('span', Math.PI * 2, pi);
14316     }
14317
14318     root.angleSpan = {
14319       begin : 0,
14320       end : 2 * Math.PI
14321     };
14322
14323     graph.eachBFS(this.root, function(elem) {
14324       var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14325       var angleInit = elem.angleSpan.begin;
14326       var len = getLength(elem);
14327       //Calculate the sum of all angular widths
14328       var totalAngularWidths = 0, subnodes = [], maxDim = {};
14329       elem.eachSubnode(function(sib) {
14330         totalAngularWidths += sib._treeAngularWidth;
14331         //get max dim
14332         for ( var i=0, l=propArray.length; i < l; i++) {
14333           var pi = propArray[i], dim = sib.getData('dim', pi);
14334           maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14335         }
14336         subnodes.push(sib);
14337       }, "ignore");
14338       //Maintain children order
14339       //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14340       if (parent && parent.id == elem.id && subnodes.length > 0
14341           && subnodes[0].dist) {
14342         subnodes.sort(function(a, b) {
14343           return (a.dist >= b.dist) - (a.dist <= b.dist);
14344         });
14345       }
14346       //Calculate nodes positions.
14347       for (var k = 0, ls=subnodes.length; k < ls; k++) {
14348         var child = subnodes[k];
14349         if (!child._flag) {
14350           var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14351           var theta = angleInit + angleProportion / 2;
14352
14353           for ( var i=0, l=propArray.length; i < l; i++) {
14354             var pi = propArray[i];
14355             child.setPos($P(theta, len), pi);
14356             child.setData('span', angleProportion, pi);
14357             child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14358           }
14359
14360           child.angleSpan = {
14361             begin : angleInit,
14362             end : angleInit + angleProportion
14363           };
14364           angleInit += angleProportion;
14365         }
14366       }
14367     }, "ignore");
14368   },
14369
14370   /*
14371    * Method: setAngularWidthForNodes
14372    * 
14373    * Sets nodes angular widths.
14374    */
14375   setAngularWidthForNodes : function(prop) {
14376     this.graph.eachBFS(this.root, function(elem, i) {
14377       var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14378       elem._angularWidth = diamValue / i;
14379     }, "ignore");
14380   },
14381
14382   /*
14383    * Method: setSubtreesAngularWidth
14384    * 
14385    * Sets subtrees angular widths.
14386    */
14387   setSubtreesAngularWidth : function() {
14388     var that = this;
14389     this.graph.eachNode(function(elem) {
14390       that.setSubtreeAngularWidth(elem);
14391     }, "ignore");
14392   },
14393
14394   /*
14395    * Method: setSubtreeAngularWidth
14396    * 
14397    * Sets the angular width for a subtree.
14398    */
14399   setSubtreeAngularWidth : function(elem) {
14400     var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14401     elem.eachSubnode(function(child) {
14402       that.setSubtreeAngularWidth(child);
14403       sumAW += child._treeAngularWidth;
14404     }, "ignore");
14405     elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14406   },
14407
14408   /*
14409    * Method: computeAngularWidths
14410    * 
14411    * Computes nodes and subtrees angular widths.
14412    */
14413   computeAngularWidths : function(prop) {
14414     this.setAngularWidthForNodes(prop);
14415     this.setSubtreesAngularWidth();
14416   }
14417
14418 });
14419
14420
14421 /*
14422  * File: Sunburst.js
14423  */
14424
14425 /*
14426    Class: Sunburst
14427       
14428    A radial space filling tree visualization.
14429    
14430    Inspired by:
14431  
14432    Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14433    
14434    Note:
14435    
14436    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.
14437    
14438   Implements:
14439   
14440   All <Loader> methods
14441   
14442    Constructor Options:
14443    
14444    Inherits options from
14445    
14446    - <Options.Canvas>
14447    - <Options.Controller>
14448    - <Options.Node>
14449    - <Options.Edge>
14450    - <Options.Label>
14451    - <Options.Events>
14452    - <Options.Tips>
14453    - <Options.NodeStyles>
14454    - <Options.Navigation>
14455    
14456    Additionally, there are other parameters and some default values changed
14457    
14458    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14459    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
14460    Node.type - Described in <Options.Node>. Default's to *multipie*.
14461    Node.height - Described in <Options.Node>. Default's *0*.
14462    Edge.type - Described in <Options.Edge>. Default's *none*.
14463    Label.textAlign - Described in <Options.Label>. Default's *start*.
14464    Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14465      
14466    Instance Properties:
14467
14468    canvas - Access a <Canvas> instance.
14469    graph - Access a <Graph> instance.
14470    op - Access a <Sunburst.Op> instance.
14471    fx - Access a <Sunburst.Plot> instance.
14472    labels - Access a <Sunburst.Label> interface implementation.   
14473
14474 */
14475
14476 $jit.Sunburst = new Class({
14477
14478   Implements: [ Loader, Extras, Layouts.Radial ],
14479
14480   initialize: function(controller) {
14481     var $Sunburst = $jit.Sunburst;
14482
14483     var config = {
14484       interpolation: 'linear',
14485       levelDistance: 100,
14486       Node: {
14487         'type': 'multipie',
14488         'height':0
14489       },
14490       Edge: {
14491         'type': 'none'
14492       },
14493       Label: {
14494         textAlign: 'start',
14495         textBaseline: 'middle'
14496       }
14497     };
14498
14499     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14500         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14501
14502     var canvasConfig = this.config;
14503     if(canvasConfig.useCanvas) {
14504       this.canvas = canvasConfig.useCanvas;
14505       this.config.labelContainer = this.canvas.id + '-label';
14506     } else {
14507       if(canvasConfig.background) {
14508         canvasConfig.background = $.merge({
14509           type: 'Fade',
14510           colorStop1: this.config.colorStop1,
14511           colorStop2: this.config.colorStop2
14512         }, canvasConfig.background);
14513       }
14514       this.canvas = new Canvas(this, canvasConfig);
14515       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14516     }
14517
14518     this.graphOptions = {
14519       'complex': false,
14520       'Node': {
14521         'selected': false,
14522         'exist': true,
14523         'drawn': true
14524       }
14525     };
14526     this.graph = new Graph(this.graphOptions, this.config.Node,
14527         this.config.Edge);
14528     this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14529     this.fx = new $Sunburst.Plot(this, $Sunburst);
14530     this.op = new $Sunburst.Op(this);
14531     this.json = null;
14532     this.root = null;
14533     this.rotated = null;
14534     this.busy = false;
14535     // initialize extras
14536     this.initializeExtras();
14537   },
14538
14539   /* 
14540   
14541     createLevelDistanceFunc 
14542   
14543     Returns the levelDistance function used for calculating a node distance 
14544     to its origin. This function returns a function that is computed 
14545     per level and not per node, such that all nodes with the same depth will have the 
14546     same distance to the origin. The resulting function gets the 
14547     parent node as parameter and returns a float.
14548
14549    */
14550   createLevelDistanceFunc: function() {
14551     var ld = this.config.levelDistance;
14552     return function(elem) {
14553       return (elem._depth + 1) * ld;
14554     };
14555   },
14556
14557   /* 
14558      Method: refresh 
14559      
14560      Computes positions and plots the tree.
14561
14562    */
14563   refresh: function() {
14564     this.compute();
14565     this.plot();
14566   },
14567
14568   /*
14569    reposition
14570   
14571    An alias for computing new positions to _endPos_
14572
14573    See also:
14574
14575    <Sunburst.compute>
14576    
14577   */
14578   reposition: function() {
14579     this.compute('end');
14580   },
14581
14582   /*
14583   Method: rotate
14584   
14585   Rotates the graph so that the selected node is horizontal on the right.
14586
14587   Parameters:
14588   
14589   node - (object) A <Graph.Node>.
14590   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14591   opt - (object) Configuration options merged with this visualization configuration options.
14592   
14593   See also:
14594
14595   <Sunburst.rotateAngle>
14596   
14597   */
14598   rotate: function(node, method, opt) {
14599     var theta = node.getPos(opt.property || 'current').getp(true).theta;
14600     this.rotated = node;
14601     this.rotateAngle(-theta, method, opt);
14602   },
14603
14604   /*
14605   Method: rotateAngle
14606   
14607   Rotates the graph of an angle theta.
14608   
14609    Parameters:
14610    
14611    node - (object) A <Graph.Node>.
14612    method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14613    opt - (object) Configuration options merged with this visualization configuration options.
14614    
14615    See also:
14616
14617    <Sunburst.rotate>
14618   
14619   */
14620   rotateAngle: function(theta, method, opt) {
14621     var that = this;
14622     var options = $.merge(this.config, opt || {}, {
14623       modes: [ 'polar' ]
14624     });
14625     var prop = opt.property || (method === "animate" ? 'end' : 'current');
14626     if(method === 'animate') {
14627       this.fx.animation.pause();
14628     }
14629     this.graph.eachNode(function(n) {
14630       var p = n.getPos(prop);
14631       p.theta += theta;
14632       if (p.theta < 0) {
14633         p.theta += Math.PI * 2;
14634       }
14635     });
14636     if (method == 'animate') {
14637       this.fx.animate(options);
14638     } else if (method == 'replot') {
14639       this.fx.plot();
14640       this.busy = false;
14641     }
14642   },
14643
14644   /*
14645    Method: plot
14646   
14647    Plots the Sunburst. This is a shortcut to *fx.plot*.
14648   */
14649   plot: function() {
14650     this.fx.plot();
14651   }
14652 });
14653
14654 $jit.Sunburst.$extend = true;
14655
14656 (function(Sunburst) {
14657
14658   /*
14659      Class: Sunburst.Op
14660
14661      Custom extension of <Graph.Op>.
14662
14663      Extends:
14664
14665      All <Graph.Op> methods
14666      
14667      See also:
14668      
14669      <Graph.Op>
14670
14671   */
14672   Sunburst.Op = new Class( {
14673
14674     Implements: Graph.Op
14675
14676   });
14677
14678   /*
14679      Class: Sunburst.Plot
14680
14681     Custom extension of <Graph.Plot>.
14682   
14683     Extends:
14684   
14685     All <Graph.Plot> methods
14686     
14687     See also:
14688     
14689     <Graph.Plot>
14690   
14691   */
14692   Sunburst.Plot = new Class( {
14693
14694     Implements: Graph.Plot
14695
14696   });
14697
14698   /*
14699     Class: Sunburst.Label
14700
14701     Custom extension of <Graph.Label>. 
14702     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
14703   
14704     Extends:
14705   
14706     All <Graph.Label> methods and subclasses.
14707   
14708     See also:
14709   
14710     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
14711   
14712    */
14713   Sunburst.Label = {};
14714
14715   /*
14716      Sunburst.Label.Native
14717
14718      Custom extension of <Graph.Label.Native>.
14719
14720      Extends:
14721
14722      All <Graph.Label.Native> methods
14723
14724      See also:
14725
14726      <Graph.Label.Native>
14727   */
14728   Sunburst.Label.Native = new Class( {
14729     Implements: Graph.Label.Native,
14730
14731     initialize: function(viz) {
14732       this.viz = viz;
14733       this.label = viz.config.Label;
14734       this.config = viz.config;
14735     },
14736
14737     renderLabel: function(canvas, node, controller) {
14738       var span = node.getData('span');
14739       if(span < Math.PI /2 && Math.tan(span) * 
14740           this.config.levelDistance * node._depth < 10) {
14741         return;
14742       }
14743       var ctx = canvas.getCtx();
14744       var measure = ctx.measureText(node.name);
14745       if (node.id == this.viz.root) {
14746         var x = -measure.width / 2, y = 0, thetap = 0;
14747         var ld = 0;
14748       } else {
14749         var indent = 5;
14750         var ld = controller.levelDistance - indent;
14751         var clone = node.pos.clone();
14752         clone.rho += indent;
14753         var p = clone.getp(true);
14754         var ct = clone.getc(true);
14755         var x = ct.x, y = ct.y;
14756         // get angle in degrees
14757         var pi = Math.PI;
14758         var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14759         var thetap = cond ? p.theta + pi : p.theta;
14760         if (cond) {
14761           x -= Math.abs(Math.cos(p.theta) * measure.width);
14762           y += Math.sin(p.theta) * measure.width;
14763         } else if (node.id == this.viz.root) {
14764           x -= measure.width / 2;
14765         }
14766       }
14767       ctx.save();
14768       ctx.translate(x, y);
14769       ctx.rotate(thetap);
14770       ctx.fillText(node.name, 0, 0);
14771       ctx.restore();
14772     }
14773   });
14774
14775   /*
14776      Sunburst.Label.SVG
14777
14778     Custom extension of <Graph.Label.SVG>.
14779   
14780     Extends:
14781   
14782     All <Graph.Label.SVG> methods
14783   
14784     See also:
14785   
14786     <Graph.Label.SVG>
14787   
14788   */
14789   Sunburst.Label.SVG = new Class( {
14790     Implements: Graph.Label.SVG,
14791
14792     initialize: function(viz) {
14793       this.viz = viz;
14794     },
14795
14796     /* 
14797        placeLabel
14798
14799        Overrides abstract method placeLabel in <Graph.Plot>.
14800
14801        Parameters:
14802
14803        tag - A DOM label element.
14804        node - A <Graph.Node>.
14805        controller - A configuration/controller object passed to the visualization.
14806       
14807      */
14808     placeLabel: function(tag, node, controller) {
14809       var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
14810       var radius = canvas.getSize();
14811       var labelPos = {
14812         x: Math.round(pos.x + radius.width / 2),
14813         y: Math.round(pos.y + radius.height / 2)
14814       };
14815       tag.setAttribute('x', labelPos.x);
14816       tag.setAttribute('y', labelPos.y);
14817
14818       var bb = tag.getBBox();
14819       if (bb) {
14820         // center the label
14821     var x = tag.getAttribute('x');
14822     var y = tag.getAttribute('y');
14823     // get polar coordinates
14824     var p = node.pos.getp(true);
14825     // get angle in degrees
14826     var pi = Math.PI;
14827     var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
14828     if (cond) {
14829       tag.setAttribute('x', x - bb.width);
14830       tag.setAttribute('y', y - bb.height);
14831     } else if (node.id == viz.root) {
14832       tag.setAttribute('x', x - bb.width / 2);
14833     }
14834
14835     var thetap = cond ? p.theta + pi : p.theta;
14836     if(node._depth)
14837       tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
14838           + ' ' + y + ')');
14839   }
14840
14841   controller.onPlaceLabel(tag, node);
14842 }
14843   });
14844
14845   /*
14846      Sunburst.Label.HTML
14847
14848      Custom extension of <Graph.Label.HTML>.
14849
14850      Extends:
14851
14852      All <Graph.Label.HTML> methods.
14853
14854      See also:
14855
14856      <Graph.Label.HTML>
14857
14858   */
14859   Sunburst.Label.HTML = new Class( {
14860     Implements: Graph.Label.HTML,
14861
14862     initialize: function(viz) {
14863       this.viz = viz;
14864     },
14865     /* 
14866        placeLabel
14867
14868        Overrides abstract method placeLabel in <Graph.Plot>.
14869
14870        Parameters:
14871
14872        tag - A DOM label element.
14873        node - A <Graph.Node>.
14874        controller - A configuration/controller object passed to the visualization.
14875       
14876      */
14877     placeLabel: function(tag, node, controller) {
14878       var pos = node.pos.clone(), 
14879           canvas = this.viz.canvas,
14880           height = node.getData('height'),
14881           ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
14882           radius = canvas.getSize();
14883       pos.rho += ldist;
14884       pos = pos.getc(true);
14885       
14886       var labelPos = {
14887         x: Math.round(pos.x + radius.width / 2),
14888         y: Math.round(pos.y + radius.height / 2)
14889       };
14890
14891       var style = tag.style;
14892       style.left = labelPos.x + 'px';
14893       style.top = labelPos.y + 'px';
14894       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
14895
14896       controller.onPlaceLabel(tag, node);
14897     }
14898   });
14899
14900   /*
14901     Class: Sunburst.Plot.NodeTypes
14902
14903     This class contains a list of <Graph.Node> built-in types. 
14904     Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
14905
14906     You can add your custom node types, customizing your visualization to the extreme.
14907
14908     Example:
14909
14910     (start code js)
14911       Sunburst.Plot.NodeTypes.implement({
14912         'mySpecialType': {
14913           'render': function(node, canvas) {
14914             //print your custom node to canvas
14915           },
14916           //optional
14917           'contains': function(node, pos) {
14918             //return true if pos is inside the node or false otherwise
14919           }
14920         }
14921       });
14922     (end code)
14923
14924   */
14925   Sunburst.Plot.NodeTypes = new Class( {
14926     'none': {
14927       'render': $.empty,
14928       'contains': $.lambda(false),
14929       'anglecontains': function(node, pos) {
14930         var span = node.getData('span') / 2, theta = node.pos.theta;
14931         var begin = theta - span, end = theta + span;
14932         if (begin < 0)
14933           begin += Math.PI * 2;
14934         var atan = Math.atan2(pos.y, pos.x);
14935         if (atan < 0)
14936           atan += Math.PI * 2;
14937         if (begin > end) {
14938           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14939         } else {
14940           return atan > begin && atan < end;
14941         }
14942       },
14943           'anglecontainsgauge': function(node, pos) {
14944         var span = node.getData('span') / 2, theta = node.pos.theta;
14945                 var config = node.getData('config');
14946         var ld = this.config.levelDistance;
14947                 var yOffset = pos.y-(ld/2);
14948                 var begin = ((theta - span)/2)+Math.PI,
14949         end = ((theta + span)/2)+Math.PI;
14950                 
14951         if (begin < 0)
14952           begin += Math.PI * 2;
14953         var atan = Math.atan2(yOffset, pos.x);
14954
14955                 
14956         if (atan < 0)
14957           atan += Math.PI * 2;
14958                   
14959                   
14960         if (begin > end) {
14961           return (atan > begin && atan <= Math.PI * 2) || atan < end;
14962         } else {
14963           return atan > begin && atan < end;
14964         }
14965       }
14966     },
14967
14968     'pie': {
14969       'render': function(node, canvas) {
14970         var span = node.getData('span') / 2, theta = node.pos.theta;
14971         var begin = theta - span, end = theta + span;
14972         var polarNode = node.pos.getp(true);
14973         var polar = new Polar(polarNode.rho, begin);
14974         var p1coord = polar.getc(true);
14975         polar.theta = end;
14976         var p2coord = polar.getc(true);
14977
14978         var ctx = canvas.getCtx();
14979         ctx.beginPath();
14980         ctx.moveTo(0, 0);
14981         ctx.lineTo(p1coord.x, p1coord.y);
14982         ctx.moveTo(0, 0);
14983         ctx.lineTo(p2coord.x, p2coord.y);
14984         ctx.moveTo(0, 0);
14985         ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
14986             false);
14987         ctx.fill();
14988       },
14989       'contains': function(node, pos) {
14990         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
14991           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
14992           var ld = this.config.levelDistance, d = node._depth;
14993           return (rho <= ld * d);
14994         }
14995         return false;
14996       }
14997     },
14998     'multipie': {
14999       'render': function(node, canvas) {
15000         var height = node.getData('height');
15001         var ldist = height? height : this.config.levelDistance;
15002         var span = node.getData('span') / 2, theta = node.pos.theta;
15003         var begin = theta - span, end = theta + span;
15004         var polarNode = node.pos.getp(true);
15005
15006         var polar = new Polar(polarNode.rho, begin);
15007         var p1coord = polar.getc(true);
15008
15009         polar.theta = end;
15010         var p2coord = polar.getc(true);
15011
15012         polar.rho += ldist;
15013         var p3coord = polar.getc(true);
15014
15015         polar.theta = begin;
15016         var p4coord = polar.getc(true);
15017
15018         var ctx = canvas.getCtx();
15019         ctx.moveTo(0, 0);
15020         ctx.beginPath();
15021         ctx.arc(0, 0, polarNode.rho, begin, end, false);
15022         ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15023         ctx.moveTo(p1coord.x, p1coord.y);
15024         ctx.lineTo(p4coord.x, p4coord.y);
15025         ctx.moveTo(p2coord.x, p2coord.y);
15026         ctx.lineTo(p3coord.x, p3coord.y);
15027         ctx.fill();
15028
15029         if (node.collapsed) {
15030           ctx.save();
15031           ctx.lineWidth = 2;
15032           ctx.moveTo(0, 0);
15033           ctx.beginPath();
15034           ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15035               true);
15036           ctx.stroke();
15037           ctx.restore();
15038         }
15039       },
15040       'contains': function(node, pos) {
15041         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15042           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15043           var height = node.getData('height');
15044           var ldist = height? height : this.config.levelDistance;
15045           var ld = this.config.levelDistance, d = node._depth;
15046           return (rho >= ld * d) && (rho <= (ld * d + ldist));
15047         }
15048         return false;
15049       }
15050     },
15051
15052     'gradient-multipie': {
15053       'render': function(node, canvas) {
15054         var ctx = canvas.getCtx();
15055         var height = node.getData('height');
15056         var ldist = height? height : this.config.levelDistance;
15057         var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15058             0, 0, node.getPos().rho + ldist);
15059
15060         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15061         $.each(colorArray, function(i) {
15062           ans.push(parseInt(i * 0.5, 10));
15063         });
15064         var endColor = $.rgbToHex(ans);
15065         radialGradient.addColorStop(0, endColor);
15066         radialGradient.addColorStop(1, node.getData('color'));
15067         ctx.fillStyle = radialGradient;
15068         this.nodeTypes['multipie'].render.call(this, node, canvas);
15069       },
15070       'contains': function(node, pos) {
15071         return this.nodeTypes['multipie'].contains.call(this, node, pos);
15072       }
15073     },
15074
15075     'gradient-pie': {
15076       'render': function(node, canvas) {
15077         var ctx = canvas.getCtx();
15078         var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15079             .getPos().rho);
15080
15081         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15082         $.each(colorArray, function(i) {
15083           ans.push(parseInt(i * 0.5, 10));
15084         });
15085         var endColor = $.rgbToHex(ans);
15086         radialGradient.addColorStop(1, endColor);
15087         radialGradient.addColorStop(0, node.getData('color'));
15088         ctx.fillStyle = radialGradient;
15089         this.nodeTypes['pie'].render.call(this, node, canvas);
15090       },
15091       'contains': function(node, pos) {
15092         return this.nodeTypes['pie'].contains.call(this, node, pos);
15093       }
15094     }
15095   });
15096
15097   /*
15098     Class: Sunburst.Plot.EdgeTypes
15099
15100     This class contains a list of <Graph.Adjacence> built-in types. 
15101     Edge types implemented are 'none', 'line' and 'arrow'.
15102   
15103     You can add your custom edge types, customizing your visualization to the extreme.
15104   
15105     Example:
15106   
15107     (start code js)
15108       Sunburst.Plot.EdgeTypes.implement({
15109         'mySpecialType': {
15110           'render': function(adj, canvas) {
15111             //print your custom edge to canvas
15112           },
15113           //optional
15114           'contains': function(adj, pos) {
15115             //return true if pos is inside the arc or false otherwise
15116           }
15117         }
15118       });
15119     (end code)
15120   
15121   */
15122   Sunburst.Plot.EdgeTypes = new Class({
15123     'none': $.empty,
15124     'line': {
15125       'render': function(adj, canvas) {
15126         var from = adj.nodeFrom.pos.getc(true),
15127             to = adj.nodeTo.pos.getc(true);
15128         this.edgeHelper.line.render(from, to, canvas);
15129       },
15130       'contains': function(adj, pos) {
15131         var from = adj.nodeFrom.pos.getc(true),
15132             to = adj.nodeTo.pos.getc(true);
15133         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15134       }
15135     },
15136     'arrow': {
15137       'render': function(adj, canvas) {
15138         var from = adj.nodeFrom.pos.getc(true),
15139             to = adj.nodeTo.pos.getc(true),
15140             dim = adj.getData('dim'),
15141             direction = adj.data.$direction,
15142             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15143         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15144       },
15145       'contains': function(adj, pos) {
15146         var from = adj.nodeFrom.pos.getc(true),
15147             to = adj.nodeTo.pos.getc(true);
15148         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15149       }
15150     },
15151     'hyperline': {
15152       'render': function(adj, canvas) {
15153         var from = adj.nodeFrom.pos.getc(),
15154             to = adj.nodeTo.pos.getc(),
15155             dim = Math.max(from.norm(), to.norm());
15156         this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15157       },
15158       'contains': $.lambda(false) //TODO(nico): Implement this!
15159     }
15160   });
15161
15162 })($jit.Sunburst);
15163
15164
15165 /*
15166  * File: PieChart.js
15167  *
15168 */
15169
15170 $jit.Sunburst.Plot.NodeTypes.implement({
15171   'piechart-stacked' : {
15172     'render' : function(node, canvas) {
15173       var pos = node.pos.getp(true),
15174           dimArray = node.getData('dimArray'),
15175           valueArray = node.getData('valueArray'),
15176           colorArray = node.getData('colorArray'),
15177           colorLength = colorArray.length,
15178           stringArray = node.getData('stringArray'),
15179           span = node.getData('span') / 2,
15180           theta = node.pos.theta,
15181           begin = theta - span,
15182           end = theta + span,
15183           polar = new Polar;
15184     
15185       var ctx = canvas.getCtx(), 
15186           opt = {},
15187           gradient = node.getData('gradient'),
15188           border = node.getData('border'),
15189           config = node.getData('config'),
15190           showLabels = config.showLabels,
15191           resizeLabels = config.resizeLabels,
15192           label = config.Label;
15193
15194       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15195       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15196
15197       if (colorArray && dimArray && stringArray) {
15198         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15199           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15200           if(dimi <= 0) continue;
15201           ctx.fillStyle = ctx.strokeStyle = colori;
15202           if(gradient && dimi) {
15203             var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15204                 xpos, ypos, acum + dimi + config.sliceOffset);
15205             var colorRgb = $.hexToRgb(colori), 
15206                 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15207                 endColor = $.rgbToHex(ans);
15208
15209             radialGradient.addColorStop(0, colori);
15210             radialGradient.addColorStop(0.5, colori);
15211             radialGradient.addColorStop(1, endColor);
15212             ctx.fillStyle = radialGradient;
15213           }
15214           
15215           polar.rho = acum + config.sliceOffset;
15216           polar.theta = begin;
15217           var p1coord = polar.getc(true);
15218           polar.theta = end;
15219           var p2coord = polar.getc(true);
15220           polar.rho += dimi;
15221           var p3coord = polar.getc(true);
15222           polar.theta = begin;
15223           var p4coord = polar.getc(true);
15224
15225           ctx.beginPath();
15226           //fixing FF arc method + fill
15227           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15228           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15229           ctx.fill();
15230           if(border && border.name == stringArray[i]) {
15231             opt.acum = acum;
15232             opt.dimValue = dimArray[i];
15233             opt.begin = begin;
15234             opt.end = end;
15235           }
15236           acum += (dimi || 0);
15237           valAcum += (valueArray[i] || 0);
15238         }
15239         if(border) {
15240           ctx.save();
15241           ctx.globalCompositeOperation = "source-over";
15242           ctx.lineWidth = 2;
15243           ctx.strokeStyle = border.color;
15244           var s = begin < end? 1 : -1;
15245           ctx.beginPath();
15246           //fixing FF arc method + fill
15247           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15248           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15249           ctx.closePath();
15250           ctx.stroke();
15251           ctx.restore();
15252         }
15253         if(showLabels && label.type == 'Native') {
15254           ctx.save();
15255           ctx.fillStyle = ctx.strokeStyle = label.color;
15256           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15257               fontSize = (label.size * scale) >> 0;
15258           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15259           
15260           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15261           ctx.textBaseline = 'middle';
15262           ctx.textAlign = 'center';
15263           
15264           polar.rho = acum + config.labelOffset + config.sliceOffset;
15265           polar.theta = node.pos.theta;
15266           var cart = polar.getc(true);
15267           
15268           ctx.fillText(node.name, cart.x, cart.y);
15269           ctx.restore();
15270         }
15271       }
15272     },
15273     'contains': function(node, pos) {
15274       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15275         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15276         var ld = this.config.levelDistance, d = node._depth;
15277         var config = node.getData('config');
15278         if(rho <=ld * d + config.sliceOffset) {
15279           var dimArray = node.getData('dimArray');
15280           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15281             var dimi = dimArray[i];
15282             if(rho >= acum && rho <= acum + dimi) {
15283               return {
15284                 name: node.getData('stringArray')[i],
15285                 color: node.getData('colorArray')[i],
15286                 value: node.getData('valueArray')[i],
15287                 label: node.name
15288               };
15289             }
15290             acum += dimi;
15291           }
15292         }
15293         return false;
15294         
15295       }
15296       return false;
15297     }
15298   },
15299     'piechart-basic' : {
15300     'render' : function(node, canvas) {
15301       var pos = node.pos.getp(true),
15302           dimArray = node.getData('dimArray'),
15303           valueArray = node.getData('valueArray'),
15304           colorArray = node.getData('colorMono'),
15305           colorLength = colorArray.length,
15306           stringArray = node.getData('stringArray'),
15307                   percentage = node.getData('percentage'),
15308                   iteration = node.getData('iteration'),
15309           span = node.getData('span') / 2,
15310           theta = node.pos.theta,
15311           begin = theta - span,
15312           end = theta + span,
15313           polar = new Polar;
15314     
15315       var ctx = canvas.getCtx(), 
15316           opt = {},
15317           gradient = node.getData('gradient'),
15318           border = node.getData('border'),
15319           config = node.getData('config'),
15320           renderSubtitle = node.getData('renderSubtitle'),
15321           renderBackground = config.renderBackground,
15322           showLabels = config.showLabels,
15323           resizeLabels = config.resizeLabels,
15324           label = config.Label;
15325
15326       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15327       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15328       //background rendering for IE
15329                 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15330                         backgroundColor = config.backgroundColor,
15331                         size = canvas.getSize();
15332                         ctx.save();
15333                     ctx.fillStyle = backgroundColor;
15334                     ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15335                     
15336                     //subtitle
15337
15338                         var margin = config.Margin,
15339                         title = config.Title,
15340                         subtitle = config.Subtitle;
15341                         ctx.fillStyle = title.color;
15342                         ctx.textAlign = 'left';
15343                         
15344                         if(title.text != "") {
15345                                 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15346                                 ctx.moveTo(0,0);
15347                                 if(label.type == 'Native') {
15348                                         ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15349                                 }
15350                         }       
15351         
15352                         if(subtitle.text != "") {
15353                                 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15354                                 if(label.type == 'Native') {
15355                                         ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15356                                 } 
15357                         }
15358                         ctx.restore();          
15359                 }
15360       if (colorArray && dimArray && stringArray) {
15361         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15362           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15363           if(dimi <= 0) continue;
15364           ctx.fillStyle = ctx.strokeStyle = colori;
15365           
15366           polar.rho = acum + config.sliceOffset;
15367           polar.theta = begin;
15368           var p1coord = polar.getc(true);
15369           polar.theta = end;
15370           var p2coord = polar.getc(true);
15371           polar.rho += dimi;
15372           var p3coord = polar.getc(true);
15373           polar.theta = begin;
15374           var p4coord = polar.getc(true);
15375           
15376           if(typeof FlashCanvas == "undefined") {
15377                   //drop shadow
15378                   ctx.beginPath();
15379                   ctx.fillStyle = "rgba(0,0,0,.2)";
15380                   ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15381                   ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);    
15382                   ctx.fill();
15383                   
15384                   if(gradient && dimi) {
15385                     var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15386                         xpos, ypos, acum + dimi + config.sliceOffset);
15387                     var colorRgb = $.hexToRgb(colori), 
15388                         endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15389                         endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15390         
15391                     radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15392                     radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15393                                 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15394                     radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15395                     ctx.fillStyle = radialGradient;
15396                   }
15397           }
15398
15399           
15400           //fixing FF arc method + fill
15401           ctx.beginPath();
15402           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15403           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15404           ctx.fill();
15405           if(border && border.name == stringArray[i]) {
15406             opt.acum = acum;
15407             opt.dimValue = dimArray[i];
15408             opt.begin = begin;
15409             opt.end = end;
15410             opt.sliceValue = valueArray[i];
15411           }
15412           acum += (dimi || 0);
15413           valAcum += (valueArray[i] || 0);
15414         }
15415         if(border) {
15416           ctx.save();
15417           ctx.globalCompositeOperation = "source-over";
15418           ctx.lineWidth = 2;
15419           ctx.strokeStyle = border.color;
15420           var s = begin < end? 1 : -1;
15421           ctx.beginPath();
15422           //fixing FF arc method + fill
15423           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15424           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15425           ctx.closePath();
15426           ctx.stroke();
15427           ctx.restore();
15428         }
15429         if(showLabels && label.type == 'Native') {
15430           ctx.save();
15431           ctx.fillStyle = ctx.strokeStyle = label.color;
15432           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15433               fontSize = (label.size * scale) >> 0;
15434           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15435           
15436           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15437           ctx.textBaseline = 'middle';
15438           ctx.textAlign = 'center';
15439           pi = Math.PI;
15440           angle = theta * 360 / (2 * pi);
15441           polar.rho = acum + config.labelOffset + config.sliceOffset;
15442           polar.theta = node.pos.theta;
15443           var cart = polar.getc(true);
15444           if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15445                 
15446                 } else {
15447                   if(config.labelType == 'name') {
15448                                 ctx.fillText(node.name, cart.x, cart.y);
15449                           } else {
15450                                 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15451                           }
15452           }
15453           ctx.restore();
15454         }
15455       }
15456     },
15457     'contains': function(node, pos) {
15458       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15459         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15460         var ld = this.config.levelDistance, d = node._depth;
15461         var config = node.getData('config');
15462
15463         if(rho <=ld * d + config.sliceOffset) {
15464           var dimArray = node.getData('dimArray');
15465           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15466             var dimi = dimArray[i];
15467             if(rho >= acum && rho <= acum + dimi) {
15468                           var url = Url.decode(node.getData('linkArray')[i]);
15469               return {
15470                 name: node.getData('stringArray')[i],
15471                 link: url,
15472                 color: node.getData('colorArray')[i],
15473                 value: node.getData('valueArray')[i],
15474                 percentage: node.getData('percentage'),
15475                 valuelabel: node.getData('valuelabelsArray')[i],
15476                 label: node.name
15477               };
15478             }
15479             acum += dimi;
15480           }
15481         }
15482         return false;
15483         
15484       }
15485       return false;
15486     }
15487   }
15488 });
15489
15490 /*
15491   Class: PieChart
15492   
15493   A visualization that displays stacked bar charts.
15494   
15495   Constructor Options:
15496   
15497   See <Options.PieChart>.
15498
15499 */
15500 $jit.PieChart = new Class({
15501   sb: null,
15502   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15503   selected: {},
15504   busy: false,
15505   
15506   initialize: function(opt) {
15507     this.controller = this.config = 
15508       $.merge(Options("Canvas", "PieChart", "Label"), {
15509         Label: { type: 'Native' }
15510       }, opt);
15511     this.initializeViz();
15512   },
15513   
15514   initializeViz: function() {
15515     var config = this.config, that = this;
15516     var nodeType = config.type.split(":")[0];
15517     var sb = new $jit.Sunburst({
15518       injectInto: config.injectInto,
15519       useCanvas: config.useCanvas,
15520       withLabels: config.Label.type != 'Native',
15521       background: config.background,
15522       renderBackground: config.renderBackground,
15523       backgroundColor: config.backgroundColor,
15524       colorStop1: config.colorStop1,
15525       colorStop2: config.colorStop2,
15526       Label: {
15527         type: config.Label.type
15528       },
15529       Node: {
15530         overridable: true,
15531         type: 'piechart-' + nodeType,
15532         width: 1,
15533         height: 1
15534       },
15535       Edge: {
15536         type: 'none'
15537       },
15538       Tips: {
15539         enable: config.Tips.enable,
15540         type: 'Native',
15541         force: true,
15542         onShow: function(tip, node, contains) {
15543           var elem = contains;
15544           config.Tips.onShow(tip, elem, node);
15545                           if(elem.link != 'undefined' && elem.link != '') {
15546                                 document.body.style.cursor = 'pointer';
15547                           }
15548         },
15549                 onHide: function() {
15550                                 document.body.style.cursor = 'default';
15551         }
15552       },
15553       Events: {
15554         enable: true,
15555         type: 'Native',
15556         onClick: function(node, eventInfo, evt) {
15557           if(!config.Events.enable) return;
15558           var elem = eventInfo.getContains();
15559           config.Events.onClick(elem, eventInfo, evt);
15560         },
15561         onMouseMove: function(node, eventInfo, evt) {
15562           if(!config.hoveredColor) return;
15563           if(node) {
15564             var elem = eventInfo.getContains();
15565             that.select(node.id, elem.name, elem.index);
15566           } else {
15567             that.select(false, false, false);
15568           }
15569         }
15570       },
15571       onCreateLabel: function(domElement, node) {
15572         var labelConf = config.Label;
15573         if(config.showLabels) {
15574           var style = domElement.style;
15575           style.fontSize = labelConf.size + 'px';
15576           style.fontFamily = labelConf.family;
15577           style.color = labelConf.color;
15578           style.textAlign = 'center';
15579           if(config.labelType == 'name') {
15580                 domElement.innerHTML = node.name;
15581           } else {
15582                 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15583           }
15584           domElement.style.width = '400px';
15585         }
15586       },
15587       onPlaceLabel: function(domElement, node) {
15588         if(!config.showLabels) return;
15589         var pos = node.pos.getp(true),
15590             dimArray = node.getData('dimArray'),
15591             span = node.getData('span') / 2,
15592             theta = node.pos.theta,
15593             begin = theta - span,
15594             end = theta + span,
15595             polar = new Polar;
15596
15597         var showLabels = config.showLabels,
15598             resizeLabels = config.resizeLabels,
15599             label = config.Label;
15600         
15601         if (dimArray) {
15602           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15603             acum += dimArray[i];
15604           }
15605           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15606               fontSize = (label.size * scale) >> 0;
15607           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15608           domElement.style.fontSize = fontSize + 'px';
15609           polar.rho = acum + config.labelOffset + config.sliceOffset;
15610           polar.theta = (begin + end) / 2;
15611           var pos = polar.getc(true);
15612           var radius = that.canvas.getSize();
15613           var labelPos = {
15614             x: Math.round(pos.x + radius.width / 2),
15615             y: Math.round(pos.y + radius.height / 2)
15616           };
15617           domElement.style.left = (labelPos.x - 200) + 'px';
15618           domElement.style.top = labelPos.y + 'px';
15619         }
15620       }
15621     });
15622     
15623     var size = sb.canvas.getSize(),
15624         min = Math.min;
15625     sb.config.levelDistance = min(size.width, size.height)/2 
15626       - config.offset - config.sliceOffset;
15627     this.sb = sb;
15628     this.canvas = this.sb.canvas;
15629     this.canvas.getCtx().globalCompositeOperation = 'lighter';
15630   },
15631     renderBackground: function() {
15632                 var canvas = this.canvas,
15633                 config = this.config,
15634                 backgroundColor = config.backgroundColor,
15635                 size = canvas.getSize(),
15636                 ctx = canvas.getCtx();
15637                 ctx.globalCompositeOperation = "destination-over";
15638             ctx.fillStyle = backgroundColor;
15639             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
15640   },
15641    renderTitle: function() {
15642         var canvas = this.canvas,
15643         size = canvas.getSize(),
15644         config = this.config,
15645         margin = config.Margin,
15646         radius = this.sb.config.levelDistance,
15647         title = config.Title,
15648         label = config.Label,
15649         subtitle = config.Subtitle;
15650         ctx = canvas.getCtx();
15651         ctx.fillStyle = title.color;
15652         ctx.textAlign = 'left';
15653         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15654         ctx.moveTo(0,0);
15655         if(label.type == 'Native') {
15656                 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15657         }       
15658   },
15659   renderSubtitle: function() {
15660         var canvas = this.canvas,
15661         size = canvas.getSize(),
15662         config = this.config,
15663         margin = config.Margin,
15664         radius = this.sb.config.levelDistance,
15665         title = config.Title,
15666         label = config.Label,
15667         subtitle = config.Subtitle;
15668         ctx = canvas.getCtx();
15669         ctx.fillStyle = title.color;
15670         ctx.textAlign = 'left';
15671         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15672         ctx.moveTo(0,0);
15673         if(label.type == 'Native') {
15674                 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15675         }       
15676   },
15677   clear: function() {
15678         var canvas = this.canvas;
15679         var ctx = canvas.getCtx(),
15680         size = canvas.getSize();
15681         ctx.fillStyle = "rgba(255,255,255,0)";
15682         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15683         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15684   },
15685   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
15686         var canvas = this.canvas,
15687         size = canvas.getSize(),
15688         config = this.config,
15689         orgHeight = size.height,
15690         margin = config.Margin,
15691         st = this.st,
15692         horz = config.orientation == 'horizontal';
15693         
15694
15695         var newWindowWidth = document.body.offsetWidth;
15696         var diff = newWindowWidth - orgWindowWidth;     
15697         var newWidth = orgContainerDivWidth + (diff/cols);
15698         var scale = newWidth/orgContainerDivWidth;
15699         canvas.resize(newWidth,orgHeight);
15700         if(typeof FlashCanvas == "undefined") {
15701                 canvas.clear();
15702         } else {
15703                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15704         }
15705         this.loadJSON(json);
15706
15707         },
15708   /*
15709     Method: loadJSON
15710    
15711     Loads JSON data into the visualization. 
15712     
15713     Parameters:
15714     
15715     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>.
15716     
15717     Example:
15718     (start code js)
15719     var pieChart = new $jit.PieChart(options);
15720     pieChart.loadJSON(json);
15721     (end code)
15722   */  
15723   loadJSON: function(json) {
15724     var prefix = $.time(), 
15725         ch = [], 
15726         sb = this.sb,
15727         name = $.splat(json.label),
15728         nameLength = name.length,
15729         color = $.splat(json.color || this.colors),
15730         colorLength = color.length,
15731         config = this.config,
15732         renderBackground = config.renderBackground,
15733         title = config.Title,
15734                 subtitle = config.Subtitle,
15735         gradient = !!config.type.split(":")[1],
15736         animate = config.animate,
15737         mono = nameLength == 1;
15738         totalValue = 0;
15739     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15740         var val = values[i];
15741         var valArray = $.splat(val.values);
15742         totalValue += parseFloat(valArray.sum());
15743     }
15744
15745     for(var i=0, values=json.values, l=values.length; i<l; i++) {
15746       var val = values[i];
15747       var valArray = $.splat(val.values);
15748           var percentage = (valArray.sum()/totalValue) * 100;
15749
15750       var linkArray = $.splat(val.links);
15751       var valuelabelsArray = $.splat(val.valuelabels);
15752       
15753  
15754       ch.push({
15755         'id': prefix + val.label,
15756         'name': val.label,
15757         'data': {
15758           'value': valArray,
15759           'valuelabel': valuelabelsArray,
15760           '$linkArray': linkArray,
15761           '$valuelabelsArray': valuelabelsArray,
15762           '$valueArray': valArray,
15763           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
15764           '$colorMono': $.splat(color[i % colorLength]),
15765           '$stringArray': name,
15766           '$gradient': gradient,
15767           '$config': config,
15768           '$iteration': i,
15769           '$percentage': percentage.toFixed(1),
15770           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
15771         },
15772         'children': []
15773       });
15774     }
15775     var root = {
15776       'id': prefix + '$root',
15777       'name': '',
15778       'data': {
15779         '$type': 'none',
15780         '$width': 1,
15781         '$height': 1
15782       },
15783       'children': ch
15784     };
15785     sb.loadJSON(root);
15786     
15787     
15788     this.normalizeDims();
15789
15790     
15791     sb.refresh();
15792     if(title.text != "") {
15793         this.renderTitle();
15794     }
15795        
15796     if(subtitle.text != "") {
15797         this.renderSubtitle();
15798     }
15799      if(renderBackground && typeof FlashCanvas == "undefined") {
15800         this.renderBackground();        
15801     }
15802     
15803     if(animate) {
15804       sb.fx.animate({
15805         modes: ['node-property:dimArray'],
15806         duration:1500
15807       });
15808     }
15809   },
15810   
15811   /*
15812     Method: updateJSON
15813    
15814     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.
15815     
15816     Parameters:
15817     
15818     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
15819     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
15820     
15821     Example:
15822     
15823     (start code js)
15824     pieChart.updateJSON(json, {
15825       onComplete: function() {
15826         alert('update complete!');
15827       }
15828     });
15829     (end code)
15830   */  
15831   updateJSON: function(json, onComplete) {
15832     if(this.busy) return;
15833     this.busy = true;
15834     
15835     var sb = this.sb;
15836     var graph = sb.graph;
15837     var values = json.values;
15838     var animate = this.config.animate;
15839     var that = this;
15840     $.each(values, function(v) {
15841       var n = graph.getByName(v.label),
15842           vals = $.splat(v.values);
15843       if(n) {
15844         n.setData('valueArray', vals);
15845         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
15846         if(json.label) {
15847           n.setData('stringArray', $.splat(json.label));
15848         }
15849       }
15850     });
15851     this.normalizeDims();
15852     if(animate) {
15853       sb.compute('end');
15854       sb.fx.animate({
15855         modes: ['node-property:dimArray:span', 'linear'],
15856         duration:1500,
15857         onComplete: function() {
15858           that.busy = false;
15859           onComplete && onComplete.onComplete();
15860         }
15861       });
15862     } else {
15863       sb.refresh();
15864     }
15865   },
15866     
15867   //adds the little brown bar when hovering the node
15868   select: function(id, name) {
15869     if(!this.config.hoveredColor) return;
15870     var s = this.selected;
15871     if(s.id != id || s.name != name) {
15872       s.id = id;
15873       s.name = name;
15874       s.color = this.config.hoveredColor;
15875       this.sb.graph.eachNode(function(n) {
15876         if(id == n.id) {
15877           n.setData('border', s);
15878         } else {
15879           n.setData('border', false);
15880         }
15881       });
15882       this.sb.plot();
15883     }
15884   },
15885   
15886   /*
15887     Method: getLegend
15888    
15889     Returns an object containing as keys the legend names and as values hex strings with color values.
15890     
15891     Example:
15892     
15893     (start code js)
15894     var legend = pieChart.getLegend();
15895     (end code)
15896   */  
15897   getLegend: function() {
15898     var legend = new Array();
15899     var name = new Array();
15900     var color = new Array();
15901     var n;
15902     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
15903       n = adj.nodeTo;
15904     });
15905     var colors = n.getData('colorArray'),
15906         len = colors.length;
15907     $.each(n.getData('stringArray'), function(s, i) {
15908       color[i] = colors[i % len];
15909       name[i] = s;
15910     });
15911         legend['name'] = name;
15912         legend['color'] = color;
15913     return legend;
15914   },
15915   
15916   /*
15917     Method: getMaxValue
15918    
15919     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
15920     
15921     Example:
15922     
15923     (start code js)
15924     var ans = pieChart.getMaxValue();
15925     (end code)
15926     
15927     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
15928     
15929     Example:
15930     
15931     (start code js)
15932     //will return 100 for all PieChart instances,
15933     //displaying all of them with the same scale
15934     $jit.PieChart.implement({
15935       'getMaxValue': function() {
15936         return 100;
15937       }
15938     });
15939     (end code)
15940     
15941   */  
15942   getMaxValue: function() {
15943     var maxValue = 0;
15944     this.sb.graph.eachNode(function(n) {
15945       var valArray = n.getData('valueArray'),
15946           acum = 0;
15947       $.each(valArray, function(v) { 
15948         acum += +v;
15949       });
15950       maxValue = maxValue>acum? maxValue:acum;
15951     });
15952     return maxValue;
15953   },
15954   
15955   normalizeDims: function() {
15956     //number of elements
15957     var root = this.sb.graph.getNode(this.sb.root), l=0;
15958     root.eachAdjacency(function() {
15959       l++;
15960     });
15961     var maxValue = this.getMaxValue() || 1,
15962         config = this.config,
15963         animate = config.animate,
15964         rho = this.sb.config.levelDistance;
15965     this.sb.graph.eachNode(function(n) {
15966       var acum = 0, animateValue = [];
15967       $.each(n.getData('valueArray'), function(v) {
15968         acum += +v;
15969         animateValue.push(1);
15970       });
15971       var stat = (animateValue.length == 1) && !config.updateHeights;
15972       if(animate) {
15973         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15974           return stat? rho: (n * rho / maxValue); 
15975         }), 'end');
15976         var dimArray = n.getData('dimArray');
15977         if(!dimArray) {
15978           n.setData('dimArray', animateValue);
15979         }
15980       } else {
15981         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
15982           return stat? rho : (n * rho / maxValue); 
15983         }));
15984       }
15985       n.setData('normalizedDim', acum / maxValue);
15986     });
15987   }
15988 });
15989
15990
15991 //Gauge Chart
15992
15993 Options.GaugeChart = {
15994   $extend: true,
15995
15996   animate: true,
15997   offset: 25, // page offset
15998   sliceOffset:0,
15999   labelOffset: 3, // label offset
16000   type: 'stacked', // gradient
16001   labelType: 'name',
16002   hoveredColor: '#9fd4ff',
16003   Events: {
16004     enable: false,
16005     onClick: $.empty
16006   },
16007   Tips: {
16008     enable: false,
16009     onShow: $.empty,
16010     onHide: $.empty
16011   },
16012   showLabels: true,
16013   resizeLabels: false,
16014   
16015   //only valid for mono-valued datasets
16016   updateHeights: false
16017 };
16018
16019
16020
16021 $jit.Sunburst.Plot.NodeTypes.implement({
16022     'gaugechart-basic' : {
16023     'render' : function(node, canvas) {
16024       var pos = node.pos.getp(true),
16025           dimArray = node.getData('dimArray'),
16026           valueArray = node.getData('valueArray'),
16027           valuelabelsArray = node.getData('valuelabelsArray'),
16028           gaugeTarget = node.getData('gaugeTarget'),
16029           nodeIteration = node.getData('nodeIteration'),
16030           nodeLength = node.getData('nodeLength'),
16031           colorArray = node.getData('colorMono'),
16032           colorLength = colorArray.length,
16033           stringArray = node.getData('stringArray'),
16034           span = node.getData('span') / 2,
16035           theta = node.pos.theta,
16036           begin = ((theta - span)/2)+Math.PI,
16037           end = ((theta + span)/2)+Math.PI,
16038           polar = new Polar;
16039
16040   
16041       var ctx = canvas.getCtx(), 
16042           opt = {},
16043           gradient = node.getData('gradient'),
16044           border = node.getData('border'),
16045           config = node.getData('config'),
16046           showLabels = config.showLabels,
16047           resizeLabels = config.resizeLabels,
16048           label = config.Label;
16049
16050       var xpos = Math.cos((begin + end) /2);
16051       var ypos = Math.sin((begin + end) /2);
16052
16053       if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16054         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16055           var dimi = dimArray[i], colori = colorArray[i % colorLength];
16056           if(dimi <= 0) continue;
16057           ctx.fillStyle = ctx.strokeStyle = colori;
16058           if(gradient && dimi) {
16059             var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16060                 xpos, (ypos + dimi/2), acum + dimi);
16061             var colorRgb = $.hexToRgb(colori), 
16062                 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16063                 endColor = $.rgbToHex(ans);
16064
16065             radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16066             radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16067             radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16068             radialGradient.addColorStop(1,  'rgba('+ans+',1)');
16069             ctx.fillStyle = radialGradient;
16070           }
16071           
16072           polar.rho = acum;
16073           polar.theta = begin;
16074           var p1coord = polar.getc(true);
16075           polar.theta = end;
16076           var p2coord = polar.getc(true);
16077           polar.rho += dimi;
16078           var p3coord = polar.getc(true);
16079           polar.theta = begin;
16080           var p4coord = polar.getc(true);
16081
16082                   
16083           ctx.beginPath();
16084           //fixing FF arc method + fill
16085           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16086           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16087           ctx.fill();
16088                   
16089
16090           acum += (dimi || 0);
16091           valAcum += (valueArray[i] || 0);                
16092         }
16093                 
16094                 if(showLabels && label.type == 'Native') {
16095                           ctx.save();
16096                           ctx.fillStyle = ctx.strokeStyle = label.color;
16097
16098                           
16099                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16100                           ctx.textBaseline = 'bottom';
16101                           ctx.textAlign = 'center';
16102
16103                           polar.rho = acum * .65;
16104                           polar.theta = begin;
16105                           var cart = polar.getc(true);
16106                           
16107                           //changes y pos of first label
16108                           if(nodeIteration == 1) {
16109                                 textY = cart.y - (label.size/2) + acum /2;
16110                           } else {
16111                                 textY = cart.y + acum/2;
16112                           }
16113                           
16114                           if(config.labelType == 'name') {
16115                                 ctx.fillText(node.name, cart.x, textY);
16116                           } else {
16117                                 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16118                           }
16119                           
16120                           //adds final label
16121                           if(nodeIteration == nodeLength) {
16122                                 polar.theta = end;
16123                                 var cart = polar.getc(true);
16124                                 if(config.labelType == 'name') {
16125                                         ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16126                                 } else {
16127                                         ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16128                                 }
16129                                 
16130                           }
16131                           ctx.restore();
16132                 }
16133
16134       }
16135       },
16136     'contains': function(node, pos) {
16137                 
16138                 
16139                 
16140       if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16141                 var config = node.getData('config');
16142         var ld = this.config.levelDistance , d = node._depth;
16143                 var yOffset = pos.y - (ld/2);
16144                 var xOffset = pos.x;
16145         var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16146         if(rho <=parseInt(ld * d)) {
16147           var dimArray = node.getData('dimArray');
16148           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16149                         var dimi = dimArray[i];
16150             if(rho >= ld * .8 && rho <= acum + dimi) {
16151                 
16152                           var url = Url.decode(node.getData('linkArray')[i]);
16153               return {
16154                 name: node.getData('stringArray')[i],
16155                 link: url,
16156                 color: node.getData('colorArray')[i],
16157                 value: node.getData('valueArray')[i],
16158                 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16159                 label: node.name
16160               };
16161             }
16162             acum += dimi;
16163                         
16164                         
16165           }
16166         }
16167         return false;
16168         
16169       }
16170       return false;
16171     }
16172   }
16173 });
16174
16175 /*
16176   Class: GaugeChart
16177   
16178   A visualization that displays gauge charts
16179   
16180   Constructor Options:
16181   
16182   See <Options.Gauge>.
16183
16184 */
16185 $jit.GaugeChart = new Class({
16186   sb: null,
16187   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16188   selected: {},
16189   busy: false,
16190   
16191   initialize: function(opt) {
16192     this.controller = this.config = 
16193       $.merge(Options("Canvas", "GaugeChart", "Label"), {
16194         Label: { type: 'Native' }
16195       }, opt);
16196     this.initializeViz();
16197   },
16198   
16199   initializeViz: function() {
16200     var config = this.config, that = this;
16201     var nodeType = config.type.split(":")[0];
16202     var sb = new $jit.Sunburst({
16203       injectInto: config.injectInto,
16204       useCanvas: config.useCanvas,
16205       withLabels: config.Label.type != 'Native',
16206       background: config.background,
16207       renderBackground: config.renderBackground,
16208       backgroundColor: config.backgroundColor,
16209       colorStop1: config.colorStop1,
16210       colorStop2: config.colorStop2,
16211       Label: {
16212         type: config.Label.type
16213       },
16214       Node: {
16215         overridable: true,
16216         type: 'gaugechart-' + nodeType,
16217         width: 1,
16218         height: 1
16219       },
16220       Edge: {
16221         type: 'none'
16222       },
16223       Tips: {
16224         enable: config.Tips.enable,
16225         type: 'Native',
16226         force: true,
16227         onShow: function(tip, node, contains) {
16228           var elem = contains;
16229           config.Tips.onShow(tip, elem, node);
16230                           if(elem.link != 'undefined' && elem.link != '') {
16231                                 document.body.style.cursor = 'pointer';
16232                           }
16233         },
16234                 onHide: function() {
16235                                 document.body.style.cursor = 'default';
16236         }
16237       },
16238       Events: {
16239         enable: true,
16240         type: 'Native',
16241         onClick: function(node, eventInfo, evt) {
16242           if(!config.Events.enable) return;
16243           var elem = eventInfo.getContains();
16244           config.Events.onClick(elem, eventInfo, evt);
16245         }
16246         },
16247       onCreateLabel: function(domElement, node) {
16248         var labelConf = config.Label;
16249         if(config.showLabels) {
16250           var style = domElement.style;
16251           style.fontSize = labelConf.size + 'px';
16252           style.fontFamily = labelConf.family;
16253           style.color = labelConf.color;
16254           style.textAlign = 'center';
16255           valuelabelsArray = node.getData('valuelabelsArray'),
16256           nodeIteration = node.getData('nodeIteration'),
16257           nodeLength = node.getData('nodeLength'),
16258           canvas = sb.canvas,
16259           prefix = $.time();
16260           
16261           if(config.labelType == 'name') {
16262                 domElement.innerHTML = node.name;
16263           } else {
16264                 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16265           }
16266           
16267           domElement.style.width = '400px';
16268           
16269           //adds final label
16270                   if(nodeIteration == nodeLength && nodeLength != 0) {
16271                   idLabel = canvas.id + "-label";
16272                   container = document.getElementById(idLabel);
16273                   finalLabel = document.createElement('div');
16274                   finalLabelStyle = finalLabel.style;
16275                   finalLabel.id = prefix + "finalLabel";
16276                   finalLabelStyle.position = "absolute";
16277                   finalLabelStyle.width = "400px";
16278                   finalLabelStyle.left = "0px";
16279                   container.appendChild(finalLabel);
16280                         if(config.labelType == 'name') {
16281                                 finalLabel.innerHTML = node.name;
16282                         } else {
16283                                 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16284                         }
16285                         
16286                   }
16287         }
16288       },
16289       onPlaceLabel: function(domElement, node) {
16290         if(!config.showLabels) return;
16291         var pos = node.pos.getp(true),
16292             dimArray = node.getData('dimArray'),
16293             nodeIteration = node.getData('nodeIteration'),
16294             nodeLength = node.getData('nodeLength'),
16295             span = node.getData('span') / 2,
16296             theta = node.pos.theta,
16297             begin = ((theta - span)/2)+Math.PI,
16298             end = ((theta + span)/2)+Math.PI,
16299             polar = new Polar;
16300
16301         var showLabels = config.showLabels,
16302             resizeLabels = config.resizeLabels,
16303             label = config.Label,
16304             radiusOffset = sb.config.levelDistance;
16305
16306         if (dimArray) {
16307           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16308             acum += dimArray[i];
16309           }
16310           var scale = resizeLabels? node.getData('normalizedDim') : 1,
16311               fontSize = (label.size * scale) >> 0;
16312           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16313           domElement.style.fontSize = fontSize + 'px';
16314           polar.rho = acum * .65;
16315           polar.theta = begin;
16316           var pos = polar.getc(true);
16317           var radius = that.canvas.getSize();
16318           var labelPos = {
16319             x: Math.round(pos.x + radius.width / 2),
16320             y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16321           };
16322           
16323
16324           
16325           domElement.style.left = (labelPos.x - 200) + 'px';
16326           domElement.style.top = labelPos.y + 'px';
16327           
16328           //reposition first label
16329           if(nodeIteration == 1) {
16330                  domElement.style.top = labelPos.y - label.size + 'px';
16331           }
16332           
16333           
16334           //position final label
16335                 if(nodeIteration == nodeLength && nodeLength != 0) {
16336                         polar.theta = end;
16337                         var final = polar.getc(true);
16338                         var finalPos = {
16339                                         x: Math.round(final.x + radius.width / 2),
16340                                 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16341                                 };
16342                         finalLabel.style.left = (finalPos.x - 200) + "px";
16343                         finalLabel.style.top = finalPos.y - label.size + "px";
16344                     }
16345           
16346         }
16347       }
16348    
16349     });
16350     this.sb = sb;
16351     this.canvas = this.sb.canvas;
16352     var size = sb.canvas.getSize(),
16353         min = Math.min;
16354         sb.config.levelDistance = min(size.width, size.height)/2 
16355       - config.offset - config.sliceOffset;
16356
16357
16358   },
16359         
16360   renderBackground: function() {
16361         var canvas = this.sb.canvas,
16362         config = this.config,
16363         style = config.gaugeStyle,
16364         ctx = canvas.getCtx(),
16365         size = canvas.getSize(),
16366         radius = this.sb.config.levelDistance,
16367         startAngle = (Math.PI/180)*1,
16368         endAngle = (Math.PI/180)*179;
16369         
16370
16371         //background border
16372         ctx.fillStyle = style.borderColor;      
16373         ctx.beginPath();
16374         ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true); 
16375         ctx.fill(); 
16376         
16377         
16378         var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16379         radialGradient.addColorStop(0, '#ffffff');  
16380         radialGradient.addColorStop(0.3, style.backgroundColor);  
16381         radialGradient.addColorStop(0.6, style.backgroundColor);  
16382         radialGradient.addColorStop(1, '#FFFFFF'); 
16383         ctx.fillStyle = radialGradient;
16384         
16385         //background
16386         startAngle = (Math.PI/180)*0;
16387         endAngle = (Math.PI/180)*180;
16388         ctx.beginPath();
16389         ctx.arc(0,radius/2,radius,startAngle,endAngle, true); 
16390         ctx.fill();     
16391         
16392         
16393  
16394   },
16395   
16396   
16397   renderNeedle: function(gaugePosition,target) {
16398         var canvas = this.sb.canvas,
16399         config = this.config,
16400         style = config.gaugeStyle,
16401         ctx = canvas.getCtx(),
16402         size = canvas.getSize(),
16403         radius = this.sb.config.levelDistance;
16404         gaugeCenter = (radius/2);
16405         startAngle = 0;
16406         endAngle = (Math.PI/180)*180;
16407         
16408         
16409         // needle
16410         ctx.fillStyle = style.needleColor;
16411         var segments = 180/target;
16412         needleAngle = gaugePosition * segments;
16413         ctx.translate(0, gaugeCenter);
16414         ctx.save();
16415         ctx.rotate(needleAngle * Math.PI / 180);  
16416         ctx.beginPath();
16417         ctx.moveTo(0,0); 
16418         ctx.lineTo(0,-4);  
16419         ctx.lineTo(-radius*.9,-1);  
16420         ctx.lineTo(-radius*.9,1);  
16421         ctx.lineTo(0,4);  
16422         ctx.lineTo(0,0);  
16423         ctx.closePath(); 
16424         ctx.fill();
16425         ctx.restore(); 
16426         
16427         
16428         // stroke needle
16429         ctx.lineWidth = 1;
16430         ctx.strokeStyle = '#aa0000';
16431         ctx.save();
16432         ctx.rotate(needleAngle * Math.PI / 180);  
16433         ctx.beginPath();
16434         ctx.moveTo(0,0); 
16435         ctx.lineTo(0,-4);  
16436         ctx.lineTo(-radius*.8,-1);  
16437         ctx.lineTo(-radius*.8,1);  
16438         ctx.lineTo(0,4);  
16439         ctx.lineTo(0,0);  
16440         ctx.closePath(); 
16441         ctx.stroke();
16442         ctx.restore(); 
16443
16444         //needle cap
16445         ctx.fillStyle = "#000000";
16446         ctx.lineWidth = style.borderSize;
16447     ctx.strokeStyle = style.borderColor;
16448         var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16449         radialGradient.addColorStop(0, '#666666');  
16450         radialGradient.addColorStop(0.8, '#444444');  
16451         radialGradient.addColorStop(1, 'rgba(0,0,0,0)'); 
16452         ctx.fillStyle = radialGradient;
16453         ctx.translate(0,5);
16454         ctx.save();
16455         ctx.beginPath();
16456         ctx.arc(0,0,radius*.2,startAngle,endAngle, true); 
16457         ctx.fill();     
16458         ctx.restore();
16459
16460         
16461   },
16462   
16463   renderTicks: function(values) {
16464         var canvas = this.sb.canvas,
16465         config = this.config,
16466         style = config.gaugeStyle,
16467         ctx = canvas.getCtx(),
16468         size = canvas.getSize(),
16469         radius = this.sb.config.levelDistance,
16470         gaugeCenter = (radius/2);
16471         
16472         
16473         ctx.strokeStyle = style.borderColor;
16474         ctx.lineWidth = 5;
16475         ctx.lineCap = "round";
16476                 for(var i=0, total = 0, l=values.length; i<l; i++) {
16477                         var val = values[i];
16478                         if(val.label != 'GaugePosition') {
16479                         total += (parseInt(val.values) || 0);
16480                         }
16481                 }
16482         
16483                 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16484                         var val = values[i];
16485                         if(val.label != 'GaugePosition') {
16486                         acum += (parseInt(val.values) || 0);
16487
16488                            var segments = 180/total;
16489                         angle = acum * segments;
16490
16491                           //alert(acum);
16492                                  ctx.save();
16493                                  ctx.translate(0, gaugeCenter);
16494                                  ctx.beginPath();
16495                                 ctx.rotate(angle * (Math.PI/180));
16496                                 ctx.moveTo(-radius,0);
16497                                 ctx.lineTo(-radius*.75,0);
16498                                 ctx.stroke();
16499                                  ctx.restore();
16500                         
16501                         }
16502                 }
16503         },
16504         
16505         renderPositionLabel: function(position) {
16506                 var canvas = this.sb.canvas,
16507                 config = this.config,
16508                 label = config.Label,
16509                 style = config.gaugeStyle,
16510                 ctx = canvas.getCtx(),
16511                 size = canvas.getSize(),
16512                 radius = this.sb.config.levelDistance,
16513                 gaugeCenter = (radius/2);
16514                 ctx.textBaseline = 'middle';
16515                 ctx.textAlign = 'center';
16516                 ctx.font = style.positionFontSize + 'px ' + label.family;
16517                 ctx.fillStyle = "#ffffff";
16518                 ctx.lineWidth = 2;
16519                 height = style.positionFontSize + 10,
16520                 cornerRadius = 8,
16521                 idLabel = canvas.id + "-label";
16522                 container = document.getElementById(idLabel);
16523                 if(label.type == 'Native') {
16524                         var m = ctx.measureText(position),
16525                         width = m.width + 40;
16526                 } else {
16527                         var width = 70;
16528                 }
16529                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16530                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16531                 if(label.type == 'Native') {
16532                         ctx.fillStyle = label.color;
16533                         ctx.fillText(position, 0, (height/2) + style.positionOffset);
16534                 } else {
16535                         var labelDiv =  document.createElement('div');
16536                         labelDivStyle = labelDiv.style;
16537                         labelDivStyle.color =  label.color;
16538                         labelDivStyle.fontSize =  style.positionFontSize + "px";
16539                         labelDivStyle.position = "absolute";
16540                         labelDivStyle.width = width + "px";
16541                         labelDivStyle.left = (size.width/2) - (width/2) + "px";
16542                         labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16543                         labelDiv.innerHTML = position;
16544                         container.appendChild(labelDiv);
16545                 }
16546         
16547     },
16548     
16549    renderSubtitle: function() {
16550         var canvas = this.canvas,
16551         size = canvas.getSize(),
16552         config = this.config,
16553         margin = config.Margin,
16554         radius = this.sb.config.levelDistance,
16555         title = config.Title,
16556         label = config.Label,
16557         subtitle = config.Subtitle;
16558         ctx = canvas.getCtx();
16559         ctx.fillStyle = title.color;
16560         ctx.textAlign = 'left';
16561         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16562         ctx.moveTo(0,0);
16563         if(label.type == 'Native') {
16564                 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2)); 
16565         }
16566   },
16567   
16568   renderChartBackground: function() {
16569                 var canvas = this.canvas,
16570                 config = this.config,
16571                 backgroundColor = config.backgroundColor,
16572                 size = canvas.getSize(),
16573                 ctx = canvas.getCtx();
16574                 //ctx.globalCompositeOperation = "destination-over";
16575             ctx.fillStyle = backgroundColor;
16576             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
16577   },
16578   clear: function() {
16579         var canvas = this.canvas;
16580         var ctx = canvas.getCtx(),
16581         size = canvas.getSize();
16582         ctx.fillStyle = "rgba(255,255,255,0)";
16583         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16584         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16585  },
16586   resizeGraph: function(json,orgWindowWidth,orgContainerDivWidth,cols) {
16587         var canvas = this.canvas,
16588         size = canvas.getSize(),
16589         config = this.config,
16590         orgHeight = size.height,
16591         margin = config.Margin,
16592         st = this.st,
16593         horz = config.orientation == 'horizontal';
16594         
16595
16596         var newWindowWidth = document.body.offsetWidth;
16597         var diff = newWindowWidth - orgWindowWidth;     
16598         var newWidth = orgContainerDivWidth + (diff/cols);
16599         canvas.resize(newWidth,orgHeight);
16600         if(typeof FlashCanvas == "undefined") {
16601                 canvas.clear();
16602         } else {
16603                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16604         }
16605         this.loadJSON(json);
16606
16607         },
16608   loadJSON: function(json) {
16609   
16610      var prefix = $.time(), 
16611         ch = [], 
16612         sb = this.sb,
16613         name = $.splat(json.label),
16614         nameLength = name.length,
16615         color = $.splat(json.color || this.colors),
16616         colorLength = color.length,
16617         config = this.config,
16618         renderBackground = config.renderBackground,
16619         gradient = !!config.type.split(":")[1],
16620         animate = config.animate,
16621         mono = nameLength == 1;
16622                 var props = $.splat(json.properties)[0];
16623
16624     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16625         
16626       var val = values[i];
16627           if(val.label != 'GaugePosition') {
16628                   var valArray = $.splat(val.values);
16629                   var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16630                   var valuelabelsArray = $.splat(val.valuelabels);
16631
16632                   ch.push({
16633                         'id': prefix + val.label,
16634                         'name': val.label,
16635                         'data': {
16636                           'value': valArray,
16637                           'valuelabel': valuelabelsArray,
16638                           '$linkArray': linkArray,
16639                           '$valuelabelsArray': valuelabelsArray,
16640                           '$valueArray': valArray,
16641                           '$nodeIteration': i,
16642                           '$nodeLength': l-1,
16643                           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16644                           '$colorMono': $.splat(color[i % colorLength]),
16645                           '$stringArray': name,
16646                           '$gradient': gradient,
16647                           '$config': config,
16648                           '$gaugeTarget': props['gaugeTarget'],
16649                           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16650                         },
16651                         'children': []
16652                   });
16653           } else {
16654                 var gaugePosition = val.gvalue;
16655                 var gaugePositionLabel = val.gvaluelabel;
16656           }
16657     }
16658     var root = {
16659       'id': prefix + '$root',
16660       'name': '',
16661       'data': {
16662         '$type': 'none',
16663         '$width': 1,
16664         '$height': 1
16665       },
16666       'children': ch
16667     };
16668         
16669         
16670     sb.loadJSON(root);
16671     
16672     if(renderBackground) {
16673         this.renderChartBackground();   
16674     }
16675     
16676     this.renderBackground();
16677     this.renderSubtitle();
16678     
16679     this.normalizeDims();
16680         
16681     sb.refresh();
16682     if(animate) {
16683       sb.fx.animate({
16684         modes: ['node-property:dimArray'],
16685         duration:1500
16686       });
16687     }
16688         
16689
16690         this.renderPositionLabel(gaugePositionLabel);
16691         if (props['gaugeTarget'] != 0) {
16692                 this.renderTicks(json.values);
16693                 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16694         }
16695         
16696         
16697
16698   },
16699   
16700   updateJSON: function(json, onComplete) {
16701     if(this.busy) return;
16702     this.busy = true;
16703     
16704     var sb = this.sb;
16705     var graph = sb.graph;
16706     var values = json.values;
16707     var animate = this.config.animate;
16708     var that = this;
16709     $.each(values, function(v) {
16710       var n = graph.getByName(v.label),
16711           vals = $.splat(v.values);
16712       if(n) {
16713         n.setData('valueArray', vals);
16714         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16715         if(json.label) {
16716           n.setData('stringArray', $.splat(json.label));
16717         }
16718       }
16719     });
16720     this.normalizeDims();
16721     if(animate) {
16722       sb.compute('end');
16723       sb.fx.animate({
16724         modes: ['node-property:dimArray:span', 'linear'],
16725         duration:1500,
16726         onComplete: function() {
16727           that.busy = false;
16728           onComplete && onComplete.onComplete();
16729         }
16730       });
16731     } else {
16732       sb.refresh();
16733     }
16734   },
16735     
16736   //adds the little brown bar when hovering the node
16737   select: function(id, name) {
16738     if(!this.config.hoveredColor) return;
16739     var s = this.selected;
16740     if(s.id != id || s.name != name) {
16741       s.id = id;
16742       s.name = name;
16743       s.color = this.config.hoveredColor;
16744       this.sb.graph.eachNode(function(n) {
16745         if(id == n.id) {
16746           n.setData('border', s);
16747         } else {
16748           n.setData('border', false);
16749         }
16750       });
16751       this.sb.plot();
16752     }
16753   },
16754   
16755   /*
16756     Method: getLegend
16757    
16758     Returns an object containing as keys the legend names and as values hex strings with color values.
16759     
16760     Example:
16761     
16762     (start code js)
16763     var legend = pieChart.getLegend();
16764     (end code)
16765   */  
16766   getLegend: function() {
16767     var legend = new Array();
16768     var name = new Array();
16769     var color = new Array();
16770     var n;
16771     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16772       n = adj.nodeTo;
16773     });
16774     var colors = n.getData('colorArray'),
16775         len = colors.length;
16776     $.each(n.getData('stringArray'), function(s, i) {
16777       color[i] = colors[i % len];
16778       name[i] = s;
16779     });
16780         legend['name'] = name;
16781         legend['color'] = color;
16782     return legend;
16783   },
16784   
16785   /*
16786     Method: getMaxValue
16787    
16788     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16789     
16790     Example:
16791     
16792     (start code js)
16793     var ans = pieChart.getMaxValue();
16794     (end code)
16795     
16796     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16797     
16798     Example:
16799     
16800     (start code js)
16801     //will return 100 for all PieChart instances,
16802     //displaying all of them with the same scale
16803     $jit.PieChart.implement({
16804       'getMaxValue': function() {
16805         return 100;
16806       }
16807     });
16808     (end code)
16809     
16810   */  
16811   getMaxValue: function() {
16812     var maxValue = 0;
16813     this.sb.graph.eachNode(function(n) {
16814       var valArray = n.getData('valueArray'),
16815           acum = 0;
16816       $.each(valArray, function(v) { 
16817         acum += +v;
16818       });
16819       maxValue = maxValue>acum? maxValue:acum;
16820     });
16821     return maxValue;
16822   },
16823   
16824   normalizeDims: function() {
16825     //number of elements
16826     var root = this.sb.graph.getNode(this.sb.root), l=0;
16827     root.eachAdjacency(function() {
16828       l++;
16829     });
16830     var maxValue = this.getMaxValue() || 1,
16831         config = this.config,
16832         animate = config.animate,
16833         rho = this.sb.config.levelDistance;
16834     this.sb.graph.eachNode(function(n) {
16835       var acum = 0, animateValue = [];
16836       $.each(n.getData('valueArray'), function(v) {
16837         acum += +v;
16838         animateValue.push(1);
16839       });
16840       var stat = (animateValue.length == 1) && !config.updateHeights;
16841       if(animate) {
16842         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16843           return stat? rho: (n * rho / maxValue); 
16844         }), 'end');
16845         var dimArray = n.getData('dimArray');
16846         if(!dimArray) {
16847           n.setData('dimArray', animateValue);
16848         }
16849       } else {
16850         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16851           return stat? rho : (n * rho / maxValue); 
16852         }));
16853       }
16854       n.setData('normalizedDim', acum / maxValue);
16855     });
16856   }
16857 });
16858
16859
16860 /*
16861  * Class: Layouts.TM
16862  * 
16863  * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
16864  * 
16865  * Implemented By:
16866  * 
16867  * <TM>
16868  * 
16869  */
16870 Layouts.TM = {};
16871
16872 Layouts.TM.SliceAndDice = new Class({
16873   compute: function(prop) {
16874     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16875     this.controller.onBeforeCompute(root);
16876     var size = this.canvas.getSize(),
16877         config = this.config,
16878         width = size.width,
16879         height = size.height;
16880     this.graph.computeLevels(this.root, 0, "ignore");
16881     //set root position and dimensions
16882     root.getPos(prop).setc(-width/2, -height/2);
16883     root.setData('width', width, prop);
16884     root.setData('height', height + config.titleHeight, prop);
16885     this.computePositions(root, root, this.layout.orientation, prop);
16886     this.controller.onAfterCompute(root);
16887   },
16888   
16889   computePositions: function(par, ch, orn, prop) {
16890     //compute children areas
16891     var totalArea = 0;
16892     par.eachSubnode(function(n) {
16893       totalArea += n.getData('area', prop);
16894     });
16895     
16896     var config = this.config,
16897         offst = config.offset,
16898         width  = par.getData('width', prop),
16899         height = par.getData('height', prop) - config.titleHeight,
16900         fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
16901     
16902     var otherSize, size, dim, pos, pos2, posth, pos2th;
16903     var horizontal = (orn == "h");
16904     if(horizontal) {
16905       orn = 'v';    
16906       otherSize = height;
16907       size = width * fact;
16908       dim = 'height';
16909       pos = 'y';
16910       pos2 = 'x';
16911       posth = config.titleHeight;
16912       pos2th = 0;
16913     } else {
16914       orn = 'h';    
16915       otherSize = height * fact;
16916       size = width;
16917       dim = 'width';
16918       pos = 'x';
16919       pos2 = 'y';
16920       posth = 0;
16921       pos2th = config.titleHeight;
16922     }
16923     var cpos = ch.getPos(prop);
16924     ch.setData('width', size, prop);
16925     ch.setData('height', otherSize, prop);
16926     var offsetSize = 0, tm = this;
16927     ch.eachSubnode(function(n) {
16928       var p = n.getPos(prop);
16929       p[pos] = offsetSize + cpos[pos] + posth;
16930       p[pos2] = cpos[pos2] + pos2th;
16931       tm.computePositions(ch, n, orn, prop);
16932       offsetSize += n.getData(dim, prop);
16933     });
16934   }
16935
16936 });
16937
16938 Layouts.TM.Area = {
16939  /*
16940     Method: compute
16941  
16942    Called by loadJSON to calculate recursively all node positions and lay out the tree.
16943  
16944     Parameters:
16945
16946        json - A JSON tree. See also <Loader.loadJSON>.
16947        coord - A coordinates object specifying width, height, left and top style properties.
16948  */
16949  compute: function(prop) {
16950     prop = prop || "current";
16951     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
16952     this.controller.onBeforeCompute(root);
16953     var config = this.config,
16954         size = this.canvas.getSize(),
16955         width = size.width,
16956         height = size.height,
16957         offst = config.offset,
16958         offwdth = width - offst,
16959         offhght = height - offst;
16960     this.graph.computeLevels(this.root, 0, "ignore");
16961     //set root position and dimensions
16962     root.getPos(prop).setc(-width/2, -height/2);
16963     root.setData('width', width, prop);
16964     root.setData('height', height, prop);
16965     //create a coordinates object
16966     var coord = {
16967         'top': -height/2 + config.titleHeight,
16968         'left': -width/2,
16969         'width': offwdth,
16970         'height': offhght - config.titleHeight
16971     };
16972     this.computePositions(root, coord, prop);
16973     this.controller.onAfterCompute(root);
16974  }, 
16975  
16976  /*
16977     Method: computeDim
16978  
16979    Computes dimensions and positions of a group of nodes
16980    according to a custom layout row condition. 
16981  
16982     Parameters:
16983
16984        tail - An array of nodes.  
16985        initElem - An array of nodes (containing the initial node to be laid).
16986        w - A fixed dimension where nodes will be layed out.
16987        coord - A coordinates object specifying width, height, left and top style properties.
16988        comp - A custom comparison function
16989  */
16990  computeDim: function(tail, initElem, w, coord, comp, prop) {
16991    if(tail.length + initElem.length == 1) {
16992      var l = (tail.length == 1)? tail : initElem;
16993      this.layoutLast(l, w, coord, prop);
16994      return;
16995    }
16996    if(tail.length >= 2 && initElem.length == 0) {
16997      initElem = [tail.shift()];
16998    }
16999    if(tail.length == 0) {
17000      if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17001      return;
17002    }
17003    var c = tail[0];
17004    if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17005      this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17006    } else {
17007      var newCoords = this.layoutRow(initElem, w, coord, prop);
17008      this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17009    }
17010  },
17011
17012  
17013  /*
17014     Method: worstAspectRatio
17015  
17016    Calculates the worst aspect ratio of a group of rectangles. 
17017        
17018     See also:
17019        
17020        <http://en.wikipedia.org/wiki/Aspect_ratio>
17021    
17022     Parameters:
17023
17024      ch - An array of nodes.  
17025      w  - The fixed dimension where rectangles are being laid out.
17026
17027     Returns:
17028  
17029         The worst aspect ratio.
17030
17031
17032  */
17033  worstAspectRatio: function(ch, w) {
17034    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17035    var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17036    for(var i=0, l=ch.length; i<l; i++) {
17037      var area = ch[i]._area;
17038      areaSum += area; 
17039      minArea = minArea < area? minArea : area;
17040      maxArea = maxArea > area? maxArea : area; 
17041    }
17042    var sqw = w * w, sqAreaSum = areaSum * areaSum;
17043    return Math.max(sqw * maxArea / sqAreaSum,
17044            sqAreaSum / (sqw * minArea));
17045  },
17046  
17047  /*
17048     Method: avgAspectRatio
17049  
17050    Calculates the average aspect ratio of a group of rectangles. 
17051        
17052        See also:
17053        
17054        <http://en.wikipedia.org/wiki/Aspect_ratio>
17055    
17056     Parameters:
17057
17058      ch - An array of nodes.  
17059        w - The fixed dimension where rectangles are being laid out.
17060
17061     Returns:
17062  
17063         The average aspect ratio.
17064
17065
17066  */
17067  avgAspectRatio: function(ch, w) {
17068    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17069    var arSum = 0;
17070    for(var i=0, l=ch.length; i<l; i++) {
17071      var area = ch[i]._area;
17072      var h = area / w;
17073      arSum += w > h? w / h : h / w;
17074    }
17075    return arSum / l;
17076  },
17077
17078  /*
17079     layoutLast
17080  
17081    Performs the layout of the last computed sibling.
17082  
17083     Parameters:
17084
17085        ch - An array of nodes.  
17086        w - A fixed dimension where nodes will be layed out.
17087      coord - A coordinates object specifying width, height, left and top style properties.
17088  */
17089  layoutLast: function(ch, w, coord, prop) {
17090    var child = ch[0];
17091    child.getPos(prop).setc(coord.left, coord.top);
17092    child.setData('width', coord.width, prop);
17093    child.setData('height', coord.height, prop);
17094  }
17095 };
17096
17097
17098 Layouts.TM.Squarified = new Class({
17099  Implements: Layouts.TM.Area,
17100  
17101  computePositions: function(node, coord, prop) {
17102    var config = this.config;
17103    
17104    if (coord.width >= coord.height) 
17105      this.layout.orientation = 'h';
17106    else
17107      this.layout.orientation = 'v';
17108    
17109    var ch = node.getSubnodes([1, 1], "ignore");
17110    if(ch.length > 0) {
17111      this.processChildrenLayout(node, ch, coord, prop);
17112      for(var i=0, l=ch.length; i<l; i++) {
17113        var chi = ch[i]; 
17114        var offst = config.offset,
17115            height = chi.getData('height', prop) - offst - config.titleHeight,
17116            width = chi.getData('width', prop) - offst;
17117        var chipos = chi.getPos(prop);
17118        coord = {
17119          'width': width,
17120          'height': height,
17121          'top': chipos.y + config.titleHeight,
17122          'left': chipos.x
17123        };
17124        this.computePositions(chi, coord, prop);
17125      }
17126    }
17127  },
17128
17129  /*
17130     Method: processChildrenLayout
17131  
17132    Computes children real areas and other useful parameters for performing the Squarified algorithm.
17133  
17134     Parameters:
17135
17136        par - The parent node of the json subtree.  
17137        ch - An Array of nodes
17138      coord - A coordinates object specifying width, height, left and top style properties.
17139  */
17140  processChildrenLayout: function(par, ch, coord, prop) {
17141    //compute children real areas
17142    var parentArea = coord.width * coord.height;
17143    var i, l=ch.length, totalChArea=0, chArea = [];
17144    for(i=0; i<l; i++) {
17145      chArea[i] = parseFloat(ch[i].getData('area', prop));
17146      totalChArea += chArea[i];
17147    }
17148    for(i=0; i<l; i++) {
17149      ch[i]._area = parentArea * chArea[i] / totalChArea;
17150    }
17151    var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17152    ch.sort(function(a, b) { 
17153      var diff = b._area - a._area; 
17154      return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1)); 
17155    });
17156    var initElem = [ch[0]];
17157    var tail = ch.slice(1);
17158    this.squarify(tail, initElem, minimumSideValue, coord, prop);
17159  },
17160
17161  /*
17162    Method: squarify
17163  
17164    Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17165  
17166     Parameters:
17167
17168        tail - An array of nodes.  
17169        initElem - An array of nodes, containing the initial node to be laid out.
17170        w - A fixed dimension where nodes will be laid out.
17171        coord - A coordinates object specifying width, height, left and top style properties.
17172  */
17173  squarify: function(tail, initElem, w, coord, prop) {
17174    this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17175  },
17176  
17177  /*
17178     Method: layoutRow
17179  
17180    Performs the layout of an array of nodes.
17181  
17182     Parameters:
17183
17184        ch - An array of nodes.  
17185        w - A fixed dimension where nodes will be laid out.
17186        coord - A coordinates object specifying width, height, left and top style properties.
17187  */
17188  layoutRow: function(ch, w, coord, prop) {
17189    if(this.layout.horizontal()) {
17190      return this.layoutV(ch, w, coord, prop);
17191    } else {
17192      return this.layoutH(ch, w, coord, prop);
17193    }
17194  },
17195  
17196  layoutV: function(ch, w, coord, prop) {
17197    var totalArea = 0, rnd = function(x) { return x; }; 
17198    $.each(ch, function(elem) { totalArea += elem._area; });
17199    var width = rnd(totalArea / w), top =  0; 
17200    for(var i=0, l=ch.length; i<l; i++) {
17201      var h = rnd(ch[i]._area / width);
17202      var chi = ch[i];
17203      chi.getPos(prop).setc(coord.left, coord.top + top);
17204      chi.setData('width', width, prop);
17205      chi.setData('height', h, prop);
17206      top += h;
17207    }
17208    var ans = {
17209      'height': coord.height,
17210      'width': coord.width - width,
17211      'top': coord.top,
17212      'left': coord.left + width
17213    };
17214    //take minimum side value.
17215    ans.dim = Math.min(ans.width, ans.height);
17216    if(ans.dim != ans.height) this.layout.change();
17217    return ans;
17218  },
17219  
17220  layoutH: function(ch, w, coord, prop) {
17221    var totalArea = 0; 
17222    $.each(ch, function(elem) { totalArea += elem._area; });
17223    var height = totalArea / w,
17224        top = coord.top, 
17225        left = 0;
17226    
17227    for(var i=0, l=ch.length; i<l; i++) {
17228      var chi = ch[i];
17229      var w = chi._area / height;
17230      chi.getPos(prop).setc(coord.left + left, top);
17231      chi.setData('width', w, prop);
17232      chi.setData('height', height, prop);
17233      left += w;
17234    }
17235    var ans = {
17236      'height': coord.height - height,
17237      'width': coord.width,
17238      'top': coord.top + height,
17239      'left': coord.left
17240    };
17241    ans.dim = Math.min(ans.width, ans.height);
17242    if(ans.dim != ans.width) this.layout.change();
17243    return ans;
17244  }
17245 });
17246
17247 Layouts.TM.Strip = new Class({
17248   Implements: Layouts.TM.Area,
17249
17250     /*
17251       Method: compute
17252     
17253      Called by loadJSON to calculate recursively all node positions and lay out the tree.
17254     
17255       Parameters:
17256     
17257          json - A JSON subtree. See also <Loader.loadJSON>. 
17258        coord - A coordinates object specifying width, height, left and top style properties.
17259     */
17260     computePositions: function(node, coord, prop) {
17261      var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17262      if(ch.length > 0) {
17263        this.processChildrenLayout(node, ch, coord, prop);
17264        for(var i=0, l=ch.length; i<l; i++) {
17265          var chi = ch[i];
17266          var offst = config.offset,
17267              height = chi.getData('height', prop) - offst - config.titleHeight,
17268              width  = chi.getData('width', prop)  - offst;
17269          var chipos = chi.getPos(prop);
17270          coord = {
17271            'width': width,
17272            'height': height,
17273            'top': chipos.y + config.titleHeight,
17274            'left': chipos.x
17275          };
17276          this.computePositions(chi, coord, prop);
17277        }
17278      }
17279     },
17280     
17281     /*
17282       Method: processChildrenLayout
17283     
17284      Computes children real areas and other useful parameters for performing the Strip algorithm.
17285     
17286       Parameters:
17287     
17288          par - The parent node of the json subtree.  
17289          ch - An Array of nodes
17290          coord - A coordinates object specifying width, height, left and top style properties.
17291     */
17292     processChildrenLayout: function(par, ch, coord, prop) {
17293      //compute children real areas
17294       var parentArea = coord.width * coord.height;
17295       var i, l=ch.length, totalChArea=0, chArea = [];
17296       for(i=0; i<l; i++) {
17297         chArea[i] = +ch[i].getData('area', prop);
17298         totalChArea += chArea[i];
17299       }
17300       for(i=0; i<l; i++) {
17301         ch[i]._area = parentArea * chArea[i] / totalChArea;
17302       }
17303      var side = this.layout.horizontal()? coord.width : coord.height;
17304      var initElem = [ch[0]];
17305      var tail = ch.slice(1);
17306      this.stripify(tail, initElem, side, coord, prop);
17307     },
17308     
17309     /*
17310       Method: stripify
17311     
17312      Performs an heuristic method to calculate div elements sizes in order to have 
17313      a good compromise between aspect ratio and order.
17314     
17315       Parameters:
17316     
17317          tail - An array of nodes.  
17318          initElem - An array of nodes.
17319          w - A fixed dimension where nodes will be layed out.
17320        coord - A coordinates object specifying width, height, left and top style properties.
17321     */
17322     stripify: function(tail, initElem, w, coord, prop) {
17323      this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17324     },
17325     
17326     /*
17327       Method: layoutRow
17328     
17329      Performs the layout of an array of nodes.
17330     
17331       Parameters:
17332     
17333          ch - An array of nodes.  
17334          w - A fixed dimension where nodes will be laid out.
17335          coord - A coordinates object specifying width, height, left and top style properties.
17336     */
17337     layoutRow: function(ch, w, coord, prop) {
17338      if(this.layout.horizontal()) {
17339        return this.layoutH(ch, w, coord, prop);
17340      } else {
17341        return this.layoutV(ch, w, coord, prop);
17342      }
17343     },
17344     
17345     layoutV: function(ch, w, coord, prop) {
17346      var totalArea = 0; 
17347      $.each(ch, function(elem) { totalArea += elem._area; });
17348      var width = totalArea / w, top =  0; 
17349      for(var i=0, l=ch.length; i<l; i++) {
17350        var chi = ch[i];
17351        var h = chi._area / width;
17352        chi.getPos(prop).setc(coord.left, 
17353            coord.top + (w - h - top));
17354        chi.setData('width', width, prop);
17355        chi.setData('height', h, prop);
17356        top += h;
17357      }
17358     
17359      return {
17360        'height': coord.height,
17361        'width': coord.width - width,
17362        'top': coord.top,
17363        'left': coord.left + width,
17364        'dim': w
17365      };
17366     },
17367     
17368     layoutH: function(ch, w, coord, prop) {
17369      var totalArea = 0; 
17370      $.each(ch, function(elem) { totalArea += elem._area; });
17371      var height = totalArea / w,
17372          top = coord.height - height, 
17373          left = 0;
17374      
17375      for(var i=0, l=ch.length; i<l; i++) {
17376        var chi = ch[i];
17377        var s = chi._area / height;
17378        chi.getPos(prop).setc(coord.left + left, coord.top + top);
17379        chi.setData('width', s, prop);
17380        chi.setData('height', height, prop);
17381        left += s;
17382      }
17383      return {
17384        'height': coord.height - height,
17385        'width': coord.width,
17386        'top': coord.top,
17387        'left': coord.left,
17388        'dim': w
17389      };
17390     }
17391  });
17392
17393 /*
17394  * Class: Layouts.Icicle
17395  *
17396  * Implements the icicle tree layout.
17397  *
17398  * Implemented By:
17399  *
17400  * <Icicle>
17401  *
17402  */
17403
17404 Layouts.Icicle = new Class({
17405  /*
17406   * Method: compute
17407   *
17408   * Called by loadJSON to calculate all node positions.
17409   *
17410   * Parameters:
17411   *
17412   * posType - The nodes' position to compute. Either "start", "end" or
17413   *            "current". Defaults to "current".
17414   */
17415   compute: function(posType) {
17416     posType = posType || "current";
17417     var root = this.graph.getNode(this.root),
17418         config = this.config,
17419         size = this.canvas.getSize(),
17420         width = size.width,
17421         height = size.height,
17422         offset = config.offset,
17423         levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17424
17425     this.controller.onBeforeCompute(root);
17426
17427     Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17428
17429     var treeDepth = 0;
17430
17431     Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17432
17433     var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17434     var maxDepth = Math.min(treeDepth, levelsToShow-1);
17435     var initialDepth = startNode._depth;
17436     if(this.layout.horizontal()) {
17437       this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17438     } else {
17439       this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17440     }
17441   },
17442
17443   computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17444     root.getPos(posType).setc(x, y);
17445     root.setData('width', width, posType);
17446     root.setData('height', height, posType);
17447
17448     var nodeLength, prevNodeLength = 0, totalDim = 0;
17449     var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17450
17451     if(!children.length)
17452       return;
17453
17454     $.each(children, function(e) { totalDim += e.getData('dim'); });
17455
17456     for(var i=0, l=children.length; i < l; i++) {
17457       if(this.layout.horizontal()) {
17458         nodeLength = height * children[i].getData('dim') / totalDim;
17459         this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17460         y += nodeLength;
17461       } else {
17462         nodeLength = width * children[i].getData('dim') / totalDim;
17463         this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17464         x += nodeLength;
17465       }
17466     }
17467   }
17468 });
17469
17470
17471
17472 /*
17473  * File: Icicle.js
17474  *
17475 */
17476
17477 /*
17478   Class: Icicle
17479   
17480   Icicle space filling visualization.
17481   
17482   Implements:
17483   
17484   All <Loader> methods
17485   
17486   Constructor Options:
17487   
17488   Inherits options from
17489   
17490   - <Options.Canvas>
17491   - <Options.Controller>
17492   - <Options.Node>
17493   - <Options.Edge>
17494   - <Options.Label>
17495   - <Options.Events>
17496   - <Options.Tips>
17497   - <Options.NodeStyles>
17498   - <Options.Navigation>
17499   
17500   Additionally, there are other parameters and some default values changed
17501
17502   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17503   offset - (number) Default's *2*. Boxes offset.
17504   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17505   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17506   animate - (boolean) Default's *false*. Whether to animate transitions.
17507   Node.type - Described in <Options.Node>. Default's *rectangle*.
17508   Label.type - Described in <Options.Label>. Default's *Native*.
17509   duration - Described in <Options.Fx>. Default's *700*.
17510   fps - Described in <Options.Fx>. Default's *45*.
17511   
17512   Instance Properties:
17513   
17514   canvas - Access a <Canvas> instance.
17515   graph - Access a <Graph> instance.
17516   op - Access a <Icicle.Op> instance.
17517   fx - Access a <Icicle.Plot> instance.
17518   labels - Access a <Icicle.Label> interface implementation.
17519
17520 */
17521
17522 $jit.Icicle = new Class({
17523   Implements: [ Loader, Extras, Layouts.Icicle ],
17524
17525   layout: {
17526     orientation: "h",
17527     vertical: function(){
17528       return this.orientation == "v";
17529     },
17530     horizontal: function(){
17531       return this.orientation == "h";
17532     },
17533     change: function(){
17534       this.orientation = this.vertical()? "h" : "v";
17535     }
17536   },
17537
17538   initialize: function(controller) {
17539     var config = {
17540       animate: false,
17541       orientation: "h",
17542       offset: 2,
17543       levelsToShow: Number.MAX_VALUE,
17544       constrained: false,
17545       Node: {
17546         type: 'rectangle',
17547         overridable: true
17548       },
17549       Edge: {
17550         type: 'none'
17551       },
17552       Label: {
17553         type: 'Native'
17554       },
17555       duration: 700,
17556       fps: 45
17557     };
17558
17559     var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17560                        "Events", "Navigation", "Controller", "Label");
17561     this.controller = this.config = $.merge(opts, config, controller);
17562     this.layout.orientation = this.config.orientation;
17563
17564     var canvasConfig = this.config;
17565     if (canvasConfig.useCanvas) {
17566       this.canvas = canvasConfig.useCanvas;
17567       this.config.labelContainer = this.canvas.id + '-label';
17568     } else {
17569       this.canvas = new Canvas(this, canvasConfig);
17570       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17571     }
17572
17573     this.graphOptions = {
17574       'complex': true,
17575       'Node': {
17576         'selected': false,
17577         'exist': true,
17578         'drawn': true
17579       }
17580     };
17581
17582     this.graph = new Graph(
17583       this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17584
17585     this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17586     this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17587     this.op = new $jit.Icicle.Op(this);
17588     this.group = new $jit.Icicle.Group(this);
17589     this.clickedNode = null;
17590
17591     this.initializeExtras();
17592   },
17593
17594   /* 
17595     Method: refresh 
17596     
17597     Computes positions and plots the tree.
17598   */
17599   refresh: function(){
17600     var labelType = this.config.Label.type;
17601     if(labelType != 'Native') {
17602       var that = this;
17603       this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17604     }
17605     this.compute();
17606     this.plot();
17607   },
17608
17609   /* 
17610     Method: plot 
17611     
17612     Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
17613   
17614    */
17615   plot: function(){
17616     this.fx.plot(this.config);
17617   },
17618
17619   /* 
17620     Method: enter 
17621     
17622     Sets the node as root.
17623     
17624      Parameters:
17625      
17626      node - (object) A <Graph.Node>.
17627   
17628    */
17629   enter: function (node) {
17630     if (this.busy)
17631       return;
17632     this.busy = true;
17633
17634     var that = this,
17635         config = this.config;
17636
17637     var callback = {
17638       onComplete: function() {
17639         //compute positions of newly inserted nodes
17640         if(config.request)
17641           that.compute();
17642
17643         if(config.animate) {
17644           that.graph.nodeList.setDataset(['current', 'end'], {
17645             'alpha': [1, 0] //fade nodes
17646           });
17647
17648           Graph.Util.eachSubgraph(node, function(n) {
17649             n.setData('alpha', 1, 'end');
17650           }, "ignore");
17651
17652           that.fx.animate({
17653             duration: 500,
17654             modes:['node-property:alpha'],
17655             onComplete: function() {
17656               that.clickedNode = node;
17657               that.compute('end');
17658
17659               that.fx.animate({
17660                 modes:['linear', 'node-property:width:height'],
17661                 duration: 1000,
17662                 onComplete: function() {
17663                   that.busy = false;
17664                   that.clickedNode = node;
17665                 }
17666               });
17667             }
17668           });
17669         } else {
17670           that.clickedNode = node;
17671           that.busy = false;
17672           that.refresh();
17673         }
17674       }
17675     };
17676
17677     if(config.request) {
17678       this.requestNodes(clickedNode, callback);
17679     } else {
17680       callback.onComplete();
17681     }
17682   },
17683
17684   /* 
17685     Method: out 
17686     
17687     Sets the parent node of the current selected node as root.
17688   
17689    */
17690   out: function(){
17691     if(this.busy)
17692       return;
17693
17694     var that = this,
17695         GUtil = Graph.Util,
17696         config = this.config,
17697         graph = this.graph,
17698         parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17699         parent = parents[0],
17700         clickedNode = parent,
17701         previousClickedNode = this.clickedNode;
17702
17703     this.busy = true;
17704     this.events.hoveredNode = false;
17705
17706     if(!parent) {
17707       this.busy = false;
17708       return;
17709     }
17710
17711     //final plot callback
17712     callback = {
17713       onComplete: function() {
17714         that.clickedNode = parent;
17715         if(config.request) {
17716           that.requestNodes(parent, {
17717             onComplete: function() {
17718               that.compute();
17719               that.plot();
17720               that.busy = false;
17721             }
17722           });
17723         } else {
17724           that.compute();
17725           that.plot();
17726           that.busy = false;
17727         }
17728       }
17729     };
17730
17731     //animate node positions
17732     if(config.animate) {
17733       this.clickedNode = clickedNode;
17734       this.compute('end');
17735       //animate the visible subtree only
17736       this.clickedNode = previousClickedNode;
17737       this.fx.animate({
17738         modes:['linear', 'node-property:width:height'],
17739         duration: 1000,
17740         onComplete: function() {
17741           //animate the parent subtree
17742           that.clickedNode = clickedNode;
17743           //change nodes alpha
17744           graph.nodeList.setDataset(['current', 'end'], {
17745             'alpha': [0, 1]
17746           });
17747           GUtil.eachSubgraph(previousClickedNode, function(node) {
17748             node.setData('alpha', 1);
17749           }, "ignore");
17750           that.fx.animate({
17751             duration: 500,
17752             modes:['node-property:alpha'],
17753             onComplete: function() {
17754               callback.onComplete();
17755             }
17756           });
17757         }
17758       });
17759     } else {
17760       callback.onComplete();
17761     }
17762   },
17763   requestNodes: function(node, onComplete){
17764     var handler = $.merge(this.controller, onComplete),
17765         levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
17766
17767     if (handler.request) {
17768       var leaves = [], d = node._depth;
17769       Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
17770         if (n.drawn && !Graph.Util.anySubnode(n)) {
17771           leaves.push(n);
17772           n._level = n._depth - d;
17773           if (this.config.constrained)
17774             n._level = levelsToShow - n._level;
17775
17776         }
17777       });
17778       this.group.requestNodes(leaves, handler);
17779     } else {
17780       handler.onComplete();
17781     }
17782   }
17783 });
17784
17785 /*
17786   Class: Icicle.Op
17787   
17788   Custom extension of <Graph.Op>.
17789   
17790   Extends:
17791   
17792   All <Graph.Op> methods
17793   
17794   See also:
17795   
17796   <Graph.Op>
17797   
17798   */
17799 $jit.Icicle.Op = new Class({
17800
17801   Implements: Graph.Op
17802
17803 });
17804
17805 /*
17806  * Performs operations on group of nodes.
17807  */
17808 $jit.Icicle.Group = new Class({
17809
17810   initialize: function(viz){
17811     this.viz = viz;
17812     this.canvas = viz.canvas;
17813     this.config = viz.config;
17814   },
17815
17816   /*
17817    * Calls the request method on the controller to request a subtree for each node.
17818    */
17819   requestNodes: function(nodes, controller){
17820     var counter = 0, len = nodes.length, nodeSelected = {};
17821     var complete = function(){
17822       controller.onComplete();
17823     };
17824     var viz = this.viz;
17825     if (len == 0)
17826       complete();
17827     for(var i = 0; i < len; i++) {
17828       nodeSelected[nodes[i].id] = nodes[i];
17829       controller.request(nodes[i].id, nodes[i]._level, {
17830         onComplete: function(nodeId, data){
17831           if (data && data.children) {
17832             data.id = nodeId;
17833             viz.op.sum(data, {
17834               type: 'nothing'
17835             });
17836           }
17837           if (++counter == len) {
17838             Graph.Util.computeLevels(viz.graph, viz.root, 0);
17839             complete();
17840           }
17841         }
17842       });
17843     }
17844   }
17845 });
17846
17847 /*
17848   Class: Icicle.Plot
17849   
17850   Custom extension of <Graph.Plot>.
17851   
17852   Extends:
17853   
17854   All <Graph.Plot> methods
17855   
17856   See also:
17857   
17858   <Graph.Plot>
17859   
17860   */
17861 $jit.Icicle.Plot = new Class({
17862   Implements: Graph.Plot,
17863
17864   plot: function(opt, animating){
17865     opt = opt || this.viz.controller;
17866     var viz = this.viz,
17867         graph = viz.graph,
17868         root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
17869         initialDepth = root._depth;
17870
17871     viz.canvas.clear();
17872     this.plotTree(root, $.merge(opt, {
17873       'withLabels': true,
17874       'hideLabels': false,
17875       'plotSubtree': function(root, node) {
17876         return !viz.config.constrained ||
17877                (node._depth - initialDepth < viz.config.levelsToShow);
17878       }
17879     }), animating);
17880   }
17881 });
17882
17883 /*
17884   Class: Icicle.Label
17885   
17886   Custom extension of <Graph.Label>. 
17887   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
17888   
17889   Extends:
17890   
17891   All <Graph.Label> methods and subclasses.
17892   
17893   See also:
17894   
17895   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
17896   
17897   */
17898 $jit.Icicle.Label = {};
17899
17900 /*
17901   Icicle.Label.Native
17902   
17903   Custom extension of <Graph.Label.Native>.
17904   
17905   Extends:
17906   
17907   All <Graph.Label.Native> methods
17908   
17909   See also:
17910   
17911   <Graph.Label.Native>
17912
17913   */
17914 $jit.Icicle.Label.Native = new Class({
17915   Implements: Graph.Label.Native,
17916
17917   renderLabel: function(canvas, node, controller) {
17918     var ctx = canvas.getCtx(),
17919         width = node.getData('width'),
17920         height = node.getData('height'),
17921         size = node.getLabelData('size'),
17922         m = ctx.measureText(node.name);
17923
17924     // Guess as much as possible if the label will fit in the node
17925     if(height < (size * 1.5) || width < m.width)
17926       return;
17927
17928     var pos = node.pos.getc(true);
17929     ctx.fillText(node.name,
17930                  pos.x + width / 2,
17931                  pos.y + height / 2);
17932   }
17933 });
17934
17935 /*
17936   Icicle.Label.SVG
17937   
17938   Custom extension of <Graph.Label.SVG>.
17939   
17940   Extends:
17941   
17942   All <Graph.Label.SVG> methods
17943   
17944   See also:
17945   
17946   <Graph.Label.SVG>
17947 */
17948 $jit.Icicle.Label.SVG = new Class( {
17949   Implements: Graph.Label.SVG,
17950
17951   initialize: function(viz){
17952     this.viz = viz;
17953   },
17954
17955   /*
17956     placeLabel
17957    
17958     Overrides abstract method placeLabel in <Graph.Plot>.
17959    
17960     Parameters:
17961    
17962     tag - A DOM label element.
17963     node - A <Graph.Node>.
17964     controller - A configuration/controller object passed to the visualization.
17965    */
17966   placeLabel: function(tag, node, controller){
17967     var pos = node.pos.getc(true), canvas = this.viz.canvas;
17968     var radius = canvas.getSize();
17969     var labelPos = {
17970       x: Math.round(pos.x + radius.width / 2),
17971       y: Math.round(pos.y + radius.height / 2)
17972     };
17973     tag.setAttribute('x', labelPos.x);
17974     tag.setAttribute('y', labelPos.y);
17975
17976     controller.onPlaceLabel(tag, node);
17977   }
17978 });
17979
17980 /*
17981   Icicle.Label.HTML
17982   
17983   Custom extension of <Graph.Label.HTML>.
17984   
17985   Extends:
17986   
17987   All <Graph.Label.HTML> methods.
17988   
17989   See also:
17990   
17991   <Graph.Label.HTML>
17992   
17993   */
17994 $jit.Icicle.Label.HTML = new Class( {
17995   Implements: Graph.Label.HTML,
17996
17997   initialize: function(viz){
17998     this.viz = viz;
17999   },
18000
18001   /*
18002     placeLabel
18003    
18004     Overrides abstract method placeLabel in <Graph.Plot>.
18005    
18006     Parameters:
18007    
18008     tag - A DOM label element.
18009     node - A <Graph.Node>.
18010     controller - A configuration/controller object passed to the visualization.
18011    */
18012   placeLabel: function(tag, node, controller){
18013     var pos = node.pos.getc(true), canvas = this.viz.canvas;
18014     var radius = canvas.getSize();
18015     var labelPos = {
18016       x: Math.round(pos.x + radius.width / 2),
18017       y: Math.round(pos.y + radius.height / 2)
18018     };
18019
18020     var style = tag.style;
18021     style.left = labelPos.x + 'px';
18022     style.top = labelPos.y + 'px';
18023     style.display = '';
18024
18025     controller.onPlaceLabel(tag, node);
18026   }
18027 });
18028
18029 /*
18030   Class: Icicle.Plot.NodeTypes
18031   
18032   This class contains a list of <Graph.Node> built-in types. 
18033   Node types implemented are 'none', 'rectangle'.
18034   
18035   You can add your custom node types, customizing your visualization to the extreme.
18036   
18037   Example:
18038   
18039   (start code js)
18040     Icicle.Plot.NodeTypes.implement({
18041       'mySpecialType': {
18042         'render': function(node, canvas) {
18043           //print your custom node to canvas
18044         },
18045         //optional
18046         'contains': function(node, pos) {
18047           //return true if pos is inside the node or false otherwise
18048         }
18049       }
18050     });
18051   (end code)
18052   
18053   */
18054 $jit.Icicle.Plot.NodeTypes = new Class( {
18055   'none': {
18056     'render': $.empty
18057   },
18058
18059   'rectangle': {
18060     'render': function(node, canvas, animating) {
18061       var config = this.viz.config;
18062       var offset = config.offset;
18063       var width = node.getData('width');
18064       var height = node.getData('height');
18065       var border = node.getData('border');
18066       var pos = node.pos.getc(true);
18067       var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18068       var ctx = canvas.getCtx();
18069       
18070       if(width - offset < 2 || height - offset < 2) return;
18071       
18072       if(config.cushion) {
18073         var color = node.getData('color');
18074         var lg = ctx.createRadialGradient(posx + (width - offset)/2, 
18075                                           posy + (height - offset)/2, 1, 
18076                                           posx + (width-offset)/2, posy + (height-offset)/2, 
18077                                           width < height? height : width);
18078         var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
18079             function(r) { return r * 0.3 >> 0; }));
18080         lg.addColorStop(0, color);
18081         lg.addColorStop(1, colorGrad);
18082         ctx.fillStyle = lg;
18083       }
18084
18085       if (border) {
18086         ctx.strokeStyle = border;
18087         ctx.lineWidth = 3;
18088       }
18089
18090       ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18091       border && ctx.strokeRect(pos.x, pos.y, width, height);
18092     },
18093
18094     'contains': function(node, pos) {
18095       if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18096       var npos = node.pos.getc(true),
18097           width = node.getData('width'),
18098           height = node.getData('height');
18099       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18100     }
18101   }
18102 });
18103
18104 $jit.Icicle.Plot.EdgeTypes = new Class( {
18105   'none': $.empty
18106 });
18107
18108
18109
18110 /*
18111  * File: Layouts.ForceDirected.js
18112  *
18113 */
18114
18115 /*
18116  * Class: Layouts.ForceDirected
18117  * 
18118  * Implements a Force Directed Layout.
18119  * 
18120  * Implemented By:
18121  * 
18122  * <ForceDirected>
18123  * 
18124  * Credits:
18125  * 
18126  * Marcus Cobden <http://marcuscobden.co.uk>
18127  * 
18128  */
18129 Layouts.ForceDirected = new Class({
18130
18131   getOptions: function(random) {
18132     var s = this.canvas.getSize();
18133     var w = s.width, h = s.height;
18134     //count nodes
18135     var count = 0;
18136     this.graph.eachNode(function(n) { 
18137       count++;
18138     });
18139     var k2 = w * h / count, k = Math.sqrt(k2);
18140     var l = this.config.levelDistance;
18141     
18142     return {
18143       width: w,
18144       height: h,
18145       tstart: w * 0.1,
18146       nodef: function(x) { return k2 / (x || 1); },
18147       edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18148     };
18149   },
18150   
18151   compute: function(property, incremental) {
18152     var prop = $.splat(property || ['current', 'start', 'end']);
18153     var opt = this.getOptions();
18154     NodeDim.compute(this.graph, prop, this.config);
18155     this.graph.computeLevels(this.root, 0, "ignore");
18156     this.graph.eachNode(function(n) {
18157       $.each(prop, function(p) {
18158         var pos = n.getPos(p);
18159         if(pos.equals(Complex.KER)) {
18160           pos.x = opt.width/5 * (Math.random() - 0.5);
18161           pos.y = opt.height/5 * (Math.random() - 0.5);
18162         }
18163         //initialize disp vector
18164         n.disp = {};
18165         $.each(prop, function(p) {
18166           n.disp[p] = $C(0, 0);
18167         });
18168       });
18169     });
18170     this.computePositions(prop, opt, incremental);
18171   },
18172   
18173   computePositions: function(property, opt, incremental) {
18174     var times = this.config.iterations, i = 0, that = this;
18175     if(incremental) {
18176       (function iter() {
18177         for(var total=incremental.iter, j=0; j<total; j++) {
18178           opt.t = opt.tstart * (1 - i++/(times -1));
18179           that.computePositionStep(property, opt);
18180           if(i >= times) {
18181             incremental.onComplete();
18182             return;
18183           }
18184         }
18185         incremental.onStep(Math.round(i / (times -1) * 100));
18186         setTimeout(iter, 1);
18187       })();
18188     } else {
18189       for(; i < times; i++) {
18190         opt.t = opt.tstart * (1 - i/(times -1));
18191         this.computePositionStep(property, opt);
18192       }
18193     }
18194   },
18195   
18196   computePositionStep: function(property, opt) {
18197     var graph = this.graph;
18198     var min = Math.min, max = Math.max;
18199     var dpos = $C(0, 0);
18200     //calculate repulsive forces
18201     graph.eachNode(function(v) {
18202       //initialize disp
18203       $.each(property, function(p) {
18204         v.disp[p].x = 0; v.disp[p].y = 0;
18205       });
18206       graph.eachNode(function(u) {
18207         if(u.id != v.id) {
18208           $.each(property, function(p) {
18209             var vp = v.getPos(p), up = u.getPos(p);
18210             dpos.x = vp.x - up.x;
18211             dpos.y = vp.y - up.y;
18212             var norm = dpos.norm() || 1;
18213             v.disp[p].$add(dpos
18214                 .$scale(opt.nodef(norm) / norm));
18215           });
18216         }
18217       });
18218     });
18219     //calculate attractive forces
18220     var T = !!graph.getNode(this.root).visited;
18221     graph.eachNode(function(node) {
18222       node.eachAdjacency(function(adj) {
18223         var nodeTo = adj.nodeTo;
18224         if(!!nodeTo.visited === T) {
18225           $.each(property, function(p) {
18226             var vp = node.getPos(p), up = nodeTo.getPos(p);
18227             dpos.x = vp.x - up.x;
18228             dpos.y = vp.y - up.y;
18229             var norm = dpos.norm() || 1;
18230             node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18231             nodeTo.disp[p].$add(dpos.$scale(-1));
18232           });
18233         }
18234       });
18235       node.visited = !T;
18236     });
18237     //arrange positions to fit the canvas
18238     var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18239     graph.eachNode(function(u) {
18240       $.each(property, function(p) {
18241         var disp = u.disp[p];
18242         var norm = disp.norm() || 1;
18243         var p = u.getPos(p);
18244         p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
18245             disp.y * min(Math.abs(disp.y), t) / norm));
18246         p.x = min(w2, max(-w2, p.x));
18247         p.y = min(h2, max(-h2, p.y));
18248       });
18249     });
18250   }
18251 });
18252
18253 /*
18254  * File: ForceDirected.js
18255  */
18256
18257 /*
18258    Class: ForceDirected
18259       
18260    A visualization that lays graphs using a Force-Directed layout algorithm.
18261    
18262    Inspired by:
18263   
18264    Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18265    
18266   Implements:
18267   
18268   All <Loader> methods
18269   
18270    Constructor Options:
18271    
18272    Inherits options from
18273    
18274    - <Options.Canvas>
18275    - <Options.Controller>
18276    - <Options.Node>
18277    - <Options.Edge>
18278    - <Options.Label>
18279    - <Options.Events>
18280    - <Options.Tips>
18281    - <Options.NodeStyles>
18282    - <Options.Navigation>
18283    
18284    Additionally, there are two parameters
18285    
18286    levelDistance - (number) Default's *50*. The natural length desired for the edges.
18287    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*. 
18288      
18289    Instance Properties:
18290
18291    canvas - Access a <Canvas> instance.
18292    graph - Access a <Graph> instance.
18293    op - Access a <ForceDirected.Op> instance.
18294    fx - Access a <ForceDirected.Plot> instance.
18295    labels - Access a <ForceDirected.Label> interface implementation.
18296
18297 */
18298
18299 $jit.ForceDirected = new Class( {
18300
18301   Implements: [ Loader, Extras, Layouts.ForceDirected ],
18302
18303   initialize: function(controller) {
18304     var $ForceDirected = $jit.ForceDirected;
18305
18306     var config = {
18307       iterations: 50,
18308       levelDistance: 50
18309     };
18310
18311     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18312         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18313
18314     var canvasConfig = this.config;
18315     if(canvasConfig.useCanvas) {
18316       this.canvas = canvasConfig.useCanvas;
18317       this.config.labelContainer = this.canvas.id + '-label';
18318     } else {
18319       if(canvasConfig.background) {
18320         canvasConfig.background = $.merge({
18321           type: 'Circles'
18322         }, canvasConfig.background);
18323       }
18324       this.canvas = new Canvas(this, canvasConfig);
18325       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18326     }
18327
18328     this.graphOptions = {
18329       'complex': true,
18330       'Node': {
18331         'selected': false,
18332         'exist': true,
18333         'drawn': true
18334       }
18335     };
18336     this.graph = new Graph(this.graphOptions, this.config.Node,
18337         this.config.Edge);
18338     this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18339     this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18340     this.op = new $ForceDirected.Op(this);
18341     this.json = null;
18342     this.busy = false;
18343     // initialize extras
18344     this.initializeExtras();
18345   },
18346
18347   /* 
18348     Method: refresh 
18349     
18350     Computes positions and plots the tree.
18351   */
18352   refresh: function() {
18353     this.compute();
18354     this.plot();
18355   },
18356
18357   reposition: function() {
18358     this.compute('end');
18359   },
18360
18361 /*
18362   Method: computeIncremental
18363   
18364   Performs the Force Directed algorithm incrementally.
18365   
18366   Description:
18367   
18368   ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
18369   This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
18370   avoiding browser messages such as "This script is taking too long to complete".
18371   
18372   Parameters:
18373   
18374   opt - (object) The object properties are described below
18375   
18376   iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
18377   of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18378   
18379   property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
18380   You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
18381   computations for final animation positions then you can just choose 'end'.
18382   
18383   onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
18384   parameter a percentage value.
18385   
18386   onComplete - A callback function called when the algorithm completed.
18387   
18388   Example:
18389   
18390   In this example I calculate the end positions and then animate the graph to those positions
18391   
18392   (start code js)
18393   var fd = new $jit.ForceDirected(...);
18394   fd.computeIncremental({
18395     iter: 20,
18396     property: 'end',
18397     onStep: function(perc) {
18398       Log.write("loading " + perc + "%");
18399     },
18400     onComplete: function() {
18401       Log.write("done");
18402       fd.animate();
18403     }
18404   });
18405   (end code)
18406   
18407   In this example I calculate all positions and (re)plot the graph
18408   
18409   (start code js)
18410   var fd = new ForceDirected(...);
18411   fd.computeIncremental({
18412     iter: 20,
18413     property: ['end', 'start', 'current'],
18414     onStep: function(perc) {
18415       Log.write("loading " + perc + "%");
18416     },
18417     onComplete: function() {
18418       Log.write("done");
18419       fd.plot();
18420     }
18421   });
18422   (end code)
18423   
18424   */
18425   computeIncremental: function(opt) {
18426     opt = $.merge( {
18427       iter: 20,
18428       property: 'end',
18429       onStep: $.empty,
18430       onComplete: $.empty
18431     }, opt || {});
18432
18433     this.config.onBeforeCompute(this.graph.getNode(this.root));
18434     this.compute(opt.property, opt);
18435   },
18436
18437   /*
18438     Method: plot
18439    
18440     Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18441    */
18442   plot: function() {
18443     this.fx.plot();
18444   },
18445
18446   /*
18447      Method: animate
18448     
18449      Animates the graph from the current positions to the 'end' node positions.
18450   */
18451   animate: function(opt) {
18452     this.fx.animate($.merge( {
18453       modes: [ 'linear' ]
18454     }, opt || {}));
18455   }
18456 });
18457
18458 $jit.ForceDirected.$extend = true;
18459
18460 (function(ForceDirected) {
18461
18462   /*
18463      Class: ForceDirected.Op
18464      
18465      Custom extension of <Graph.Op>.
18466
18467      Extends:
18468
18469      All <Graph.Op> methods
18470      
18471      See also:
18472      
18473      <Graph.Op>
18474
18475   */
18476   ForceDirected.Op = new Class( {
18477
18478     Implements: Graph.Op
18479
18480   });
18481
18482   /*
18483     Class: ForceDirected.Plot
18484     
18485     Custom extension of <Graph.Plot>.
18486   
18487     Extends:
18488   
18489     All <Graph.Plot> methods
18490     
18491     See also:
18492     
18493     <Graph.Plot>
18494   
18495   */
18496   ForceDirected.Plot = new Class( {
18497
18498     Implements: Graph.Plot
18499
18500   });
18501
18502   /*
18503     Class: ForceDirected.Label
18504     
18505     Custom extension of <Graph.Label>. 
18506     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18507   
18508     Extends:
18509   
18510     All <Graph.Label> methods and subclasses.
18511   
18512     See also:
18513   
18514     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18515   
18516   */
18517   ForceDirected.Label = {};
18518
18519   /*
18520      ForceDirected.Label.Native
18521      
18522      Custom extension of <Graph.Label.Native>.
18523
18524      Extends:
18525
18526      All <Graph.Label.Native> methods
18527
18528      See also:
18529
18530      <Graph.Label.Native>
18531
18532   */
18533   ForceDirected.Label.Native = new Class( {
18534     Implements: Graph.Label.Native
18535   });
18536
18537   /*
18538     ForceDirected.Label.SVG
18539     
18540     Custom extension of <Graph.Label.SVG>.
18541   
18542     Extends:
18543   
18544     All <Graph.Label.SVG> methods
18545   
18546     See also:
18547   
18548     <Graph.Label.SVG>
18549   
18550   */
18551   ForceDirected.Label.SVG = new Class( {
18552     Implements: Graph.Label.SVG,
18553
18554     initialize: function(viz) {
18555       this.viz = viz;
18556     },
18557
18558     /* 
18559        placeLabel
18560
18561        Overrides abstract method placeLabel in <Graph.Label>.
18562
18563        Parameters:
18564
18565        tag - A DOM label element.
18566        node - A <Graph.Node>.
18567        controller - A configuration/controller object passed to the visualization.
18568       
18569      */
18570     placeLabel: function(tag, node, controller) {
18571       var pos = node.pos.getc(true), 
18572           canvas = this.viz.canvas,
18573           ox = canvas.translateOffsetX,
18574           oy = canvas.translateOffsetY,
18575           sx = canvas.scaleOffsetX,
18576           sy = canvas.scaleOffsetY,
18577           radius = canvas.getSize();
18578       var labelPos = {
18579         x: Math.round(pos.x * sx + ox + radius.width / 2),
18580         y: Math.round(pos.y * sy + oy + radius.height / 2)
18581       };
18582       tag.setAttribute('x', labelPos.x);
18583       tag.setAttribute('y', labelPos.y);
18584
18585       controller.onPlaceLabel(tag, node);
18586     }
18587   });
18588
18589   /*
18590      ForceDirected.Label.HTML
18591      
18592      Custom extension of <Graph.Label.HTML>.
18593
18594      Extends:
18595
18596      All <Graph.Label.HTML> methods.
18597
18598      See also:
18599
18600      <Graph.Label.HTML>
18601
18602   */
18603   ForceDirected.Label.HTML = new Class( {
18604     Implements: Graph.Label.HTML,
18605
18606     initialize: function(viz) {
18607       this.viz = viz;
18608     },
18609     /* 
18610        placeLabel
18611
18612        Overrides abstract method placeLabel in <Graph.Plot>.
18613
18614        Parameters:
18615
18616        tag - A DOM label element.
18617        node - A <Graph.Node>.
18618        controller - A configuration/controller object passed to the visualization.
18619       
18620      */
18621     placeLabel: function(tag, node, controller) {
18622       var pos = node.pos.getc(true), 
18623           canvas = this.viz.canvas,
18624           ox = canvas.translateOffsetX,
18625           oy = canvas.translateOffsetY,
18626           sx = canvas.scaleOffsetX,
18627           sy = canvas.scaleOffsetY,
18628           radius = canvas.getSize();
18629       var labelPos = {
18630         x: Math.round(pos.x * sx + ox + radius.width / 2),
18631         y: Math.round(pos.y * sy + oy + radius.height / 2)
18632       };
18633       var style = tag.style;
18634       style.left = labelPos.x + 'px';
18635       style.top = labelPos.y + 'px';
18636       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18637
18638       controller.onPlaceLabel(tag, node);
18639     }
18640   });
18641
18642   /*
18643     Class: ForceDirected.Plot.NodeTypes
18644
18645     This class contains a list of <Graph.Node> built-in types. 
18646     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18647
18648     You can add your custom node types, customizing your visualization to the extreme.
18649
18650     Example:
18651
18652     (start code js)
18653       ForceDirected.Plot.NodeTypes.implement({
18654         'mySpecialType': {
18655           'render': function(node, canvas) {
18656             //print your custom node to canvas
18657           },
18658           //optional
18659           'contains': function(node, pos) {
18660             //return true if pos is inside the node or false otherwise
18661           }
18662         }
18663       });
18664     (end code)
18665
18666   */
18667   ForceDirected.Plot.NodeTypes = new Class({
18668     'none': {
18669       'render': $.empty,
18670       'contains': $.lambda(false)
18671     },
18672     'circle': {
18673       'render': function(node, canvas){
18674         var pos = node.pos.getc(true), 
18675             dim = node.getData('dim');
18676         this.nodeHelper.circle.render('fill', pos, dim, canvas);
18677       },
18678       'contains': function(node, pos){
18679         var npos = node.pos.getc(true), 
18680             dim = node.getData('dim');
18681         return this.nodeHelper.circle.contains(npos, pos, dim);
18682       }
18683     },
18684     'ellipse': {
18685       'render': function(node, canvas){
18686         var pos = node.pos.getc(true), 
18687             width = node.getData('width'), 
18688             height = node.getData('height');
18689         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18690         },
18691       // TODO(nico): be more precise...
18692       'contains': function(node, pos){
18693         var npos = node.pos.getc(true), 
18694             width = node.getData('width'), 
18695             height = node.getData('height');
18696         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18697       }
18698     },
18699     'square': {
18700       'render': function(node, canvas){
18701         var pos = node.pos.getc(true), 
18702             dim = node.getData('dim');
18703         this.nodeHelper.square.render('fill', pos, dim, canvas);
18704       },
18705       'contains': function(node, pos){
18706         var npos = node.pos.getc(true), 
18707             dim = node.getData('dim');
18708         return this.nodeHelper.square.contains(npos, pos, dim);
18709       }
18710     },
18711     'rectangle': {
18712       'render': function(node, canvas){
18713         var pos = node.pos.getc(true), 
18714             width = node.getData('width'), 
18715             height = node.getData('height');
18716         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
18717       },
18718       'contains': function(node, pos){
18719         var npos = node.pos.getc(true), 
18720             width = node.getData('width'), 
18721             height = node.getData('height');
18722         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
18723       }
18724     },
18725     'triangle': {
18726       'render': function(node, canvas){
18727         var pos = node.pos.getc(true), 
18728             dim = node.getData('dim');
18729         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
18730       },
18731       'contains': function(node, pos) {
18732         var npos = node.pos.getc(true), 
18733             dim = node.getData('dim');
18734         return this.nodeHelper.triangle.contains(npos, pos, dim);
18735       }
18736     },
18737     'star': {
18738       'render': function(node, canvas){
18739         var pos = node.pos.getc(true),
18740             dim = node.getData('dim');
18741         this.nodeHelper.star.render('fill', pos, dim, canvas);
18742       },
18743       'contains': function(node, pos) {
18744         var npos = node.pos.getc(true),
18745             dim = node.getData('dim');
18746         return this.nodeHelper.star.contains(npos, pos, dim);
18747       }
18748     }
18749   });
18750
18751   /*
18752     Class: ForceDirected.Plot.EdgeTypes
18753   
18754     This class contains a list of <Graph.Adjacence> built-in types. 
18755     Edge types implemented are 'none', 'line' and 'arrow'.
18756   
18757     You can add your custom edge types, customizing your visualization to the extreme.
18758   
18759     Example:
18760   
18761     (start code js)
18762       ForceDirected.Plot.EdgeTypes.implement({
18763         'mySpecialType': {
18764           'render': function(adj, canvas) {
18765             //print your custom edge to canvas
18766           },
18767           //optional
18768           'contains': function(adj, pos) {
18769             //return true if pos is inside the arc or false otherwise
18770           }
18771         }
18772       });
18773     (end code)
18774   
18775   */
18776   ForceDirected.Plot.EdgeTypes = new Class({
18777     'none': $.empty,
18778     'line': {
18779       'render': function(adj, canvas) {
18780         var from = adj.nodeFrom.pos.getc(true),
18781             to = adj.nodeTo.pos.getc(true);
18782         this.edgeHelper.line.render(from, to, canvas);
18783       },
18784       'contains': function(adj, pos) {
18785         var from = adj.nodeFrom.pos.getc(true),
18786             to = adj.nodeTo.pos.getc(true);
18787         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
18788       }
18789     },
18790     'arrow': {
18791       'render': function(adj, canvas) {
18792         var from = adj.nodeFrom.pos.getc(true),
18793             to = adj.nodeTo.pos.getc(true),
18794             dim = adj.getData('dim'),
18795             direction = adj.data.$direction,
18796             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
18797         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
18798       },
18799       'contains': function(adj, pos) {
18800         var from = adj.nodeFrom.pos.getc(true),
18801             to = adj.nodeTo.pos.getc(true);
18802         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
18803       }
18804     }
18805   });
18806
18807 })($jit.ForceDirected);
18808
18809
18810 /*
18811  * File: Treemap.js
18812  *
18813 */
18814
18815 $jit.TM = {};
18816
18817 var TM = $jit.TM;
18818
18819 $jit.TM.$extend = true;
18820
18821 /*
18822   Class: TM.Base
18823   
18824   Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
18825   
18826   Implements:
18827   
18828   All <Loader> methods
18829   
18830   Constructor Options:
18831   
18832   Inherits options from
18833   
18834   - <Options.Canvas>
18835   - <Options.Controller>
18836   - <Options.Node>
18837   - <Options.Edge>
18838   - <Options.Label>
18839   - <Options.Events>
18840   - <Options.Tips>
18841   - <Options.NodeStyles>
18842   - <Options.Navigation>
18843   
18844   Additionally, there are other parameters and some default values changed
18845
18846   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
18847   titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
18848   offset - (number) Default's *2*. Boxes offset.
18849   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
18850   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
18851   animate - (boolean) Default's *false*. Whether to animate transitions.
18852   Node.type - Described in <Options.Node>. Default's *rectangle*.
18853   duration - Described in <Options.Fx>. Default's *700*.
18854   fps - Described in <Options.Fx>. Default's *45*.
18855   
18856   Instance Properties:
18857   
18858   canvas - Access a <Canvas> instance.
18859   graph - Access a <Graph> instance.
18860   op - Access a <TM.Op> instance.
18861   fx - Access a <TM.Plot> instance.
18862   labels - Access a <TM.Label> interface implementation.
18863
18864   Inspired by:
18865   
18866   Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
18867   
18868   Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
18869   
18870    Note:
18871    
18872    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.
18873
18874 */
18875 TM.Base = {
18876   layout: {
18877     orientation: "h",
18878     vertical: function(){
18879       return this.orientation == "v";
18880     },
18881     horizontal: function(){
18882       return this.orientation == "h";
18883     },
18884     change: function(){
18885       this.orientation = this.vertical()? "h" : "v";
18886     }
18887   },
18888
18889   initialize: function(controller){
18890     var config = {
18891       orientation: "h",
18892       titleHeight: 13,
18893       offset: 2,
18894       levelsToShow: 0,
18895       constrained: false,
18896       animate: false,
18897       Node: {
18898         type: 'rectangle',
18899         overridable: true,
18900         //we all know why this is not zero,
18901         //right, Firefox?
18902         width: 3,
18903         height: 3,
18904         color: '#444'
18905       },
18906       Label: {
18907         textAlign: 'center',
18908         textBaseline: 'top'
18909       },
18910       Edge: {
18911         type: 'none'
18912       },
18913       duration: 700,
18914       fps: 45
18915     };
18916
18917     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18918         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
18919     this.layout.orientation = this.config.orientation;
18920
18921     var canvasConfig = this.config;
18922     if (canvasConfig.useCanvas) {
18923       this.canvas = canvasConfig.useCanvas;
18924       this.config.labelContainer = this.canvas.id + '-label';
18925     } else {
18926       if(canvasConfig.background) {
18927         canvasConfig.background = $.merge({
18928           type: 'Circles'
18929         }, canvasConfig.background);
18930       }
18931       this.canvas = new Canvas(this, canvasConfig);
18932       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18933     }
18934
18935     this.graphOptions = {
18936       'complex': true,
18937       'Node': {
18938         'selected': false,
18939         'exist': true,
18940         'drawn': true
18941       }
18942     };
18943     this.graph = new Graph(this.graphOptions, this.config.Node,
18944         this.config.Edge);
18945     this.labels = new TM.Label[canvasConfig.Label.type](this);
18946     this.fx = new TM.Plot(this);
18947     this.op = new TM.Op(this);
18948     this.group = new TM.Group(this);
18949     this.geom = new TM.Geom(this);
18950     this.clickedNode = null;
18951     this.busy = false;
18952     // initialize extras
18953     this.initializeExtras();
18954   },
18955
18956   /* 
18957     Method: refresh 
18958     
18959     Computes positions and plots the tree.
18960   */
18961   refresh: function(){
18962     if(this.busy) return;
18963     this.busy = true;
18964     var that = this;
18965     if(this.config.animate) {
18966       this.compute('end');
18967       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18968           && this.clickedNode.id || this.root));
18969       this.fx.animate($.merge(this.config, {
18970         modes: ['linear', 'node-property:width:height'],
18971         onComplete: function() {
18972           that.busy = false;
18973         }
18974       }));
18975     } else {
18976       var labelType = this.config.Label.type;
18977       if(labelType != 'Native') {
18978         var that = this;
18979         this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
18980       }
18981       this.busy = false;
18982       this.compute();
18983       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
18984           && this.clickedNode.id || this.root));
18985       this.plot();
18986     }
18987   },
18988
18989   /* 
18990     Method: plot 
18991     
18992     Plots the TreeMap. This is a shortcut to *fx.plot*. 
18993   
18994    */
18995   plot: function(){
18996     this.fx.plot();
18997   },
18998
18999   /* 
19000   Method: leaf 
19001   
19002   Returns whether the node is a leaf.
19003   
19004    Parameters:
19005    
19006    n - (object) A <Graph.Node>.
19007
19008  */
19009   leaf: function(n){
19010     return n.getSubnodes([
19011         1, 1
19012     ], "ignore").length == 0;
19013   },
19014   
19015   /* 
19016   Method: enter 
19017   
19018   Sets the node as root.
19019   
19020    Parameters:
19021    
19022    n - (object) A <Graph.Node>.
19023
19024  */
19025   enter: function(n){
19026     if(this.busy) return;
19027     this.busy = true;
19028     
19029     var that = this,
19030         config = this.config,
19031         graph = this.graph,
19032         clickedNode = n,
19033         previousClickedNode = this.clickedNode;
19034
19035     var callback = {
19036       onComplete: function() {
19037         //ensure that nodes are shown for that level
19038         if(config.levelsToShow > 0) {
19039           that.geom.setRightLevelToShow(n);
19040         }
19041         //compute positions of newly inserted nodes
19042         if(config.levelsToShow > 0 || config.request) that.compute();
19043         if(config.animate) {
19044           //fade nodes
19045           graph.nodeList.setData('alpha', 0, 'end');
19046           n.eachSubgraph(function(n) {
19047             n.setData('alpha', 1, 'end');
19048           }, "ignore");
19049           that.fx.animate({
19050             duration: 500,
19051             modes:['node-property:alpha'],
19052             onComplete: function() {
19053               //compute end positions
19054               that.clickedNode = clickedNode;
19055               that.compute('end');
19056               //animate positions
19057               //TODO(nico) commenting this line didn't seem to throw errors...
19058               that.clickedNode = previousClickedNode;
19059               that.fx.animate({
19060                 modes:['linear', 'node-property:width:height'],
19061                 duration: 1000,
19062                 onComplete: function() { 
19063                   that.busy = false;
19064                   //TODO(nico) check comment above
19065                   that.clickedNode = clickedNode;
19066                 }
19067               });
19068             }
19069           });
19070         } else {
19071           that.busy = false;
19072           that.clickedNode = n;
19073           that.refresh();
19074         }
19075       }
19076     };
19077     if(config.request) {
19078       this.requestNodes(clickedNode, callback);
19079     } else {
19080       callback.onComplete();
19081     }
19082   },
19083
19084   /* 
19085   Method: out 
19086   
19087   Sets the parent node of the current selected node as root.
19088
19089  */
19090   out: function(){
19091     if(this.busy) return;
19092     this.busy = true;
19093     this.events.hoveredNode = false;
19094     var that = this,
19095         config = this.config,
19096         graph = this.graph,
19097         parents = graph.getNode(this.clickedNode 
19098             && this.clickedNode.id || this.root).getParents(),
19099         parent = parents[0],
19100         clickedNode = parent,
19101         previousClickedNode = this.clickedNode;
19102     
19103     //if no parents return
19104     if(!parent) {
19105       this.busy = false;
19106       return;
19107     }
19108     //final plot callback
19109     callback = {
19110       onComplete: function() {
19111         that.clickedNode = parent;
19112         if(config.request) {
19113           that.requestNodes(parent, {
19114             onComplete: function() {
19115               that.compute();
19116               that.plot();
19117               that.busy = false;
19118             }
19119           });
19120         } else {
19121           that.compute();
19122           that.plot();
19123           that.busy = false;
19124         }
19125       }
19126     };
19127     //prune tree
19128     if (config.levelsToShow > 0)
19129       this.geom.setRightLevelToShow(parent);
19130     //animate node positions
19131     if(config.animate) {
19132       this.clickedNode = clickedNode;
19133       this.compute('end');
19134       //animate the visible subtree only
19135       this.clickedNode = previousClickedNode;
19136       this.fx.animate({
19137         modes:['linear', 'node-property:width:height'],
19138         duration: 1000,
19139         onComplete: function() {
19140           //animate the parent subtree
19141           that.clickedNode = clickedNode;
19142           //change nodes alpha
19143           graph.eachNode(function(n) {
19144             n.setDataset(['current', 'end'], {
19145               'alpha': [0, 1]
19146             });
19147           }, "ignore");
19148           previousClickedNode.eachSubgraph(function(node) {
19149             node.setData('alpha', 1);
19150           }, "ignore");
19151           that.fx.animate({
19152             duration: 500,
19153             modes:['node-property:alpha'],
19154             onComplete: function() {
19155               callback.onComplete();
19156             }
19157           });
19158         }
19159       });
19160     } else {
19161       callback.onComplete();
19162     }
19163   },
19164
19165   requestNodes: function(node, onComplete){
19166     var handler = $.merge(this.controller, onComplete), 
19167         lev = this.config.levelsToShow;
19168     if (handler.request) {
19169       var leaves = [], d = node._depth;
19170       node.eachLevel(0, lev, function(n){
19171         var nodeLevel = lev - (n._depth - d);
19172         if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19173           leaves.push(n);
19174           n._level = nodeLevel;
19175         }
19176       });
19177       this.group.requestNodes(leaves, handler);
19178     } else {
19179       handler.onComplete();
19180     }
19181   }
19182 };
19183
19184 /*
19185   Class: TM.Op
19186   
19187   Custom extension of <Graph.Op>.
19188   
19189   Extends:
19190   
19191   All <Graph.Op> methods
19192   
19193   See also:
19194   
19195   <Graph.Op>
19196   
19197   */
19198 TM.Op = new Class({
19199   Implements: Graph.Op,
19200
19201   initialize: function(viz){
19202     this.viz = viz;
19203   }
19204 });
19205
19206 //extend level methods of Graph.Geom
19207 TM.Geom = new Class({
19208   Implements: Graph.Geom,
19209   
19210   getRightLevelToShow: function() {
19211     return this.viz.config.levelsToShow;
19212   },
19213   
19214   setRightLevelToShow: function(node) {
19215     var level = this.getRightLevelToShow(), 
19216         fx = this.viz.labels;
19217     node.eachLevel(0, level+1, function(n) {
19218       var d = n._depth - node._depth;
19219       if(d > level) {
19220         n.drawn = false; 
19221         n.exist = false;
19222         n.ignore = true;
19223         fx.hideLabel(n, false);
19224       } else {
19225         n.drawn = true;
19226         n.exist = true;
19227         delete n.ignore;
19228       }
19229     });
19230     node.drawn = true;
19231     delete node.ignore;
19232   }
19233 });
19234
19235 /*
19236
19237 Performs operations on group of nodes.
19238
19239 */
19240 TM.Group = new Class( {
19241
19242   initialize: function(viz){
19243     this.viz = viz;
19244     this.canvas = viz.canvas;
19245     this.config = viz.config;
19246   },
19247
19248   /*
19249   
19250     Calls the request method on the controller to request a subtree for each node. 
19251   */
19252   requestNodes: function(nodes, controller){
19253     var counter = 0, len = nodes.length, nodeSelected = {};
19254     var complete = function(){
19255       controller.onComplete();
19256     };
19257     var viz = this.viz;
19258     if (len == 0)
19259       complete();
19260     for ( var i = 0; i < len; i++) {
19261       nodeSelected[nodes[i].id] = nodes[i];
19262       controller.request(nodes[i].id, nodes[i]._level, {
19263         onComplete: function(nodeId, data){
19264           if (data && data.children) {
19265             data.id = nodeId;
19266             viz.op.sum(data, {
19267               type: 'nothing'
19268             });
19269           }
19270           if (++counter == len) {
19271             viz.graph.computeLevels(viz.root, 0);
19272             complete();
19273           }
19274         }
19275       });
19276     }
19277   }
19278 });
19279
19280 /*
19281   Class: TM.Plot
19282   
19283   Custom extension of <Graph.Plot>.
19284   
19285   Extends:
19286   
19287   All <Graph.Plot> methods
19288   
19289   See also:
19290   
19291   <Graph.Plot>
19292   
19293   */
19294 TM.Plot = new Class({
19295
19296   Implements: Graph.Plot,
19297
19298   initialize: function(viz){
19299     this.viz = viz;
19300     this.config = viz.config;
19301     this.node = this.config.Node;
19302     this.edge = this.config.Edge;
19303     this.animation = new Animation;
19304     this.nodeTypes = new TM.Plot.NodeTypes;
19305     this.edgeTypes = new TM.Plot.EdgeTypes;
19306     this.labels = viz.labels;
19307   },
19308
19309   plot: function(opt, animating){
19310     var viz = this.viz, 
19311         graph = viz.graph;
19312     viz.canvas.clear();
19313     this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19314       'withLabels': true,
19315       'hideLabels': false,
19316       'plotSubtree': function(n, ch){
19317         return n.anySubnode("exist");
19318       }
19319     }), animating);
19320   }
19321 });
19322
19323 /*
19324   Class: TM.Label
19325   
19326   Custom extension of <Graph.Label>. 
19327   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19328
19329   Extends:
19330
19331   All <Graph.Label> methods and subclasses.
19332
19333   See also:
19334
19335   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19336   
19337 */
19338 TM.Label = {};
19339
19340 /*
19341  TM.Label.Native
19342
19343  Custom extension of <Graph.Label.Native>.
19344
19345  Extends:
19346
19347  All <Graph.Label.Native> methods
19348
19349  See also:
19350
19351  <Graph.Label.Native>
19352 */
19353 TM.Label.Native = new Class({
19354   Implements: Graph.Label.Native,
19355
19356   initialize: function(viz) {
19357     this.config = viz.config;
19358     this.leaf = viz.leaf;
19359   },
19360   
19361   renderLabel: function(canvas, node, controller){
19362     if(!this.leaf(node) && !this.config.titleHeight) return;
19363     var pos = node.pos.getc(true), 
19364         ctx = canvas.getCtx(),
19365         width = node.getData('width'),
19366         height = node.getData('height'),
19367         x = pos.x + width/2,
19368         y = pos.y;
19369         
19370     ctx.fillText(node.name, x, y, width);
19371   }
19372 });
19373
19374 /*
19375  TM.Label.SVG
19376
19377   Custom extension of <Graph.Label.SVG>.
19378
19379   Extends:
19380
19381   All <Graph.Label.SVG> methods
19382
19383   See also:
19384
19385   <Graph.Label.SVG>
19386 */
19387 TM.Label.SVG = new Class( {
19388   Implements: Graph.Label.SVG,
19389
19390   initialize: function(viz){
19391     this.viz = viz;
19392     this.leaf = viz.leaf;
19393     this.config = viz.config;
19394   },
19395
19396   /* 
19397   placeLabel
19398
19399   Overrides abstract method placeLabel in <Graph.Plot>.
19400
19401   Parameters:
19402
19403   tag - A DOM label element.
19404   node - A <Graph.Node>.
19405   controller - A configuration/controller object passed to the visualization.
19406   
19407   */
19408   placeLabel: function(tag, node, controller){
19409     var pos = node.pos.getc(true), 
19410         canvas = this.viz.canvas,
19411         ox = canvas.translateOffsetX,
19412         oy = canvas.translateOffsetY,
19413         sx = canvas.scaleOffsetX,
19414         sy = canvas.scaleOffsetY,
19415         radius = canvas.getSize();
19416     var labelPos = {
19417       x: Math.round(pos.x * sx + ox + radius.width / 2),
19418       y: Math.round(pos.y * sy + oy + radius.height / 2)
19419     };
19420     tag.setAttribute('x', labelPos.x);
19421     tag.setAttribute('y', labelPos.y);
19422
19423     if(!this.leaf(node) && !this.config.titleHeight) {
19424       tag.style.display = 'none';
19425     }
19426     controller.onPlaceLabel(tag, node);
19427   }
19428 });
19429
19430 /*
19431  TM.Label.HTML
19432
19433  Custom extension of <Graph.Label.HTML>.
19434
19435  Extends:
19436
19437  All <Graph.Label.HTML> methods.
19438
19439  See also:
19440
19441  <Graph.Label.HTML>
19442
19443 */
19444 TM.Label.HTML = new Class( {
19445   Implements: Graph.Label.HTML,
19446
19447   initialize: function(viz){
19448     this.viz = viz;
19449     this.leaf = viz.leaf;
19450     this.config = viz.config;
19451   },
19452
19453   /* 
19454     placeLabel
19455   
19456     Overrides abstract method placeLabel in <Graph.Plot>.
19457   
19458     Parameters:
19459   
19460     tag - A DOM label element.
19461     node - A <Graph.Node>.
19462     controller - A configuration/controller object passed to the visualization.
19463   
19464   */
19465   placeLabel: function(tag, node, controller){
19466     var pos = node.pos.getc(true), 
19467         canvas = this.viz.canvas,
19468         ox = canvas.translateOffsetX,
19469         oy = canvas.translateOffsetY,
19470         sx = canvas.scaleOffsetX,
19471         sy = canvas.scaleOffsetY,
19472         radius = canvas.getSize();
19473     var labelPos = {
19474       x: Math.round(pos.x * sx + ox + radius.width / 2),
19475       y: Math.round(pos.y * sy + oy + radius.height / 2)
19476     };
19477
19478     var style = tag.style;
19479     style.left = labelPos.x + 'px';
19480     style.top = labelPos.y + 'px';
19481     style.width = node.getData('width') * sx + 'px';
19482     style.height = node.getData('height') * sy + 'px';
19483     style.zIndex = node._depth * 100;
19484     style.display = '';
19485
19486     if(!this.leaf(node) && !this.config.titleHeight) {
19487       tag.style.display = 'none';
19488     }
19489     controller.onPlaceLabel(tag, node);
19490   }
19491 });
19492
19493 /*
19494   Class: TM.Plot.NodeTypes
19495
19496   This class contains a list of <Graph.Node> built-in types. 
19497   Node types implemented are 'none', 'rectangle'.
19498
19499   You can add your custom node types, customizing your visualization to the extreme.
19500
19501   Example:
19502
19503   (start code js)
19504     TM.Plot.NodeTypes.implement({
19505       'mySpecialType': {
19506         'render': function(node, canvas) {
19507           //print your custom node to canvas
19508         },
19509         //optional
19510         'contains': function(node, pos) {
19511           //return true if pos is inside the node or false otherwise
19512         }
19513       }
19514     });
19515   (end code)
19516
19517 */
19518 TM.Plot.NodeTypes = new Class( {
19519   'none': {
19520     'render': $.empty
19521   },
19522
19523   'rectangle': {
19524     'render': function(node, canvas, animating){
19525       var leaf = this.viz.leaf(node),
19526           config = this.config,
19527           offst = config.offset,
19528           titleHeight = config.titleHeight,
19529           pos = node.pos.getc(true),
19530           width = node.getData('width'),
19531           height = node.getData('height'),
19532           border = node.getData('border'),
19533           ctx = canvas.getCtx(),
19534           posx = pos.x + offst / 2, 
19535           posy = pos.y + offst / 2;
19536       if(width <= offst || height <= offst) return;
19537       if (leaf) {
19538         if(config.cushion) {
19539           var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1, 
19540               posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19541           var color = node.getData('color');
19542           var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
19543               function(r) { return r * 0.2 >> 0; }));
19544           lg.addColorStop(0, color);
19545           lg.addColorStop(1, colorGrad);
19546           ctx.fillStyle = lg;
19547         }
19548         ctx.fillRect(posx, posy, width - offst, height - offst);
19549         if(border) {
19550           ctx.save();
19551           ctx.strokeStyle = border;
19552           ctx.strokeRect(posx, posy, width - offst, height - offst);
19553           ctx.restore();
19554         }
19555       } else if(titleHeight > 0){
19556         ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19557             titleHeight - offst);
19558         if(border) {
19559           ctx.save();
19560           ctx.strokeStyle = border;
19561           ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19562               height - offst);
19563           ctx.restore();
19564         }
19565       }
19566     },
19567     'contains': function(node, pos) {
19568       if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19569       var npos = node.pos.getc(true),
19570           width = node.getData('width'), 
19571           leaf = this.viz.leaf(node),
19572           height = leaf? node.getData('height') : this.config.titleHeight;
19573       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19574     }
19575   }
19576 });
19577
19578 TM.Plot.EdgeTypes = new Class( {
19579   'none': $.empty
19580 });
19581
19582 /*
19583   Class: TM.SliceAndDice
19584   
19585   A slice and dice TreeMap visualization.
19586   
19587   Implements:
19588   
19589   All <TM.Base> methods and properties.
19590 */
19591 TM.SliceAndDice = new Class( {
19592   Implements: [
19593       Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19594   ]
19595 });
19596
19597 /*
19598   Class: TM.Squarified
19599   
19600   A squarified TreeMap visualization.
19601
19602   Implements:
19603   
19604   All <TM.Base> methods and properties.
19605 */
19606 TM.Squarified = new Class( {
19607   Implements: [
19608       Loader, Extras, TM.Base, Layouts.TM.Squarified
19609   ]
19610 });
19611
19612 /*
19613   Class: TM.Strip
19614   
19615   A strip TreeMap visualization.
19616
19617   Implements:
19618   
19619   All <TM.Base> methods and properties.
19620 */
19621 TM.Strip = new Class( {
19622   Implements: [
19623       Loader, Extras, TM.Base, Layouts.TM.Strip
19624   ]
19625 });
19626
19627
19628 /*
19629  * File: RGraph.js
19630  *
19631  */
19632
19633 /*
19634    Class: RGraph
19635    
19636    A radial graph visualization with advanced animations.
19637    
19638    Inspired by:
19639  
19640    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>
19641    
19642    Note:
19643    
19644    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.
19645    
19646   Implements:
19647   
19648   All <Loader> methods
19649   
19650    Constructor Options:
19651    
19652    Inherits options from
19653    
19654    - <Options.Canvas>
19655    - <Options.Controller>
19656    - <Options.Node>
19657    - <Options.Edge>
19658    - <Options.Label>
19659    - <Options.Events>
19660    - <Options.Tips>
19661    - <Options.NodeStyles>
19662    - <Options.Navigation>
19663    
19664    Additionally, there are other parameters and some default values changed
19665    
19666    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19667    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
19668      
19669    Instance Properties:
19670
19671    canvas - Access a <Canvas> instance.
19672    graph - Access a <Graph> instance.
19673    op - Access a <RGraph.Op> instance.
19674    fx - Access a <RGraph.Plot> instance.
19675    labels - Access a <RGraph.Label> interface implementation.   
19676 */
19677
19678 $jit.RGraph = new Class( {
19679
19680   Implements: [
19681       Loader, Extras, Layouts.Radial
19682   ],
19683
19684   initialize: function(controller){
19685     var $RGraph = $jit.RGraph;
19686
19687     var config = {
19688       interpolation: 'linear',
19689       levelDistance: 100
19690     };
19691
19692     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19693         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19694
19695     var canvasConfig = this.config;
19696     if(canvasConfig.useCanvas) {
19697       this.canvas = canvasConfig.useCanvas;
19698       this.config.labelContainer = this.canvas.id + '-label';
19699     } else {
19700       if(canvasConfig.background) {
19701         canvasConfig.background = $.merge({
19702           type: 'Circles'
19703         }, canvasConfig.background);
19704       }
19705       this.canvas = new Canvas(this, canvasConfig);
19706       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19707     }
19708
19709     this.graphOptions = {
19710       'complex': false,
19711       'Node': {
19712         'selected': false,
19713         'exist': true,
19714         'drawn': true
19715       }
19716     };
19717     this.graph = new Graph(this.graphOptions, this.config.Node,
19718         this.config.Edge);
19719     this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
19720     this.fx = new $RGraph.Plot(this, $RGraph);
19721     this.op = new $RGraph.Op(this);
19722     this.json = null;
19723     this.root = null;
19724     this.busy = false;
19725     this.parent = false;
19726     // initialize extras
19727     this.initializeExtras();
19728   },
19729
19730   /* 
19731   
19732     createLevelDistanceFunc 
19733   
19734     Returns the levelDistance function used for calculating a node distance 
19735     to its origin. This function returns a function that is computed 
19736     per level and not per node, such that all nodes with the same depth will have the 
19737     same distance to the origin. The resulting function gets the 
19738     parent node as parameter and returns a float.
19739
19740    */
19741   createLevelDistanceFunc: function(){
19742     var ld = this.config.levelDistance;
19743     return function(elem){
19744       return (elem._depth + 1) * ld;
19745     };
19746   },
19747
19748   /* 
19749      Method: refresh 
19750      
19751      Computes positions and plots the tree.
19752
19753    */
19754   refresh: function(){
19755     this.compute();
19756     this.plot();
19757   },
19758
19759   reposition: function(){
19760     this.compute('end');
19761   },
19762
19763   /*
19764    Method: plot
19765   
19766    Plots the RGraph. This is a shortcut to *fx.plot*.
19767   */
19768   plot: function(){
19769     this.fx.plot();
19770   },
19771   /*
19772    getNodeAndParentAngle
19773   
19774    Returns the _parent_ of the given node, also calculating its angle span.
19775   */
19776   getNodeAndParentAngle: function(id){
19777     var theta = false;
19778     var n = this.graph.getNode(id);
19779     var ps = n.getParents();
19780     var p = (ps.length > 0)? ps[0] : false;
19781     if (p) {
19782       var posParent = p.pos.getc(), posChild = n.pos.getc();
19783       var newPos = posParent.add(posChild.scale(-1));
19784       theta = Math.atan2(newPos.y, newPos.x);
19785       if (theta < 0)
19786         theta += 2 * Math.PI;
19787     }
19788     return {
19789       parent: p,
19790       theta: theta
19791     };
19792   },
19793   /*
19794    tagChildren
19795   
19796    Enumerates the children in order to maintain child ordering (second constraint of the paper).
19797   */
19798   tagChildren: function(par, id){
19799     if (par.angleSpan) {
19800       var adjs = [];
19801       par.eachAdjacency(function(elem){
19802         adjs.push(elem.nodeTo);
19803       }, "ignore");
19804       var len = adjs.length;
19805       for ( var i = 0; i < len && id != adjs[i].id; i++)
19806         ;
19807       for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
19808         adjs[j].dist = k++;
19809       }
19810     }
19811   },
19812   /* 
19813   Method: onClick 
19814   
19815   Animates the <RGraph> to center the node specified by *id*.
19816
19817    Parameters:
19818
19819    id - A <Graph.Node> id.
19820    opt - (optional|object) An object containing some extra properties described below
19821    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
19822
19823    Example:
19824
19825    (start code js)
19826      rgraph.onClick('someid');
19827      //or also...
19828      rgraph.onClick('someid', {
19829       hideLabels: false
19830      });
19831     (end code)
19832     
19833   */
19834   onClick: function(id, opt){
19835     if (this.root != id && !this.busy) {
19836       this.busy = true;
19837       this.root = id;
19838       that = this;
19839       this.controller.onBeforeCompute(this.graph.getNode(id));
19840       var obj = this.getNodeAndParentAngle(id);
19841
19842       // second constraint
19843       this.tagChildren(obj.parent, id);
19844       this.parent = obj.parent;
19845       this.compute('end');
19846
19847       // first constraint
19848       var thetaDiff = obj.theta - obj.parent.endPos.theta;
19849       this.graph.eachNode(function(elem){
19850         elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
19851       });
19852
19853       var mode = this.config.interpolation;
19854       opt = $.merge( {
19855         onComplete: $.empty
19856       }, opt || {});
19857
19858       this.fx.animate($.merge( {
19859         hideLabels: true,
19860         modes: [
19861           mode
19862         ]
19863       }, opt, {
19864         onComplete: function(){
19865           that.busy = false;
19866           opt.onComplete();
19867         }
19868       }));
19869     }
19870   }
19871 });
19872
19873 $jit.RGraph.$extend = true;
19874
19875 (function(RGraph){
19876
19877   /*
19878      Class: RGraph.Op
19879      
19880      Custom extension of <Graph.Op>.
19881
19882      Extends:
19883
19884      All <Graph.Op> methods
19885      
19886      See also:
19887      
19888      <Graph.Op>
19889
19890   */
19891   RGraph.Op = new Class( {
19892
19893     Implements: Graph.Op
19894
19895   });
19896
19897   /*
19898      Class: RGraph.Plot
19899     
19900     Custom extension of <Graph.Plot>.
19901   
19902     Extends:
19903   
19904     All <Graph.Plot> methods
19905     
19906     See also:
19907     
19908     <Graph.Plot>
19909   
19910   */
19911   RGraph.Plot = new Class( {
19912
19913     Implements: Graph.Plot
19914
19915   });
19916
19917   /*
19918     Object: RGraph.Label
19919
19920     Custom extension of <Graph.Label>. 
19921     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19922   
19923     Extends:
19924   
19925     All <Graph.Label> methods and subclasses.
19926   
19927     See also:
19928   
19929     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19930   
19931    */
19932   RGraph.Label = {};
19933
19934   /*
19935      RGraph.Label.Native
19936
19937      Custom extension of <Graph.Label.Native>.
19938
19939      Extends:
19940
19941      All <Graph.Label.Native> methods
19942
19943      See also:
19944
19945      <Graph.Label.Native>
19946
19947   */
19948   RGraph.Label.Native = new Class( {
19949     Implements: Graph.Label.Native
19950   });
19951
19952   /*
19953      RGraph.Label.SVG
19954     
19955     Custom extension of <Graph.Label.SVG>.
19956   
19957     Extends:
19958   
19959     All <Graph.Label.SVG> methods
19960   
19961     See also:
19962   
19963     <Graph.Label.SVG>
19964   
19965   */
19966   RGraph.Label.SVG = new Class( {
19967     Implements: Graph.Label.SVG,
19968
19969     initialize: function(viz){
19970       this.viz = viz;
19971     },
19972
19973     /* 
19974        placeLabel
19975
19976        Overrides abstract method placeLabel in <Graph.Plot>.
19977
19978        Parameters:
19979
19980        tag - A DOM label element.
19981        node - A <Graph.Node>.
19982        controller - A configuration/controller object passed to the visualization.
19983       
19984      */
19985     placeLabel: function(tag, node, controller){
19986       var pos = node.pos.getc(true), 
19987           canvas = this.viz.canvas,
19988           ox = canvas.translateOffsetX,
19989           oy = canvas.translateOffsetY,
19990           sx = canvas.scaleOffsetX,
19991           sy = canvas.scaleOffsetY,
19992           radius = canvas.getSize();
19993       var labelPos = {
19994         x: Math.round(pos.x * sx + ox + radius.width / 2),
19995         y: Math.round(pos.y * sy + oy + radius.height / 2)
19996       };
19997       tag.setAttribute('x', labelPos.x);
19998       tag.setAttribute('y', labelPos.y);
19999
20000       controller.onPlaceLabel(tag, node);
20001     }
20002   });
20003
20004   /*
20005      RGraph.Label.HTML
20006
20007      Custom extension of <Graph.Label.HTML>.
20008
20009      Extends:
20010
20011      All <Graph.Label.HTML> methods.
20012
20013      See also:
20014
20015      <Graph.Label.HTML>
20016
20017   */
20018   RGraph.Label.HTML = new Class( {
20019     Implements: Graph.Label.HTML,
20020
20021     initialize: function(viz){
20022       this.viz = viz;
20023     },
20024     /* 
20025        placeLabel
20026
20027        Overrides abstract method placeLabel in <Graph.Plot>.
20028
20029        Parameters:
20030
20031        tag - A DOM label element.
20032        node - A <Graph.Node>.
20033        controller - A configuration/controller object passed to the visualization.
20034       
20035      */
20036     placeLabel: function(tag, node, controller){
20037       var pos = node.pos.getc(true), 
20038           canvas = this.viz.canvas,
20039           ox = canvas.translateOffsetX,
20040           oy = canvas.translateOffsetY,
20041           sx = canvas.scaleOffsetX,
20042           sy = canvas.scaleOffsetY,
20043           radius = canvas.getSize();
20044       var labelPos = {
20045         x: Math.round(pos.x * sx + ox + radius.width / 2),
20046         y: Math.round(pos.y * sy + oy + radius.height / 2)
20047       };
20048
20049       var style = tag.style;
20050       style.left = labelPos.x + 'px';
20051       style.top = labelPos.y + 'px';
20052       style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20053
20054       controller.onPlaceLabel(tag, node);
20055     }
20056   });
20057
20058   /*
20059     Class: RGraph.Plot.NodeTypes
20060
20061     This class contains a list of <Graph.Node> built-in types. 
20062     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20063
20064     You can add your custom node types, customizing your visualization to the extreme.
20065
20066     Example:
20067
20068     (start code js)
20069       RGraph.Plot.NodeTypes.implement({
20070         'mySpecialType': {
20071           'render': function(node, canvas) {
20072             //print your custom node to canvas
20073           },
20074           //optional
20075           'contains': function(node, pos) {
20076             //return true if pos is inside the node or false otherwise
20077           }
20078         }
20079       });
20080     (end code)
20081
20082   */
20083   RGraph.Plot.NodeTypes = new Class({
20084     'none': {
20085       'render': $.empty,
20086       'contains': $.lambda(false)
20087     },
20088     'circle': {
20089       'render': function(node, canvas){
20090         var pos = node.pos.getc(true), 
20091             dim = node.getData('dim');
20092         this.nodeHelper.circle.render('fill', pos, dim, canvas);
20093       },
20094       'contains': function(node, pos){
20095         var npos = node.pos.getc(true), 
20096             dim = node.getData('dim');
20097         return this.nodeHelper.circle.contains(npos, pos, dim);
20098       }
20099     },
20100     'ellipse': {
20101       'render': function(node, canvas){
20102         var pos = node.pos.getc(true), 
20103             width = node.getData('width'), 
20104             height = node.getData('height');
20105         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20106         },
20107       // TODO(nico): be more precise...
20108       'contains': function(node, pos){
20109         var npos = node.pos.getc(true), 
20110             width = node.getData('width'), 
20111             height = node.getData('height');
20112         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20113       }
20114     },
20115     'square': {
20116       'render': function(node, canvas){
20117         var pos = node.pos.getc(true), 
20118             dim = node.getData('dim');
20119         this.nodeHelper.square.render('fill', pos, dim, canvas);
20120       },
20121       'contains': function(node, pos){
20122         var npos = node.pos.getc(true), 
20123             dim = node.getData('dim');
20124         return this.nodeHelper.square.contains(npos, pos, dim);
20125       }
20126     },
20127     'rectangle': {
20128       'render': function(node, canvas){
20129         var pos = node.pos.getc(true), 
20130             width = node.getData('width'), 
20131             height = node.getData('height');
20132         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20133       },
20134       'contains': function(node, pos){
20135         var npos = node.pos.getc(true), 
20136             width = node.getData('width'), 
20137             height = node.getData('height');
20138         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20139       }
20140     },
20141     'triangle': {
20142       'render': function(node, canvas){
20143         var pos = node.pos.getc(true), 
20144             dim = node.getData('dim');
20145         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20146       },
20147       'contains': function(node, pos) {
20148         var npos = node.pos.getc(true), 
20149             dim = node.getData('dim');
20150         return this.nodeHelper.triangle.contains(npos, pos, dim);
20151       }
20152     },
20153     'star': {
20154       'render': function(node, canvas){
20155         var pos = node.pos.getc(true),
20156             dim = node.getData('dim');
20157         this.nodeHelper.star.render('fill', pos, dim, canvas);
20158       },
20159       'contains': function(node, pos) {
20160         var npos = node.pos.getc(true),
20161             dim = node.getData('dim');
20162         return this.nodeHelper.star.contains(npos, pos, dim);
20163       }
20164     }
20165   });
20166
20167   /*
20168     Class: RGraph.Plot.EdgeTypes
20169
20170     This class contains a list of <Graph.Adjacence> built-in types. 
20171     Edge types implemented are 'none', 'line' and 'arrow'.
20172   
20173     You can add your custom edge types, customizing your visualization to the extreme.
20174   
20175     Example:
20176   
20177     (start code js)
20178       RGraph.Plot.EdgeTypes.implement({
20179         'mySpecialType': {
20180           'render': function(adj, canvas) {
20181             //print your custom edge to canvas
20182           },
20183           //optional
20184           'contains': function(adj, pos) {
20185             //return true if pos is inside the arc or false otherwise
20186           }
20187         }
20188       });
20189     (end code)
20190   
20191   */
20192   RGraph.Plot.EdgeTypes = new Class({
20193     'none': $.empty,
20194     'line': {
20195       'render': function(adj, canvas) {
20196         var from = adj.nodeFrom.pos.getc(true),
20197             to = adj.nodeTo.pos.getc(true);
20198         this.edgeHelper.line.render(from, to, canvas);
20199       },
20200       'contains': function(adj, pos) {
20201         var from = adj.nodeFrom.pos.getc(true),
20202             to = adj.nodeTo.pos.getc(true);
20203         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20204       }
20205     },
20206     'arrow': {
20207       'render': function(adj, canvas) {
20208         var from = adj.nodeFrom.pos.getc(true),
20209             to = adj.nodeTo.pos.getc(true),
20210             dim = adj.getData('dim'),
20211             direction = adj.data.$direction,
20212             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20213         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20214       },
20215       'contains': function(adj, pos) {
20216         var from = adj.nodeFrom.pos.getc(true),
20217             to = adj.nodeTo.pos.getc(true);
20218         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20219       }
20220     }
20221   });
20222
20223 })($jit.RGraph);
20224
20225
20226 /*
20227  * File: Hypertree.js
20228  * 
20229 */
20230
20231 /* 
20232      Complex 
20233      
20234      A multi-purpose Complex Class with common methods. Extended for the Hypertree. 
20235  
20236 */
20237 /* 
20238    moebiusTransformation 
20239  
20240    Calculates a moebius transformation for this point / complex. 
20241     For more information go to: 
20242         http://en.wikipedia.org/wiki/Moebius_transformation. 
20243  
20244    Parameters: 
20245  
20246       c - An initialized Complex instance representing a translation Vector. 
20247 */
20248
20249 Complex.prototype.moebiusTransformation = function(c) {
20250   var num = this.add(c);
20251   var den = c.$conjugate().$prod(this);
20252   den.x++;
20253   return num.$div(den);
20254 };
20255
20256 /* 
20257     moebiusTransformation 
20258      
20259     Calculates a moebius transformation for the hyperbolic tree. 
20260      
20261     <http://en.wikipedia.org/wiki/Moebius_transformation> 
20262       
20263      Parameters: 
20264      
20265         graph - A <Graph> instance.
20266         pos - A <Complex>.
20267         prop - A property array.
20268         theta - Rotation angle. 
20269         startPos - _optional_ start position. 
20270 */
20271 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20272   this.eachNode(graph, function(elem) {
20273     for ( var i = 0; i < prop.length; i++) {
20274       var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20275       elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20276     }
20277   }, flags);
20278 };
20279
20280 /* 
20281    Class: Hypertree 
20282    
20283    A Hyperbolic Tree/Graph visualization.
20284    
20285    Inspired by:
20286  
20287    A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli). 
20288    <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20289  
20290   Note:
20291  
20292   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.
20293
20294   Implements:
20295   
20296   All <Loader> methods
20297   
20298   Constructor Options:
20299   
20300   Inherits options from
20301   
20302   - <Options.Canvas>
20303   - <Options.Controller>
20304   - <Options.Node>
20305   - <Options.Edge>
20306   - <Options.Label>
20307   - <Options.Events>
20308   - <Options.Tips>
20309   - <Options.NodeStyles>
20310   - <Options.Navigation>
20311   
20312   Additionally, there are other parameters and some default values changed
20313   
20314   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*.
20315   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.
20316   fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20317   duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20318   Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
20319   
20320   Instance Properties:
20321   
20322   canvas - Access a <Canvas> instance.
20323   graph - Access a <Graph> instance.
20324   op - Access a <Hypertree.Op> instance.
20325   fx - Access a <Hypertree.Plot> instance.
20326   labels - Access a <Hypertree.Label> interface implementation.
20327
20328 */
20329
20330 $jit.Hypertree = new Class( {
20331
20332   Implements: [ Loader, Extras, Layouts.Radial ],
20333
20334   initialize: function(controller) {
20335     var $Hypertree = $jit.Hypertree;
20336
20337     var config = {
20338       radius: "auto",
20339       offset: 0,
20340       Edge: {
20341         type: 'hyperline'
20342       },
20343       duration: 1500,
20344       fps: 35
20345     };
20346     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20347         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20348
20349     var canvasConfig = this.config;
20350     if(canvasConfig.useCanvas) {
20351       this.canvas = canvasConfig.useCanvas;
20352       this.config.labelContainer = this.canvas.id + '-label';
20353     } else {
20354       if(canvasConfig.background) {
20355         canvasConfig.background = $.merge({
20356           type: 'Circles'
20357         }, canvasConfig.background);
20358       }
20359       this.canvas = new Canvas(this, canvasConfig);
20360       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20361     }
20362
20363     this.graphOptions = {
20364       'complex': false,
20365       'Node': {
20366         'selected': false,
20367         'exist': true,
20368         'drawn': true
20369       }
20370     };
20371     this.graph = new Graph(this.graphOptions, this.config.Node,
20372         this.config.Edge);
20373     this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20374     this.fx = new $Hypertree.Plot(this, $Hypertree);
20375     this.op = new $Hypertree.Op(this);
20376     this.json = null;
20377     this.root = null;
20378     this.busy = false;
20379     // initialize extras
20380     this.initializeExtras();
20381   },
20382
20383   /* 
20384   
20385   createLevelDistanceFunc 
20386
20387   Returns the levelDistance function used for calculating a node distance 
20388   to its origin. This function returns a function that is computed 
20389   per level and not per node, such that all nodes with the same depth will have the 
20390   same distance to the origin. The resulting function gets the 
20391   parent node as parameter and returns a float.
20392
20393   */
20394   createLevelDistanceFunc: function() {
20395     // get max viz. length.
20396     var r = this.getRadius();
20397     // get max depth.
20398     var depth = 0, max = Math.max, config = this.config;
20399     this.graph.eachNode(function(node) {
20400       depth = max(node._depth, depth);
20401     }, "ignore");
20402     depth++;
20403     // node distance generator
20404     var genDistFunc = function(a) {
20405       return function(node) {
20406         node.scale = r;
20407         var d = node._depth + 1;
20408         var acum = 0, pow = Math.pow;
20409         while (d) {
20410           acum += pow(a, d--);
20411         }
20412         return acum - config.offset;
20413       };
20414     };
20415     // estimate better edge length.
20416     for ( var i = 0.51; i <= 1; i += 0.01) {
20417       var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20418       if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20419     }
20420     return genDistFunc(0.75);
20421   },
20422
20423   /* 
20424     Method: getRadius 
20425     
20426     Returns the current radius of the visualization. If *config.radius* is *auto* then it 
20427     calculates the radius by taking the smaller size of the <Canvas> widget.
20428     
20429     See also:
20430     
20431     <Canvas.getSize>
20432    
20433   */
20434   getRadius: function() {
20435     var rad = this.config.radius;
20436     if (rad !== "auto") { return rad; }
20437     var s = this.canvas.getSize();
20438     return Math.min(s.width, s.height) / 2;
20439   },
20440
20441   /* 
20442     Method: refresh 
20443     
20444     Computes positions and plots the tree.
20445
20446     Parameters:
20447
20448     reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20449
20450    */
20451   refresh: function(reposition) {
20452     if (reposition) {
20453       this.reposition();
20454       this.graph.eachNode(function(node) {
20455         node.startPos.rho = node.pos.rho = node.endPos.rho;
20456         node.startPos.theta = node.pos.theta = node.endPos.theta;
20457       });
20458     } else {
20459       this.compute();
20460     }
20461     this.plot();
20462   },
20463
20464   /* 
20465    reposition 
20466    
20467    Computes nodes' positions and restores the tree to its previous position.
20468
20469    For calculating nodes' positions the root must be placed on its origin. This method does this 
20470      and then attemps to restore the hypertree to its previous position.
20471     
20472   */
20473   reposition: function() {
20474     this.compute('end');
20475     var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20476     Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20477         'end', "ignore");
20478     this.graph.eachNode(function(node) {
20479       if (node.ignore) {
20480         node.endPos.rho = node.pos.rho;
20481         node.endPos.theta = node.pos.theta;
20482       }
20483     });
20484   },
20485
20486   /* 
20487    Method: plot 
20488    
20489    Plots the <Hypertree>. This is a shortcut to *fx.plot*. 
20490
20491   */
20492   plot: function() {
20493     this.fx.plot();
20494   },
20495
20496   /* 
20497    Method: onClick 
20498    
20499    Animates the <Hypertree> to center the node specified by *id*.
20500
20501    Parameters:
20502
20503    id - A <Graph.Node> id.
20504    opt - (optional|object) An object containing some extra properties described below
20505    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20506
20507    Example:
20508
20509    (start code js)
20510      ht.onClick('someid');
20511      //or also...
20512      ht.onClick('someid', {
20513       hideLabels: false
20514      });
20515     (end code)
20516     
20517   */
20518   onClick: function(id, opt) {
20519     var pos = this.graph.getNode(id).pos.getc(true);
20520     this.move(pos, opt);
20521   },
20522
20523   /* 
20524    Method: move 
20525
20526    Translates the tree to the given position. 
20527
20528    Parameters:
20529
20530    pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20531    opt - This object has been defined in <Hypertree.onClick>
20532    
20533    Example:
20534    
20535    (start code js)
20536      ht.move({ x: 0, y: 0.7 }, {
20537        hideLabels: false
20538      });
20539    (end code)
20540
20541   */
20542   move: function(pos, opt) {
20543     var versor = $C(pos.x, pos.y);
20544     if (this.busy === false && versor.norm() < 1) {
20545       this.busy = true;
20546       var root = this.graph.getClosestNodeToPos(versor), that = this;
20547       this.graph.computeLevels(root.id, 0);
20548       this.controller.onBeforeCompute(root);
20549       opt = $.merge( {
20550         onComplete: $.empty
20551       }, opt || {});
20552       this.fx.animate($.merge( {
20553         modes: [ 'moebius' ],
20554         hideLabels: true
20555       }, opt, {
20556         onComplete: function() {
20557           that.busy = false;
20558           opt.onComplete();
20559         }
20560       }), versor);
20561     }
20562   }
20563 });
20564
20565 $jit.Hypertree.$extend = true;
20566
20567 (function(Hypertree) {
20568
20569   /* 
20570      Class: Hypertree.Op 
20571    
20572      Custom extension of <Graph.Op>.
20573
20574      Extends:
20575
20576      All <Graph.Op> methods
20577      
20578      See also:
20579      
20580      <Graph.Op>
20581
20582   */
20583   Hypertree.Op = new Class( {
20584
20585     Implements: Graph.Op
20586
20587   });
20588
20589   /* 
20590      Class: Hypertree.Plot 
20591    
20592     Custom extension of <Graph.Plot>.
20593   
20594     Extends:
20595   
20596     All <Graph.Plot> methods
20597     
20598     See also:
20599     
20600     <Graph.Plot>
20601   
20602   */
20603   Hypertree.Plot = new Class( {
20604
20605     Implements: Graph.Plot
20606
20607   });
20608
20609   /*
20610     Object: Hypertree.Label
20611
20612     Custom extension of <Graph.Label>. 
20613     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20614   
20615     Extends:
20616   
20617     All <Graph.Label> methods and subclasses.
20618   
20619     See also:
20620   
20621     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20622
20623    */
20624   Hypertree.Label = {};
20625
20626   /*
20627      Hypertree.Label.Native
20628
20629      Custom extension of <Graph.Label.Native>.
20630
20631      Extends:
20632
20633      All <Graph.Label.Native> methods
20634
20635      See also:
20636
20637      <Graph.Label.Native>
20638
20639   */
20640   Hypertree.Label.Native = new Class( {
20641     Implements: Graph.Label.Native,
20642
20643     initialize: function(viz) {
20644       this.viz = viz;
20645     },
20646
20647     renderLabel: function(canvas, node, controller) {
20648       var ctx = canvas.getCtx();
20649       var coord = node.pos.getc(true);
20650       var s = this.viz.getRadius();
20651       ctx.fillText(node.name, coord.x * s, coord.y * s);
20652     }
20653   });
20654
20655   /*
20656      Hypertree.Label.SVG
20657
20658     Custom extension of <Graph.Label.SVG>.
20659   
20660     Extends:
20661   
20662     All <Graph.Label.SVG> methods
20663   
20664     See also:
20665   
20666     <Graph.Label.SVG>
20667   
20668   */
20669   Hypertree.Label.SVG = new Class( {
20670     Implements: Graph.Label.SVG,
20671
20672     initialize: function(viz) {
20673       this.viz = viz;
20674     },
20675
20676     /* 
20677        placeLabel
20678
20679        Overrides abstract method placeLabel in <Graph.Plot>.
20680
20681        Parameters:
20682
20683        tag - A DOM label element.
20684        node - A <Graph.Node>.
20685        controller - A configuration/controller object passed to the visualization.
20686       
20687      */
20688     placeLabel: function(tag, node, controller) {
20689       var pos = node.pos.getc(true), 
20690           canvas = this.viz.canvas,
20691           ox = canvas.translateOffsetX,
20692           oy = canvas.translateOffsetY,
20693           sx = canvas.scaleOffsetX,
20694           sy = canvas.scaleOffsetY,
20695           radius = canvas.getSize(),
20696           r = this.viz.getRadius();
20697       var labelPos = {
20698         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20699         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20700       };
20701       tag.setAttribute('x', labelPos.x);
20702       tag.setAttribute('y', labelPos.y);
20703       controller.onPlaceLabel(tag, node);
20704     }
20705   });
20706
20707   /*
20708      Hypertree.Label.HTML
20709
20710      Custom extension of <Graph.Label.HTML>.
20711
20712      Extends:
20713
20714      All <Graph.Label.HTML> methods.
20715
20716      See also:
20717
20718      <Graph.Label.HTML>
20719
20720   */
20721   Hypertree.Label.HTML = new Class( {
20722     Implements: Graph.Label.HTML,
20723
20724     initialize: function(viz) {
20725       this.viz = viz;
20726     },
20727     /* 
20728        placeLabel
20729
20730        Overrides abstract method placeLabel in <Graph.Plot>.
20731
20732        Parameters:
20733
20734        tag - A DOM label element.
20735        node - A <Graph.Node>.
20736        controller - A configuration/controller object passed to the visualization.
20737       
20738      */
20739     placeLabel: function(tag, node, controller) {
20740       var pos = node.pos.getc(true), 
20741           canvas = this.viz.canvas,
20742           ox = canvas.translateOffsetX,
20743           oy = canvas.translateOffsetY,
20744           sx = canvas.scaleOffsetX,
20745           sy = canvas.scaleOffsetY,
20746           radius = canvas.getSize(),
20747           r = this.viz.getRadius();
20748       var labelPos = {
20749         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20750         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20751       };
20752       var style = tag.style;
20753       style.left = labelPos.x + 'px';
20754       style.top = labelPos.y + 'px';
20755       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
20756
20757       controller.onPlaceLabel(tag, node);
20758     }
20759   });
20760
20761   /*
20762     Class: Hypertree.Plot.NodeTypes
20763
20764     This class contains a list of <Graph.Node> built-in types. 
20765     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20766
20767     You can add your custom node types, customizing your visualization to the extreme.
20768
20769     Example:
20770
20771     (start code js)
20772       Hypertree.Plot.NodeTypes.implement({
20773         'mySpecialType': {
20774           'render': function(node, canvas) {
20775             //print your custom node to canvas
20776           },
20777           //optional
20778           'contains': function(node, pos) {
20779             //return true if pos is inside the node or false otherwise
20780           }
20781         }
20782       });
20783     (end code)
20784
20785   */
20786   Hypertree.Plot.NodeTypes = new Class({
20787     'none': {
20788       'render': $.empty,
20789       'contains': $.lambda(false)
20790     },
20791     'circle': {
20792       'render': function(node, canvas) {
20793         var nconfig = this.node,
20794             dim = node.getData('dim'),
20795             p = node.pos.getc();
20796         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20797         p.$scale(node.scale);
20798         if (dim > 0.2) {
20799           this.nodeHelper.circle.render('fill', p, dim, canvas);
20800         }
20801       },
20802       'contains': function(node, pos) {
20803         var dim = node.getData('dim'),
20804             npos = node.pos.getc().$scale(node.scale);
20805         return this.nodeHelper.circle.contains(npos, pos, dim);
20806       }
20807     },
20808     'ellipse': {
20809       'render': function(node, canvas) {
20810         var pos = node.pos.getc().$scale(node.scale),
20811             width = node.getData('width'),
20812             height = node.getData('height');
20813         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20814       },
20815       'contains': function(node, pos) {
20816         var width = node.getData('width'),
20817             height = node.getData('height'),
20818             npos = node.pos.getc().$scale(node.scale);
20819         return this.nodeHelper.circle.contains(npos, pos, width, height);
20820       }
20821     },
20822     'square': {
20823       'render': function(node, canvas) {
20824         var nconfig = this.node,
20825             dim = node.getData('dim'),
20826             p = node.pos.getc();
20827         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20828         p.$scale(node.scale);
20829         if (dim > 0.2) {
20830           this.nodeHelper.square.render('fill', p, dim, canvas);
20831         }
20832       },
20833       'contains': function(node, pos) {
20834         var dim = node.getData('dim'),
20835             npos = node.pos.getc().$scale(node.scale);
20836         return this.nodeHelper.square.contains(npos, pos, dim);
20837       }
20838     },
20839     'rectangle': {
20840       'render': function(node, canvas) {
20841         var nconfig = this.node,
20842             width = node.getData('width'),
20843             height = node.getData('height'),
20844             pos = node.pos.getc();
20845         width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
20846         height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
20847         pos.$scale(node.scale);
20848         if (width > 0.2 && height > 0.2) {
20849           this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20850         }
20851       },
20852       'contains': function(node, pos) {
20853         var width = node.getData('width'),
20854             height = node.getData('height'),
20855             npos = node.pos.getc().$scale(node.scale);
20856         return this.nodeHelper.square.contains(npos, pos, width, height);
20857       }
20858     },
20859     'triangle': {
20860       'render': function(node, canvas) {
20861         var nconfig = this.node,
20862             dim = node.getData('dim'),
20863             p = node.pos.getc();
20864         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20865         p.$scale(node.scale);
20866         if (dim > 0.2) {
20867           this.nodeHelper.triangle.render('fill', p, dim, canvas);
20868         }
20869       },
20870       'contains': function(node, pos) {
20871         var dim = node.getData('dim'),
20872             npos = node.pos.getc().$scale(node.scale);
20873         return this.nodeHelper.triangle.contains(npos, pos, dim);
20874       }
20875     },
20876     'star': {
20877       'render': function(node, canvas) {
20878         var nconfig = this.node,
20879             dim = node.getData('dim'),
20880             p = node.pos.getc();
20881         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
20882         p.$scale(node.scale);
20883         if (dim > 0.2) {
20884           this.nodeHelper.star.render('fill', p, dim, canvas);
20885         }
20886       },
20887       'contains': function(node, pos) {
20888         var dim = node.getData('dim'),
20889             npos = node.pos.getc().$scale(node.scale);
20890         return this.nodeHelper.star.contains(npos, pos, dim);
20891       }
20892     }
20893   });
20894
20895   /*
20896    Class: Hypertree.Plot.EdgeTypes
20897
20898     This class contains a list of <Graph.Adjacence> built-in types. 
20899     Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
20900   
20901     You can add your custom edge types, customizing your visualization to the extreme.
20902   
20903     Example:
20904   
20905     (start code js)
20906       Hypertree.Plot.EdgeTypes.implement({
20907         'mySpecialType': {
20908           'render': function(adj, canvas) {
20909             //print your custom edge to canvas
20910           },
20911           //optional
20912           'contains': function(adj, pos) {
20913             //return true if pos is inside the arc or false otherwise
20914           }
20915         }
20916       });
20917     (end code)
20918   
20919   */
20920   Hypertree.Plot.EdgeTypes = new Class({
20921     'none': $.empty,
20922     'line': {
20923       'render': function(adj, canvas) {
20924         var from = adj.nodeFrom.pos.getc(true),
20925           to = adj.nodeTo.pos.getc(true),
20926           r = adj.nodeFrom.scale;
20927           this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
20928       },
20929       'contains': function(adj, pos) {
20930         var from = adj.nodeFrom.pos.getc(true),
20931             to = adj.nodeTo.pos.getc(true),
20932             r = adj.nodeFrom.scale;
20933             this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20934       }
20935     },
20936     'arrow': {
20937       'render': function(adj, canvas) {
20938         var from = adj.nodeFrom.pos.getc(true),
20939             to = adj.nodeTo.pos.getc(true),
20940             r = adj.nodeFrom.scale,
20941             dim = adj.getData('dim'),
20942             direction = adj.data.$direction,
20943             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20944         this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
20945       },
20946       'contains': function(adj, pos) {
20947         var from = adj.nodeFrom.pos.getc(true),
20948             to = adj.nodeTo.pos.getc(true),
20949             r = adj.nodeFrom.scale;
20950         this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
20951       }
20952     },
20953     'hyperline': {
20954       'render': function(adj, canvas) {
20955         var from = adj.nodeFrom.pos.getc(),
20956             to = adj.nodeTo.pos.getc(),
20957             dim = this.viz.getRadius();
20958         this.edgeHelper.hyperline.render(from, to, dim, canvas);
20959       },
20960       'contains': $.lambda(false)
20961     }
20962   });
20963
20964 })($jit.Hypertree);
20965
20966
20967
20968
20969  })();