]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/SugarCharts/Jit/js/Jit/jit.js
Release 6.5.6
[Github/sugarcrm.git] / jssource / src_files / include / SugarCharts / Jit / js / Jit / jit.js
1 /*
2   Copyright (c) 2010, Nicolas Garcia Belmonte
3   All rights reserved
4
5   > Redistribution and use in source and binary forms, with or without
6   > modification, are permitted provided that the following conditions are met:
7   >      * Redistributions of source code must retain the above copyright
8   >        notice, this list of conditions and the following disclaimer.
9   >      * Redistributions in binary form must reproduce the above copyright
10   >        notice, this list of conditions and the following disclaimer in the
11   >        documentation and/or other materials provided with the distribution.
12   >      * Neither the name of the organization nor the
13   >        names of its contributors may be used to endorse or promote products
14   >        derived from this software without specific prior written permission.
15   >
16   >  THIS SOFTWARE IS PROVIDED BY NICOLAS GARCIA BELMONTE ``AS IS'' AND ANY
17   >  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   >  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   >  DISCLAIMED. IN NO EVENT SHALL NICOLAS GARCIA BELMONTE BE LIABLE FOR ANY
20   >  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   >  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   >  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   >  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   >  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   >  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27  
28  /** Lam Huynh on 10/10/2010 added funnel,guage charts **/
29  /** Lam Huynh on 02/27/2011 added image exporting **/
30  /** Lam Huynh on 02/23/2011 added line charts **/
31  
32  (function () { 
33
34 /*
35   File: Core.js
36
37  */
38
39 /*
40  Object: $jit
41  
42  Defines the namespace for all library Classes and Objects. 
43  This variable is the *only* global variable defined in the Toolkit. 
44  There are also other interesting properties attached to this variable described below.
45  */
46 window.$jit = function(w) {
47   w = w || window;
48   for(var k in $jit) {
49     if($jit[k].$extend) {
50       w[k] = $jit[k];
51     }
52   }
53 };
54
55 $jit.version = '2.0.0b';
56 /*
57   Object: $jit.id
58   
59   Works just like *document.getElementById*
60   
61   Example:
62   (start code js)
63   var element = $jit.id('elementId');
64   (end code)
65
66 */
67
68 /*
69  Object: $jit.util
70  
71  Contains utility functions.
72  
73  Some of the utility functions and the Class system were based in the MooTools Framework 
74  <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. 
75  MIT license <http://mootools.net/license.txt>.
76  
77  These methods are generally also implemented in DOM manipulation frameworks like JQuery, MooTools and Prototype.
78  I'd suggest you to use the functions from those libraries instead of using these, since their functions 
79  are widely used and tested in many different platforms/browsers. Use these functions only if you have to.
80  
81  */
82 var $ = function(d) {
83   return document.getElementById(d);
84 };
85
86 $.empty = function() {
87 };
88
89 function pad(number, length) {
90    
91     var str = '' + number;
92     while (str.length < length) {
93         str =  str + '0';
94     }
95    
96     return str;
97
98 };
99
100 var Url = {
101  
102         // public method for url encoding
103         encode : function (string) {
104                 return escape(this._utf8_encode(string));
105         },
106  
107         // public method for url decoding
108         decode : function (string) {
109                 return this._utf8_decode(unescape(string));
110         },
111  
112         // private method for UTF-8 encoding
113         _utf8_encode : function (string) {
114                 string = string.replace(/\r\n/g,"\n");
115                 var utftext = "";
116  
117                 for (var n = 0; n < string.length; n++) {
118  
119                         var c = string.charCodeAt(n);
120  
121                         if (c < 128) {
122                                 utftext += String.fromCharCode(c);
123                         }
124                         else if((c > 127) && (c < 2048)) {
125                                 utftext += String.fromCharCode((c >> 6) | 192);
126                                 utftext += String.fromCharCode((c & 63) | 128);
127                         }
128                         else {
129                                 utftext += String.fromCharCode((c >> 12) | 224);
130                                 utftext += String.fromCharCode(((c >> 6) & 63) | 128);
131                                 utftext += String.fromCharCode((c & 63) | 128);
132                         }
133  
134                 }
135  
136                 return utftext;
137         },
138  
139         // private method for UTF-8 decoding
140         _utf8_decode : function (utftext) {
141                 var string = "";
142                 var i = 0;
143                 var c = c1 = c2 = 0;
144  
145                 while ( i < utftext.length ) {
146  
147                         c = utftext.charCodeAt(i);
148  
149                         if (c < 128) {
150                                 string += String.fromCharCode(c);
151                                 i++;
152                         }
153                         else if((c > 191) && (c < 224)) {
154                                 c2 = utftext.charCodeAt(i+1);
155                                 string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
156                                 i += 2;
157                         }
158                         else {
159                                 c2 = utftext.charCodeAt(i+1);
160                                 c3 = utftext.charCodeAt(i+2);
161                                 string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
162                                 i += 3;
163                         }
164  
165                 }
166  
167                 return string;
168         }
169  
170 };
171
172 Array.prototype.sum = function() {
173   return (! this.length) ? 0 : this.slice(1).sum() +
174       ((typeof this[0] == 'number') ? this[0] : 0);
175 };
176
177 function array_match(needle, haystack) {
178     var length = haystack.length;
179     var indexValue = new Array();
180     for(var i = 0, count = 0; i < length; i++) {
181         if(haystack[i] == needle) {
182                 indexValue[count] = i;
183                 count++;
184         }
185     }
186     return new Array(count,indexValue);
187 };
188
189 $.roundedRect = function (ctx,x,y,width,height,radius,fillType){
190   ctx.beginPath();
191   ctx.moveTo(x,y+radius);
192   ctx.lineTo(x,y+height-radius);
193   ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
194   ctx.lineTo(x+width-radius,y+height);
195   ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
196   ctx.lineTo(x+width,y+radius);
197   ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
198   ctx.lineTo(x+radius,y);
199   ctx.quadraticCurveTo(x,y,x,y+radius);
200   if(fillType=="fill") {
201         ctx.fill();
202   } else {
203         ctx.stroke();
204         }
205 };
206
207 $.saveImageFile = function (id,jsonfilename,imageExt) {
208         var parts = jsonfilename.split("/");
209         var filename = parts[2].replace(".js","."+imageExt);
210         var oCanvas = document.getElementById(id+"-canvas");
211         
212         if(oCanvas) {
213                 if(imageExt == "jpg") {
214                         var strDataURI = oCanvas.toDataURL("image/jpeg"); 
215                 } else {
216                         var strDataURI = oCanvas.toDataURL("image/png");
217                 }
218                 var handleFailure = function(o){
219                         //alert('failed to write image' + filename);
220                         //remove alert since chrome triggers this function when user navigates away from page before image gets written.
221                 }       
222                 var handleSuccess = function(o){
223                 }                       
224                 var callback =
225                 {
226                   success:handleSuccess,
227                   failure:handleFailure,
228                   argument: { foo:'foo', bar:''}
229                 };
230                 var path = "index.php?action=DynamicAction&DynamicAction=saveImage&module=Charts&to_pdf=1";
231                 var postData = "imageStr=" + strDataURI + "&filename=" + filename;
232                 var request = YAHOO.util.Connect.asyncRequest('POST', path, callback, postData);
233         }
234 };
235
236 $.saveImageTest = function (id,jsonfilename,imageExt) {
237                 if(typeof FlashCanvas != "undefined") {
238                         setTimeout(function(){$.saveImageFile(id,jsonfilename,imageExt)},10000);
239                 } else {
240                         $.saveImageFile(id,jsonfilename,imageExt);
241                 }
242         };
243 /*
244   Method: extend
245   
246   Augment an object by appending another object's properties.
247   
248   Parameters:
249   
250   original - (object) The object to be extended.
251   extended - (object) An object which properties are going to be appended to the original object.
252   
253   Example:
254   (start code js)
255   $jit.util.extend({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
256   (end code)
257 */
258 $.extend = function(original, extended) {
259   for ( var key in (extended || {}))
260     original[key] = extended[key];
261   return original;
262 };
263
264 $.lambda = function(value) {
265   return (typeof value == 'function') ? value : function() {
266     return value;
267   };
268 };
269
270 $.time = Date.now || function() {
271   return +new Date;
272 };
273
274 /*
275   Method: splat
276   
277   Returns an array wrapping *obj* if *obj* is not an array. Returns *obj* otherwise.
278   
279   Parameters:
280   
281   obj - (mixed) The object to be wrapped in an array.
282   
283   Example:
284   (start code js)
285   $jit.util.splat(3);   //[3]
286   $jit.util.splat([3]); //[3]
287   (end code)
288 */
289 $.splat = function(obj) {
290   var type = $.type(obj);
291   return type ? ((type != 'array') ? [ obj ] : obj) : [];
292 };
293
294 $.type = function(elem) {
295   var type = $.type.s.call(elem).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
296   if(type != 'object') return type;
297   if(elem && elem.$$family) return elem.$$family;
298   return (elem && elem.nodeName && elem.nodeType == 1)? 'element' : type;
299 };
300 $.type.s = Object.prototype.toString;
301
302 /*
303   Method: each
304   
305   Iterates through an iterable applying *f*.
306   
307   Parameters:
308   
309   iterable - (array) The original array.
310   fn - (function) The function to apply to the array elements.
311   
312   Example:
313   (start code js)
314   $jit.util.each([3, 4, 5], function(n) { alert('number ' + n); });
315   (end code)
316 */
317 $.each = function(iterable, fn) {
318   var type = $.type(iterable);
319   if (type == 'object') {
320     for ( var key in iterable)
321       fn(iterable[key], key);
322   } else {
323     for ( var i = 0, l = iterable.length; i < l; i++)
324       fn(iterable[i], i);
325   }
326 };
327
328 $.indexOf = function(array, item) {
329   if(Array.indexOf) return array.indexOf(item);
330   for(var i=0,l=array.length; i<l; i++) {
331     if(array[i] === item) return i;
332   }
333   return -1;
334 };
335
336 /*
337   Method: map
338   
339   Maps or collects an array by applying *f*.
340   
341   Parameters:
342   
343   array - (array) The original array.
344   f - (function) The function to apply to the array elements.
345   
346   Example:
347   (start code js)
348   $jit.util.map([3, 4, 5], function(n) { return n*n; }); //[9, 16, 25]
349   (end code)
350 */
351 $.map = function(array, f) {
352   var ans = [];
353   $.each(array, function(elem, i) {
354     ans.push(f(elem, i));
355   });
356   return ans;
357 };
358
359 /*
360   Method: reduce
361   
362   Iteratively applies the binary function *f* storing the result in an accumulator.
363   
364   Parameters:
365   
366   array - (array) The original array.
367   f - (function) The function to apply to the array elements.
368   opt - (optional|mixed) The starting value for the acumulator.
369   
370   Example:
371   (start code js)
372   $jit.util.reduce([3, 4, 5], function(x, y) { return x + y; }, 0); //12
373   (end code)
374 */
375 $.reduce = function(array, f, opt) {
376   var l = array.length;
377   if(l==0) return opt;
378   var acum = arguments.length == 3? opt : array[--l];
379   while(l--) {
380     acum = f(acum, array[l]);
381   }
382   return acum;
383 };
384
385 /*
386   Method: merge
387   
388   Merges n-objects and their sub-objects creating a new, fresh object.
389   
390   Parameters:
391   
392   An arbitrary number of objects.
393   
394   Example:
395   (start code js)
396   $jit.util.merge({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 4 }); //{ 'a':1, 'b': 3, 'c': 4 }
397   (end code)
398 */
399 $.merge = function() {
400   var mix = {};
401   for ( var i = 0, l = arguments.length; i < l; i++) {
402     var object = arguments[i];
403     if ($.type(object) != 'object')
404       continue;
405     for ( var key in object) {
406       var op = object[key], mp = mix[key];
407       mix[key] = (mp && $.type(op) == 'object' && $.type(mp) == 'object') ? $
408           .merge(mp, op) : $.unlink(op);
409     }
410   }
411   return mix;
412 };
413
414 $.unlink = function(object) {
415   var unlinked;
416   switch ($.type(object)) {
417   case 'object':
418     unlinked = {};
419     for ( var p in object)
420       unlinked[p] = $.unlink(object[p]);
421     break;
422   case 'array':
423     unlinked = [];
424     for ( var i = 0, l = object.length; i < l; i++)
425       unlinked[i] = $.unlink(object[i]);
426     break;
427   default:
428     return object;
429   }
430   return unlinked;
431 };
432
433 $.zip = function() {
434   if(arguments.length === 0) return [];
435   for(var j=0, ans=[], l=arguments.length, ml=arguments[0].length; j<ml; j++) {
436     for(var i=0, row=[]; i<l; i++) {
437       row.push(arguments[i][j]);
438     }
439     ans.push(row);
440   }
441   return ans;
442 };
443
444 /*
445   Method: rgbToHex
446   
447   Converts an RGB array into a Hex string.
448   
449   Parameters:
450   
451   srcArray - (array) An array with R, G and B values
452   
453   Example:
454   (start code js)
455   $jit.util.rgbToHex([255, 255, 255]); //'#ffffff'
456   (end code)
457 */
458 $.rgbToHex = function(srcArray, array) {
459   if (srcArray.length < 3)
460     return null;
461   if (srcArray.length == 4 && srcArray[3] == 0 && !array)
462     return 'transparent';
463   var hex = [];
464   for ( var i = 0; i < 3; i++) {
465     var bit = (srcArray[i] - 0).toString(16);
466     hex.push(bit.length == 1 ? '0' + bit : bit);
467   }
468   return array ? hex : '#' + hex.join('');
469 };
470
471 /*
472   Method: hexToRgb
473   
474   Converts an Hex color string into an RGB array.
475   
476   Parameters:
477   
478   hex - (string) A color hex string.
479   
480   Example:
481   (start code js)
482   $jit.util.hexToRgb('#fff'); //[255, 255, 255]
483   (end code)
484 */
485 $.hexToRgb = function(hex) {
486   if (hex.length != 7) {
487     hex = hex.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
488     hex.shift();
489     if (hex.length != 3)
490       return null;
491     var rgb = [];
492     for ( var i = 0; i < 3; i++) {
493       var value = hex[i];
494       if (value.length == 1)
495         value += value;
496       rgb.push(parseInt(value, 16));
497     }
498     return rgb;
499   } else {
500     hex = parseInt(hex.slice(1), 16);
501     return [ hex >> 16, hex >> 8 & 0xff, hex & 0xff ];
502   }
503 };
504
505 $.destroy = function(elem) {
506   $.clean(elem);
507   if (elem.parentNode)
508     elem.parentNode.removeChild(elem);
509   if (elem.clearAttributes)
510     elem.clearAttributes();
511 };
512
513 $.clean = function(elem) {
514   for (var ch = elem.childNodes, i = 0, l = ch.length; i < l; i++) {
515     $.destroy(ch[i]);
516   }
517 };
518
519 /*
520   Method: addEvent
521   
522   Cross-browser add event listener.
523   
524   Parameters:
525   
526   obj - (obj) The Element to attach the listener to.
527   type - (string) The listener type. For example 'click', or 'mousemove'.
528   fn - (function) The callback function to be used when the event is fired.
529   
530   Example:
531   (start code js)
532   $jit.util.addEvent(elem, 'click', function(){ alert('hello'); });
533   (end code)
534 */
535 $.addEvent = function(obj, type, fn) {
536   if (obj.addEventListener)
537     obj.addEventListener(type, fn, false);
538   else
539     obj.attachEvent('on' + type, fn);
540 };
541
542 $.addEvents = function(obj, typeObj) {
543   for(var type in typeObj) {
544     $.addEvent(obj, type, typeObj[type]);
545   }
546 };
547
548 $.hasClass = function(obj, klass) {
549   return (' ' + obj.className + ' ').indexOf(' ' + klass + ' ') > -1;
550 };
551
552 $.addClass = function(obj, klass) {
553   if (!$.hasClass(obj, klass))
554     obj.className = (obj.className + " " + klass);
555 };
556
557 $.removeClass = function(obj, klass) {
558   obj.className = obj.className.replace(new RegExp(
559       '(^|\\s)' + klass + '(?:\\s|$)'), '$1');
560 };
561
562 $.getPos = function(elem) {
563   var offset = getOffsets(elem);
564   var scroll = getScrolls(elem);
565   return {
566     x: offset.x - scroll.x,
567     y: offset.y - scroll.y
568   };
569
570   function getOffsets(elem) {
571     var position = {
572       x: 0,
573       y: 0
574     };
575     while (elem && !isBody(elem)) {
576       position.x += elem.offsetLeft;
577       position.y += elem.offsetTop;
578       try{elem=elem.offsetParent;}catch(e){elem=document.body;}
579     }
580     return position;
581   }
582
583   function getScrolls(elem) {
584     var position = {
585       x: 0,
586       y: 0
587     };
588     while (elem && !isBody(elem)) {
589       position.x += elem.scrollLeft;
590       position.y += elem.scrollTop;
591       elem = elem.parentNode;
592     }
593     return position;
594   }
595
596   function isBody(element) {
597     return (/^(?:body|html)$/i).test(element.tagName);
598   }
599 };
600
601 $.event = {
602   get: function(e, win) {
603     win = win || window;
604     return e || win.event;
605   },
606   getWheel: function(e) {
607     return e.wheelDelta? e.wheelDelta / 120 : -(e.detail || 0) / 3;
608   },
609   isRightClick: function(e) {
610     return (e.which == 3 || e.button == 2);
611   },
612   getPos: function(e, win) {
613     // get mouse position
614     win = win || window;
615     e = e || win.event;
616     var doc = win.document;
617     doc = doc.documentElement || doc.body;
618     //TODO(nico): make touch event handling better
619     if(e.touches && e.touches.length) {
620       e = e.touches[0];
621     }
622     var page = {
623       x: e.pageX || (e.clientX + doc.scrollLeft),
624       y: e.pageY || (e.clientY + doc.scrollTop)
625     };
626     return page;
627   },
628   stop: function(e) {
629     if (e.stopPropagation) e.stopPropagation();
630     e.cancelBubble = true;
631     if (e.preventDefault) e.preventDefault();
632     else e.returnValue = false;
633   }
634 };
635
636 $jit.util = $jit.id = $;
637
638 var Class = function(properties) {
639   properties = properties || {};
640   var klass = function() {
641     for ( var key in this) {
642       if (typeof this[key] != 'function')
643         this[key] = $.unlink(this[key]);
644     }
645     this.constructor = klass;
646     if (Class.prototyping)
647       return this;
648     var instance = this.initialize ? this.initialize.apply(this, arguments)
649         : this;
650     //typize
651     this.$$family = 'class';
652     return instance;
653   };
654
655   for ( var mutator in Class.Mutators) {
656     if (!properties[mutator])
657       continue;
658     properties = Class.Mutators[mutator](properties, properties[mutator]);
659     delete properties[mutator];
660   }
661
662   $.extend(klass, this);
663   klass.constructor = Class;
664   klass.prototype = properties;
665   return klass;
666 };
667
668 Class.Mutators = {
669
670   Implements: function(self, klasses) {
671     $.each($.splat(klasses), function(klass) {
672       Class.prototyping = klass;
673       var instance = (typeof klass == 'function') ? new klass : klass;
674       for ( var prop in instance) {
675         if (!(prop in self)) {
676           self[prop] = instance[prop];
677         }
678       }
679       delete Class.prototyping;
680     });
681     return self;
682   }
683
684 };
685
686 $.extend(Class, {
687
688   inherit: function(object, properties) {
689     for ( var key in properties) {
690       var override = properties[key];
691       var previous = object[key];
692       var type = $.type(override);
693       if (previous && type == 'function') {
694         if (override != previous) {
695           Class.override(object, key, override);
696         }
697       } else if (type == 'object') {
698         object[key] = $.merge(previous, override);
699       } else {
700         object[key] = override;
701       }
702     }
703     return object;
704   },
705
706   override: function(object, name, method) {
707     var parent = Class.prototyping;
708     if (parent && object[name] != parent[name])
709       parent = null;
710     var override = function() {
711       var previous = this.parent;
712       this.parent = parent ? parent[name] : object[name];
713       var value = method.apply(this, arguments);
714       this.parent = previous;
715       return value;
716     };
717     object[name] = override;
718   }
719
720 });
721
722 Class.prototype.implement = function() {
723   var proto = this.prototype;
724   $.each(Array.prototype.slice.call(arguments || []), function(properties) {
725     Class.inherit(proto, properties);
726   });
727   return this;
728 };
729
730 $jit.Class = Class;
731
732 /*
733   Object: $jit.json
734   
735   Provides JSON utility functions.
736   
737   Most of these functions are JSON-tree traversal and manipulation functions.
738 */
739 $jit.json = {
740   /*
741      Method: prune
742   
743      Clears all tree nodes having depth greater than maxLevel.
744   
745      Parameters:
746   
747         tree - (object) A JSON tree object. For more information please see <Loader.loadJSON>.
748         maxLevel - (number) An integer specifying the maximum level allowed for this tree. All nodes having depth greater than max level will be deleted.
749
750   */
751   prune: function(tree, maxLevel) {
752     this.each(tree, function(elem, i) {
753       if (i == maxLevel && elem.children) {
754         delete elem.children;
755         elem.children = [];
756       }
757     });
758   },
759   /*
760      Method: getParent
761   
762      Returns the parent node of the node having _id_ as id.
763   
764      Parameters:
765   
766         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
767         id - (string) The _id_ of the child node whose parent will be returned.
768
769     Returns:
770
771         A tree JSON node if any, or false otherwise.
772   
773   */
774   getParent: function(tree, id) {
775     if (tree.id == id)
776       return false;
777     var ch = tree.children;
778     if (ch && ch.length > 0) {
779       for ( var i = 0; i < ch.length; i++) {
780         if (ch[i].id == id)
781           return tree;
782         else {
783           var ans = this.getParent(ch[i], id);
784           if (ans)
785             return ans;
786         }
787       }
788     }
789     return false;
790   },
791   /*
792      Method: getSubtree
793   
794      Returns the subtree that matches the given id.
795   
796      Parameters:
797   
798         tree - (object) A JSON tree object. See also <Loader.loadJSON>.
799         id - (string) A node *unique* identifier.
800   
801      Returns:
802   
803         A subtree having a root node matching the given id. Returns null if no subtree matching the id is found.
804
805   */
806   getSubtree: function(tree, id) {
807     if (tree.id == id)
808       return tree;
809     for ( var i = 0, ch = tree.children; i < ch.length; i++) {
810       var t = this.getSubtree(ch[i], id);
811       if (t != null)
812         return t;
813     }
814     return null;
815   },
816   /*
817      Method: eachLevel
818   
819       Iterates on tree nodes with relative depth less or equal than a specified level.
820   
821      Parameters:
822   
823         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
824         initLevel - (number) An integer specifying the initial relative level. Usually zero.
825         toLevel - (number) An integer specifying a top level. This method will iterate only through nodes with depth less than or equal this number.
826         action - (function) A function that receives a node and an integer specifying the actual level of the node.
827           
828     Example:
829    (start code js)
830      $jit.json.eachLevel(tree, 0, 3, function(node, depth) {
831         alert(node.name + ' ' + depth);
832      });
833    (end code)
834   */
835   eachLevel: function(tree, initLevel, toLevel, action) {
836     if (initLevel <= toLevel) {
837       action(tree, initLevel);
838       if(!tree.children) return;
839       for ( var i = 0, ch = tree.children; i < ch.length; i++) {
840         this.eachLevel(ch[i], initLevel + 1, toLevel, action);
841       }
842     }
843   },
844   /*
845      Method: each
846   
847       A JSON tree iterator.
848   
849      Parameters:
850   
851         tree - (object) A JSON tree or subtree. See also <Loader.loadJSON>.
852         action - (function) A function that receives a node.
853
854     Example:
855     (start code js)
856       $jit.json.each(tree, function(node) {
857         alert(node.name);
858       });
859     (end code)
860           
861   */
862   each: function(tree, action) {
863     this.eachLevel(tree, 0, Number.MAX_VALUE, action);
864   }
865 };
866
867
868 /*
869      An object containing multiple type of transformations. 
870 */
871
872 $jit.Trans = {
873   $extend: true,
874   
875   linear: function(p){
876     return p;
877   }
878 };
879
880 var Trans = $jit.Trans;
881
882 (function(){
883
884   var makeTrans = function(transition, params){
885     params = $.splat(params);
886     return $.extend(transition, {
887       easeIn: function(pos){
888         return transition(pos, params);
889       },
890       easeOut: function(pos){
891         return 1 - transition(1 - pos, params);
892       },
893       easeInOut: function(pos){
894         return (pos <= 0.5)? transition(2 * pos, params) / 2 : (2 - transition(
895             2 * (1 - pos), params)) / 2;
896       }
897     });
898   };
899
900   var transitions = {
901
902     Pow: function(p, x){
903       return Math.pow(p, x[0] || 6);
904     },
905
906     Expo: function(p){
907       return Math.pow(2, 8 * (p - 1));
908     },
909
910     Circ: function(p){
911       return 1 - Math.sin(Math.acos(p));
912     },
913
914     Sine: function(p){
915       return 1 - Math.sin((1 - p) * Math.PI / 2);
916     },
917
918     Back: function(p, x){
919       x = x[0] || 1.618;
920       return Math.pow(p, 2) * ((x + 1) * p - x);
921     },
922
923     Bounce: function(p){
924       var value;
925       for ( var a = 0, b = 1; 1; a += b, b /= 2) {
926         if (p >= (7 - 4 * a) / 11) {
927           value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
928           break;
929         }
930       }
931       return value;
932     },
933
934     Elastic: function(p, x){
935       return Math.pow(2, 10 * --p)
936           * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
937     }
938
939   };
940
941   $.each(transitions, function(val, key){
942     Trans[key] = makeTrans(val);
943   });
944
945   $.each( [
946       'Quad', 'Cubic', 'Quart', 'Quint'
947   ], function(elem, i){
948     Trans[elem] = makeTrans(function(p){
949       return Math.pow(p, [
950         i + 2
951       ]);
952     });
953   });
954
955 })();
956
957 /*
958    A Class that can perform animations for generic objects.
959
960    If you are looking for animation transitions please take a look at the <Trans> object.
961
962    Used by:
963
964    <Graph.Plot>
965    
966    Based on:
967    
968    The Animation class is based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2009 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
969
970 */
971
972 var Animation = new Class( {
973
974   initialize: function(options){
975     this.setOptions(options);
976   },
977
978   setOptions: function(options){
979     var opt = {
980       duration: 2500,
981       fps: 40,
982       transition: Trans.Quart.easeInOut,
983       compute: $.empty,
984       complete: $.empty,
985       link: 'ignore'
986     };
987     this.opt = $.merge(opt, options || {});
988     return this;
989   },
990
991   step: function(){
992     var time = $.time(), opt = this.opt;
993     if (time < this.time + opt.duration) {
994       var delta = opt.transition((time - this.time) / opt.duration);
995       opt.compute(delta);
996     } else {
997       this.timer = clearInterval(this.timer);
998       opt.compute(1);
999       opt.complete();
1000     }
1001   },
1002
1003   start: function(){
1004     if (!this.check())
1005       return this;
1006     this.time = 0;
1007     this.startTimer();
1008     return this;
1009   },
1010
1011   startTimer: function(){
1012     var that = this, fps = this.opt.fps;
1013     if (this.timer)
1014       return false;
1015     this.time = $.time() - this.time;
1016     this.timer = setInterval((function(){
1017       that.step();
1018     }), Math.round(1000 / fps));
1019     return true;
1020   },
1021
1022   pause: function(){
1023     this.stopTimer();
1024     return this;
1025   },
1026
1027   resume: function(){
1028     this.startTimer();
1029     return this;
1030   },
1031
1032   stopTimer: function(){
1033     if (!this.timer)
1034       return false;
1035     this.time = $.time() - this.time;
1036     this.timer = clearInterval(this.timer);
1037     return true;
1038   },
1039
1040   check: function(){
1041     if (!this.timer)
1042       return true;
1043     if (this.opt.link == 'cancel') {
1044       this.stopTimer();
1045       return true;
1046     }
1047     return false;
1048   }
1049 });
1050
1051
1052 var Options = function() {
1053   var args = arguments;
1054   for(var i=0, l=args.length, ans={}; i<l; i++) {
1055     var opt = Options[args[i]];
1056     if(opt.$extend) {
1057       $.extend(ans, opt);
1058     } else {
1059       ans[args[i]] = opt;  
1060     }
1061   }
1062   return ans;
1063 };
1064
1065 /*
1066  * File: Options.AreaChart.js
1067  *
1068 */
1069
1070 /*
1071   Object: Options.AreaChart
1072   
1073   <AreaChart> options. 
1074   Other options included in the AreaChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
1075   
1076   Syntax:
1077   
1078   (start code js)
1079
1080   Options.AreaChart = {
1081     animate: true,
1082     labelOffset: 3,
1083     type: 'stacked',
1084     selectOnHover: true,
1085     showAggregates: true,
1086     showLabels: true,
1087     filterOnClick: false,
1088     restoreOnRightClick: false
1089   };
1090   
1091   (end code)
1092   
1093   Example:
1094   
1095   (start code js)
1096
1097   var areaChart = new $jit.AreaChart({
1098     animate: true,
1099     type: 'stacked:gradient',
1100     selectOnHover: true,
1101     filterOnClick: true,
1102     restoreOnRightClick: true
1103   });
1104   
1105   (end code)
1106
1107   Parameters:
1108   
1109   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
1110   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
1111   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
1112   selectOnHover - (boolean) Default's *true*. If true, it will add a mark to the hovered stack.
1113   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
1114   showLabels - (boolean) Default's *true*. Display the name of the slots.
1115   filterOnClick - (boolean) Default's *true*. Select the clicked stack by hiding all other stacks.
1116   restoreOnRightClick - (boolean) Default's *true*. Show all stacks by right clicking.
1117   
1118 */
1119   
1120 Options.AreaChart = {
1121   $extend: true,
1122
1123   animate: true,
1124   labelOffset: 3, // label offset
1125   type: 'stacked', // gradient
1126   Tips: {
1127     enable: false,
1128     onShow: $.empty,
1129     onHide: $.empty
1130   },
1131   Events: {
1132     enable: false,
1133     onClick: $.empty
1134   },
1135   selectOnHover: true,
1136   showAggregates: true,
1137   showLabels: true,
1138   filterOnClick: false,
1139   restoreOnRightClick: false
1140 };
1141
1142 /*
1143  * File: Options.Margin.js
1144  *
1145 */
1146
1147 /*
1148   Object: Options.Margin
1149   
1150   Canvas drawing margins. 
1151   
1152   Syntax:
1153   
1154   (start code js)
1155
1156   Options.Margin = {
1157     top: 0,
1158     left: 0,
1159     right: 0,
1160     bottom: 0
1161   };
1162   
1163   (end code)
1164   
1165   Example:
1166   
1167   (start code js)
1168
1169   var viz = new $jit.Viz({
1170     Margin: {
1171       right: 10,
1172       bottom: 20
1173     }
1174   });
1175   
1176   (end code)
1177
1178   Parameters:
1179   
1180   top - (number) Default's *0*. Top margin.
1181   left - (number) Default's *0*. Left margin.
1182   right - (number) Default's *0*. Right margin.
1183   bottom - (number) Default's *0*. Bottom margin.
1184   
1185 */
1186
1187 Options.Margin = {
1188   $extend: false,
1189   
1190   top: 0,
1191   left: 0,
1192   right: 0,
1193   bottom: 0
1194 };
1195
1196 /*
1197  * File: Options.Canvas.js
1198  *
1199 */
1200
1201 /*
1202   Object: Options.Canvas
1203   
1204   These are Canvas general options, like where to append it in the DOM, its dimensions, background, 
1205   and other more advanced options.
1206   
1207   Syntax:
1208   
1209   (start code js)
1210
1211   Options.Canvas = {
1212     injectInto: 'id',
1213     width: false,
1214     height: false,
1215     useCanvas: false,
1216     withLabels: true,
1217     background: false
1218   };  
1219   (end code)
1220   
1221   Example:
1222   
1223   (start code js)
1224   var viz = new $jit.Viz({
1225     injectInto: 'someContainerId',
1226     width: 500,
1227     height: 700
1228   });
1229   (end code)
1230   
1231   Parameters:
1232   
1233   injectInto - *required* (string|element) The id of the DOM container for the visualization. It can also be an Element provided that it has an id.
1234   width - (number) Default's to the *container's offsetWidth*. The width of the canvas.
1235   height - (number) Default's to the *container's offsetHeight*. The height of the canvas.
1236   useCanvas - (boolean|object) Default's *false*. You can pass another <Canvas> instance to be used by the visualization.
1237   withLabels - (boolean) Default's *true*. Whether to use a label container for the visualization.
1238   background - (boolean|object) Default's *false*. An object containing information about the rendering of a background canvas.
1239 */
1240
1241 Options.Canvas = {
1242     $extend: true,
1243     
1244     injectInto: 'id',
1245     width: false,
1246     height: false,
1247     useCanvas: false,
1248     withLabels: true,
1249     background: false,
1250     colorStop1: 'rgba(255,255,255,1)',
1251     colorStop2: 'rgba(255,255,255,0)'
1252 };
1253
1254 /*
1255  * File: Options.Tree.js
1256  *
1257 */
1258
1259 /*
1260   Object: Options.Tree
1261   
1262   Options related to (strict) Tree layout algorithms. These options are used by the <ST> visualization.
1263   
1264   Syntax:
1265   
1266   (start code js)
1267   Options.Tree = {
1268     orientation: "left",
1269     subtreeOffset: 8,
1270     siblingOffset: 5,
1271     indent:10,
1272     multitree: false,
1273     align:"center"
1274   };
1275   (end code)
1276   
1277   Example:
1278   
1279   (start code js)
1280   var st = new $jit.ST({
1281     orientation: 'left',
1282     subtreeOffset: 1,
1283     siblingOFfset: 5,
1284     multitree: true
1285   });
1286   (end code)
1287
1288   Parameters:
1289     
1290   subtreeOffset - (number) Default's 8. Separation offset between subtrees.
1291   siblingOffset - (number) Default's 5. Separation offset between siblings.
1292   orientation - (string) Default's 'left'. Tree orientation layout. Possible values are 'left', 'top', 'right', 'bottom'.
1293   align - (string) Default's *center*. Whether the tree alignment is 'left', 'center' or 'right'.
1294   indent - (number) Default's 10. Used when *align* is left or right and shows an indentation between parent and children.
1295   multitree - (boolean) Default's *false*. Used with the node $orn data property for creating multitrees.
1296      
1297 */
1298 Options.Tree = {
1299     $extend: true,
1300     
1301     orientation: "left",
1302     subtreeOffset: 8,
1303     siblingOffset: 5,
1304     indent:10,
1305     multitree: false,
1306     align:"center"
1307 };
1308
1309
1310 /*
1311  * File: Options.Node.js
1312  *
1313 */
1314
1315 /*
1316   Object: Options.Node
1317
1318   Provides Node rendering options for Tree and Graph based visualizations.
1319
1320   Syntax:
1321     
1322   (start code js)
1323   Options.Node = {
1324     overridable: false,
1325     type: 'circle',
1326     color: '#ccb',
1327     alpha: 1,
1328     dim: 3,
1329     height: 20,
1330     width: 90,
1331     autoHeight: false,
1332     autoWidth: false,
1333     lineWidth: 1,
1334     transform: true,
1335     align: "center",
1336     angularWidth:1,
1337     span:1,
1338     CanvasStyles: {}
1339   };
1340   (end code)
1341   
1342   Example:
1343   
1344   (start code js)
1345   var viz = new $jit.Viz({
1346     Node: {
1347       overridable: true,
1348       width: 30,
1349       autoHeight: true,
1350       type: 'rectangle'
1351     }
1352   });
1353   (end code)
1354   
1355   Parameters:
1356
1357   overridable - (boolean) Default's *false*. Determine whether or not general node properties can be overridden by a particular <Graph.Node>.
1358   type - (string) Default's *circle*. Node's shape. Node built-in types include 'circle', 'rectangle', 'square', 'ellipse', 'triangle', 'star'. The default Node type might vary in each visualization. You can also implement (non built-in) custom Node types into your visualizations.
1359   color - (string) Default's *#ccb*. Node color.
1360   alpha - (number) Default's *1*. The Node's alpha value. *1* is for full opacity.
1361   dim - (number) Default's *3*. An extra parameter used by other node shapes such as circle or square, to determine the shape's diameter.
1362   height - (number) Default's *20*. Used by 'rectangle' and 'ellipse' node types. The height of the node shape.
1363   width - (number) Default's *90*. Used by 'rectangle' and 'ellipse' node types. The width of the node shape.
1364   autoHeight - (boolean) Default's *false*. Whether to set an auto height for the node depending on the content of the Node's label.
1365   autoWidth - (boolean) Default's *false*. Whether to set an auto width for the node depending on the content of the Node's label.
1366   lineWidth - (number) Default's *1*. Used only by some Node shapes. The line width of the strokes of a node.
1367   transform - (boolean) Default's *true*. Only used by the <Hypertree> visualization. Whether to scale the nodes according to the moebius transformation.
1368   align - (string) Default's *center*. Possible values are 'center', 'left' or 'right'. Used only by the <ST> visualization, these parameters are used for aligning nodes when some of they dimensions vary.
1369   angularWidth - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The amount of relative 'space' set for a node.
1370   span - (number) Default's *1*. Used in radial layouts (like <RGraph> or <Sunburst> visualizations). The angle span amount set for a node.
1371   CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting a Node.
1372
1373 */
1374 Options.Node = {
1375   $extend: false,
1376   
1377   overridable: false,
1378   type: 'circle',
1379   color: '#ccb',
1380   alpha: 1,
1381   dim: 3,
1382   height: 20,
1383   width: 90,
1384   autoHeight: false,
1385   autoWidth: false,
1386   lineWidth: 1,
1387   transform: true,
1388   align: "center",
1389   angularWidth:1,
1390   span:1,
1391   //Raw canvas styles to be
1392   //applied to the context instance
1393   //before plotting a node
1394   CanvasStyles: {}
1395 };
1396
1397
1398 /*
1399  * File: Options.Edge.js
1400  *
1401 */
1402
1403 /*
1404   Object: Options.Edge
1405
1406   Provides Edge rendering options for Tree and Graph based visualizations.
1407
1408   Syntax:
1409     
1410   (start code js)
1411   Options.Edge = {
1412     overridable: false,
1413     type: 'line',
1414     color: '#ccb',
1415     lineWidth: 1,
1416     dim:15,
1417     alpha: 1,
1418     CanvasStyles: {}
1419   };
1420   (end code)
1421   
1422   Example:
1423   
1424   (start code js)
1425   var viz = new $jit.Viz({
1426     Edge: {
1427       overridable: true,
1428       type: 'line',
1429       color: '#fff',
1430       CanvasStyles: {
1431         shadowColor: '#ccc',
1432         shadowBlur: 10
1433       }
1434     }
1435   });
1436   (end code)
1437   
1438   Parameters:
1439     
1440    overridable - (boolean) Default's *false*. Determine whether or not general edges properties can be overridden by a particular <Graph.Adjacence>.
1441    type - (string) Default's 'line'. Edge styles include 'line', 'hyperline', 'arrow'. The default Edge type might vary in each visualization. You can also implement custom Edge types.
1442    color - (string) Default's '#ccb'. Edge color.
1443    lineWidth - (number) Default's *1*. Line/Edge width.
1444    alpha - (number) Default's *1*. The Edge's alpha value. *1* is for full opacity.
1445    dim - (number) Default's *15*. An extra parameter used by other complex shapes such as quadratic, bezier or arrow, to determine the shape's diameter.
1446    epsilon - (number) Default's *7*. Only used when using *enableForEdges* in <Options.Events>. This dimension is used to create an area for the line where the contains method for the edge returns *true*.
1447    CanvasStyles - (object) Default's an empty object (i.e. {}). Attach any other canvas specific property that you'd set to the canvas context before plotting an Edge.
1448
1449   See also:
1450    
1451    If you want to know more about how to customize Node/Edge data per element, in the JSON or programmatically, take a look at this article.
1452 */
1453 Options.Edge = {
1454   $extend: false,
1455   
1456   overridable: false,
1457   type: 'line',
1458   color: '#ccb',
1459   lineWidth: 1,
1460   dim:15,
1461   alpha: 1,
1462   epsilon: 7,
1463
1464   //Raw canvas styles to be
1465   //applied to the context instance
1466   //before plotting an edge
1467   CanvasStyles: {}
1468 };
1469
1470
1471 /*
1472  * File: Options.Fx.js
1473  *
1474 */
1475
1476 /*
1477   Object: Options.Fx
1478
1479   Provides animation options like duration of the animations, frames per second and animation transitions.  
1480
1481   Syntax:
1482   
1483   (start code js)
1484     Options.Fx = {
1485       fps:40,
1486       duration: 2500,
1487       transition: $jit.Trans.Quart.easeInOut,
1488       clearCanvas: true
1489     };
1490   (end code)
1491   
1492   Example:
1493   
1494   (start code js)
1495   var viz = new $jit.Viz({
1496     duration: 1000,
1497     fps: 35,
1498     transition: $jit.Trans.linear
1499   });
1500   (end code)
1501   
1502   Parameters:
1503   
1504   clearCanvas - (boolean) Default's *true*. Whether to clear the frame/canvas when the viz is plotted or animated.
1505   duration - (number) Default's *2500*. Duration of the animation in milliseconds.
1506   fps - (number) Default's *40*. Frames per second.
1507   transition - (object) Default's *$jit.Trans.Quart.easeInOut*. The transition used for the animations. See below for a more detailed explanation.
1508   
1509   Object: $jit.Trans
1510   
1511   This object is used for specifying different animation transitions in all visualizations.
1512
1513   There are many different type of animation transitions.
1514
1515   linear:
1516
1517   Displays a linear transition
1518
1519   >Trans.linear
1520   
1521   (see Linear.png)
1522
1523   Quad:
1524
1525   Displays a Quadratic transition.
1526
1527   >Trans.Quad.easeIn
1528   >Trans.Quad.easeOut
1529   >Trans.Quad.easeInOut
1530   
1531  (see Quad.png)
1532
1533  Cubic:
1534
1535  Displays a Cubic transition.
1536
1537  >Trans.Cubic.easeIn
1538  >Trans.Cubic.easeOut
1539  >Trans.Cubic.easeInOut
1540
1541  (see Cubic.png)
1542
1543  Quart:
1544
1545  Displays a Quartetic transition.
1546
1547  >Trans.Quart.easeIn
1548  >Trans.Quart.easeOut
1549  >Trans.Quart.easeInOut
1550
1551  (see Quart.png)
1552
1553  Quint:
1554
1555  Displays a Quintic transition.
1556
1557  >Trans.Quint.easeIn
1558  >Trans.Quint.easeOut
1559  >Trans.Quint.easeInOut
1560
1561  (see Quint.png)
1562
1563  Expo:
1564
1565  Displays an Exponential transition.
1566
1567  >Trans.Expo.easeIn
1568  >Trans.Expo.easeOut
1569  >Trans.Expo.easeInOut
1570
1571  (see Expo.png)
1572
1573  Circ:
1574
1575  Displays a Circular transition.
1576
1577  >Trans.Circ.easeIn
1578  >Trans.Circ.easeOut
1579  >Trans.Circ.easeInOut
1580
1581  (see Circ.png)
1582
1583  Sine:
1584
1585  Displays a Sineousidal transition.
1586
1587  >Trans.Sine.easeIn
1588  >Trans.Sine.easeOut
1589  >Trans.Sine.easeInOut
1590
1591  (see Sine.png)
1592
1593  Back:
1594
1595  >Trans.Back.easeIn
1596  >Trans.Back.easeOut
1597  >Trans.Back.easeInOut
1598
1599  (see Back.png)
1600
1601  Bounce:
1602
1603  Bouncy transition.
1604
1605  >Trans.Bounce.easeIn
1606  >Trans.Bounce.easeOut
1607  >Trans.Bounce.easeInOut
1608
1609  (see Bounce.png)
1610
1611  Elastic:
1612
1613  Elastic curve.
1614
1615  >Trans.Elastic.easeIn
1616  >Trans.Elastic.easeOut
1617  >Trans.Elastic.easeInOut
1618
1619  (see Elastic.png)
1620  
1621  Based on:
1622      
1623  Easing and Transition animation methods are based in the MooTools Framework <http://mootools.net>. Copyright (c) 2006-2010 Valerio Proietti, <http://mad4milk.net/>. MIT license <http://mootools.net/license.txt>.
1624
1625
1626 */
1627 Options.Fx = {
1628   $extend: true,
1629   
1630   fps:40,
1631   duration: 2500,
1632   transition: $jit.Trans.Quart.easeInOut,
1633   clearCanvas: true
1634 };
1635
1636 /*
1637  * File: Options.Label.js
1638  *
1639 */
1640 /*
1641   Object: Options.Label
1642
1643   Provides styling for Labels such as font size, family, etc. Also sets Node labels as HTML, SVG or Native canvas elements.  
1644
1645   Syntax:
1646   
1647   (start code js)
1648     Options.Label = {
1649       overridable: false,
1650       type: 'HTML', //'SVG', 'Native'
1651       style: ' ',
1652       size: 10,
1653       family: 'sans-serif',
1654       textAlign: 'center',
1655       textBaseline: 'alphabetic',
1656       color: '#fff'
1657     };
1658   (end code)
1659   
1660   Example:
1661   
1662   (start code js)
1663   var viz = new $jit.Viz({
1664     Label: {
1665       type: 'Native',
1666       size: 11,
1667       color: '#ccc'
1668     }
1669   });
1670   (end code)
1671   
1672   Parameters:
1673     
1674   overridable - (boolean) Default's *false*. Determine whether or not general label properties can be overridden by a particular <Graph.Node>.
1675   type - (string) Default's *HTML*. The type for the labels. Can be 'HTML', 'SVG' or 'Native' canvas labels.
1676   style - (string) Default's *empty string*. Can be 'italic' or 'bold'. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1677   size - (number) Default's *10*. The font's size. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1678   family - (string) Default's *sans-serif*. The font's family. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1679   color - (string) Default's *#fff*. The font's color. This parameter is only taken into account when using 'Native' canvas labels. For DOM based labels the className *node* is added to the DOM element for styling via CSS. You can also use <Options.Controller> methods to style individual labels.
1680 */
1681 Options.Label = {
1682   $extend: false,
1683   
1684   overridable: false,
1685   type: 'HTML', //'SVG', 'Native'
1686   style: ' ',
1687   size: 10,
1688   family: 'sans-serif',
1689   textAlign: 'center',
1690   textBaseline: 'alphabetic',
1691   color: '#fff'
1692 };
1693
1694
1695 /*
1696  * File: Options.Tips.js
1697  *
1698  */
1699
1700 /*
1701   Object: Options.Tips
1702   
1703   Tips options
1704   
1705   Syntax:
1706     
1707   (start code js)
1708   Options.Tips = {
1709     enable: false,
1710     type: 'auto',
1711     offsetX: 20,
1712     offsetY: 20,
1713     onShow: $.empty,
1714     onHide: $.empty
1715   };
1716   (end code)
1717   
1718   Example:
1719   
1720   (start code js)
1721   var viz = new $jit.Viz({
1722     Tips: {
1723       enable: true,
1724       type: 'Native',
1725       offsetX: 10,
1726       offsetY: 10,
1727       onShow: function(tip, node) {
1728         tip.innerHTML = node.name;
1729       }
1730     }
1731   });
1732   (end code)
1733
1734   Parameters:
1735
1736   enable - (boolean) Default's *false*. If *true*, a tooltip will be shown when a node is hovered. The tooltip is a div DOM element having "tip" as CSS class. 
1737   type - (string) Default's *auto*. Defines where to attach the MouseEnter/Leave tooltip events. Possible values are 'Native' to attach them to the canvas or 'HTML' to attach them to DOM label elements (if defined). 'auto' sets this property to the value of <Options.Label>'s *type* property.
1738   offsetX - (number) Default's *20*. An offset added to the current tooltip x-position (which is the same as the current mouse position). Default's 20.
1739   offsetY - (number) Default's *20*. An offset added to the current tooltip y-position (which is the same as the current mouse position). Default's 20.
1740   onShow(tip, node) - This callack is used right before displaying a tooltip. The first formal parameter is the tip itself (which is a DivElement). The second parameter may be a <Graph.Node> for graph based visualizations or an object with label, value properties for charts.
1741   onHide() - This callack is used when hiding a tooltip.
1742
1743 */
1744 Options.Tips = {
1745   $extend: false,
1746   
1747   enable: false,
1748   type: 'auto',
1749   offsetX: 20,
1750   offsetY: 20,
1751   force: false,
1752   onShow: $.empty,
1753   onHide: $.empty
1754 };
1755
1756
1757 /*
1758  * File: Options.NodeStyles.js
1759  *
1760  */
1761
1762 /*
1763   Object: Options.NodeStyles
1764   
1765   Apply different styles when a node is hovered or selected.
1766   
1767   Syntax:
1768     
1769   (start code js)
1770   Options.NodeStyles = {
1771     enable: false,
1772     type: 'auto',
1773     stylesHover: false,
1774     stylesClick: false
1775   };
1776   (end code)
1777   
1778   Example:
1779   
1780   (start code js)
1781   var viz = new $jit.Viz({
1782     NodeStyles: {
1783       enable: true,
1784       type: 'Native',
1785       stylesHover: {
1786         dim: 30,
1787         color: '#fcc'
1788       },
1789       duration: 600
1790     }
1791   });
1792   (end code)
1793
1794   Parameters:
1795   
1796   enable - (boolean) Default's *false*. Whether to enable this option.
1797   type - (string) Default's *auto*. Use this to attach the hover/click events in the nodes or the nodes labels (if they have been defined as DOM elements: 'HTML' or 'SVG', see <Options.Label> for more details). The default 'auto' value will set NodeStyles to the same type defined for <Options.Label>.
1798   stylesHover - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1799   stylesClick - (boolean|object) Default's *false*. An object with node styles just like the ones defined for <Options.Node> or *false* otherwise.
1800 */
1801
1802 Options.NodeStyles = {
1803   $extend: false,
1804   
1805   enable: false,
1806   type: 'auto',
1807   stylesHover: false,
1808   stylesClick: false
1809 };
1810
1811
1812 /*
1813  * File: Options.Events.js
1814  *
1815 */
1816
1817 /*
1818   Object: Options.Events
1819   
1820   Configuration for adding mouse/touch event handlers to Nodes.
1821   
1822   Syntax:
1823   
1824   (start code js)
1825   Options.Events = {
1826     enable: false,
1827     enableForEdges: false,
1828     type: 'auto',
1829     onClick: $.empty,
1830     onRightClick: $.empty,
1831     onMouseMove: $.empty,
1832     onMouseEnter: $.empty,
1833     onMouseLeave: $.empty,
1834     onDragStart: $.empty,
1835     onDragMove: $.empty,
1836     onDragCancel: $.empty,
1837     onDragEnd: $.empty,
1838     onTouchStart: $.empty,
1839     onTouchMove: $.empty,
1840     onTouchEnd: $.empty,
1841     onTouchCancel: $.empty,
1842     onMouseWheel: $.empty
1843   };
1844   (end code)
1845   
1846   Example:
1847   
1848   (start code js)
1849   var viz = new $jit.Viz({
1850     Events: {
1851       enable: true,
1852       onClick: function(node, eventInfo, e) {
1853         viz.doSomething();
1854       },
1855       onMouseEnter: function(node, eventInfo, e) {
1856         viz.canvas.getElement().style.cursor = 'pointer';
1857       },
1858       onMouseLeave: function(node, eventInfo, e) {
1859         viz.canvas.getElement().style.cursor = '';
1860       }
1861     }
1862   });
1863   (end code)
1864   
1865   Parameters:
1866   
1867   enable - (boolean) Default's *false*. Whether to enable the Event system.
1868   enableForEdges - (boolean) Default's *false*. Whether to track events also in arcs. If *true* the same callbacks -described below- are used for nodes *and* edges. A simple duck type check for edges is to check for *node.nodeFrom*.
1869   type - (string) Default's 'auto'. Whether to attach the events onto the HTML labels (via event delegation) or to use the custom 'Native' canvas Event System of the library. 'auto' is set when you let the <Options.Label> *type* parameter decide this.
1870   onClick(node, eventInfo, e) - Triggered when a user performs a click in the canvas. *node* is the <Graph.Node> clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1871   onRightClick(node, eventInfo, e) - Triggered when a user performs a right click in the canvas. *node* is the <Graph.Node> right clicked or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1872   onMouseMove(node, eventInfo, e) - Triggered when the user moves the mouse. *node* is the <Graph.Node> under the cursor as it's moving over the canvas or false if no node has been clicked. *e* is the grabbed event (should return the native event in a cross-browser manner).  *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas.
1873   onMouseEnter(node, eventInfo, e) - Triggered when a user moves the mouse over a node. *node* is the <Graph.Node> that the mouse just entered. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1874   onMouseLeave(node, eventInfo, e) - Triggered when the user mouse-outs a node. *node* is the <Graph.Node> 'mouse-outed'. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1875   onDragStart(node, eventInfo, e) - Triggered when the user mouse-downs over a node. *node* is the <Graph.Node> being pressed. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1876   onDragMove(node, eventInfo, e) - Triggered when a user, after pressing the mouse button over a node, moves the mouse around. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1877   onDragEnd(node, eventInfo, e) - Triggered when a user finished dragging a node. *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1878   onDragCancel(node, eventInfo, e) - Triggered when the user releases the mouse button over a <Graph.Node> that wasn't dragged (i.e. the user didn't perform any mouse movement after pressing the mouse button). *node* is the <Graph.Node> being dragged. *e* is the grabbed event (should return the native event in a cross-browser manner). *eventInfo* is an object containing useful methods like *getPos* to get the mouse position relative to the canvas. 
1879   onTouchStart(node, eventInfo, e) - Behaves just like onDragStart. 
1880   onTouchMove(node, eventInfo, e) - Behaves just like onDragMove. 
1881   onTouchEnd(node, eventInfo, e) - Behaves just like onDragEnd. 
1882   onTouchCancel(node, eventInfo, e) - Behaves just like onDragCancel.
1883   onMouseWheel(delta, e) - Triggered when the user uses the mouse scroll over the canvas. *delta* is 1 or -1 depending on the sense of the mouse scroll.
1884 */
1885
1886 Options.Events = {
1887   $extend: false,
1888   
1889   enable: false,
1890   enableForEdges: false,
1891   type: 'auto',
1892   onClick: $.empty,
1893   onRightClick: $.empty,
1894   onMouseMove: $.empty,
1895   onMouseEnter: $.empty,
1896   onMouseLeave: $.empty,
1897   onDragStart: $.empty,
1898   onDragMove: $.empty,
1899   onDragCancel: $.empty,
1900   onDragEnd: $.empty,
1901   onTouchStart: $.empty,
1902   onTouchMove: $.empty,
1903   onTouchEnd: $.empty,
1904   onMouseWheel: $.empty
1905 };
1906
1907 /*
1908  * File: Options.Navigation.js
1909  *
1910 */
1911
1912 /*
1913   Object: Options.Navigation
1914   
1915   Panning and zooming options for Graph/Tree based visualizations. These options are implemented 
1916   by all visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1917   
1918   Syntax:
1919   
1920   (start code js)
1921
1922   Options.Navigation = {
1923     enable: false,
1924     type: 'auto',
1925     panning: false, //true, 'avoid nodes'
1926     zooming: false
1927   };
1928   
1929   (end code)
1930   
1931   Example:
1932     
1933   (start code js)
1934   var viz = new $jit.Viz({
1935     Navigation: {
1936       enable: true,
1937       panning: 'avoid nodes',
1938       zooming: 20
1939     }
1940   });
1941   (end code)
1942   
1943   Parameters:
1944   
1945   enable - (boolean) Default's *false*. Whether to enable Navigation capabilities.
1946   panning - (boolean|string) Default's *false*. Set this property to *true* if you want to add Drag and Drop panning support to the visualization. You can also set this parameter to 'avoid nodes' to enable DnD panning but disable it if the DnD is taking place over a node. This is useful when some other events like Drag & Drop for nodes are added to <Graph.Nodes>.
1947   zooming - (boolean|number) Default's *false*. Set this property to a numeric value to turn mouse-scroll zooming on. The number will be proportional to the mouse-scroll sensitivity.
1948   
1949 */
1950
1951 Options.Navigation = {
1952   $extend: false,
1953   
1954   enable: false,
1955   type: 'auto',
1956   panning: false, //true | 'avoid nodes'
1957   zooming: false
1958 };
1959
1960 /*
1961  * File: Options.Controller.js
1962  *
1963 */
1964
1965 /*
1966   Object: Options.Controller
1967   
1968   Provides controller methods. Controller methods are callback functions that get called at different stages 
1969   of the animation, computing or plotting of the visualization.
1970   
1971   Implemented by:
1972     
1973   All visualizations except charts (<AreaChart>, <BarChart> and <PieChart>).
1974   
1975   Syntax:
1976   
1977   (start code js)
1978
1979   Options.Controller = {
1980     onBeforeCompute: $.empty,
1981     onAfterCompute:  $.empty,
1982     onCreateLabel:   $.empty,
1983     onPlaceLabel:    $.empty,
1984     onComplete:      $.empty,
1985     onBeforePlotLine:$.empty,
1986     onAfterPlotLine: $.empty,
1987     onBeforePlotNode:$.empty,
1988     onAfterPlotNode: $.empty,
1989     request:         false
1990   };
1991   
1992   (end code)
1993   
1994   Example:
1995     
1996   (start code js)
1997   var viz = new $jit.Viz({
1998     onBeforePlotNode: function(node) {
1999       if(node.selected) {
2000         node.setData('color', '#ffc');
2001       } else {
2002         node.removeData('color');
2003       }
2004     },
2005     onBeforePlotLine: function(adj) {
2006       if(adj.nodeFrom.selected && adj.nodeTo.selected) {
2007         adj.setData('color', '#ffc');
2008       } else {
2009         adj.removeData('color');
2010       }
2011     },
2012     onAfterCompute: function() {
2013       alert("computed!");
2014     }
2015   });
2016   (end code)
2017   
2018   Parameters:
2019
2020    onBeforeCompute(node) - This method is called right before performing all computations and animations. The selected <Graph.Node> is passed as parameter.
2021    onAfterCompute() - This method is triggered after all animations or computations ended.
2022    onCreateLabel(domElement, node) - This method receives a new label DIV element as first parameter, and the corresponding <Graph.Node> as second parameter. This method will only be called once for each label. This method is useful when adding events or styles to the labels used by the JIT.
2023    onPlaceLabel(domElement, node) - This method receives a label DIV element as first parameter and the corresponding <Graph.Node> as second parameter. This method is called each time a label has been placed in the visualization, for example at each step of an animation, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating the labels positions. That means that, for example, the left and top css properties are already updated to match the nodes positions. Width and height properties are not set however.
2024    onBeforePlotNode(node) - This method is triggered right before plotting each <Graph.Node>. This method is useful for changing a node style right before plotting it.
2025    onAfterPlotNode(node) - This method is triggered right after plotting each <Graph.Node>.
2026    onBeforePlotLine(adj) - This method is triggered right before plotting a <Graph.Adjacence>. This method is useful for adding some styles to a particular edge before being plotted.
2027    onAfterPlotLine(adj) - This method is triggered right after plotting a <Graph.Adjacence>.
2028
2029     *Used in <ST>, <TM.Base> and <Icicle> visualizations*
2030     
2031     request(nodeId, level, onComplete) - This method is used for buffering information into the visualization. When clicking on an empty node, the visualization will make a request for this node's subtrees, specifying a given level for this subtree (defined by _levelsToShow_). Once the request is completed, the onComplete callback should be called with the given result. This is useful to provide on-demand information into the visualizations withought having to load the entire information from start. The parameters used by this method are _nodeId_, which is the id of the root of the subtree to request, _level_ which is the depth of the subtree to be requested (0 would mean just the root node). _onComplete_ is an object having the callback method _onComplete.onComplete(json)_ that should be called once the json has been retrieved.  
2032  
2033  */
2034 Options.Controller = {
2035   $extend: true,
2036   
2037   onBeforeCompute: $.empty,
2038   onAfterCompute:  $.empty,
2039   onCreateLabel:   $.empty,
2040   onPlaceLabel:    $.empty,
2041   onComplete:      $.empty,
2042   onBeforePlotLine:$.empty,
2043   onAfterPlotLine: $.empty,
2044   onBeforePlotNode:$.empty,
2045   onAfterPlotNode: $.empty,
2046   request:         false
2047 };
2048
2049
2050 /*
2051  * File: Extras.js
2052  * 
2053  * Provides Extras such as Tips and Style Effects.
2054  * 
2055  * Description:
2056  * 
2057  * Provides the <Tips> and <NodeStyles> classes and functions.
2058  *
2059  */
2060
2061 /*
2062  * Manager for mouse events (clicking and mouse moving).
2063  * 
2064  * This class is used for registering objects implementing onClick
2065  * and onMousemove methods. These methods are called when clicking or
2066  * moving the mouse around  the Canvas.
2067  * For now, <Tips> and <NodeStyles> are classes implementing these methods.
2068  * 
2069  */
2070 var ExtrasInitializer = {
2071   initialize: function(className, viz) {
2072     this.viz = viz;
2073     this.canvas = viz.canvas;
2074     this.config = viz.config[className];
2075     this.nodeTypes = viz.fx.nodeTypes;
2076     var type = this.config.type;
2077     this.dom = type == 'auto'? (viz.config.Label.type != 'Native') : (type != 'Native');
2078     this.labelContainer = this.dom && viz.labels.getLabelContainer();
2079     this.isEnabled() && this.initializePost();
2080   },
2081   initializePost: $.empty,
2082   setAsProperty: $.lambda(false),
2083   isEnabled: function() {
2084     return this.config.enable;
2085   },
2086   isLabel: function(e, win) {
2087     e = $.event.get(e, win);
2088     var labelContainer = this.labelContainer,
2089         target = e.target || e.srcElement;
2090     if(target && target.parentNode == labelContainer)
2091       return target;
2092     return false;
2093   }
2094 };
2095
2096 var EventsInterface = {
2097   onMouseUp: $.empty,
2098   onMouseDown: $.empty,
2099   onMouseMove: $.empty,
2100   onMouseOver: $.empty,
2101   onMouseOut: $.empty,
2102   onMouseWheel: $.empty,
2103   onTouchStart: $.empty,
2104   onTouchMove: $.empty,
2105   onTouchEnd: $.empty,
2106   onTouchCancel: $.empty
2107 };
2108
2109 var MouseEventsManager = new Class({
2110   initialize: function(viz) {
2111     this.viz = viz;
2112     this.canvas = viz.canvas;
2113     this.node = false;
2114     this.edge = false;
2115     this.registeredObjects = [];
2116     this.attachEvents();
2117   },
2118   
2119   attachEvents: function() {
2120     var htmlCanvas = this.canvas.getElement(), 
2121         that = this;
2122     htmlCanvas.oncontextmenu = $.lambda(false);
2123     $.addEvents(htmlCanvas, {
2124       'mouseup': function(e, win) {
2125         var event = $.event.get(e, win);
2126         that.handleEvent('MouseUp', e, win, 
2127             that.makeEventObject(e, win), 
2128             $.event.isRightClick(event));
2129       },
2130       'mousedown': function(e, win) {
2131         var event = $.event.get(e, win);
2132         that.handleEvent('MouseDown', e, win, that.makeEventObject(e, win), 
2133             $.event.isRightClick(event));
2134       },
2135       'mousemove': function(e, win) {
2136         that.handleEvent('MouseMove', e, win, that.makeEventObject(e, win));
2137       },
2138       'mouseover': function(e, win) {
2139         that.handleEvent('MouseOver', e, win, that.makeEventObject(e, win));
2140       },
2141       'mouseout': function(e, win) {
2142         that.handleEvent('MouseOut', e, win, that.makeEventObject(e, win));
2143       },
2144       'touchstart': function(e, win) {
2145         that.handleEvent('TouchStart', e, win, that.makeEventObject(e, win));
2146       },
2147       'touchmove': function(e, win) {
2148         that.handleEvent('TouchMove', e, win, that.makeEventObject(e, win));
2149       },
2150       'touchend': function(e, win) {
2151         that.handleEvent('TouchEnd', e, win, that.makeEventObject(e, win));
2152       }
2153     });
2154     //attach mousewheel event
2155     var handleMouseWheel = function(e, win) {
2156       var event = $.event.get(e, win);
2157       var wheel = $.event.getWheel(event);
2158       that.handleEvent('MouseWheel', e, win, wheel);
2159     };
2160     //TODO(nico): this is a horrible check for non-gecko browsers!
2161     if(!document.getBoxObjectFor && window.mozInnerScreenX == null) {
2162       $.addEvent(htmlCanvas, 'mousewheel', handleMouseWheel);
2163     } else {
2164       htmlCanvas.addEventListener('DOMMouseScroll', handleMouseWheel, false);
2165     }
2166   },
2167   
2168   register: function(obj) {
2169     this.registeredObjects.push(obj);
2170   },
2171   
2172   handleEvent: function() {
2173     var args = Array.prototype.slice.call(arguments),
2174         type = args.shift();
2175     for(var i=0, regs=this.registeredObjects, l=regs.length; i<l; i++) {
2176       regs[i]['on' + type].apply(regs[i], args);
2177     }
2178   },
2179   
2180   makeEventObject: function(e, win) {
2181     var that = this,
2182         graph = this.viz.graph,
2183         fx = this.viz.fx,
2184         ntypes = fx.nodeTypes,
2185         etypes = fx.edgeTypes;
2186     return {
2187       pos: false,
2188       node: false,
2189       edge: false,
2190       contains: false,
2191       getNodeCalled: false,
2192       getEdgeCalled: false,
2193       getPos: function() {
2194         //TODO(nico): check why this can't be cache anymore when using edge detection
2195         //if(this.pos) return this.pos;
2196         var canvas = that.viz.canvas,
2197             s = canvas.getSize(),
2198             p = canvas.getPos(),
2199             ox = canvas.translateOffsetX,
2200             oy = canvas.translateOffsetY,
2201             sx = canvas.scaleOffsetX,
2202             sy = canvas.scaleOffsetY,
2203             pos = $.event.getPos(e, win);
2204         this.pos = {
2205           x: (pos.x - p.x - s.width/2 - ox) * 1/sx,
2206           y: (pos.y - p.y - s.height/2 - oy) * 1/sy
2207         };
2208         return this.pos;
2209       },
2210       getNode: function() {
2211         if(this.getNodeCalled) return this.node;
2212         this.getNodeCalled = true;
2213         for(var id in graph.nodes) {
2214           var n = graph.nodes[id],
2215               geom = n && ntypes[n.getData('type')],
2216               contains = geom && geom.contains && geom.contains.call(fx, n, this.getPos());
2217           if(contains) {
2218             this.contains = contains;
2219             return that.node = this.node = n;
2220           }
2221         }
2222         return that.node = this.node = false;
2223       },
2224       getEdge: function() {
2225         if(this.getEdgeCalled) return this.edge;
2226         this.getEdgeCalled = true;
2227         var hashset = {};
2228         for(var id in graph.edges) {
2229           var edgeFrom = graph.edges[id];
2230           hashset[id] = true;
2231           for(var edgeId in edgeFrom) {
2232             if(edgeId in hashset) continue;
2233             var e = edgeFrom[edgeId],
2234                 geom = e && etypes[e.getData('type')],
2235                 contains = geom && geom.contains && geom.contains.call(fx, e, this.getPos());
2236             if(contains) {
2237               this.contains = contains;
2238               return that.edge = this.edge = e;
2239             }
2240           }
2241         }
2242         return that.edge = this.edge = false;
2243       },
2244       getContains: function() {
2245         if(this.getNodeCalled) return this.contains;
2246         this.getNode();
2247         return this.contains;
2248       }
2249     };
2250   }
2251 });
2252
2253 /* 
2254  * Provides the initialization function for <NodeStyles> and <Tips> implemented 
2255  * by all main visualizations.
2256  *
2257  */
2258 var Extras = {
2259   initializeExtras: function() {
2260     var mem = new MouseEventsManager(this), that = this;
2261     $.each(['NodeStyles', 'Tips', 'Navigation', 'Events'], function(k) {
2262       var obj = new Extras.Classes[k](k, that);
2263       if(obj.isEnabled()) {
2264         mem.register(obj);
2265       }
2266       if(obj.setAsProperty()) {
2267         that[k.toLowerCase()] = obj;
2268       }
2269     });
2270   }   
2271 };
2272
2273 Extras.Classes = {};
2274 /*
2275   Class: Events
2276    
2277   This class defines an Event API to be accessed by the user.
2278   The methods implemented are the ones defined in the <Options.Events> object.
2279 */
2280
2281 Extras.Classes.Events = new Class({
2282   Implements: [ExtrasInitializer, EventsInterface],
2283   
2284   initializePost: function() {
2285     this.fx = this.viz.fx;
2286     this.ntypes = this.viz.fx.nodeTypes;
2287     this.etypes = this.viz.fx.edgeTypes;
2288     
2289     this.hovered = false;
2290     this.pressed = false;
2291     this.touched = false;
2292
2293     this.touchMoved = false;
2294     this.moved = false;
2295     
2296   },
2297   
2298   setAsProperty: $.lambda(true),
2299   
2300   onMouseUp: function(e, win, event, isRightClick) {
2301     var evt = $.event.get(e, win);
2302     if(!this.moved) {
2303       if(isRightClick) {
2304         this.config.onRightClick(this.hovered, event, evt);
2305       } else {
2306         this.config.onClick(this.pressed, event, evt);
2307       }
2308     }
2309     if(this.pressed) {
2310       if(this.moved) {
2311         this.config.onDragEnd(this.pressed, event, evt);
2312       } else {
2313         this.config.onDragCancel(this.pressed, event, evt);
2314       }
2315       this.pressed = this.moved = false;
2316     }
2317   },
2318
2319   onMouseOut: function(e, win, event) {
2320    //mouseout a label
2321    var evt = $.event.get(e, win), label;
2322    if(this.dom && (label = this.isLabel(e, win))) {
2323      this.config.onMouseLeave(this.viz.graph.getNode(label.id),
2324                               event, evt);
2325      this.hovered = false;
2326      return;
2327    }
2328    //mouseout canvas
2329    var rt = evt.relatedTarget,
2330        canvasWidget = this.canvas.getElement();
2331    while(rt && rt.parentNode) {
2332      if(canvasWidget == rt.parentNode) return;
2333      rt = rt.parentNode;
2334    }
2335    if(this.hovered) {
2336      this.config.onMouseLeave(this.hovered,
2337          event, evt);
2338      this.hovered = false;
2339    }
2340   },
2341   
2342   onMouseOver: function(e, win, event) {
2343     //mouseover a label
2344     var evt = $.event.get(e, win), label;
2345     if(this.dom && (label = this.isLabel(e, win))) {
2346       this.hovered = this.viz.graph.getNode(label.id);
2347       this.config.onMouseEnter(this.hovered,
2348                                event, evt);
2349     }
2350   },
2351   
2352   onMouseMove: function(e, win, event) {
2353    var label, evt = $.event.get(e, win);
2354    if(this.pressed) {
2355      this.moved = true;
2356      this.config.onDragMove(this.pressed, event, evt);
2357      return;
2358    }
2359    if(this.dom) {
2360      this.config.onMouseMove(this.hovered,
2361          event, evt);
2362    } else {
2363      if(this.hovered) {
2364        var hn = this.hovered;
2365        var geom = hn.nodeFrom? this.etypes[hn.getData('type')] : this.ntypes[hn.getData('type')];
2366        var contains = geom && geom.contains 
2367          && geom.contains.call(this.fx, hn, event.getPos());
2368        if(contains) {
2369          this.config.onMouseMove(hn, event, evt);
2370          return;
2371        } else {
2372          this.config.onMouseLeave(hn, event, evt);
2373          this.hovered = false;
2374        }
2375      }
2376      if(this.hovered = (event.getNode() || (this.config.enableForEdges && event.getEdge()))) {
2377        this.config.onMouseEnter(this.hovered, event, evt);
2378      } else {
2379        this.config.onMouseMove(false, event, evt);
2380      }
2381    }
2382   },
2383   
2384   onMouseWheel: function(e, win, delta) {
2385     this.config.onMouseWheel(delta, $.event.get(e, win));
2386   },
2387   
2388   onMouseDown: function(e, win, event) {
2389     var evt = $.event.get(e, win);
2390     this.pressed = event.getNode() || (this.config.enableForEdges && event.getEdge());
2391     this.config.onDragStart(this.pressed, event, evt);
2392   },
2393   
2394   onTouchStart: function(e, win, event) {
2395     var evt = $.event.get(e, win);
2396     this.touched = event.getNode() || (this.config.enableForEdges && event.getEdge());
2397     this.config.onTouchStart(this.touched, event, evt);
2398   },
2399   
2400   onTouchMove: function(e, win, event) {
2401     var evt = $.event.get(e, win);
2402     if(this.touched) {
2403       this.touchMoved = true;
2404       this.config.onTouchMove(this.touched, event, evt);
2405     }
2406   },
2407   
2408   onTouchEnd: function(e, win, event) {
2409     var evt = $.event.get(e, win);
2410     if(this.touched) {
2411       if(this.touchMoved) {
2412         this.config.onTouchEnd(this.touched, event, evt);
2413       } else {
2414         this.config.onTouchCancel(this.touched, event, evt);
2415       }
2416       this.touched = this.touchMoved = false;
2417     }
2418   }
2419 });
2420
2421 /*
2422    Class: Tips
2423     
2424    A class containing tip related functions. This class is used internally.
2425    
2426    Used by:
2427    
2428    <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2429    
2430    See also:
2431    
2432    <Options.Tips>
2433 */
2434
2435 Extras.Classes.Tips = new Class({
2436   Implements: [ExtrasInitializer, EventsInterface],
2437   
2438   initializePost: function() {
2439     //add DOM tooltip
2440     if(document.body) {
2441       var tip = $('_tooltip') || document.createElement('div');
2442       tip.id = '_tooltip';
2443       tip.className = 'tip';
2444       $.extend(tip.style, {
2445         position: 'absolute',
2446         display: 'none',
2447         zIndex: 13000
2448       });
2449       document.body.appendChild(tip);
2450       this.tip = tip;
2451       this.node = false;
2452     }
2453   },
2454   
2455   setAsProperty: $.lambda(true),
2456   
2457   onMouseOut: function(e, win) {
2458     //mouseout a label
2459     if(this.dom && this.isLabel(e, win)) {
2460       this.hide(true);
2461       return;
2462     }
2463     //mouseout canvas
2464     var rt = e.relatedTarget,
2465         canvasWidget = this.canvas.getElement();
2466     while(rt && rt.parentNode) {
2467       if(canvasWidget == rt.parentNode) return;
2468       rt = rt.parentNode;
2469     }
2470     this.hide(false);
2471   },
2472   
2473   onMouseOver: function(e, win) {
2474     //mouseover a label
2475     var label;
2476     if(this.dom && (label = this.isLabel(e, win))) {
2477       this.node = this.viz.graph.getNode(label.id);
2478       this.config.onShow(this.tip, this.node, label);
2479     }
2480   },
2481   
2482   onMouseMove: function(e, win, opt) {
2483     if(this.dom && this.isLabel(e, win)) {
2484       this.setTooltipPosition($.event.getPos(e, win));
2485     }
2486     if(!this.dom) {
2487       var node = opt.getNode();
2488       if(!node) {
2489         this.hide(true);
2490         return;
2491       }
2492       if(this.config.force || !this.node || this.node.id != node.id) {
2493         this.node = node;
2494         this.config.onShow(this.tip, node, opt.getContains());
2495       }
2496       this.setTooltipPosition($.event.getPos(e, win));
2497     }
2498   },
2499   
2500   setTooltipPosition: function(pos) {
2501     var tip = this.tip, 
2502         style = tip.style, 
2503         cont = this.config;
2504     style.display = '';
2505     //get window dimensions
2506     var win = {
2507       'height': document.body.clientHeight,
2508       'width': document.body.clientWidth
2509     };
2510     //get tooltip dimensions
2511     var obj = {
2512       'width': tip.offsetWidth,
2513       'height': tip.offsetHeight  
2514     };
2515     //set tooltip position
2516     var x = cont.offsetX, y = cont.offsetY;
2517     style.top = ((pos.y + y + obj.height > win.height)?  
2518         (pos.y - obj.height - y) : pos.y + y) + 'px';
2519     style.left = ((pos.x + obj.width + x > win.width)? 
2520         (pos.x - obj.width - x) : pos.x + x) + 'px';
2521   },
2522   
2523   hide: function(triggerCallback) {
2524     if(!SUGAR.util.isTouchScreen()) {
2525         this.tip.style.display = 'none';
2526     }
2527     triggerCallback && this.config.onHide();
2528   }
2529 });
2530
2531 /*
2532   Class: NodeStyles
2533    
2534   Change node styles when clicking or hovering a node. This class is used internally.
2535   
2536   Used by:
2537   
2538   <ST>, <Sunburst>, <Hypertree>, <RGraph>, <TM>, <ForceDirected>, <Icicle>
2539   
2540   See also:
2541   
2542   <Options.NodeStyles>
2543 */
2544 Extras.Classes.NodeStyles = new Class({
2545   Implements: [ExtrasInitializer, EventsInterface],
2546   
2547   initializePost: function() {
2548     this.fx = this.viz.fx;
2549     this.types = this.viz.fx.nodeTypes;
2550     this.nStyles = this.config;
2551     this.nodeStylesOnHover = this.nStyles.stylesHover;
2552     this.nodeStylesOnClick = this.nStyles.stylesClick;
2553     this.hoveredNode = false;
2554     this.fx.nodeFxAnimation = new Animation();
2555     
2556     this.down = false;
2557     this.move = false;
2558   },
2559   
2560   onMouseOut: function(e, win) {
2561     this.down = this.move = false;
2562     if(!this.hoveredNode) return;
2563     //mouseout a label
2564     if(this.dom && this.isLabel(e, win)) {
2565       this.toggleStylesOnHover(this.hoveredNode, false);
2566     }
2567     //mouseout canvas
2568     var rt = e.relatedTarget,
2569         canvasWidget = this.canvas.getElement();
2570     while(rt && rt.parentNode) {
2571       if(canvasWidget == rt.parentNode) return;
2572       rt = rt.parentNode;
2573     }
2574     this.toggleStylesOnHover(this.hoveredNode, false);
2575     this.hoveredNode = false;
2576   },
2577   
2578   onMouseOver: function(e, win) {
2579     //mouseover a label
2580     var label;
2581     if(this.dom && (label = this.isLabel(e, win))) {
2582       var node = this.viz.graph.getNode(label.id);
2583       if(node.selected) return;
2584       this.hoveredNode = node;
2585       this.toggleStylesOnHover(this.hoveredNode, true);
2586     }
2587   },
2588   
2589   onMouseDown: function(e, win, event, isRightClick) {
2590     if(isRightClick) return;
2591     var label;
2592     if(this.dom && (label = this.isLabel(e, win))) {
2593       this.down = this.viz.graph.getNode(label.id);
2594     } else if(!this.dom) {
2595       this.down = event.getNode();
2596     }
2597     this.move = false;
2598   },
2599   
2600   onMouseUp: function(e, win, event, isRightClick) {
2601     if(isRightClick) return;
2602     if(!this.move) {
2603       this.onClick(event.getNode());
2604     }
2605     this.down = this.move = false;
2606   },
2607   
2608   getRestoredStyles: function(node, type) {
2609     var restoredStyles = {}, 
2610         nStyles = this['nodeStylesOn' + type];
2611     for(var prop in nStyles) {
2612       restoredStyles[prop] = node.styles['$' + prop];
2613     }
2614     return restoredStyles;
2615   },
2616   
2617   toggleStylesOnHover: function(node, set) {
2618     if(this.nodeStylesOnHover) {
2619       this.toggleStylesOn('Hover', node, set);
2620     }
2621   },
2622
2623   toggleStylesOnClick: function(node, set) {
2624     if(this.nodeStylesOnClick) {
2625       this.toggleStylesOn('Click', node, set);
2626     }
2627   },
2628   
2629   toggleStylesOn: function(type, node, set) {
2630     var viz = this.viz;
2631     var nStyles = this.nStyles;
2632     if(set) {
2633       var that = this;
2634       if(!node.styles) {
2635         node.styles = $.merge(node.data, {});
2636       }
2637       for(var s in this['nodeStylesOn' + type]) {
2638         var $s = '$' + s;
2639         if(!($s in node.styles)) {
2640             node.styles[$s] = node.getData(s); 
2641         }
2642       }
2643       viz.fx.nodeFx($.extend({
2644         'elements': {
2645           'id': node.id,
2646           'properties': that['nodeStylesOn' + type]
2647          },
2648          transition: Trans.Quart.easeOut,
2649          duration:300,
2650          fps:40
2651       }, this.config));
2652     } else {
2653       var restoredStyles = this.getRestoredStyles(node, type);
2654       viz.fx.nodeFx($.extend({
2655         'elements': {
2656           'id': node.id,
2657           'properties': restoredStyles
2658          },
2659          transition: Trans.Quart.easeOut,
2660          duration:300,
2661          fps:40
2662       }, this.config));
2663     }
2664   },
2665
2666   onClick: function(node) {
2667     if(!node) return;
2668     var nStyles = this.nodeStylesOnClick;
2669     if(!nStyles) return;
2670     //if the node is selected then unselect it
2671     if(node.selected) {
2672       this.toggleStylesOnClick(node, false);
2673       delete node.selected;
2674     } else {
2675       //unselect all selected nodes...
2676       this.viz.graph.eachNode(function(n) {
2677         if(n.selected) {
2678           for(var s in nStyles) {
2679             n.setData(s, n.styles['$' + s], 'end');
2680           }
2681           delete n.selected;
2682         }
2683       });
2684       //select clicked node
2685       this.toggleStylesOnClick(node, true);
2686       node.selected = true;
2687       delete node.hovered;
2688       this.hoveredNode = false;
2689     }
2690   },
2691   
2692   onMouseMove: function(e, win, event) {
2693     //if mouse button is down and moving set move=true
2694     if(this.down) this.move = true;
2695     //already handled by mouseover/out
2696     if(this.dom && this.isLabel(e, win)) return;
2697     var nStyles = this.nodeStylesOnHover;
2698     if(!nStyles) return;
2699     
2700     if(!this.dom) {
2701       if(this.hoveredNode) {
2702         var geom = this.types[this.hoveredNode.getData('type')];
2703         var contains = geom && geom.contains && geom.contains.call(this.fx, 
2704             this.hoveredNode, event.getPos());
2705         if(contains) return;
2706       }
2707       var node = event.getNode();
2708       //if no node is being hovered then just exit
2709       if(!this.hoveredNode && !node) return;
2710       //if the node is hovered then exit
2711       if(node.hovered) return;
2712       //select hovered node
2713       if(node && !node.selected) {
2714         //check if an animation is running and exit it
2715         this.fx.nodeFxAnimation.stopTimer();
2716         //unselect all hovered nodes...
2717         this.viz.graph.eachNode(function(n) {
2718           if(n.hovered && !n.selected) {
2719             for(var s in nStyles) {
2720               n.setData(s, n.styles['$' + s], 'end');
2721             }
2722             delete n.hovered;
2723           }
2724         });
2725         //select hovered node
2726         node.hovered = true;
2727         this.hoveredNode = node;
2728         this.toggleStylesOnHover(node, true);
2729       } else if(this.hoveredNode && !this.hoveredNode.selected) {
2730         //check if an animation is running and exit it
2731         this.fx.nodeFxAnimation.stopTimer();
2732         //unselect hovered node
2733         this.toggleStylesOnHover(this.hoveredNode, false);
2734         delete this.hoveredNode.hovered;
2735         this.hoveredNode = false;
2736       }
2737     }
2738   }
2739 });
2740
2741 Extras.Classes.Navigation = new Class({
2742   Implements: [ExtrasInitializer, EventsInterface],
2743   
2744   initializePost: function() {
2745     this.pos = false;
2746     this.pressed = false;
2747   },
2748   
2749   onMouseWheel: function(e, win, scroll) {
2750     if(!this.config.zooming) return;
2751     $.event.stop($.event.get(e, win));
2752     var val = this.config.zooming / 1000,
2753         ans = 1 + scroll * val;
2754     this.canvas.scale(ans, ans);
2755   },
2756   
2757   onMouseDown: function(e, win, eventInfo) {
2758     if(!this.config.panning) return;
2759     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2760     this.pressed = true;
2761     this.pos = eventInfo.getPos();
2762     var canvas = this.canvas,
2763         ox = canvas.translateOffsetX,
2764         oy = canvas.translateOffsetY,
2765         sx = canvas.scaleOffsetX,
2766         sy = canvas.scaleOffsetY;
2767     this.pos.x *= sx;
2768     this.pos.x += ox;
2769     this.pos.y *= sy;
2770     this.pos.y += oy;
2771   },
2772   
2773   onMouseMove: function(e, win, eventInfo) {
2774     if(!this.config.panning) return;
2775     if(!this.pressed) return;
2776     if(this.config.panning == 'avoid nodes' && eventInfo.getNode()) return;
2777     var thispos = this.pos, 
2778         currentPos = eventInfo.getPos(),
2779         canvas = this.canvas,
2780         ox = canvas.translateOffsetX,
2781         oy = canvas.translateOffsetY,
2782         sx = canvas.scaleOffsetX,
2783         sy = canvas.scaleOffsetY;
2784     currentPos.x *= sx;
2785     currentPos.y *= sy;
2786     currentPos.x += ox;
2787     currentPos.y += oy;
2788     var x = currentPos.x - thispos.x,
2789         y = currentPos.y - thispos.y;
2790     this.pos = currentPos;
2791     this.canvas.translate(x * 1/sx, y * 1/sy);
2792   },
2793   
2794   onMouseUp: function(e, win, eventInfo, isRightClick) {
2795     if(!this.config.panning) return;
2796     this.pressed = false;
2797   }
2798 });
2799
2800
2801 /*
2802  * File: Canvas.js
2803  *
2804  */
2805
2806 /*
2807  Class: Canvas
2808  
2809         A canvas widget used by all visualizations. The canvas object can be accessed by doing *viz.canvas*. If you want to 
2810         know more about <Canvas> options take a look at <Options.Canvas>.
2811  
2812  A canvas widget is a set of DOM elements that wrap the native canvas DOM Element providing a consistent API and behavior 
2813  across all browsers. It can also include Elements to add DOM (SVG or HTML) label support to all visualizations.
2814  
2815  Example:
2816  
2817  Suppose we have this HTML
2818  
2819  (start code xml)
2820         <div id="infovis"></div>
2821  (end code)
2822  
2823  Now we create a new Visualization
2824  
2825  (start code js)
2826         var viz = new $jit.Viz({
2827                 //Where to inject the canvas. Any div container will do.
2828                 'injectInto':'infovis',
2829                  //width and height for canvas. 
2830                  //Default's to the container offsetWidth and Height.
2831                  'width': 900,
2832                  'height':500
2833          });
2834  (end code)
2835
2836  The generated HTML will look like this
2837  
2838  (start code xml)
2839  <div id="infovis">
2840         <div id="infovis-canvaswidget" style="position:relative;">
2841         <canvas id="infovis-canvas" width=900 height=500
2842         style="position:absolute; top:0; left:0; width:900px; height:500px;" />
2843         <div id="infovis-label"
2844         style="overflow:visible; position:absolute; top:0; left:0; width:900px; height:0px">
2845         </div>
2846         </div>
2847  </div>
2848  (end code)
2849  
2850  As you can see, the generated HTML consists of a canvas DOM Element of id *infovis-canvas* and a div label container
2851  of id *infovis-label*, wrapped in a main div container of id *infovis-canvaswidget*.
2852  */
2853
2854 var Canvas;
2855 (function() {
2856   //check for native canvas support
2857   var canvasType = typeof HTMLCanvasElement,
2858       supportsCanvas = (canvasType == 'object' || canvasType == 'function');
2859   //create element function
2860   function $E(tag, props) {
2861     var elem = document.createElement(tag);
2862     for(var p in props) {
2863       if(typeof props[p] == "object") {
2864         $.extend(elem[p], props[p]);
2865       } else {
2866         elem[p] = props[p];
2867       }
2868     }
2869     if (tag == "canvas" && !supportsCanvas && G_vmlCanvasManager) {
2870       elem = G_vmlCanvasManager.initElement(elem);
2871     }
2872     return elem;
2873   }
2874   //canvas widget which we will call just Canvas
2875   $jit.Canvas = Canvas = new Class({
2876     canvases: [],
2877     pos: false,
2878     element: false,
2879     labelContainer: false,
2880     translateOffsetX: 0,
2881     translateOffsetY: 0,
2882     scaleOffsetX: 1,
2883     scaleOffsetY: 1,
2884     
2885     initialize: function(viz, opt) {
2886       this.viz = viz;
2887       this.opt = opt;
2888       var id = $.type(opt.injectInto) == 'string'? 
2889           opt.injectInto:opt.injectInto.id,
2890           idLabel = id + "-label", 
2891           wrapper = $(id),
2892           width = opt.width || wrapper.offsetWidth,
2893           height = opt.height || wrapper.offsetHeight;
2894       this.id = id;
2895       //canvas options
2896       var canvasOptions = {
2897         injectInto: id,
2898         width: width,
2899         height: height
2900       };
2901       //create main wrapper
2902       this.element = $E('div', {
2903         'id': id + '-canvaswidget',
2904         'style': {
2905           'position': 'relative',
2906           'width': width + 'px',
2907           'height': height + 'px'
2908         }
2909       });
2910       //create label container
2911       this.labelContainer = this.createLabelContainer(opt.Label.type, 
2912           idLabel, canvasOptions);
2913       //create primary canvas
2914       this.canvases.push(new Canvas.Base({
2915         config: $.extend({idSuffix: '-canvas'}, canvasOptions),
2916         plot: function(base) {
2917           viz.fx.plot();
2918         },
2919         resize: function() {
2920           viz.refresh();
2921         }
2922       }));
2923       //create secondary canvas
2924       var back = opt.background;
2925       if(back) {
2926         var backCanvas = new Canvas.Background[back.type](viz, $.extend(back, canvasOptions));
2927         this.canvases.push(new Canvas.Base(backCanvas));
2928       }
2929       //insert canvases
2930       var len = this.canvases.length;
2931       while(len--) {
2932         this.element.appendChild(this.canvases[len].canvas);
2933         if(len > 0) {
2934           this.canvases[len].plot();
2935         }
2936       }
2937       this.element.appendChild(this.labelContainer);
2938       wrapper.appendChild(this.element);
2939       //Update canvas position when the page is scrolled.
2940       var timer = null, that = this;
2941       $.addEvent(window, 'scroll', function() {
2942         clearTimeout(timer);
2943         timer = setTimeout(function() {
2944           that.getPos(true); //update canvas position
2945         }, 500);
2946       });
2947       $.addEvent(window, 'click', function() {
2948         clearTimeout(timer);
2949         timer = setTimeout(function() {
2950           that.getPos(true); //update canvas position
2951         }, 500);
2952       });
2953       sb = document.getElementById('sb'+id);
2954       $.addEvent(sb, 'scroll', function() {
2955         clearTimeout(timer);
2956         timer = setTimeout(function() {
2957           that.getPos(true); //update canvas position
2958         }, 500);
2959       });
2960     },
2961     /*
2962       Method: getCtx
2963       
2964       Returns the main canvas context object
2965       
2966       Example:
2967       
2968       (start code js)
2969        var ctx = canvas.getCtx();
2970        //Now I can use the native canvas context
2971        //and for example change some canvas styles
2972        ctx.globalAlpha = 1;
2973       (end code)
2974     */
2975     getCtx: function(i) {
2976       return this.canvases[i || 0].getCtx();
2977     },
2978     /*
2979       Method: getConfig
2980       
2981       Returns the current Configuration for this Canvas Widget.
2982       
2983       Example:
2984       
2985       (start code js)
2986        var config = canvas.getConfig();
2987       (end code)
2988     */
2989     getConfig: function() {
2990       return this.opt;
2991     },
2992     /*
2993       Method: getElement
2994
2995       Returns the main Canvas DOM wrapper
2996       
2997       Example:
2998       
2999       (start code js)
3000        var wrapper = canvas.getElement();
3001        //Returns <div id="infovis-canvaswidget" ... >...</div> as element
3002       (end code)
3003     */
3004     getElement: function() {
3005       return this.element;
3006     },
3007     /*
3008       Method: getSize
3009       
3010       Returns canvas dimensions.
3011       
3012       Returns:
3013       
3014       An object with *width* and *height* properties.
3015       
3016       Example:
3017       (start code js)
3018       canvas.getSize(); //returns { width: 900, height: 500 }
3019       (end code)
3020     */
3021     getSize: function(i) {
3022       return this.canvases[i || 0].getSize();
3023     },
3024     /*
3025       Method: resize
3026       
3027       Resizes the canvas.
3028       
3029       Parameters:
3030       
3031       width - New canvas width.
3032       height - New canvas height.
3033       
3034       Example:
3035       
3036       (start code js)
3037        canvas.resize(width, height);
3038       (end code)
3039     
3040     */
3041     resize: function(width, height) {
3042       this.getPos(true);
3043       this.translateOffsetX = this.translateOffsetY = 0;
3044       this.scaleOffsetX = this.scaleOffsetY = 1;
3045       for(var i=0, l=this.canvases.length; i<l; i++) {
3046         this.canvases[i].resize(width, height);
3047       }
3048       var style = this.element.style;
3049       style.width = width + 'px';
3050       style.height = height + 'px';
3051       if(this.labelContainer)
3052         this.labelContainer.style.width = width + 'px';
3053     },
3054     /*
3055       Method: translate
3056       
3057       Applies a translation to the canvas.
3058       
3059       Parameters:
3060       
3061       x - (number) x offset.
3062       y - (number) y offset.
3063       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3064       
3065       Example:
3066       
3067       (start code js)
3068        canvas.translate(30, 30);
3069       (end code)
3070     
3071     */
3072     translate: function(x, y, disablePlot) {
3073       this.translateOffsetX += x*this.scaleOffsetX;
3074       this.translateOffsetY += y*this.scaleOffsetY;
3075       for(var i=0, l=this.canvases.length; i<l; i++) {
3076         this.canvases[i].translate(x, y, disablePlot);
3077       }
3078     },
3079     /*
3080       Method: scale
3081       
3082       Scales the canvas.
3083       
3084       Parameters:
3085       
3086       x - (number) scale value.
3087       y - (number) scale value.
3088       disablePlot - (boolean) Default's *false*. Set this to *true* if you don't want to refresh the visualization.
3089       
3090       Example:
3091       
3092       (start code js)
3093        canvas.scale(0.5, 0.5);
3094       (end code)
3095     
3096     */
3097     scale: function(x, y, disablePlot) {
3098         if(!disablePlot) {
3099                 disablePlot = false;
3100                 }
3101       var px = this.scaleOffsetX * x,
3102           py = this.scaleOffsetY * y;
3103       var dx = this.translateOffsetX * (x -1) / px,
3104           dy = this.translateOffsetY * (y -1) / py;
3105       this.scaleOffsetX = px;
3106       this.scaleOffsetY = py;
3107       for(var i=0, l=this.canvases.length; i<l; i++) {
3108         this.canvases[i].scale(x, y, true);
3109       }
3110       this.translate(dx, dy, disablePlot);
3111     },
3112     /*
3113       Method: getPos
3114       
3115       Returns the canvas position as an *x, y* object.
3116       
3117       Parameters:
3118       
3119       force - (boolean) Default's *false*. Set this to *true* if you want to recalculate the position without using any cache information.
3120       
3121       Returns:
3122       
3123       An object with *x* and *y* properties.
3124       
3125       Example:
3126       (start code js)
3127       canvas.getPos(true); //returns { x: 900, y: 500 }
3128       (end code)
3129     */
3130     getPos: function(force){
3131       if(force || !this.pos) {
3132         return this.pos = $.getPos(this.getElement());
3133       }
3134       return this.pos;
3135     },
3136     /*
3137        Method: clear
3138        
3139        Clears the canvas.
3140     */
3141     clear: function(i){
3142       this.canvases[i||0].clear();
3143     },
3144     
3145     path: function(type, action){
3146       var ctx = this.canvases[0].getCtx();
3147       ctx.beginPath();
3148       action(ctx);
3149       ctx[type]();
3150       ctx.closePath();
3151     },
3152     
3153     createLabelContainer: function(type, idLabel, dim) {
3154       var NS = 'http://www.w3.org/2000/svg';
3155       if(type == 'HTML' || type == 'Native') {
3156         return $E('div', {
3157           'id': idLabel,
3158           'style': {
3159             'overflow': 'visible',
3160             'position': 'absolute',
3161             'top': 0,
3162             'left': 0,
3163             'width': dim.width + 'px',
3164             'height': 0
3165           }
3166         });
3167       } else if(type == 'SVG') {
3168         var svgContainer = document.createElementNS(NS, 'svg:svg');
3169         svgContainer.setAttribute("width", dim.width);
3170         svgContainer.setAttribute('height', dim.height);
3171         var style = svgContainer.style;
3172         style.position = 'absolute';
3173         style.left = style.top = '0px';
3174         var labelContainer = document.createElementNS(NS, 'svg:g');
3175         labelContainer.setAttribute('width', dim.width);
3176         labelContainer.setAttribute('height', dim.height);
3177         labelContainer.setAttribute('x', 0);
3178         labelContainer.setAttribute('y', 0);
3179         labelContainer.setAttribute('id', idLabel);
3180         svgContainer.appendChild(labelContainer);
3181         return svgContainer;
3182       }
3183     }
3184   });
3185   //base canvas wrapper
3186   Canvas.Base = new Class({
3187     translateOffsetX: 0,
3188     translateOffsetY: 0,
3189     scaleOffsetX: 1,
3190     scaleOffsetY: 1,
3191
3192     initialize: function(viz) {
3193       this.viz = viz;
3194       this.opt = viz.config;
3195       this.size = false;
3196       this.createCanvas();
3197       this.translateToCenter();
3198     },
3199     createCanvas: function() {
3200       var opt = this.opt,
3201           width = opt.width,
3202           height = opt.height;
3203       this.canvas = $E('canvas', {
3204         'id': opt.injectInto + opt.idSuffix,
3205         'width': width,
3206         'height': height,
3207         'style': {
3208           'position': 'absolute',
3209           'top': 0,
3210           'left': 0,
3211           'width': width + 'px',
3212           'height': height + 'px'
3213         }
3214       });
3215     },
3216     getCtx: function() {
3217       if(!this.ctx) 
3218         return this.ctx = this.canvas.getContext('2d');
3219       return this.ctx;
3220     },
3221     getSize: function() {
3222       if(this.size) return this.size;
3223       var canvas = this.canvas;
3224       return this.size = {
3225         width: canvas.width,
3226         height: canvas.height
3227       };
3228     },
3229     translateToCenter: function(ps) {
3230       var size = this.getSize(),
3231           width = ps? (size.width - ps.width - this.translateOffsetX*2) : size.width;
3232           height = ps? (size.height - ps.height - this.translateOffsetY*2) : size.height;
3233       var ctx = this.getCtx();
3234       ps && ctx.scale(1/this.scaleOffsetX, 1/this.scaleOffsetY);
3235       ctx.translate(width/2, height/2);
3236     },
3237     resize: function(width, height) {
3238       var size = this.getSize(),
3239           canvas = this.canvas,
3240           styles = canvas.style;
3241       this.size = false;
3242       canvas.width = width;
3243       canvas.height = height;
3244       styles.width = width + "px";
3245       styles.height = height + "px";
3246       //small ExCanvas fix
3247       if (!supportsCanvas) {
3248         // for some unknown reason _resize() method is not being called in some
3249         // IE browser instances, so call it manually
3250         // in obfuscated version of flashcanvas.js the method is renamed to I()
3251         this.getCtx().I(width, height);
3252       }
3253       this.translateToCenter();
3254       this.translateOffsetX =
3255         this.translateOffsetY = 0;
3256       this.scaleOffsetX = 
3257         this.scaleOffsetY = 1;
3258       this.clear();
3259       this.viz.resize(width, height, this);
3260     },
3261     translate: function(x, y, disablePlot) {
3262       var sx = this.scaleOffsetX,
3263           sy = this.scaleOffsetY;
3264       this.translateOffsetX += x*sx;
3265       this.translateOffsetY += y*sy;
3266       this.getCtx().translate(x, y);
3267       !disablePlot && this.plot();
3268     },
3269     scale: function(x, y, disablePlot) {
3270       this.scaleOffsetX *= x;
3271       this.scaleOffsetY *= y;
3272       this.getCtx().scale(x, y);
3273       !disablePlot && this.plot();
3274     },
3275     clear: function(){
3276       var size = this.getSize(),
3277           ox = this.translateOffsetX,
3278           oy = this.translateOffsetY,
3279           sx = this.scaleOffsetX,
3280           sy = this.scaleOffsetY;
3281       this.getCtx().clearRect((-size.width / 2 - ox) * 1/sx, 
3282                               (-size.height / 2 - oy) * 1/sy, 
3283                               size.width * 1/sx, size.height * 1/sy);
3284     },
3285     plot: function() {
3286       this.clear();
3287       this.viz.plot(this);
3288     }
3289   });
3290   //background canvases
3291   //TODO(nico): document this!
3292   Canvas.Background = {};
3293   Canvas.Background.Circles = new Class({
3294     initialize: function(viz, options) {
3295       this.viz = viz;
3296       this.config = $.merge({
3297         idSuffix: '-bkcanvas',
3298         levelDistance: 100,
3299         numberOfCircles: 6,
3300         CanvasStyles: {},
3301         offset: 0
3302       }, options);
3303     },
3304     resize: function(width, height, base) {
3305       this.plot(base);
3306     },
3307     plot: function(base) {
3308       var canvas = base.canvas,
3309           ctx = base.getCtx(),
3310           conf = this.config,
3311           styles = conf.CanvasStyles;
3312       //set canvas styles
3313       for(var s in styles) ctx[s] = styles[s];
3314       var n = conf.numberOfCircles,
3315           rho = conf.levelDistance;
3316       for(var i=1; i<=n; i++) {
3317         ctx.beginPath();
3318         ctx.arc(0, 0, rho * i, 0, 2 * Math.PI, false);
3319         ctx.stroke();
3320         ctx.closePath();
3321       }
3322       //TODO(nico): print labels too!
3323     }
3324   });
3325   Canvas.Background.Fade = new Class({
3326     initialize: function(viz, options) {
3327       this.viz = viz;
3328       this.config = $.merge({
3329         idSuffix: '-bkcanvas',
3330         CanvasStyles: {},
3331         offset: 0
3332       }, options);
3333     },
3334     resize: function(width, height, base) {
3335       this.plot(base);
3336     },
3337     plot: function(base) {
3338       var canvas = base.canvas,
3339           ctx = base.getCtx(),
3340           conf = this.config,
3341           styles = conf.CanvasStyles,
3342           size = base.getSize();
3343                   ctx.fillStyle = 'rgb(255,255,255)';
3344                   ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
3345       //TODO(nico): print labels too!
3346     }
3347   });
3348 })();
3349
3350
3351 /*
3352  * File: Polar.js
3353  * 
3354  * Defines the <Polar> class.
3355  *
3356  * Description:
3357  *
3358  * The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3359  *
3360  * See also:
3361  *
3362  * <http://en.wikipedia.org/wiki/Polar_coordinates>
3363  *
3364 */
3365
3366 /*
3367    Class: Polar
3368
3369    A multi purpose polar representation.
3370
3371    Description:
3372  
3373    The <Polar> class, just like the <Complex> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3374  
3375    See also:
3376  
3377    <http://en.wikipedia.org/wiki/Polar_coordinates>
3378  
3379    Parameters:
3380
3381       theta - An angle.
3382       rho - The norm.
3383 */
3384
3385 var Polar = function(theta, rho) {
3386   this.theta = theta;
3387   this.rho = rho;
3388 };
3389
3390 $jit.Polar = Polar;
3391
3392 Polar.prototype = {
3393     /*
3394        Method: getc
3395     
3396        Returns a complex number.
3397     
3398        Parameters:
3399
3400        simple - _optional_ If *true*, this method will return only an object holding x and y properties and not a <Complex> instance. Default's *false*.
3401
3402       Returns:
3403     
3404           A complex number.
3405     */
3406     getc: function(simple) {
3407         return this.toComplex(simple);
3408     },
3409
3410     /*
3411        Method: getp
3412     
3413        Returns a <Polar> representation.
3414     
3415        Returns:
3416     
3417           A variable in polar coordinates.
3418     */
3419     getp: function() {
3420         return this;
3421     },
3422
3423
3424     /*
3425        Method: set
3426     
3427        Sets a number.
3428
3429        Parameters:
3430
3431        v - A <Complex> or <Polar> instance.
3432     
3433     */
3434     set: function(v) {
3435         v = v.getp();
3436         this.theta = v.theta; this.rho = v.rho;
3437     },
3438
3439     /*
3440        Method: setc
3441     
3442        Sets a <Complex> number.
3443
3444        Parameters:
3445
3446        x - A <Complex> number real part.
3447        y - A <Complex> number imaginary part.
3448     
3449     */
3450     setc: function(x, y) {
3451         this.rho = Math.sqrt(x * x + y * y);
3452         this.theta = Math.atan2(y, x);
3453         if(this.theta < 0) this.theta += Math.PI * 2;
3454     },
3455
3456     /*
3457        Method: setp
3458     
3459        Sets a polar number.
3460
3461        Parameters:
3462
3463        theta - A <Polar> number angle property.
3464        rho - A <Polar> number rho property.
3465     
3466     */
3467     setp: function(theta, rho) {
3468         this.theta = theta; 
3469         this.rho = rho;
3470     },
3471
3472     /*
3473        Method: clone
3474     
3475        Returns a copy of the current object.
3476     
3477        Returns:
3478     
3479           A copy of the real object.
3480     */
3481     clone: function() {
3482         return new Polar(this.theta, this.rho);
3483     },
3484
3485     /*
3486        Method: toComplex
3487     
3488         Translates from polar to cartesian coordinates and returns a new <Complex> instance.
3489     
3490         Parameters:
3491
3492         simple - _optional_ If *true* this method will only return an object with x and y properties (and not the whole <Complex> instance). Default's *false*.
3493  
3494         Returns:
3495     
3496           A new <Complex> instance.
3497     */
3498     toComplex: function(simple) {
3499         var x = Math.cos(this.theta) * this.rho;
3500         var y = Math.sin(this.theta) * this.rho;
3501         if(simple) return { 'x': x, 'y': y};
3502         return new Complex(x, y);
3503     },
3504
3505     /*
3506        Method: add
3507     
3508         Adds two <Polar> instances.
3509     
3510        Parameters:
3511
3512        polar - A <Polar> number.
3513
3514        Returns:
3515     
3516           A new Polar instance.
3517     */
3518     add: function(polar) {
3519         return new Polar(this.theta + polar.theta, this.rho + polar.rho);
3520     },
3521     
3522     /*
3523        Method: scale
3524     
3525         Scales a polar norm.
3526     
3527         Parameters:
3528
3529         number - A scale factor.
3530         
3531         Returns:
3532     
3533           A new Polar instance.
3534     */
3535     scale: function(number) {
3536         return new Polar(this.theta, this.rho * number);
3537     },
3538     
3539     /*
3540        Method: equals
3541     
3542        Comparison method.
3543
3544        Returns *true* if the theta and rho properties are equal.
3545
3546        Parameters:
3547
3548        c - A <Polar> number.
3549
3550        Returns:
3551
3552        *true* if the theta and rho parameters for these objects are equal. *false* otherwise.
3553     */
3554     equals: function(c) {
3555         return this.theta == c.theta && this.rho == c.rho;
3556     },
3557     
3558     /*
3559        Method: $add
3560     
3561         Adds two <Polar> instances affecting the current object.
3562     
3563        Paramters:
3564
3565        polar - A <Polar> instance.
3566
3567        Returns:
3568     
3569           The changed object.
3570     */
3571     $add: function(polar) {
3572         this.theta = this.theta + polar.theta; this.rho += polar.rho;
3573         return this;
3574     },
3575
3576     /*
3577        Method: $madd
3578     
3579         Adds two <Polar> instances affecting the current object. The resulting theta angle is modulo 2pi.
3580     
3581        Parameters:
3582
3583        polar - A <Polar> instance.
3584
3585        Returns:
3586     
3587           The changed object.
3588     */
3589     $madd: function(polar) {
3590         this.theta = (this.theta + polar.theta) % (Math.PI * 2); this.rho += polar.rho;
3591         return this;
3592     },
3593
3594     
3595     /*
3596        Method: $scale
3597     
3598         Scales a polar instance affecting the object.
3599     
3600       Parameters:
3601
3602       number - A scaling factor.
3603
3604       Returns:
3605     
3606           The changed object.
3607     */
3608     $scale: function(number) {
3609         this.rho *= number;
3610         return this;
3611     },
3612     
3613     /*
3614        Method: interpolate
3615     
3616         Calculates a polar interpolation between two points at a given delta moment.
3617
3618         Parameters:
3619       
3620         elem - A <Polar> instance.
3621         delta - A delta factor ranging [0, 1].
3622     
3623        Returns:
3624     
3625           A new <Polar> instance representing an interpolation between _this_ and _elem_
3626     */
3627     interpolate: function(elem, delta) {
3628         var pi = Math.PI, pi2 = pi * 2;
3629         var ch = function(t) {
3630             var a =  (t < 0)? (t % pi2) + pi2 : t % pi2;
3631             return a;
3632         };
3633         var tt = this.theta, et = elem.theta;
3634         var sum, diff = Math.abs(tt - et);
3635         if(diff == pi) {
3636           if(tt > et) {
3637             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3638           } else {
3639             sum = ch((et - pi2 + (tt - (et)) * delta));
3640           }
3641         } else if(diff >= pi) {
3642           if(tt > et) {
3643             sum = ch((et + ((tt - pi2) - et) * delta)) ;
3644           } else {
3645             sum = ch((et - pi2 + (tt - (et - pi2)) * delta));
3646           }
3647         } else {  
3648           sum = ch((et + (tt - et) * delta)) ;
3649         }
3650         var r = (this.rho - elem.rho) * delta + elem.rho;
3651         return {
3652           'theta': sum,
3653           'rho': r
3654         };
3655     }
3656 };
3657
3658
3659 var $P = function(a, b) { return new Polar(a, b); };
3660
3661 Polar.KER = $P(0, 0);
3662
3663
3664
3665 /*
3666  * File: Complex.js
3667  * 
3668  * Defines the <Complex> class.
3669  *
3670  * Description:
3671  *
3672  * The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3673  *
3674  * See also:
3675  *
3676  * <http://en.wikipedia.org/wiki/Complex_number>
3677  *
3678 */
3679
3680 /*
3681    Class: Complex
3682     
3683    A multi-purpose Complex Class with common methods.
3684  
3685    Description:
3686  
3687    The <Complex> class, just like the <Polar> class, is used by the <Hypertree>, <ST> and <RGraph> as a 2D point representation.
3688  
3689    See also:
3690  
3691    <http://en.wikipedia.org/wiki/Complex_number>
3692
3693    Parameters:
3694
3695    x - _optional_ A Complex number real part.
3696    y - _optional_ A Complex number imaginary part.
3697  
3698 */
3699
3700 var Complex = function(x, y) {
3701   this.x = x;
3702   this.y = y;
3703 };
3704
3705 $jit.Complex = Complex;
3706
3707 Complex.prototype = {
3708     /*
3709        Method: getc
3710     
3711        Returns a complex number.
3712     
3713        Returns:
3714     
3715           A complex number.
3716     */
3717     getc: function() {
3718         return this;
3719     },
3720
3721     /*
3722        Method: getp
3723     
3724        Returns a <Polar> representation of this number.
3725     
3726        Parameters:
3727
3728        simple - _optional_ If *true*, this method will return only an object holding theta and rho properties and not a <Polar> instance. Default's *false*.
3729
3730        Returns:
3731     
3732           A variable in <Polar> coordinates.
3733     */
3734     getp: function(simple) {
3735         return this.toPolar(simple);
3736     },
3737
3738
3739     /*
3740        Method: set
3741     
3742        Sets a number.
3743
3744        Parameters:
3745
3746        c - A <Complex> or <Polar> instance.
3747     
3748     */
3749     set: function(c) {
3750       c = c.getc(true);
3751       this.x = c.x; 
3752       this.y = c.y;
3753     },
3754
3755     /*
3756        Method: setc
3757     
3758        Sets a complex number.
3759
3760        Parameters:
3761
3762        x - A <Complex> number Real part.
3763        y - A <Complex> number Imaginary part.
3764     
3765     */
3766     setc: function(x, y) {
3767         this.x = x; 
3768         this.y = y;
3769     },
3770
3771     /*
3772        Method: setp
3773     
3774        Sets a polar number.
3775
3776        Parameters:
3777
3778        theta - A <Polar> number theta property.
3779        rho - A <Polar> number rho property.
3780     
3781     */
3782     setp: function(theta, rho) {
3783         this.x = Math.cos(theta) * rho;
3784         this.y = Math.sin(theta) * rho;
3785     },
3786
3787     /*
3788        Method: clone
3789     
3790        Returns a copy of the current object.
3791     
3792        Returns:
3793     
3794           A copy of the real object.
3795     */
3796     clone: function() {
3797         return new Complex(this.x, this.y);
3798     },
3799
3800     /*
3801        Method: toPolar
3802     
3803        Transforms cartesian to polar coordinates.
3804     
3805        Parameters:
3806
3807        simple - _optional_ If *true* this method will only return an object with theta and rho properties (and not the whole <Polar> instance). Default's *false*.
3808        
3809        Returns:
3810     
3811           A new <Polar> instance.
3812     */
3813     
3814     toPolar: function(simple) {
3815         var rho = this.norm();
3816         var atan = Math.atan2(this.y, this.x);
3817         if(atan < 0) atan += Math.PI * 2;
3818         if(simple) return { 'theta': atan, 'rho': rho };
3819         return new Polar(atan, rho);
3820     },
3821     /*
3822        Method: norm
3823     
3824        Calculates a <Complex> number norm.
3825     
3826        Returns:
3827     
3828           A real number representing the complex norm.
3829     */
3830     norm: function () {
3831         return Math.sqrt(this.squaredNorm());
3832     },
3833     
3834     /*
3835        Method: squaredNorm
3836     
3837        Calculates a <Complex> number squared norm.
3838     
3839        Returns:
3840     
3841           A real number representing the complex squared norm.
3842     */
3843     squaredNorm: function () {
3844         return this.x*this.x + this.y*this.y;
3845     },
3846
3847     /*
3848        Method: add
3849     
3850        Returns the result of adding two complex numbers.
3851        
3852        Does not alter the original object.
3853
3854        Parameters:
3855     
3856           pos - A <Complex> instance.
3857     
3858        Returns:
3859     
3860          The result of adding two complex numbers.
3861     */
3862     add: function(pos) {
3863         return new Complex(this.x + pos.x, this.y + pos.y);
3864     },
3865
3866     /*
3867        Method: prod
3868     
3869        Returns the result of multiplying two <Complex> numbers.
3870        
3871        Does not alter the original object.
3872
3873        Parameters:
3874     
3875           pos - A <Complex> instance.
3876     
3877        Returns:
3878     
3879          The result of multiplying two complex numbers.
3880     */
3881     prod: function(pos) {
3882         return new Complex(this.x*pos.x - this.y*pos.y, this.y*pos.x + this.x*pos.y);
3883     },
3884
3885     /*
3886        Method: conjugate
3887     
3888        Returns the conjugate of this <Complex> number.
3889
3890        Does not alter the original object.
3891
3892        Returns:
3893     
3894          The conjugate of this <Complex> number.
3895     */
3896     conjugate: function() {
3897         return new Complex(this.x, -this.y);
3898     },
3899
3900
3901     /*
3902        Method: scale
3903     
3904        Returns the result of scaling a <Complex> instance.
3905        
3906        Does not alter the original object.
3907
3908        Parameters:
3909     
3910           factor - A scale factor.
3911     
3912        Returns:
3913     
3914          The result of scaling this complex to a factor.
3915     */
3916     scale: function(factor) {
3917         return new Complex(this.x * factor, this.y * factor);
3918     },
3919
3920     /*
3921        Method: equals
3922     
3923        Comparison method.
3924
3925        Returns *true* if both real and imaginary parts are equal.
3926
3927        Parameters:
3928
3929        c - A <Complex> instance.
3930
3931        Returns:
3932
3933        A boolean instance indicating if both <Complex> numbers are equal.
3934     */
3935     equals: function(c) {
3936         return this.x == c.x && this.y == c.y;
3937     },
3938
3939     /*
3940        Method: $add
3941     
3942        Returns the result of adding two <Complex> numbers.
3943        
3944        Alters the original object.
3945
3946        Parameters:
3947     
3948           pos - A <Complex> instance.
3949     
3950        Returns:
3951     
3952          The result of adding two complex numbers.
3953     */
3954     $add: function(pos) {
3955         this.x += pos.x; this.y += pos.y;
3956         return this;    
3957     },
3958     
3959     /*
3960        Method: $prod
3961     
3962        Returns the result of multiplying two <Complex> numbers.
3963        
3964        Alters the original object.
3965
3966        Parameters:
3967     
3968           pos - A <Complex> instance.
3969     
3970        Returns:
3971     
3972          The result of multiplying two complex numbers.
3973     */
3974     $prod:function(pos) {
3975         var x = this.x, y = this.y;
3976         this.x = x*pos.x - y*pos.y;
3977         this.y = y*pos.x + x*pos.y;
3978         return this;
3979     },
3980     
3981     /*
3982        Method: $conjugate
3983     
3984        Returns the conjugate for this <Complex>.
3985        
3986        Alters the original object.
3987
3988        Returns:
3989     
3990          The conjugate for this complex.
3991     */
3992     $conjugate: function() {
3993         this.y = -this.y;
3994         return this;
3995     },
3996     
3997     /*
3998        Method: $scale
3999     
4000        Returns the result of scaling a <Complex> instance.
4001        
4002        Alters the original object.
4003
4004        Parameters:
4005     
4006           factor - A scale factor.
4007     
4008        Returns:
4009     
4010          The result of scaling this complex to a factor.
4011     */
4012     $scale: function(factor) {
4013         this.x *= factor; this.y *= factor;
4014         return this;
4015     },
4016     
4017     /*
4018        Method: $div
4019     
4020        Returns the division of two <Complex> numbers.
4021        
4022        Alters the original object.
4023
4024        Parameters:
4025     
4026           pos - A <Complex> number.
4027     
4028        Returns:
4029     
4030          The result of scaling this complex to a factor.
4031     */
4032     $div: function(pos) {
4033         var x = this.x, y = this.y;
4034         var sq = pos.squaredNorm();
4035         this.x = x * pos.x + y * pos.y; this.y = y * pos.x - x * pos.y;
4036         return this.$scale(1 / sq);
4037     }
4038 };
4039
4040 var $C = function(a, b) { return new Complex(a, b); };
4041
4042 Complex.KER = $C(0, 0);
4043
4044
4045
4046 /*
4047  * File: Graph.js
4048  *
4049 */
4050
4051 /*
4052  Class: Graph
4053
4054  A Graph Class that provides useful manipulation functions. You can find more manipulation methods in the <Graph.Util> object.
4055
4056  An instance of this class can be accessed by using the *graph* parameter of any tree or graph visualization.
4057  
4058  Example:
4059
4060  (start code js)
4061    //create new visualization
4062    var viz = new $jit.Viz(options);
4063    //load JSON data
4064    viz.loadJSON(json);
4065    //access model
4066    viz.graph; //<Graph> instance
4067  (end code)
4068  
4069  Implements:
4070  
4071  The following <Graph.Util> methods are implemented in <Graph>
4072  
4073   - <Graph.Util.getNode>
4074   - <Graph.Util.eachNode>
4075   - <Graph.Util.computeLevels>
4076   - <Graph.Util.eachBFS>
4077   - <Graph.Util.clean>
4078   - <Graph.Util.getClosestNodeToPos>
4079   - <Graph.Util.getClosestNodeToOrigin>
4080  
4081 */  
4082
4083 $jit.Graph = new Class({
4084
4085   initialize: function(opt, Node, Edge, Label) {
4086     var innerOptions = {
4087     'complex': false,
4088     'Node': {}
4089     };
4090     this.Node = Node;
4091     this.Edge = Edge;
4092     this.Label = Label;
4093     this.opt = $.merge(innerOptions, opt || {});
4094     this.nodes = {};
4095     this.edges = {};
4096     
4097     //add nodeList methods
4098     var that = this;
4099     this.nodeList = {};
4100     for(var p in Accessors) {
4101       that.nodeList[p] = (function(p) {
4102         return function() {
4103           var args = Array.prototype.slice.call(arguments);
4104           that.eachNode(function(n) {
4105             n[p].apply(n, args);
4106           });
4107         };
4108       })(p);
4109     }
4110
4111  },
4112
4113 /*
4114      Method: getNode
4115     
4116      Returns a <Graph.Node> by *id*.
4117
4118      Parameters:
4119
4120      id - (string) A <Graph.Node> id.
4121
4122      Example:
4123
4124      (start code js)
4125        var node = graph.getNode('nodeId');
4126      (end code)
4127 */  
4128  getNode: function(id) {
4129     if(this.hasNode(id)) return this.nodes[id];
4130     return false;
4131  },
4132
4133  /*
4134    Method: getByName
4135   
4136    Returns a <Graph.Node> by *name*.
4137   
4138    Parameters:
4139   
4140    name - (string) A <Graph.Node> name.
4141   
4142    Example:
4143   
4144    (start code js)
4145      var node = graph.getByName('someName');
4146    (end code)
4147   */  
4148   getByName: function(name) {
4149     for(var id in this.nodes) {
4150       var n = this.nodes[id];
4151       if(n.name == name) return n;
4152     }
4153     return false;
4154   },
4155
4156 /*
4157    Method: getAdjacence
4158   
4159    Returns a <Graph.Adjacence> object connecting nodes with ids *id* and *id2*.
4160
4161    Parameters:
4162
4163    id - (string) A <Graph.Node> id.
4164    id2 - (string) A <Graph.Node> id.
4165 */  
4166   getAdjacence: function (id, id2) {
4167     if(id in this.edges) {
4168       return this.edges[id][id2];
4169     }
4170     return false;
4171  },
4172
4173     /*
4174      Method: addNode
4175     
4176      Adds a node.
4177      
4178      Parameters:
4179     
4180       obj - An object with the properties described below
4181
4182       id - (string) A node id
4183       name - (string) A node's name
4184       data - (object) A node's data hash
4185
4186     See also:
4187     <Graph.Node>
4188
4189   */  
4190   addNode: function(obj) { 
4191    if(!this.nodes[obj.id]) {  
4192      var edges = this.edges[obj.id] = {};
4193      this.nodes[obj.id] = new Graph.Node($.extend({
4194         'id': obj.id,
4195         'name': obj.name,
4196         'data': $.merge(obj.data || {}, {}),
4197         'adjacencies': edges 
4198       }, this.opt.Node), 
4199       this.opt.complex, 
4200       this.Node, 
4201       this.Edge,
4202       this.Label);
4203     }
4204     return this.nodes[obj.id];
4205   },
4206   
4207     /*
4208      Method: addAdjacence
4209     
4210      Connects nodes specified by *obj* and *obj2*. If not found, nodes are created.
4211      
4212      Parameters:
4213     
4214       obj - (object) A <Graph.Node> object.
4215       obj2 - (object) Another <Graph.Node> object.
4216       data - (object) A data object. Used to store some extra information in the <Graph.Adjacence> object created.
4217
4218     See also:
4219
4220     <Graph.Node>, <Graph.Adjacence>
4221     */  
4222   addAdjacence: function (obj, obj2, data) {
4223     if(!this.hasNode(obj.id)) { this.addNode(obj); }
4224     if(!this.hasNode(obj2.id)) { this.addNode(obj2); }
4225     obj = this.nodes[obj.id]; obj2 = this.nodes[obj2.id];
4226     if(!obj.adjacentTo(obj2)) {
4227       var adjsObj = this.edges[obj.id] = this.edges[obj.id] || {};
4228       var adjsObj2 = this.edges[obj2.id] = this.edges[obj2.id] || {};
4229       adjsObj[obj2.id] = adjsObj2[obj.id] = new Graph.Adjacence(obj, obj2, data, this.Edge, this.Label);
4230       return adjsObj[obj2.id];
4231     }
4232     return this.edges[obj.id][obj2.id];
4233  },
4234
4235     /*
4236      Method: removeNode
4237     
4238      Removes a <Graph.Node> matching the specified *id*.
4239
4240      Parameters:
4241
4242      id - (string) A node's id.
4243
4244     */  
4245   removeNode: function(id) {
4246     if(this.hasNode(id)) {
4247       delete this.nodes[id];
4248       var adjs = this.edges[id];
4249       for(var to in adjs) {
4250         delete this.edges[to][id];
4251       }
4252       delete this.edges[id];
4253     }
4254   },
4255   
4256 /*
4257      Method: removeAdjacence
4258     
4259      Removes a <Graph.Adjacence> matching *id1* and *id2*.
4260
4261      Parameters:
4262
4263      id1 - (string) A <Graph.Node> id.
4264      id2 - (string) A <Graph.Node> id.
4265 */  
4266   removeAdjacence: function(id1, id2) {
4267     delete this.edges[id1][id2];
4268     delete this.edges[id2][id1];
4269   },
4270
4271    /*
4272      Method: hasNode
4273     
4274      Returns a boolean indicating if the node belongs to the <Graph> or not.
4275      
4276      Parameters:
4277     
4278         id - (string) Node id.
4279    */  
4280   hasNode: function(id) {
4281     return id in this.nodes;
4282   },
4283   
4284   /*
4285     Method: empty
4286
4287     Empties the Graph
4288
4289   */
4290   empty: function() { this.nodes = {}; this.edges = {};}
4291
4292 });
4293
4294 var Graph = $jit.Graph;
4295
4296 /*
4297  Object: Accessors
4298  
4299  Defines a set of methods for data, canvas and label styles manipulation implemented by <Graph.Node> and <Graph.Adjacence> instances.
4300  
4301  */
4302 var Accessors;
4303
4304 (function () {
4305   var getDataInternal = function(prefix, prop, type, force, prefixConfig) {
4306     var data;
4307     type = type || 'current';
4308     prefix = "$" + (prefix ? prefix + "-" : "");
4309
4310     if(type == 'current') {
4311       data = this.data;
4312     } else if(type == 'start') {
4313       data = this.startData;
4314     } else if(type == 'end') {
4315       data = this.endData;
4316     }
4317
4318     var dollar = prefix + prop;
4319
4320     if(force) {
4321       return data[dollar];
4322     }
4323
4324     if(!this.Config.overridable)
4325       return prefixConfig[prop] || 0;
4326
4327     return (dollar in data) ?
4328       data[dollar] : ((dollar in this.data) ? this.data[dollar] : (prefixConfig[prop] || 0));
4329   }
4330
4331   var setDataInternal = function(prefix, prop, value, type) {
4332     type = type || 'current';
4333     prefix = '$' + (prefix ? prefix + '-' : '');
4334
4335     var data;
4336
4337     if(type == 'current') {
4338       data = this.data;
4339     } else if(type == 'start') {
4340       data = this.startData;
4341     } else if(type == 'end') {
4342       data = this.endData;
4343     }
4344
4345     data[prefix + prop] = value;
4346   }
4347
4348   var removeDataInternal = function(prefix, properties) {
4349     prefix = '$' + (prefix ? prefix + '-' : '');
4350     var that = this;
4351     $.each(properties, function(prop) {
4352       var pref = prefix + prop;
4353       delete that.data[pref];
4354       delete that.endData[pref];
4355       delete that.startData[pref];
4356     });
4357   }
4358
4359   Accessors = {
4360     /*
4361     Method: getData
4362
4363     Returns the specified data value property.
4364     This is useful for querying special/reserved <Graph.Node> data properties
4365     (i.e dollar prefixed properties).
4366
4367     Parameters:
4368
4369       prop  - (string) The name of the property. The dollar sign is not needed. For
4370               example *getData(width)* will return *data.$width*.
4371       type  - (string) The type of the data property queried. Default's "current". You can access *start* and *end* 
4372               data properties also. These properties are used when making animations.
4373       force - (boolean) Whether to obtain the true value of the property (equivalent to
4374               *data.$prop*) or to check for *node.overridable = true* first.
4375
4376     Returns:
4377
4378       The value of the dollar prefixed property or the global Node/Edge property
4379       value if *overridable=false*
4380
4381     Example:
4382     (start code js)
4383      node.getData('width'); //will return node.data.$width if Node.overridable=true;
4384     (end code)
4385     */
4386     getData: function(prop, type, force) {
4387       return getDataInternal.call(this, "", prop, type, force, this.Config);
4388     },
4389
4390
4391     /*
4392     Method: setData
4393
4394     Sets the current data property with some specific value.
4395     This method is only useful for reserved (dollar prefixed) properties.
4396
4397     Parameters:
4398
4399       prop  - (string) The name of the property. The dollar sign is not necessary. For
4400               example *setData(width)* will set *data.$width*.
4401       value - (mixed) The value to store.
4402       type  - (string) The type of the data property to store. Default's "current" but
4403               can also be "start" or "end".
4404
4405     Example:
4406     
4407     (start code js)
4408      node.setData('width', 30);
4409     (end code)
4410     
4411     If we were to make an animation of a node/edge width then we could do
4412     
4413     (start code js)
4414       var node = viz.getNode('nodeId');
4415       //set start and end values
4416       node.setData('width', 10, 'start');
4417       node.setData('width', 30, 'end');
4418       //will animate nodes width property
4419       viz.fx.animate({
4420         modes: ['node-property:width'],
4421         duration: 1000
4422       });
4423     (end code)
4424     */
4425     setData: function(prop, value, type) {
4426       setDataInternal.call(this, "", prop, value, type);
4427     },
4428
4429     /*
4430     Method: setDataset
4431
4432     Convenience method to set multiple data values at once.
4433     
4434     Parameters:
4435     
4436     types - (array|string) A set of 'current', 'end' or 'start' values.
4437     obj - (object) A hash containing the names and values of the properties to be altered.
4438
4439     Example:
4440     (start code js)
4441       node.setDataset(['current', 'end'], {
4442         'width': [100, 5],
4443         'color': ['#fff', '#ccc']
4444       });
4445       //...or also
4446       node.setDataset('end', {
4447         'width': 5,
4448         'color': '#ccc'
4449       });
4450     (end code)
4451     
4452     See also: 
4453     
4454     <Accessors.setData>
4455     
4456     */
4457     setDataset: function(types, obj) {
4458       types = $.splat(types);
4459       for(var attr in obj) {
4460         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4461           this.setData(attr, val[i], types[i]);
4462         }
4463       }
4464     },
4465     
4466     /*
4467     Method: removeData
4468
4469     Remove data properties.
4470
4471     Parameters:
4472
4473     One or more property names as arguments. The dollar sign is not needed.
4474
4475     Example:
4476     (start code js)
4477     node.removeData('width'); //now the default width value is returned
4478     (end code)
4479     */
4480     removeData: function() {
4481       removeDataInternal.call(this, "", Array.prototype.slice.call(arguments));
4482     },
4483
4484     /*
4485     Method: getCanvasStyle
4486
4487     Returns the specified canvas style data value property. This is useful for
4488     querying special/reserved <Graph.Node> canvas style data properties (i.e.
4489     dollar prefixed properties that match with $canvas-<name of canvas style>).
4490
4491     Parameters:
4492
4493       prop  - (string) The name of the property. The dollar sign is not needed. For
4494               example *getCanvasStyle(shadowBlur)* will return *data[$canvas-shadowBlur]*.
4495       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4496               data properties also.
4497               
4498     Example:
4499     (start code js)
4500       node.getCanvasStyle('shadowBlur');
4501     (end code)
4502     
4503     See also:
4504     
4505     <Accessors.getData>
4506     */
4507     getCanvasStyle: function(prop, type, force) {
4508       return getDataInternal.call(
4509           this, 'canvas', prop, type, force, this.Config.CanvasStyles);
4510     },
4511
4512     /*
4513     Method: setCanvasStyle
4514
4515     Sets the canvas style data property with some specific value.
4516     This method is only useful for reserved (dollar prefixed) properties.
4517     
4518     Parameters:
4519     
4520     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4521     value - (mixed) The value to set to the property.
4522     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4523     
4524     Example:
4525     
4526     (start code js)
4527      node.setCanvasStyle('shadowBlur', 30);
4528     (end code)
4529     
4530     If we were to make an animation of a node/edge shadowBlur canvas style then we could do
4531     
4532     (start code js)
4533       var node = viz.getNode('nodeId');
4534       //set start and end values
4535       node.setCanvasStyle('shadowBlur', 10, 'start');
4536       node.setCanvasStyle('shadowBlur', 30, 'end');
4537       //will animate nodes canvas style property for nodes
4538       viz.fx.animate({
4539         modes: ['node-style:shadowBlur'],
4540         duration: 1000
4541       });
4542     (end code)
4543     
4544     See also:
4545     
4546     <Accessors.setData>.
4547     */
4548     setCanvasStyle: function(prop, value, type) {
4549       setDataInternal.call(this, 'canvas', prop, value, type);
4550     },
4551
4552     /*
4553     Method: setCanvasStyles
4554
4555     Convenience method to set multiple styles at once.
4556
4557     Parameters:
4558     
4559     types - (array|string) A set of 'current', 'end' or 'start' values.
4560     obj - (object) A hash containing the names and values of the properties to be altered.
4561
4562     See also:
4563     
4564     <Accessors.setDataset>.
4565     */
4566     setCanvasStyles: function(types, obj) {
4567       types = $.splat(types);
4568       for(var attr in obj) {
4569         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4570           this.setCanvasStyle(attr, val[i], types[i]);
4571         }
4572       }
4573     },
4574
4575     /*
4576     Method: removeCanvasStyle
4577
4578     Remove canvas style properties from data.
4579
4580     Parameters:
4581     
4582     A variable number of canvas style strings.
4583
4584     See also:
4585     
4586     <Accessors.removeData>.
4587     */
4588     removeCanvasStyle: function() {
4589       removeDataInternal.call(this, 'canvas', Array.prototype.slice.call(arguments));
4590     },
4591
4592     /*
4593     Method: getLabelData
4594
4595     Returns the specified label data value property. This is useful for
4596     querying special/reserved <Graph.Node> label options (i.e.
4597     dollar prefixed properties that match with $label-<name of label style>).
4598
4599     Parameters:
4600
4601       prop  - (string) The name of the property. The dollar sign prefix is not needed. For
4602               example *getLabelData(size)* will return *data[$label-size]*.
4603       type  - (string) The type of the data property queried. Default's *current*. You can access *start* and *end* 
4604               data properties also.
4605               
4606     See also:
4607     
4608     <Accessors.getData>.
4609     */
4610     getLabelData: function(prop, type, force) {
4611       return getDataInternal.call(
4612           this, 'label', prop, type, force, this.Label);
4613     },
4614
4615     /*
4616     Method: setLabelData
4617
4618     Sets the current label data with some specific value.
4619     This method is only useful for reserved (dollar prefixed) properties.
4620
4621     Parameters:
4622     
4623     prop - (string) Name of the property. Can be any canvas property like 'shadowBlur', 'shadowColor', 'strokeStyle', etc.
4624     value - (mixed) The value to set to the property.
4625     type - (string) Default's *current*. Whether to set *start*, *current* or *end* type properties.
4626     
4627     Example:
4628     
4629     (start code js)
4630      node.setLabelData('size', 30);
4631     (end code)
4632     
4633     If we were to make an animation of a node label size then we could do
4634     
4635     (start code js)
4636       var node = viz.getNode('nodeId');
4637       //set start and end values
4638       node.setLabelData('size', 10, 'start');
4639       node.setLabelData('size', 30, 'end');
4640       //will animate nodes label size
4641       viz.fx.animate({
4642         modes: ['label-property:size'],
4643         duration: 1000
4644       });
4645     (end code)
4646     
4647     See also:
4648     
4649     <Accessors.setData>.
4650     */
4651     setLabelData: function(prop, value, type) {
4652       setDataInternal.call(this, 'label', prop, value, type);
4653     },
4654
4655     /*
4656     Method: setLabelDataset
4657
4658     Convenience function to set multiple label data at once.
4659
4660     Parameters:
4661     
4662     types - (array|string) A set of 'current', 'end' or 'start' values.
4663     obj - (object) A hash containing the names and values of the properties to be altered.
4664
4665     See also:
4666     
4667     <Accessors.setDataset>.
4668     */
4669     setLabelDataset: function(types, obj) {
4670       types = $.splat(types);
4671       for(var attr in obj) {
4672         for(var i=0, val = $.splat(obj[attr]), l=types.length; i<l; i++) {
4673           this.setLabelData(attr, val[i], types[i]);
4674         }
4675       }
4676     },
4677
4678     /*
4679     Method: removeLabelData
4680
4681     Remove label properties from data.
4682     
4683     Parameters:
4684     
4685     A variable number of label property strings.
4686
4687     See also:
4688     
4689     <Accessors.removeData>.
4690     */
4691     removeLabelData: function() {
4692       removeDataInternal.call(this, 'label', Array.prototype.slice.call(arguments));
4693     }
4694   };
4695 })();
4696
4697 /*
4698      Class: Graph.Node
4699
4700      A <Graph> node.
4701      
4702      Implements:
4703      
4704      <Accessors> methods.
4705      
4706      The following <Graph.Util> methods are implemented by <Graph.Node>
4707      
4708     - <Graph.Util.eachAdjacency>
4709     - <Graph.Util.eachLevel>
4710     - <Graph.Util.eachSubgraph>
4711     - <Graph.Util.eachSubnode>
4712     - <Graph.Util.anySubnode>
4713     - <Graph.Util.getSubnodes>
4714     - <Graph.Util.getParents>
4715     - <Graph.Util.isDescendantOf>     
4716 */
4717 Graph.Node = new Class({
4718     
4719   initialize: function(opt, complex, Node, Edge, Label) {
4720     var innerOptions = {
4721       'id': '',
4722       'name': '',
4723       'data': {},
4724       'startData': {},
4725       'endData': {},
4726       'adjacencies': {},
4727
4728       'selected': false,
4729       'drawn': false,
4730       'exist': false,
4731
4732       'angleSpan': {
4733         'begin': 0,
4734         'end' : 0
4735       },
4736
4737       'pos': (complex && $C(0, 0)) || $P(0, 0),
4738       'startPos': (complex && $C(0, 0)) || $P(0, 0),
4739       'endPos': (complex && $C(0, 0)) || $P(0, 0)
4740     };
4741     
4742     $.extend(this, $.extend(innerOptions, opt));
4743     this.Config = this.Node = Node;
4744     this.Edge = Edge;
4745     this.Label = Label;
4746   },
4747
4748     /*
4749        Method: adjacentTo
4750     
4751        Indicates if the node is adjacent to the node specified by id
4752
4753        Parameters:
4754     
4755           id - (string) A node id.
4756     
4757        Example:
4758        (start code js)
4759         node.adjacentTo('nodeId') == true;
4760        (end code)
4761     */
4762     adjacentTo: function(node) {
4763         return node.id in this.adjacencies;
4764     },
4765
4766     /*
4767        Method: getAdjacency
4768     
4769        Returns a <Graph.Adjacence> object connecting the current <Graph.Node> and the node having *id* as id.
4770
4771        Parameters:
4772     
4773           id - (string) A node id.
4774     */  
4775     getAdjacency: function(id) {
4776         return this.adjacencies[id];
4777     },
4778
4779     /*
4780       Method: getPos
4781    
4782       Returns the position of the node.
4783   
4784       Parameters:
4785    
4786          type - (string) Default's *current*. Possible values are "start", "end" or "current".
4787    
4788       Returns:
4789    
4790         A <Complex> or <Polar> instance.
4791   
4792       Example:
4793       (start code js)
4794        var pos = node.getPos('end');
4795       (end code)
4796    */
4797    getPos: function(type) {
4798        type = type || "current";
4799        if(type == "current") {
4800          return this.pos;
4801        } else if(type == "end") {
4802          return this.endPos;
4803        } else if(type == "start") {
4804          return this.startPos;
4805        }
4806    },
4807    /*
4808      Method: setPos
4809   
4810      Sets the node's position.
4811   
4812      Parameters:
4813   
4814         value - (object) A <Complex> or <Polar> instance.
4815         type - (string) Default's *current*. Possible values are "start", "end" or "current".
4816   
4817      Example:
4818      (start code js)
4819       node.setPos(new $jit.Complex(0, 0), 'end');
4820      (end code)
4821   */
4822   setPos: function(value, type) {
4823       type = type || "current";
4824       var pos;
4825       if(type == "current") {
4826         pos = this.pos;
4827       } else if(type == "end") {
4828         pos = this.endPos;
4829       } else if(type == "start") {
4830         pos = this.startPos;
4831       }
4832       pos.set(value);
4833   }
4834 });
4835
4836 Graph.Node.implement(Accessors);
4837
4838 /*
4839      Class: Graph.Adjacence
4840
4841      A <Graph> adjacence (or edge) connecting two <Graph.Nodes>.
4842      
4843      Implements:
4844      
4845      <Accessors> methods.
4846
4847      See also:
4848
4849      <Graph>, <Graph.Node>
4850
4851      Properties:
4852      
4853       nodeFrom - A <Graph.Node> connected by this edge.
4854       nodeTo - Another  <Graph.Node> connected by this edge.
4855       data - Node data property containing a hash (i.e {}) with custom options.
4856 */
4857 Graph.Adjacence = new Class({
4858   
4859   initialize: function(nodeFrom, nodeTo, data, Edge, Label) {
4860     this.nodeFrom = nodeFrom;
4861     this.nodeTo = nodeTo;
4862     this.data = data || {};
4863     this.startData = {};
4864     this.endData = {};
4865     this.Config = this.Edge = Edge;
4866     this.Label = Label;
4867   }
4868 });
4869
4870 Graph.Adjacence.implement(Accessors);
4871
4872 /*
4873    Object: Graph.Util
4874
4875    <Graph> traversal and processing utility object.
4876    
4877    Note:
4878    
4879    For your convenience some of these methods have also been appended to <Graph> and <Graph.Node> classes.
4880 */
4881 Graph.Util = {
4882     /*
4883        filter
4884     
4885        For internal use only. Provides a filtering function based on flags.
4886     */
4887     filter: function(param) {
4888         if(!param || !($.type(param) == 'string')) return function() { return true; };
4889         var props = param.split(" ");
4890         return function(elem) {
4891             for(var i=0; i<props.length; i++) { 
4892               if(elem[props[i]]) { 
4893                 return false; 
4894               }
4895             }
4896             return true;
4897         };
4898     },
4899     /*
4900        Method: getNode
4901     
4902        Returns a <Graph.Node> by *id*.
4903        
4904        Also implemented by:
4905        
4906        <Graph>
4907
4908        Parameters:
4909
4910        graph - (object) A <Graph> instance.
4911        id - (string) A <Graph.Node> id.
4912
4913        Example:
4914
4915        (start code js)
4916          $jit.Graph.Util.getNode(graph, 'nodeid');
4917          //or...
4918          graph.getNode('nodeid');
4919        (end code)
4920     */
4921     getNode: function(graph, id) {
4922         return graph.nodes[id];
4923     },
4924     
4925     /*
4926        Method: eachNode
4927     
4928        Iterates over <Graph> nodes performing an *action*.
4929        
4930        Also implemented by:
4931        
4932        <Graph>.
4933
4934        Parameters:
4935
4936        graph - (object) A <Graph> instance.
4937        action - (function) A callback function having a <Graph.Node> as first formal parameter.
4938
4939        Example:
4940        (start code js)
4941          $jit.Graph.Util.eachNode(graph, function(node) {
4942           alert(node.name);
4943          });
4944          //or...
4945          graph.eachNode(function(node) {
4946            alert(node.name);
4947          });
4948        (end code)
4949     */
4950     eachNode: function(graph, action, flags) {
4951         var filter = this.filter(flags);
4952         for(var i in graph.nodes) {
4953           if(filter(graph.nodes[i])) action(graph.nodes[i]);
4954         } 
4955     },
4956     
4957     /*
4958        Method: eachAdjacency
4959     
4960        Iterates over <Graph.Node> adjacencies applying the *action* function.
4961        
4962        Also implemented by:
4963        
4964        <Graph.Node>.
4965
4966        Parameters:
4967
4968        node - (object) A <Graph.Node>.
4969        action - (function) A callback function having <Graph.Adjacence> as first formal parameter.
4970
4971        Example:
4972        (start code js)
4973          $jit.Graph.Util.eachAdjacency(node, function(adj) {
4974           alert(adj.nodeTo.name);
4975          });
4976          //or...
4977          node.eachAdjacency(function(adj) {
4978            alert(adj.nodeTo.name);
4979          });
4980        (end code)
4981     */
4982     eachAdjacency: function(node, action, flags) {
4983         var adj = node.adjacencies, filter = this.filter(flags);
4984         for(var id in adj) {
4985           var a = adj[id];
4986           if(filter(a)) {
4987             if(a.nodeFrom != node) {
4988               var tmp = a.nodeFrom;
4989               a.nodeFrom = a.nodeTo;
4990               a.nodeTo = tmp;
4991             }
4992             action(a, id);
4993           }
4994         }
4995     },
4996
4997      /*
4998        Method: computeLevels
4999     
5000        Performs a BFS traversal setting the correct depth for each node.
5001         
5002        Also implemented by:
5003        
5004        <Graph>.
5005        
5006        Note:
5007        
5008        The depth of each node can then be accessed by 
5009        >node._depth
5010
5011        Parameters:
5012
5013        graph - (object) A <Graph>.
5014        id - (string) A starting node id for the BFS traversal.
5015        startDepth - (optional|number) A minimum depth value. Default's 0.
5016
5017     */
5018     computeLevels: function(graph, id, startDepth, flags) {
5019         startDepth = startDepth || 0;
5020         var filter = this.filter(flags);
5021         this.eachNode(graph, function(elem) {
5022             elem._flag = false;
5023             elem._depth = -1;
5024         }, flags);
5025         var root = graph.getNode(id);
5026         root._depth = startDepth;
5027         var queue = [root];
5028         while(queue.length != 0) {
5029             var node = queue.pop();
5030             node._flag = true;
5031             this.eachAdjacency(node, function(adj) {
5032                 var n = adj.nodeTo;
5033                 if(n._flag == false && filter(n)) {
5034                     if(n._depth < 0) n._depth = node._depth + 1 + startDepth;
5035                     queue.unshift(n);
5036                 }
5037             }, flags);
5038         }
5039     },
5040
5041     /*
5042        Method: eachBFS
5043     
5044        Performs a BFS traversal applying *action* to each <Graph.Node>.
5045        
5046        Also implemented by:
5047        
5048        <Graph>.
5049
5050        Parameters:
5051
5052        graph - (object) A <Graph>.
5053        id - (string) A starting node id for the BFS traversal.
5054        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5055
5056        Example:
5057        (start code js)
5058          $jit.Graph.Util.eachBFS(graph, 'mynodeid', function(node) {
5059           alert(node.name);
5060          });
5061          //or...
5062          graph.eachBFS('mynodeid', function(node) {
5063            alert(node.name);
5064          });
5065        (end code)
5066     */
5067     eachBFS: function(graph, id, action, flags) {
5068         var filter = this.filter(flags);
5069         this.clean(graph);
5070         var queue = [graph.getNode(id)];
5071         while(queue.length != 0) {
5072             var node = queue.pop();
5073             node._flag = true;
5074             action(node, node._depth);
5075             this.eachAdjacency(node, function(adj) {
5076                 var n = adj.nodeTo;
5077                 if(n._flag == false && filter(n)) {
5078                     n._flag = true;
5079                     queue.unshift(n);
5080                 }
5081             }, flags);
5082         }
5083     },
5084     
5085     /*
5086        Method: eachLevel
5087     
5088        Iterates over a node's subgraph applying *action* to the nodes of relative depth between *levelBegin* and *levelEnd*.
5089        
5090        Also implemented by:
5091        
5092        <Graph.Node>.
5093
5094        Parameters:
5095        
5096        node - (object) A <Graph.Node>.
5097        levelBegin - (number) A relative level value.
5098        levelEnd - (number) A relative level value.
5099        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5100
5101     */
5102     eachLevel: function(node, levelBegin, levelEnd, action, flags) {
5103         var d = node._depth, filter = this.filter(flags), that = this;
5104         levelEnd = levelEnd === false? Number.MAX_VALUE -d : levelEnd;
5105         (function loopLevel(node, levelBegin, levelEnd) {
5106             var d = node._depth;
5107             if(d >= levelBegin && d <= levelEnd && filter(node)) action(node, d);
5108             if(d < levelEnd) {
5109                 that.eachAdjacency(node, function(adj) {
5110                     var n = adj.nodeTo;
5111                     if(n._depth > d) loopLevel(n, levelBegin, levelEnd);
5112                 });
5113             }
5114         })(node, levelBegin + d, levelEnd + d);      
5115     },
5116
5117     /*
5118        Method: eachSubgraph
5119     
5120        Iterates over a node's children recursively.
5121        
5122        Also implemented by:
5123        
5124        <Graph.Node>.
5125
5126        Parameters:
5127        node - (object) A <Graph.Node>.
5128        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5129
5130        Example:
5131        (start code js)
5132          $jit.Graph.Util.eachSubgraph(node, function(node) {
5133            alert(node.name);
5134          });
5135          //or...
5136          node.eachSubgraph(function(node) {
5137            alert(node.name);
5138          });
5139        (end code)
5140     */
5141     eachSubgraph: function(node, action, flags) {
5142       this.eachLevel(node, 0, false, action, flags);
5143     },
5144
5145     /*
5146        Method: eachSubnode
5147     
5148        Iterates over a node's children (without deeper recursion).
5149        
5150        Also implemented by:
5151        
5152        <Graph.Node>.
5153        
5154        Parameters:
5155        node - (object) A <Graph.Node>.
5156        action - (function) A callback function having a <Graph.Node> as first formal parameter.
5157
5158        Example:
5159        (start code js)
5160          $jit.Graph.Util.eachSubnode(node, function(node) {
5161           alert(node.name);
5162          });
5163          //or...
5164          node.eachSubnode(function(node) {
5165            alert(node.name);
5166          });
5167        (end code)
5168     */
5169     eachSubnode: function(node, action, flags) {
5170         this.eachLevel(node, 1, 1, action, flags);
5171     },
5172
5173     /*
5174        Method: anySubnode
5175     
5176        Returns *true* if any subnode matches the given condition.
5177        
5178        Also implemented by:
5179        
5180        <Graph.Node>.
5181
5182        Parameters:
5183        node - (object) A <Graph.Node>.
5184        cond - (function) A callback function returning a Boolean instance. This function has as first formal parameter a <Graph.Node>.
5185
5186        Example:
5187        (start code js)
5188          $jit.Graph.Util.anySubnode(node, function(node) { return node.name == "mynodename"; });
5189          //or...
5190          node.anySubnode(function(node) { return node.name == 'mynodename'; });
5191        (end code)
5192     */
5193     anySubnode: function(node, cond, flags) {
5194       var flag = false;
5195       cond = cond || $.lambda(true);
5196       var c = $.type(cond) == 'string'? function(n) { return n[cond]; } : cond;
5197       this.eachSubnode(node, function(elem) {
5198         if(c(elem)) flag = true;
5199       }, flags);
5200       return flag;
5201     },
5202   
5203     /*
5204        Method: getSubnodes
5205     
5206        Collects all subnodes for a specified node. 
5207        The *level* parameter filters nodes having relative depth of *level* from the root node. 
5208        
5209        Also implemented by:
5210        
5211        <Graph.Node>.
5212
5213        Parameters:
5214        node - (object) A <Graph.Node>.
5215        level - (optional|number) Default's *0*. A starting relative depth for collecting nodes.
5216
5217        Returns:
5218        An array of nodes.
5219
5220     */
5221     getSubnodes: function(node, level, flags) {
5222         var ans = [], that = this;
5223         level = level || 0;
5224         var levelStart, levelEnd;
5225         if($.type(level) == 'array') {
5226             levelStart = level[0];
5227             levelEnd = level[1];
5228         } else {
5229             levelStart = level;
5230             levelEnd = Number.MAX_VALUE - node._depth;
5231         }
5232         this.eachLevel(node, levelStart, levelEnd, function(n) {
5233             ans.push(n);
5234         }, flags);
5235         return ans;
5236     },
5237   
5238   
5239     /*
5240        Method: getParents
5241     
5242        Returns an Array of <Graph.Nodes> which are parents of the given node.
5243        
5244        Also implemented by:
5245        
5246        <Graph.Node>.
5247
5248        Parameters:
5249        node - (object) A <Graph.Node>.
5250
5251        Returns:
5252        An Array of <Graph.Nodes>.
5253
5254        Example:
5255        (start code js)
5256          var pars = $jit.Graph.Util.getParents(node);
5257          //or...
5258          var pars = node.getParents();
5259          
5260          if(pars.length > 0) {
5261            //do stuff with parents
5262          }
5263        (end code)
5264     */
5265     getParents: function(node) {
5266         var ans = [];
5267         this.eachAdjacency(node, function(adj) {
5268             var n = adj.nodeTo;
5269             if(n._depth < node._depth) ans.push(n);
5270         });
5271         return ans;
5272     },
5273     
5274     /*
5275     Method: isDescendantOf
5276  
5277     Returns a boolean indicating if some node is descendant of the node with the given id. 
5278
5279     Also implemented by:
5280     
5281     <Graph.Node>.
5282     
5283     
5284     Parameters:
5285     node - (object) A <Graph.Node>.
5286     id - (string) A <Graph.Node> id.
5287
5288     Example:
5289     (start code js)
5290       $jit.Graph.Util.isDescendantOf(node, "nodeid"); //true|false
5291       //or...
5292       node.isDescendantOf('nodeid');//true|false
5293     (end code)
5294  */
5295  isDescendantOf: function(node, id) {
5296     if(node.id == id) return true;
5297     var pars = this.getParents(node), ans = false;
5298     for ( var i = 0; !ans && i < pars.length; i++) {
5299     ans = ans || this.isDescendantOf(pars[i], id);
5300   }
5301     return ans;
5302  },
5303
5304  /*
5305      Method: clean
5306   
5307      Cleans flags from nodes.
5308
5309      Also implemented by:
5310      
5311      <Graph>.
5312      
5313      Parameters:
5314      graph - A <Graph> instance.
5315   */
5316   clean: function(graph) { this.eachNode(graph, function(elem) { elem._flag = false; }); },
5317   
5318   /* 
5319     Method: getClosestNodeToOrigin 
5320   
5321     Returns the closest node to the center of canvas.
5322   
5323     Also implemented by:
5324     
5325     <Graph>.
5326     
5327     Parameters:
5328    
5329      graph - (object) A <Graph> instance.
5330      prop - (optional|string) Default's 'current'. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5331   
5332   */
5333   getClosestNodeToOrigin: function(graph, prop, flags) {
5334    return this.getClosestNodeToPos(graph, Polar.KER, prop, flags);
5335   },
5336   
5337   /* 
5338     Method: getClosestNodeToPos
5339   
5340     Returns the closest node to the given position.
5341   
5342     Also implemented by:
5343     
5344     <Graph>.
5345     
5346     Parameters:
5347    
5348      graph - (object) A <Graph> instance.
5349      pos - (object) A <Complex> or <Polar> instance.
5350      prop - (optional|string) Default's *current*. A <Graph.Node> position property. Possible properties are 'start', 'current' or 'end'.
5351   
5352   */
5353   getClosestNodeToPos: function(graph, pos, prop, flags) {
5354    var node = null;
5355    prop = prop || 'current';
5356    pos = pos && pos.getc(true) || Complex.KER;
5357    var distance = function(a, b) {
5358      var d1 = a.x - b.x, d2 = a.y - b.y;
5359      return d1 * d1 + d2 * d2;
5360    };
5361    this.eachNode(graph, function(elem) {
5362      node = (node == null || distance(elem.getPos(prop).getc(true), pos) < distance(
5363          node.getPos(prop).getc(true), pos)) ? elem : node;
5364    }, flags);
5365    return node;
5366   } 
5367 };
5368
5369 //Append graph methods to <Graph>
5370 $.each(['getNode', 'eachNode', 'computeLevels', 'eachBFS', 'clean', 'getClosestNodeToPos', 'getClosestNodeToOrigin'], function(m) {
5371   Graph.prototype[m] = function() {
5372     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5373   };
5374 });
5375
5376 //Append node methods to <Graph.Node>
5377 $.each(['eachAdjacency', 'eachLevel', 'eachSubgraph', 'eachSubnode', 'anySubnode', 'getSubnodes', 'getParents', 'isDescendantOf'], function(m) {
5378   Graph.Node.prototype[m] = function() {
5379     return Graph.Util[m].apply(Graph.Util, [this].concat(Array.prototype.slice.call(arguments)));
5380   };
5381 });
5382
5383 /*
5384  * File: Graph.Op.js
5385  *
5386 */
5387
5388 /*
5389    Object: Graph.Op
5390
5391    Perform <Graph> operations like adding/removing <Graph.Nodes> or <Graph.Adjacences>, 
5392    morphing a <Graph> into another <Graph>, contracting or expanding subtrees, etc.
5393
5394 */
5395 Graph.Op = {
5396
5397     options: {
5398       type: 'nothing',
5399       duration: 2000,
5400       hideLabels: true,
5401       fps:30
5402     },
5403     
5404     initialize: function(viz) {
5405       this.viz = viz;
5406     },
5407
5408     /*
5409        Method: removeNode
5410     
5411        Removes one or more <Graph.Nodes> from the visualization. 
5412        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5413
5414        Parameters:
5415     
5416         node - (string|array) The node's id. Can also be an array having many ids.
5417         opt - (object) Animation options. It's an object with optional properties described below
5418         type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5419         duration - Described in <Options.Fx>.
5420         fps - Described in <Options.Fx>.
5421         transition - Described in <Options.Fx>.
5422         hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5423    
5424       Example:
5425       (start code js)
5426         var viz = new $jit.Viz(options);
5427         viz.op.removeNode('nodeId', {
5428           type: 'fade:seq',
5429           duration: 1000,
5430           hideLabels: false,
5431           transition: $jit.Trans.Quart.easeOut
5432         });
5433         //or also
5434         viz.op.removeNode(['someId', 'otherId'], {
5435           type: 'fade:con',
5436           duration: 1500
5437         });
5438       (end code)
5439     */
5440   
5441     removeNode: function(node, opt) {
5442         var viz = this.viz;
5443         var options = $.merge(this.options, viz.controller, opt);
5444         var n = $.splat(node);
5445         var i, that, nodeObj;
5446         switch(options.type) {
5447             case 'nothing':
5448                 for(i=0; i<n.length; i++) viz.graph.removeNode(n[i]);
5449                 break;
5450             
5451             case 'replot':
5452                 this.removeNode(n, { type: 'nothing' });
5453                 viz.labels.clearLabels();
5454                 viz.refresh(true);
5455                 break;
5456             
5457             case 'fade:seq': case 'fade':
5458                 that = this;
5459                 //set alpha to 0 for nodes to remove.
5460                 for(i=0; i<n.length; i++) {
5461                     nodeObj = viz.graph.getNode(n[i]);
5462                     nodeObj.setData('alpha', 0, 'end');
5463                 }
5464                 viz.fx.animate($.merge(options, {
5465                     modes: ['node-property:alpha'],
5466                     onComplete: function() {
5467                         that.removeNode(n, { type: 'nothing' });
5468                         viz.labels.clearLabels();
5469                         viz.reposition();
5470                         viz.fx.animate($.merge(options, {
5471                             modes: ['linear']
5472                         }));
5473                     }
5474                 }));
5475                 break;
5476             
5477             case 'fade:con':
5478                 that = this;
5479                 //set alpha to 0 for nodes to remove. Tag them for being ignored on computing positions.
5480                 for(i=0; i<n.length; i++) {
5481                     nodeObj = viz.graph.getNode(n[i]);
5482                     nodeObj.setData('alpha', 0, 'end');
5483                     nodeObj.ignore = true;
5484                 }
5485                 viz.reposition();
5486                 viz.fx.animate($.merge(options, {
5487                     modes: ['node-property:alpha', 'linear'],
5488                     onComplete: function() {
5489                         that.removeNode(n, { type: 'nothing' });
5490                     }
5491                 }));
5492                 break;
5493             
5494             case 'iter':
5495                 that = this;
5496                 viz.fx.sequence({
5497                     condition: function() { return n.length != 0; },
5498                     step: function() { that.removeNode(n.shift(), { type: 'nothing' });  viz.labels.clearLabels(); },
5499                     onComplete: function() { options.onComplete(); },
5500                     duration: Math.ceil(options.duration / n.length)
5501                 });
5502                 break;
5503                 
5504             default: this.doError();
5505         }
5506     },
5507     
5508     /*
5509        Method: removeEdge
5510     
5511        Removes one or more <Graph.Adjacences> from the visualization. 
5512        It can also perform several animations like fading sequentially, fading concurrently, iterating or replotting.
5513
5514        Parameters:
5515     
5516        vertex - (array) An array having two strings which are the ids of the nodes connected by this edge (i.e ['id1', 'id2']). Can also be a two dimensional array holding many edges (i.e [['id1', 'id2'], ['id3', 'id4'], ...]).
5517        opt - (object) Animation options. It's an object with optional properties described below
5518        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con" or "iter".
5519        duration - Described in <Options.Fx>.
5520        fps - Described in <Options.Fx>.
5521        transition - Described in <Options.Fx>.
5522        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5523    
5524       Example:
5525       (start code js)
5526         var viz = new $jit.Viz(options);
5527         viz.op.removeEdge(['nodeId', 'otherId'], {
5528           type: 'fade:seq',
5529           duration: 1000,
5530           hideLabels: false,
5531           transition: $jit.Trans.Quart.easeOut
5532         });
5533         //or also
5534         viz.op.removeEdge([['someId', 'otherId'], ['id3', 'id4']], {
5535           type: 'fade:con',
5536           duration: 1500
5537         });
5538       (end code)
5539     
5540     */
5541     removeEdge: function(vertex, opt) {
5542         var viz = this.viz;
5543         var options = $.merge(this.options, viz.controller, opt);
5544         var v = ($.type(vertex[0]) == 'string')? [vertex] : vertex;
5545         var i, that, adj;
5546         switch(options.type) {
5547             case 'nothing':
5548                 for(i=0; i<v.length; i++)   viz.graph.removeAdjacence(v[i][0], v[i][1]);
5549                 break;
5550             
5551             case 'replot':
5552                 this.removeEdge(v, { type: 'nothing' });
5553                 viz.refresh(true);
5554                 break;
5555             
5556             case 'fade:seq': case 'fade':
5557                 that = this;
5558                 //set alpha to 0 for edges to remove.
5559                 for(i=0; i<v.length; i++) {
5560                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5561                     if(adj) {
5562                         adj.setData('alpha', 0,'end');
5563                     }
5564                 }
5565                 viz.fx.animate($.merge(options, {
5566                     modes: ['edge-property:alpha'],
5567                     onComplete: function() {
5568                         that.removeEdge(v, { type: 'nothing' });
5569                         viz.reposition();
5570                         viz.fx.animate($.merge(options, {
5571                             modes: ['linear']
5572                         }));
5573                     }
5574                 }));
5575                 break;
5576             
5577             case 'fade:con':
5578                 that = this;
5579                 //set alpha to 0 for nodes to remove. Tag them for being ignored when computing positions.
5580                 for(i=0; i<v.length; i++) {
5581                     adj = viz.graph.getAdjacence(v[i][0], v[i][1]);
5582                     if(adj) {
5583                         adj.setData('alpha',0 ,'end');
5584                         adj.ignore = true;
5585                     }
5586                 }
5587                 viz.reposition();
5588                 viz.fx.animate($.merge(options, {
5589                     modes: ['edge-property:alpha', 'linear'],
5590                     onComplete: function() {
5591                         that.removeEdge(v, { type: 'nothing' });
5592                     }
5593                 }));
5594                 break;
5595             
5596             case 'iter':
5597                 that = this;
5598                 viz.fx.sequence({
5599                     condition: function() { return v.length != 0; },
5600                     step: function() { that.removeEdge(v.shift(), { type: 'nothing' }); viz.labels.clearLabels(); },
5601                     onComplete: function() { options.onComplete(); },
5602                     duration: Math.ceil(options.duration / v.length)
5603                 });
5604                 break;
5605                 
5606             default: this.doError();
5607         }
5608     },
5609     
5610     /*
5611        Method: sum
5612     
5613        Adds a new graph to the visualization. 
5614        The JSON graph (or tree) must at least have a common node with the current graph plotted by the visualization. 
5615        The resulting graph can be defined as follows <http://mathworld.wolfram.com/GraphSum.html>
5616
5617        Parameters:
5618     
5619        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5620        opt - (object) Animation options. It's an object with optional properties described below
5621        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:seq",  "fade:con".
5622        duration - Described in <Options.Fx>.
5623        fps - Described in <Options.Fx>.
5624        transition - Described in <Options.Fx>.
5625        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5626    
5627       Example:
5628       (start code js)
5629         //...json contains a tree or graph structure...
5630
5631         var viz = new $jit.Viz(options);
5632         viz.op.sum(json, {
5633           type: 'fade:seq',
5634           duration: 1000,
5635           hideLabels: false,
5636           transition: $jit.Trans.Quart.easeOut
5637         });
5638         //or also
5639         viz.op.sum(json, {
5640           type: 'fade:con',
5641           duration: 1500
5642         });
5643       (end code)
5644     
5645     */
5646     sum: function(json, opt) {
5647         var viz = this.viz;
5648         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5649         var graph;
5650         viz.root = opt.id || viz.root;
5651         switch(options.type) {
5652             case 'nothing':
5653                 graph = viz.construct(json);
5654                 graph.eachNode(function(elem) {
5655                     elem.eachAdjacency(function(adj) {
5656                         viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5657                     });
5658                 });
5659                 break;
5660             
5661             case 'replot':
5662                 viz.refresh(true);
5663                 this.sum(json, { type: 'nothing' });
5664                 viz.refresh(true);
5665                 break;
5666             
5667             case 'fade:seq': case 'fade': case 'fade:con':
5668                 that = this;
5669                 graph = viz.construct(json);
5670
5671                 //set alpha to 0 for nodes to add.
5672                 var fadeEdges = this.preprocessSum(graph);
5673                 var modes = !fadeEdges? ['node-property:alpha'] : ['node-property:alpha', 'edge-property:alpha'];
5674                 viz.reposition();
5675                 if(options.type != 'fade:con') {
5676                     viz.fx.animate($.merge(options, {
5677                         modes: ['linear'],
5678                         onComplete: function() {
5679                             viz.fx.animate($.merge(options, {
5680                                 modes: modes,
5681                                 onComplete: function() {
5682                                     options.onComplete();
5683                                 }
5684                             }));
5685                         }
5686                     }));
5687                 } else {
5688                     viz.graph.eachNode(function(elem) {
5689                         if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5690                           elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5691                         }
5692                     });
5693                     viz.fx.animate($.merge(options, {
5694                         modes: ['linear'].concat(modes)
5695                     }));
5696                 }
5697                 break;
5698
5699             default: this.doError();
5700         }
5701     },
5702     
5703     /*
5704        Method: morph
5705     
5706        This method will transform the current visualized graph into the new JSON representation passed in the method. 
5707        The JSON object must at least have the root node in common with the current visualized graph.
5708
5709        Parameters:
5710     
5711        json - (object) A json tree or graph structure. See also <Loader.loadJSON>.
5712        opt - (object) Animation options. It's an object with optional properties described below
5713        type - (string) Default's *nothing*. Type of the animation. Can be "nothing", "replot", "fade:con".
5714        duration - Described in <Options.Fx>.
5715        fps - Described in <Options.Fx>.
5716        transition - Described in <Options.Fx>.
5717        hideLabels - (boolean) Default's *true*. Hide labels during the animation.
5718        id - (string) The shared <Graph.Node> id between both graphs.
5719        
5720        extraModes - (optional|object) When morphing with an animation, dollar prefixed data parameters are added to 
5721                     *endData* and not *data* itself. This way you can animate dollar prefixed parameters during your morphing operation. 
5722                     For animating these extra-parameters you have to specify an object that has animation groups as keys and animation 
5723                     properties as values, just like specified in <Graph.Plot.animate>.
5724    
5725       Example:
5726       (start code js)
5727         //...json contains a tree or graph structure...
5728
5729         var viz = new $jit.Viz(options);
5730         viz.op.morph(json, {
5731           type: 'fade',
5732           duration: 1000,
5733           hideLabels: false,
5734           transition: $jit.Trans.Quart.easeOut
5735         });
5736         //or also
5737         viz.op.morph(json, {
5738           type: 'fade',
5739           duration: 1500
5740         });
5741         //if the json data contains dollar prefixed params
5742         //like $width or $height these too can be animated
5743         viz.op.morph(json, {
5744           type: 'fade',
5745           duration: 1500
5746         }, {
5747           'node-property': ['width', 'height']
5748         });
5749       (end code)
5750     
5751     */
5752     morph: function(json, opt, extraModes) {
5753         var viz = this.viz;
5754         var options = $.merge(this.options, viz.controller, opt), root = viz.root;
5755         var graph;
5756         //TODO(nico) this hack makes morphing work with the Hypertree. 
5757         //Need to check if it has been solved and this can be removed.
5758         viz.root = opt.id || viz.root;
5759         switch(options.type) {
5760             case 'nothing':
5761                 graph = viz.construct(json);
5762                 graph.eachNode(function(elem) {
5763                   var nodeExists = viz.graph.hasNode(elem.id);  
5764                   elem.eachAdjacency(function(adj) {
5765                     var adjExists = !!viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5766                     viz.graph.addAdjacence(adj.nodeFrom, adj.nodeTo, adj.data);
5767                     //Update data properties if the node existed
5768                     if(adjExists) {
5769                       var addedAdj = viz.graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5770                       for(var prop in (adj.data || {})) {
5771                         addedAdj.data[prop] = adj.data[prop];
5772                       }
5773                     }
5774                   });
5775                   //Update data properties if the node existed
5776                   if(nodeExists) {
5777                     var addedNode = viz.graph.getNode(elem.id);
5778                     for(var prop in (elem.data || {})) {
5779                       addedNode.data[prop] = elem.data[prop];
5780                     }
5781                   }
5782                 });
5783                 viz.graph.eachNode(function(elem) {
5784                     elem.eachAdjacency(function(adj) {
5785                         if(!graph.getAdjacence(adj.nodeFrom.id, adj.nodeTo.id)) {
5786                             viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5787                         }
5788                     });
5789                     if(!graph.hasNode(elem.id)) viz.graph.removeNode(elem.id);
5790                 });
5791                 
5792                 break;
5793             
5794             case 'replot':
5795                 viz.labels.clearLabels(true);
5796                 this.morph(json, { type: 'nothing' });
5797                 viz.refresh(true);
5798                 viz.refresh(true);
5799                 break;
5800                 
5801             case 'fade:seq': case 'fade': case 'fade:con':
5802                 that = this;
5803                 graph = viz.construct(json);
5804                 //preprocessing for nodes to delete.
5805                 //get node property modes to interpolate
5806                 var nodeModes = extraModes && ('node-property' in extraModes) 
5807                   && $.map($.splat(extraModes['node-property']), 
5808                       function(n) { return '$' + n; });
5809                 viz.graph.eachNode(function(elem) {
5810                   var graphNode = graph.getNode(elem.id);   
5811                   if(!graphNode) {
5812                       elem.setData('alpha', 1);
5813                       elem.setData('alpha', 1, 'start');
5814                       elem.setData('alpha', 0, 'end');
5815                       elem.ignore = true;
5816                     } else {
5817                       //Update node data information
5818                       var graphNodeData = graphNode.data;
5819                       for(var prop in graphNodeData) {
5820                         if(nodeModes && ($.indexOf(nodeModes, prop) > -1)) {
5821                           elem.endData[prop] = graphNodeData[prop];
5822                         } else {
5823                           elem.data[prop] = graphNodeData[prop];
5824                         }
5825                       }
5826                     }
5827                 }); 
5828                 viz.graph.eachNode(function(elem) {
5829                     if(elem.ignore) return;
5830                     elem.eachAdjacency(function(adj) {
5831                         if(adj.nodeFrom.ignore || adj.nodeTo.ignore) return;
5832                         var nodeFrom = graph.getNode(adj.nodeFrom.id);
5833                         var nodeTo = graph.getNode(adj.nodeTo.id);
5834                         if(!nodeFrom.adjacentTo(nodeTo)) {
5835                             var adj = viz.graph.getAdjacence(nodeFrom.id, nodeTo.id);
5836                             fadeEdges = true;
5837                             adj.setData('alpha', 1);
5838                             adj.setData('alpha', 1, 'start');
5839                             adj.setData('alpha', 0, 'end');
5840                         }
5841                     });
5842                 }); 
5843                 //preprocessing for adding nodes.
5844                 var fadeEdges = this.preprocessSum(graph);
5845
5846                 var modes = !fadeEdges? ['node-property:alpha'] : 
5847                                         ['node-property:alpha', 
5848                                          'edge-property:alpha'];
5849                 //Append extra node-property animations (if any)
5850                 modes[0] = modes[0] + ((extraModes && ('node-property' in extraModes))? 
5851                     (':' + $.splat(extraModes['node-property']).join(':')) : '');
5852                 //Append extra edge-property animations (if any)
5853                 modes[1] = (modes[1] || 'edge-property:alpha') + ((extraModes && ('edge-property' in extraModes))? 
5854                     (':' + $.splat(extraModes['edge-property']).join(':')) : '');
5855                 //Add label-property animations (if any)
5856                 if(extraModes && ('label-property' in extraModes)) {
5857                   modes.push('label-property:' + $.splat(extraModes['label-property']).join(':'))
5858                 }
5859                 viz.reposition();
5860                 viz.graph.eachNode(function(elem) {
5861                     if (elem.id != root && elem.pos.getp().equals(Polar.KER)) {
5862                       elem.pos.set(elem.endPos); elem.startPos.set(elem.endPos);
5863                     }
5864                 });
5865                 viz.fx.animate($.merge(options, {
5866                     modes: ['polar'].concat(modes),
5867                     onComplete: function() {
5868                         viz.graph.eachNode(function(elem) {
5869                             if(elem.ignore) viz.graph.removeNode(elem.id);
5870                         });
5871                         viz.graph.eachNode(function(elem) {
5872                             elem.eachAdjacency(function(adj) {
5873                                 if(adj.ignore) viz.graph.removeAdjacence(adj.nodeFrom.id, adj.nodeTo.id);
5874                             });
5875                         });
5876                         options.onComplete();
5877                     }
5878                 }));
5879                 break;
5880
5881             default:;
5882         }
5883     },
5884
5885     
5886   /*
5887     Method: contract
5888  
5889     Collapses the subtree of the given node. The node will have a _collapsed=true_ property.
5890     
5891     Parameters:
5892  
5893     node - (object) A <Graph.Node>.
5894     opt - (object) An object containing options described below
5895     type - (string) Whether to 'replot' or 'animate' the contraction.
5896    
5897     There are also a number of Animation options. For more information see <Options.Fx>.
5898
5899     Example:
5900     (start code js)
5901      var viz = new $jit.Viz(options);
5902      viz.op.contract(node, {
5903        type: 'animate',
5904        duration: 1000,
5905        hideLabels: true,
5906        transition: $jit.Trans.Quart.easeOut
5907      });
5908    (end code)
5909  
5910    */
5911     contract: function(node, opt) {
5912       var viz = this.viz;
5913       if(node.collapsed || !node.anySubnode($.lambda(true))) return;
5914       opt = $.merge(this.options, viz.config, opt || {}, {
5915         'modes': ['node-property:alpha:span', 'linear']
5916       });
5917       node.collapsed = true;
5918       (function subn(n) {
5919         n.eachSubnode(function(ch) {
5920           ch.ignore = true;
5921           ch.setData('alpha', 0, opt.type == 'animate'? 'end' : 'current');
5922           subn(ch);
5923         });
5924       })(node);
5925       if(opt.type == 'animate') {
5926         viz.compute('end');
5927         if(viz.rotated) {
5928           viz.rotate(viz.rotated, 'none', {
5929             'property':'end'
5930           });
5931         }
5932         (function subn(n) {
5933           n.eachSubnode(function(ch) {
5934             ch.setPos(node.getPos('end'), 'end');
5935             subn(ch);
5936           });
5937         })(node);
5938         viz.fx.animate(opt);
5939       } else if(opt.type == 'replot'){
5940         viz.refresh();
5941       }
5942     },
5943     
5944     /*
5945     Method: expand
5946  
5947     Expands the previously contracted subtree. The given node must have the _collapsed=true_ property.
5948     
5949     Parameters:
5950  
5951     node - (object) A <Graph.Node>.
5952     opt - (object) An object containing options described below
5953     type - (string) Whether to 'replot' or 'animate'.
5954      
5955     There are also a number of Animation options. For more information see <Options.Fx>.
5956
5957     Example:
5958     (start code js)
5959       var viz = new $jit.Viz(options);
5960       viz.op.expand(node, {
5961         type: 'animate',
5962         duration: 1000,
5963         hideLabels: true,
5964         transition: $jit.Trans.Quart.easeOut
5965       });
5966     (end code)
5967  
5968    */
5969     expand: function(node, opt) {
5970       if(!('collapsed' in node)) return;
5971       var viz = this.viz;
5972       opt = $.merge(this.options, viz.config, opt || {}, {
5973         'modes': ['node-property:alpha:span', 'linear']
5974       });
5975       delete node.collapsed;
5976       (function subn(n) {
5977         n.eachSubnode(function(ch) {
5978           delete ch.ignore;
5979           ch.setData('alpha', 1, opt.type == 'animate'? 'end' : 'current');
5980           subn(ch);
5981         });
5982       })(node);
5983       if(opt.type == 'animate') {
5984         viz.compute('end');
5985         if(viz.rotated) {
5986           viz.rotate(viz.rotated, 'none', {
5987             'property':'end'
5988           });
5989         }
5990         viz.fx.animate(opt);
5991       } else if(opt.type == 'replot'){
5992         viz.refresh();
5993       }
5994     },
5995
5996     preprocessSum: function(graph) {
5997         var viz = this.viz;
5998         graph.eachNode(function(elem) {
5999             if(!viz.graph.hasNode(elem.id)) {
6000                 viz.graph.addNode(elem);
6001                 var n = viz.graph.getNode(elem.id);
6002                 n.setData('alpha', 0);
6003                 n.setData('alpha', 0, 'start');
6004                 n.setData('alpha', 1, 'end');
6005             }
6006         }); 
6007         var fadeEdges = false;
6008         graph.eachNode(function(elem) {
6009             elem.eachAdjacency(function(adj) {
6010                 var nodeFrom = viz.graph.getNode(adj.nodeFrom.id);
6011                 var nodeTo = viz.graph.getNode(adj.nodeTo.id);
6012                 if(!nodeFrom.adjacentTo(nodeTo)) {
6013                     var adj = viz.graph.addAdjacence(nodeFrom, nodeTo, adj.data);
6014                     if(nodeFrom.startAlpha == nodeFrom.endAlpha 
6015                     && nodeTo.startAlpha == nodeTo.endAlpha) {
6016                         fadeEdges = true;
6017                         adj.setData('alpha', 0);
6018                         adj.setData('alpha', 0, 'start');
6019                         adj.setData('alpha', 1, 'end');
6020                     } 
6021                 }
6022             });
6023         }); 
6024         return fadeEdges;
6025     }
6026 };
6027
6028
6029
6030 /*
6031    File: Helpers.js
6032  
6033    Helpers are objects that contain rendering primitives (like rectangles, ellipses, etc), for plotting nodes and edges.
6034    Helpers also contain implementations of the *contains* method, a method returning a boolean indicating whether the mouse
6035    position is over the rendered shape.
6036    
6037    Helpers are very useful when implementing new NodeTypes, since you can access them through *this.nodeHelper* and 
6038    *this.edgeHelper* <Graph.Plot> properties, providing you with simple primitives and mouse-position check functions.
6039    
6040    Example:
6041    (start code js)
6042    //implement a new node type
6043    $jit.Viz.Plot.NodeTypes.implement({
6044      'customNodeType': {
6045        'render': function(node, canvas) {
6046          this.nodeHelper.circle.render ...
6047        },
6048        'contains': function(node, pos) {
6049          this.nodeHelper.circle.contains ...
6050        }
6051      }
6052    });
6053    //implement an edge type
6054    $jit.Viz.Plot.EdgeTypes.implement({
6055      'customNodeType': {
6056        'render': function(node, canvas) {
6057          this.edgeHelper.circle.render ...
6058        },
6059        //optional
6060        'contains': function(node, pos) {
6061          this.edgeHelper.circle.contains ...
6062        }
6063      }
6064    });
6065    (end code)
6066
6067 */
6068
6069 /*
6070    Object: NodeHelper
6071    
6072    Contains rendering and other type of primitives for simple shapes.
6073  */
6074 var NodeHelper = {
6075   'none': {
6076     'render': $.empty,
6077     'contains': $.lambda(false)
6078   },
6079   /*
6080    Object: NodeHelper.circle
6081    */
6082   'circle': {
6083     /*
6084      Method: render
6085      
6086      Renders a circle into the canvas.
6087      
6088      Parameters:
6089      
6090      type - (string) Possible options are 'fill' or 'stroke'.
6091      pos - (object) An *x*, *y* object with the position of the center of the circle.
6092      radius - (number) The radius of the circle to be rendered.
6093      canvas - (object) A <Canvas> instance.
6094      
6095      Example:
6096      (start code js)
6097      NodeHelper.circle.render('fill', { x: 10, y: 30 }, 30, viz.canvas);
6098      (end code)
6099      */
6100     'render': function(type, pos, radius, canvas){
6101       var ctx = canvas.getCtx();
6102       ctx.beginPath();
6103       ctx.arc(pos.x, pos.y, radius, 0, Math.PI * 2, true);
6104       ctx.closePath();
6105       ctx[type]();
6106     },
6107     /*
6108     Method: contains
6109     
6110     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6111     
6112     Parameters:
6113     
6114     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6115     pos - (object) An *x*, *y* object with the position to check.
6116     radius - (number) The radius of the rendered circle.
6117     
6118     Example:
6119     (start code js)
6120     NodeHelper.circle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30); //true
6121     (end code)
6122     */
6123     'contains': function(npos, pos, radius){
6124       var diffx = npos.x - pos.x, 
6125           diffy = npos.y - pos.y, 
6126           diff = diffx * diffx + diffy * diffy;
6127       return diff <= radius * radius;
6128     }
6129   },
6130   /*
6131   Object: NodeHelper.ellipse
6132   */
6133   'ellipse': {
6134     /*
6135     Method: render
6136     
6137     Renders an ellipse into the canvas.
6138     
6139     Parameters:
6140     
6141     type - (string) Possible options are 'fill' or 'stroke'.
6142     pos - (object) An *x*, *y* object with the position of the center of the ellipse.
6143     width - (number) The width of the ellipse.
6144     height - (number) The height of the ellipse.
6145     canvas - (object) A <Canvas> instance.
6146     
6147     Example:
6148     (start code js)
6149     NodeHelper.ellipse.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6150     (end code)
6151     */
6152     'render': function(type, pos, width, height, canvas){
6153       var ctx = canvas.getCtx();
6154       height /= 2;
6155       width /= 2;
6156       ctx.save();
6157       ctx.scale(width / height, height / width);
6158       ctx.beginPath();
6159       ctx.arc(pos.x * (height / width), pos.y * (width / height), height, 0,
6160           Math.PI * 2, true);
6161       ctx.closePath();
6162       ctx[type]();
6163       ctx.restore();
6164     },
6165     /*
6166     Method: contains
6167     
6168     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6169     
6170     Parameters:
6171     
6172     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6173     pos - (object) An *x*, *y* object with the position to check.
6174     width - (number) The width of the rendered ellipse.
6175     height - (number) The height of the rendered ellipse.
6176     
6177     Example:
6178     (start code js)
6179     NodeHelper.ellipse.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6180     (end code)
6181     */
6182     'contains': function(npos, pos, width, height){
6183       // TODO(nico): be more precise...
6184       width /= 2; 
6185       height /= 2;
6186       var dist = (width + height) / 2, 
6187           diffx = npos.x - pos.x, 
6188           diffy = npos.y - pos.y, 
6189           diff = diffx * diffx + diffy * diffy;
6190       return diff <= dist * dist;
6191     }
6192   },
6193   /*
6194   Object: NodeHelper.square
6195   */
6196   'square': {
6197     /*
6198     Method: render
6199     
6200     Renders a square into the canvas.
6201     
6202     Parameters:
6203     
6204     type - (string) Possible options are 'fill' or 'stroke'.
6205     pos - (object) An *x*, *y* object with the position of the center of the square.
6206     dim - (number) The radius (or half-diameter) of the square.
6207     canvas - (object) A <Canvas> instance.
6208     
6209     Example:
6210     (start code js)
6211     NodeHelper.square.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6212     (end code)
6213     */
6214     'render': function(type, pos, dim, canvas){
6215       canvas.getCtx()[type + "Rect"](pos.x - dim, pos.y - dim, 2*dim, 2*dim);
6216     },
6217     /*
6218     Method: contains
6219     
6220     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6221     
6222     Parameters:
6223     
6224     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6225     pos - (object) An *x*, *y* object with the position to check.
6226     dim - (number) The radius (or half-diameter) of the square.
6227     
6228     Example:
6229     (start code js)
6230     NodeHelper.square.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6231     (end code)
6232     */
6233     'contains': function(npos, pos, dim){
6234       return Math.abs(pos.x - npos.x) <= dim && Math.abs(pos.y - npos.y) <= dim;
6235     }
6236   },
6237   /*
6238   Object: NodeHelper.rectangle
6239   */
6240   'rectangle': {
6241     /*
6242     Method: render
6243     
6244     Renders a rectangle into the canvas.
6245     
6246     Parameters:
6247     
6248     type - (string) Possible options are 'fill' or 'stroke'.
6249     pos - (object) An *x*, *y* object with the position of the center of the rectangle.
6250     width - (number) The width of the rectangle.
6251     height - (number) The height of the rectangle.
6252     canvas - (object) A <Canvas> instance.
6253     
6254     Example:
6255     (start code js)
6256     NodeHelper.rectangle.render('fill', { x: 10, y: 30 }, 30, 40, viz.canvas);
6257     (end code)
6258     */
6259     'render': function(type, pos, width, height, canvas){
6260       canvas.getCtx()[type + "Rect"](pos.x - width / 2, pos.y - height / 2, 
6261                                       width, height);
6262     },
6263     /*
6264     Method: contains
6265     
6266     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6267     
6268     Parameters:
6269     
6270     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6271     pos - (object) An *x*, *y* object with the position to check.
6272     width - (number) The width of the rendered rectangle.
6273     height - (number) The height of the rendered rectangle.
6274     
6275     Example:
6276     (start code js)
6277     NodeHelper.rectangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30, 40);
6278     (end code)
6279     */
6280     'contains': function(npos, pos, width, height){
6281       return Math.abs(pos.x - npos.x) <= width / 2
6282           && Math.abs(pos.y - npos.y) <= height / 2;
6283     }
6284   },
6285   /*
6286   Object: NodeHelper.triangle
6287   */
6288   'triangle': {
6289     /*
6290     Method: render
6291     
6292     Renders a triangle into the canvas.
6293     
6294     Parameters:
6295     
6296     type - (string) Possible options are 'fill' or 'stroke'.
6297     pos - (object) An *x*, *y* object with the position of the center of the triangle.
6298     dim - (number) The dimension of the triangle.
6299     canvas - (object) A <Canvas> instance.
6300     
6301     Example:
6302     (start code js)
6303     NodeHelper.triangle.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6304     (end code)
6305     */
6306     'render': function(type, pos, dim, canvas){
6307       var ctx = canvas.getCtx(), 
6308           c1x = pos.x, 
6309           c1y = pos.y - dim, 
6310           c2x = c1x - dim, 
6311           c2y = pos.y + dim, 
6312           c3x = c1x + dim, 
6313           c3y = c2y;
6314       ctx.beginPath();
6315       ctx.moveTo(c1x, c1y);
6316       ctx.lineTo(c2x, c2y);
6317       ctx.lineTo(c3x, c3y);
6318       ctx.closePath();
6319       ctx[type]();
6320     },
6321     /*
6322     Method: contains
6323     
6324     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6325     
6326     Parameters:
6327     
6328     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6329     pos - (object) An *x*, *y* object with the position to check.
6330     dim - (number) The dimension of the shape.
6331     
6332     Example:
6333     (start code js)
6334     NodeHelper.triangle.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6335     (end code)
6336     */
6337     'contains': function(npos, pos, dim) {
6338       return NodeHelper.circle.contains(npos, pos, dim);
6339     }
6340   },
6341   /*
6342   Object: NodeHelper.star
6343   */
6344   'star': {
6345     /*
6346     Method: render
6347     
6348     Renders a star into the canvas.
6349     
6350     Parameters:
6351     
6352     type - (string) Possible options are 'fill' or 'stroke'.
6353     pos - (object) An *x*, *y* object with the position of the center of the star.
6354     dim - (number) The dimension of the star.
6355     canvas - (object) A <Canvas> instance.
6356     
6357     Example:
6358     (start code js)
6359     NodeHelper.star.render('stroke', { x: 10, y: 30 }, 40, viz.canvas);
6360     (end code)
6361     */
6362     'render': function(type, pos, dim, canvas){
6363       var ctx = canvas.getCtx(), 
6364           pi5 = Math.PI / 5;
6365       ctx.save();
6366       ctx.translate(pos.x, pos.y);
6367       ctx.beginPath();
6368       ctx.moveTo(dim, 0);
6369       for (var i = 0; i < 9; i++) {
6370         ctx.rotate(pi5);
6371         if (i % 2 == 0) {
6372           ctx.lineTo((dim / 0.525731) * 0.200811, 0);
6373         } else {
6374           ctx.lineTo(dim, 0);
6375         }
6376       }
6377       ctx.closePath();
6378       ctx[type]();
6379       ctx.restore();
6380     },
6381     /*
6382     Method: contains
6383     
6384     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6385     
6386     Parameters:
6387     
6388     npos - (object) An *x*, *y* object with the <Graph.Node> position.
6389     pos - (object) An *x*, *y* object with the position to check.
6390     dim - (number) The dimension of the shape.
6391     
6392     Example:
6393     (start code js)
6394     NodeHelper.star.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, 30);
6395     (end code)
6396     */
6397     'contains': function(npos, pos, dim) {
6398       return NodeHelper.circle.contains(npos, pos, dim);
6399     }
6400   }
6401 };
6402
6403 /*
6404   Object: EdgeHelper
6405   
6406   Contains rendering primitives for simple edge shapes.
6407 */
6408 var EdgeHelper = {
6409   /*
6410     Object: EdgeHelper.line
6411   */
6412   'line': {
6413       /*
6414       Method: render
6415       
6416       Renders a line into the canvas.
6417       
6418       Parameters:
6419       
6420       from - (object) An *x*, *y* object with the starting position of the line.
6421       to - (object) An *x*, *y* object with the ending position of the line.
6422       canvas - (object) A <Canvas> instance.
6423       
6424       Example:
6425       (start code js)
6426       EdgeHelper.line.render({ x: 10, y: 30 }, { x: 10, y: 50 }, viz.canvas);
6427       (end code)
6428       */
6429       'render': function(from, to, canvas){
6430         var ctx = canvas.getCtx();
6431         ctx.beginPath();
6432         ctx.moveTo(from.x, from.y);
6433         ctx.lineTo(to.x, to.y);
6434         ctx.stroke();
6435       },
6436       /*
6437       Method: contains
6438       
6439       Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6440       
6441       Parameters:
6442       
6443       posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6444       posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6445       pos - (object) An *x*, *y* object with the position to check.
6446       epsilon - (number) The dimension of the shape.
6447       
6448       Example:
6449       (start code js)
6450       EdgeHelper.line.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6451       (end code)
6452       */
6453       'contains': function(posFrom, posTo, pos, epsilon) {
6454         var min = Math.min, 
6455             max = Math.max,
6456             minPosX = min(posFrom.x, posTo.x),
6457             maxPosX = max(posFrom.x, posTo.x),
6458             minPosY = min(posFrom.y, posTo.y),
6459             maxPosY = max(posFrom.y, posTo.y);
6460         
6461         if(pos.x >= minPosX && pos.x <= maxPosX 
6462             && pos.y >= minPosY && pos.y <= maxPosY) {
6463           if(Math.abs(posTo.x - posFrom.x) <= epsilon) {
6464             return true;
6465           }
6466           var dist = (posTo.y - posFrom.y) / (posTo.x - posFrom.x) * (pos.x - posFrom.x) + posFrom.y;
6467           return Math.abs(dist - pos.y) <= epsilon;
6468         }
6469         return false;
6470       }
6471     },
6472   /*
6473     Object: EdgeHelper.arrow
6474   */
6475   'arrow': {
6476       /*
6477       Method: render
6478       
6479       Renders an arrow into the canvas.
6480       
6481       Parameters:
6482       
6483       from - (object) An *x*, *y* object with the starting position of the arrow.
6484       to - (object) An *x*, *y* object with the ending position of the arrow.
6485       dim - (number) The dimension of the arrow.
6486       swap - (boolean) Whether to set the arrow pointing to the starting position or the ending position.
6487       canvas - (object) A <Canvas> instance.
6488       
6489       Example:
6490       (start code js)
6491       EdgeHelper.arrow.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 13, false, viz.canvas);
6492       (end code)
6493       */
6494     'render': function(from, to, dim, swap, canvas){
6495         var ctx = canvas.getCtx();
6496         // invert edge direction
6497         if (swap) {
6498           var tmp = from;
6499           from = to;
6500           to = tmp;
6501         }
6502         var vect = new Complex(to.x - from.x, to.y - from.y);
6503         vect.$scale(dim / vect.norm());
6504         var intermediatePoint = new Complex(to.x - vect.x, to.y - vect.y),
6505             normal = new Complex(-vect.y / 2, vect.x / 2),
6506             v1 = intermediatePoint.add(normal), 
6507             v2 = intermediatePoint.$add(normal.$scale(-1));
6508         
6509         ctx.beginPath();
6510         ctx.moveTo(from.x, from.y);
6511         ctx.lineTo(to.x, to.y);
6512         ctx.stroke();
6513         ctx.beginPath();
6514         ctx.moveTo(v1.x, v1.y);
6515         ctx.lineTo(v2.x, v2.y);
6516         ctx.lineTo(to.x, to.y);
6517         ctx.closePath();
6518         ctx.fill();
6519     },
6520     /*
6521     Method: contains
6522     
6523     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6524     
6525     Parameters:
6526     
6527     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6528     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6529     pos - (object) An *x*, *y* object with the position to check.
6530     epsilon - (number) The dimension of the shape.
6531     
6532     Example:
6533     (start code js)
6534     EdgeHelper.arrow.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6535     (end code)
6536     */
6537     'contains': function(posFrom, posTo, pos, epsilon) {
6538       return EdgeHelper.line.contains(posFrom, posTo, pos, epsilon);
6539     }
6540   },
6541   /*
6542     Object: EdgeHelper.hyperline
6543   */
6544   'hyperline': {
6545     /*
6546     Method: render
6547     
6548     Renders a hyperline into the canvas. A hyperline are the lines drawn for the <Hypertree> visualization.
6549     
6550     Parameters:
6551     
6552     from - (object) An *x*, *y* object with the starting position of the hyperline. *x* and *y* must belong to [0, 1).
6553     to - (object) An *x*, *y* object with the ending position of the hyperline. *x* and *y* must belong to [0, 1).
6554     r - (number) The scaling factor.
6555     canvas - (object) A <Canvas> instance.
6556     
6557     Example:
6558     (start code js)
6559     EdgeHelper.hyperline.render({ x: 10, y: 30 }, { x: 10, y: 50 }, 100, viz.canvas);
6560     (end code)
6561     */
6562     'render': function(from, to, r, canvas){
6563       var ctx = canvas.getCtx();  
6564       var centerOfCircle = computeArcThroughTwoPoints(from, to);
6565       if (centerOfCircle.a > 1000 || centerOfCircle.b > 1000
6566           || centerOfCircle.ratio < 0) {
6567         ctx.beginPath();
6568         ctx.moveTo(from.x * r, from.y * r);
6569         ctx.lineTo(to.x * r, to.y * r);
6570         ctx.stroke();
6571       } else {
6572         var angleBegin = Math.atan2(to.y - centerOfCircle.y, to.x
6573             - centerOfCircle.x);
6574         var angleEnd = Math.atan2(from.y - centerOfCircle.y, from.x
6575             - centerOfCircle.x);
6576         var sense = sense(angleBegin, angleEnd);
6577         ctx.beginPath();
6578         ctx.arc(centerOfCircle.x * r, centerOfCircle.y * r, centerOfCircle.ratio
6579             * r, angleBegin, angleEnd, sense);
6580         ctx.stroke();
6581       }
6582       /*      
6583         Calculates the arc parameters through two points.
6584         
6585         More information in <http://en.wikipedia.org/wiki/Poincar%C3%A9_disc_model#Analytic_geometry_constructions_in_the_hyperbolic_plane> 
6586       
6587         Parameters:
6588       
6589         p1 - A <Complex> instance.
6590         p2 - A <Complex> instance.
6591         scale - The Disk's diameter.
6592       
6593         Returns:
6594       
6595         An object containing some arc properties.
6596       */
6597       function computeArcThroughTwoPoints(p1, p2){
6598         var aDen = (p1.x * p2.y - p1.y * p2.x), bDen = aDen;
6599         var sq1 = p1.squaredNorm(), sq2 = p2.squaredNorm();
6600         // Fall back to a straight line
6601         if (aDen == 0)
6602           return {
6603             x: 0,
6604             y: 0,
6605             ratio: -1
6606           };
6607     
6608         var a = (p1.y * sq2 - p2.y * sq1 + p1.y - p2.y) / aDen;
6609         var b = (p2.x * sq1 - p1.x * sq2 + p2.x - p1.x) / bDen;
6610         var x = -a / 2;
6611         var y = -b / 2;
6612         var squaredRatio = (a * a + b * b) / 4 - 1;
6613         // Fall back to a straight line
6614         if (squaredRatio < 0)
6615           return {
6616             x: 0,
6617             y: 0,
6618             ratio: -1
6619           };
6620         var ratio = Math.sqrt(squaredRatio);
6621         var out = {
6622           x: x,
6623           y: y,
6624           ratio: ratio > 1000? -1 : ratio,
6625           a: a,
6626           b: b
6627         };
6628     
6629         return out;
6630       }
6631       /*      
6632         Sets angle direction to clockwise (true) or counterclockwise (false). 
6633          
6634         Parameters: 
6635       
6636            angleBegin - Starting angle for drawing the arc. 
6637            angleEnd - The HyperLine will be drawn from angleBegin to angleEnd. 
6638       
6639         Returns: 
6640       
6641            A Boolean instance describing the sense for drawing the HyperLine. 
6642       */
6643       function sense(angleBegin, angleEnd){
6644         return (angleBegin < angleEnd)? ((angleBegin + Math.PI > angleEnd)? false
6645             : true) : ((angleEnd + Math.PI > angleBegin)? true : false);
6646       }
6647     },
6648     /*
6649     Method: contains
6650     
6651     Not Implemented
6652     
6653     Returns *true* if *pos* is contained in the area of the shape. Returns *false* otherwise.
6654     
6655     Parameters:
6656     
6657     posFrom - (object) An *x*, *y* object with a <Graph.Node> position.
6658     posTo - (object) An *x*, *y* object with a <Graph.Node> position.
6659     pos - (object) An *x*, *y* object with the position to check.
6660     epsilon - (number) The dimension of the shape.
6661     
6662     Example:
6663     (start code js)
6664     EdgeHelper.hyperline.contains({ x: 10, y: 30 }, { x: 15, y: 35 }, { x: 15, y: 35 }, 30);
6665     (end code)
6666     */
6667     'contains': $.lambda(false)
6668   }
6669 };
6670
6671
6672 /*
6673  * File: Graph.Plot.js
6674  */
6675
6676 /*
6677    Object: Graph.Plot
6678
6679    <Graph> rendering and animation methods.
6680    
6681    Properties:
6682    
6683    nodeHelper - <NodeHelper> object.
6684    edgeHelper - <EdgeHelper> object.
6685 */
6686 Graph.Plot = {
6687     //Default intializer
6688     initialize: function(viz, klass){
6689       this.viz = viz;
6690       this.config = viz.config;
6691       this.node = viz.config.Node;
6692       this.edge = viz.config.Edge;
6693       this.animation = new Animation;
6694       this.nodeTypes = new klass.Plot.NodeTypes;
6695       this.edgeTypes = new klass.Plot.EdgeTypes;
6696       this.labels = viz.labels;
6697    },
6698
6699     //Add helpers
6700     nodeHelper: NodeHelper,
6701     edgeHelper: EdgeHelper,
6702     
6703     Interpolator: {
6704         //node/edge property parsers
6705         'map': {
6706           'border': 'color',
6707           'color': 'color',
6708           'width': 'number',
6709           'height': 'number',
6710           'dim': 'number',
6711           'alpha': 'number',
6712           'lineWidth': 'number',
6713           'angularWidth':'number',
6714           'span':'number',
6715           'valueArray':'array-number',
6716           'dimArray':'array-number'
6717           //'colorArray':'array-color'
6718         },
6719         
6720         //canvas specific parsers
6721         'canvas': {
6722           'globalAlpha': 'number',
6723           'fillStyle': 'color',
6724           'strokeStyle': 'color',
6725           'lineWidth': 'number',
6726           'shadowBlur': 'number',
6727           'shadowColor': 'color',
6728           'shadowOffsetX': 'number',
6729           'shadowOffsetY': 'number',
6730           'miterLimit': 'number'
6731         },
6732   
6733         //label parsers
6734         'label': {
6735           'size': 'number',
6736           'color': 'color'
6737         },
6738   
6739         //Number interpolator
6740         'compute': function(from, to, delta) {
6741           return from + (to - from) * delta;
6742         },
6743         
6744         //Position interpolators
6745         'moebius': function(elem, props, delta, vector) {
6746           var v = vector.scale(-delta);  
6747           if(v.norm() < 1) {
6748               var x = v.x, y = v.y;
6749               var ans = elem.startPos
6750                 .getc().moebiusTransformation(v);
6751               elem.pos.setc(ans.x, ans.y);
6752               v.x = x; v.y = y;
6753             }           
6754         },
6755
6756         'linear': function(elem, props, delta) {
6757             var from = elem.startPos.getc(true);
6758             var to = elem.endPos.getc(true);
6759             elem.pos.setc(this.compute(from.x, to.x, delta), 
6760                           this.compute(from.y, to.y, delta));
6761         },
6762
6763         'polar': function(elem, props, delta) {
6764           var from = elem.startPos.getp(true);
6765           var to = elem.endPos.getp();
6766           var ans = to.interpolate(from, delta);
6767           elem.pos.setp(ans.theta, ans.rho);
6768         },
6769         
6770         //Graph's Node/Edge interpolators
6771         'number': function(elem, prop, delta, getter, setter) {
6772           var from = elem[getter](prop, 'start');
6773           var to = elem[getter](prop, 'end');
6774           elem[setter](prop, this.compute(from, to, delta));
6775         },
6776
6777         'color': function(elem, prop, delta, getter, setter) {
6778           var from = $.hexToRgb(elem[getter](prop, 'start'));
6779           var to = $.hexToRgb(elem[getter](prop, 'end'));
6780           var comp = this.compute;
6781           var val = $.rgbToHex([parseInt(comp(from[0], to[0], delta)),
6782                                 parseInt(comp(from[1], to[1], delta)),
6783                                 parseInt(comp(from[2], to[2], delta))]);
6784           
6785           elem[setter](prop, val);
6786         },
6787         
6788         'array-number': function(elem, prop, delta, getter, setter) {
6789           var from = elem[getter](prop, 'start'),
6790               to = elem[getter](prop, 'end'),
6791               cur = [];
6792           for(var i=0, l=from.length; i<l; i++) {
6793             var fromi = from[i], toi = to[i];
6794             if(fromi.length) {
6795               for(var j=0, len=fromi.length, curi=[]; j<len; j++) {
6796                 curi.push(this.compute(fromi[j], toi[j], delta));
6797               }
6798               cur.push(curi);
6799             } else {
6800               cur.push(this.compute(fromi, toi, delta));
6801             }
6802           }
6803           elem[setter](prop, cur);
6804         },
6805         
6806         'node': function(elem, props, delta, map, getter, setter) {
6807           map = this[map];
6808           if(props) {
6809             var len = props.length;
6810             for(var i=0; i<len; i++) {
6811               var pi = props[i];
6812               this[map[pi]](elem, pi, delta, getter, setter);
6813             }
6814           } else {
6815             for(var pi in map) {
6816               this[map[pi]](elem, pi, delta, getter, setter);
6817             }
6818           }
6819         },
6820         
6821         'edge': function(elem, props, delta, mapKey, getter, setter) {
6822             var adjs = elem.adjacencies;
6823             for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
6824         },
6825         
6826         'node-property': function(elem, props, delta) {
6827           this['node'](elem, props, delta, 'map', 'getData', 'setData');
6828         },
6829         
6830         'edge-property': function(elem, props, delta) {
6831           this['edge'](elem, props, delta, 'map', 'getData', 'setData');  
6832         },
6833
6834         'label-property': function(elem, props, delta) {
6835           this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
6836         },
6837         
6838         'node-style': function(elem, props, delta) {
6839           this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
6840         },
6841         
6842         'edge-style': function(elem, props, delta) {
6843           this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');  
6844         }
6845     },
6846     
6847   
6848     /*
6849        sequence
6850     
6851        Iteratively performs an action while refreshing the state of the visualization.
6852
6853        Parameters:
6854
6855        options - (object) An object containing some sequence options described below
6856        condition - (function) A function returning a boolean instance in order to stop iterations.
6857        step - (function) A function to execute on each step of the iteration.
6858        onComplete - (function) A function to execute when the sequence finishes.
6859        duration - (number) Duration (in milliseconds) of each step.
6860
6861       Example:
6862        (start code js)
6863         var rg = new $jit.RGraph(options);
6864         var i = 0;
6865         rg.fx.sequence({
6866           condition: function() {
6867            return i == 10;
6868           },
6869           step: function() {
6870             alert(i++);
6871           },
6872           onComplete: function() {
6873            alert('done!');
6874           }
6875         });
6876        (end code)
6877
6878     */
6879     sequence: function(options) {
6880         var that = this;
6881         options = $.merge({
6882           condition: $.lambda(false),
6883           step: $.empty,
6884           onComplete: $.empty,
6885           duration: 200
6886         }, options || {});
6887
6888         var interval = setInterval(function() {
6889           if(options.condition()) {
6890             options.step();
6891           } else {
6892             clearInterval(interval);
6893             options.onComplete();
6894           }
6895           that.viz.refresh(true);
6896         }, options.duration);
6897     },
6898     
6899     /*
6900       prepare
6901  
6902       Prepare graph position and other attribute values before performing an Animation. 
6903       This method is used internally by the Toolkit.
6904       
6905       See also:
6906        
6907        <Animation>, <Graph.Plot.animate>
6908
6909     */
6910     prepare: function(modes) {
6911       var graph = this.viz.graph,
6912           accessors = {
6913             'node-property': {
6914               'getter': 'getData',
6915               'setter': 'setData'
6916             },
6917             'edge-property': {
6918               'getter': 'getData',
6919               'setter': 'setData'
6920             },
6921             'node-style': {
6922               'getter': 'getCanvasStyle',
6923               'setter': 'setCanvasStyle'
6924             },
6925             'edge-style': {
6926               'getter': 'getCanvasStyle',
6927               'setter': 'setCanvasStyle'
6928             }
6929           };
6930
6931       //parse modes
6932       var m = {};
6933       if($.type(modes) == 'array') {
6934         for(var i=0, len=modes.length; i < len; i++) {
6935           var elems = modes[i].split(':');
6936           m[elems.shift()] = elems;
6937         }
6938       } else {
6939         for(var p in modes) {
6940           if(p == 'position') {
6941             m[modes.position] = [];
6942           } else {
6943             m[p] = $.splat(modes[p]);
6944           }
6945         }
6946       }
6947       
6948       graph.eachNode(function(node) { 
6949         node.startPos.set(node.pos);
6950         $.each(['node-property', 'node-style'], function(p) {
6951           if(p in m) {
6952             var prop = m[p];
6953             for(var i=0, l=prop.length; i < l; i++) {
6954               node[accessors[p].setter](prop[i], node[accessors[p].getter](prop[i]), 'start');
6955             }
6956           }
6957         });
6958         $.each(['edge-property', 'edge-style'], function(p) {
6959           if(p in m) {
6960             var prop = m[p];
6961             node.eachAdjacency(function(adj) {
6962               for(var i=0, l=prop.length; i < l; i++) {
6963                 adj[accessors[p].setter](prop[i], adj[accessors[p].getter](prop[i]), 'start');
6964               }
6965             });
6966           }
6967         });
6968       });
6969       return m;
6970     },
6971     
6972     /*
6973        Method: animate
6974     
6975        Animates a <Graph> by interpolating some <Graph.Node>, <Graph.Adjacence> or <Graph.Label> properties.
6976
6977        Parameters:
6978
6979        opt - (object) Animation options. The object properties are described below
6980        duration - (optional) Described in <Options.Fx>.
6981        fps - (optional) Described in <Options.Fx>.
6982        hideLabels - (optional|boolean) Whether to hide labels during the animation.
6983        modes - (required|object) An object with animation modes (described below).
6984
6985        Animation modes:
6986        
6987        Animation modes are strings representing different node/edge and graph properties that you'd like to animate. 
6988        They are represented by an object that has as keys main categories of properties to animate and as values a list 
6989        of these specific properties. The properties are described below
6990        
6991        position - Describes the way nodes' positions must be interpolated. Possible values are 'linear', 'polar' or 'moebius'.
6992        node-property - Describes which Node properties will be interpolated. These properties can be any of the ones defined in <Options.Node>.
6993        edge-property - Describes which Edge properties will be interpolated. These properties can be any the ones defined in <Options.Edge>.
6994        label-property - Describes which Label properties will be interpolated. These properties can be any of the ones defined in <Options.Label> like color or size.
6995        node-style - Describes which Node Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6996        edge-style - Describes which Edge Canvas Styles will be interpolated. These are specific canvas properties like fillStyle, strokeStyle, lineWidth, shadowBlur, shadowColor, shadowOffsetX, shadowOffsetY, etc.
6997
6998        Example:
6999        (start code js)
7000        var viz = new $jit.Viz(options);
7001        //...tweak some Data, CanvasStyles or LabelData properties...
7002        viz.fx.animate({
7003          modes: {
7004            'position': 'linear',
7005            'node-property': ['width', 'height'],
7006            'node-style': 'shadowColor',
7007            'label-property': 'size'
7008          },
7009          hideLabels: false
7010        });
7011        //...can also be written like this...
7012        viz.fx.animate({
7013          modes: ['linear',
7014                  'node-property:width:height',
7015                  'node-style:shadowColor',
7016                  'label-property:size'],
7017          hideLabels: false
7018        });
7019        (end code)
7020     */
7021     animate: function(opt, versor) {
7022       opt = $.merge(this.viz.config, opt || {});
7023       var that = this,
7024           viz = this.viz,
7025           graph  = viz.graph,
7026           interp = this.Interpolator,
7027           animation =  opt.type === 'nodefx'? this.nodeFxAnimation : this.animation;
7028       //prepare graph values
7029       var m = this.prepare(opt.modes);
7030       
7031       //animate
7032       if(opt.hideLabels) this.labels.hideLabels(true);
7033       animation.setOptions($.merge(opt, {
7034         $animating: false,
7035         compute: function(delta) {
7036           graph.eachNode(function(node) { 
7037             for(var p in m) {
7038               interp[p](node, m[p], delta, versor);
7039             }
7040           });
7041           that.plot(opt, this.$animating, delta);
7042           this.$animating = true;
7043         },
7044         complete: function() {
7045           if(opt.hideLabels) that.labels.hideLabels(false);
7046           that.plot(opt);
7047           opt.onComplete();
7048           opt.onAfterCompute();
7049         }       
7050       })).start();
7051     },
7052     
7053     /*
7054       nodeFx
7055    
7056       Apply animation to node properties like color, width, height, dim, etc.
7057   
7058       Parameters:
7059   
7060       options - Animation options. This object properties is described below
7061       elements - The Elements to be transformed. This is an object that has a properties
7062       
7063       (start code js)
7064       'elements': {
7065         //can also be an array of ids
7066         'id': 'id-of-node-to-transform',
7067         //properties to be modified. All properties are optional.
7068         'properties': {
7069           'color': '#ccc', //some color
7070           'width': 10, //some width
7071           'height': 10, //some height
7072           'dim': 20, //some dim
7073           'lineWidth': 10 //some line width
7074         } 
7075       }
7076       (end code)
7077       
7078       - _reposition_ Whether to recalculate positions and add a motion animation. 
7079       This might be used when changing _width_ or _height_ properties in a <Layouts.Tree> like layout. Default's *false*.
7080       
7081       - _onComplete_ A method that is called when the animation completes.
7082       
7083       ...and all other <Graph.Plot.animate> options like _duration_, _fps_, _transition_, etc.
7084   
7085       Example:
7086       (start code js)
7087        var rg = new RGraph(canvas, config); //can be also Hypertree or ST
7088        rg.fx.nodeFx({
7089          'elements': {
7090            'id':'mynodeid',
7091            'properties': {
7092              'color':'#ccf'
7093            },
7094            'transition': Trans.Quart.easeOut
7095          }
7096        });
7097       (end code)    
7098    */
7099    nodeFx: function(opt) {
7100      var viz = this.viz,
7101          graph  = viz.graph,
7102          animation = this.nodeFxAnimation,
7103          options = $.merge(this.viz.config, {
7104            'elements': {
7105              'id': false,
7106              'properties': {}
7107            },
7108            'reposition': false
7109          });
7110      opt = $.merge(options, opt || {}, {
7111        onBeforeCompute: $.empty,
7112        onAfterCompute: $.empty
7113      });
7114      //check if an animation is running
7115      animation.stopTimer();
7116      var props = opt.elements.properties;
7117      //set end values for nodes
7118      if(!opt.elements.id) {
7119        graph.eachNode(function(n) {
7120          for(var prop in props) {
7121            n.setData(prop, props[prop], 'end');
7122          }
7123        });
7124      } else {
7125        var ids = $.splat(opt.elements.id);
7126        $.each(ids, function(id) {
7127          var n = graph.getNode(id);
7128          if(n) {
7129            for(var prop in props) {
7130              n.setData(prop, props[prop], 'end');
7131            }
7132          }
7133        });
7134      }
7135      //get keys
7136      var propnames = [];
7137      for(var prop in props) propnames.push(prop);
7138      //add node properties modes
7139      var modes = ['node-property:' + propnames.join(':')];
7140      //set new node positions
7141      if(opt.reposition) {
7142        modes.push('linear');
7143        viz.compute('end');
7144      }
7145      //animate
7146      this.animate($.merge(opt, {
7147        modes: modes,
7148        type: 'nodefx'
7149      }));
7150    },
7151
7152     
7153     /*
7154        Method: plot
7155     
7156        Plots a <Graph>.
7157
7158        Parameters:
7159
7160        opt - (optional) Plotting options. Most of them are described in <Options.Fx>.
7161
7162        Example:
7163
7164        (start code js)
7165        var viz = new $jit.Viz(options);
7166        viz.fx.plot(); 
7167        (end code)
7168
7169     */
7170     plot: function(opt, animating) {
7171       var viz = this.viz, 
7172       aGraph = viz.graph, 
7173       canvas = viz.canvas, 
7174       id = viz.root, 
7175       that = this, 
7176       ctx = canvas.getCtx(), 
7177       min = Math.min,
7178       opt = opt || this.viz.controller;
7179       opt.clearCanvas && canvas.clear();
7180         
7181       var root = aGraph.getNode(id);
7182       if(!root) return;
7183       
7184       var T = !!root.visited;
7185       aGraph.eachNode(function(node) {
7186         var nodeAlpha = node.getData('alpha');
7187         node.eachAdjacency(function(adj) {
7188           var nodeTo = adj.nodeTo;
7189           if(!!nodeTo.visited === T && node.drawn && nodeTo.drawn) {
7190             !animating && opt.onBeforePlotLine(adj);
7191             ctx.save();
7192             ctx.globalAlpha = min(nodeAlpha, 
7193                 nodeTo.getData('alpha'), 
7194                 adj.getData('alpha'));
7195             that.plotLine(adj, canvas, animating);
7196             ctx.restore();
7197             !animating && opt.onAfterPlotLine(adj);
7198           }
7199         });
7200         ctx.save();
7201         if(node.drawn) {
7202           !animating && opt.onBeforePlotNode(node);
7203           that.plotNode(node, canvas, animating);
7204           !animating && opt.onAfterPlotNode(node);
7205         }
7206         if(!that.labelsHidden && opt.withLabels) {
7207           if(node.drawn && nodeAlpha >= 0.95) {
7208             that.labels.plotLabel(canvas, node, opt);
7209           } else {
7210             that.labels.hideLabel(node, false);
7211           }
7212         }
7213         ctx.restore();
7214         node.visited = !T;
7215       });
7216     },
7217
7218   /*
7219       Plots a Subtree.
7220    */
7221    plotTree: function(node, opt, animating) {
7222        var that = this, 
7223        viz = this.viz, 
7224        canvas = viz.canvas,
7225        config = this.config,
7226        ctx = canvas.getCtx();
7227        var nodeAlpha = node.getData('alpha');
7228        node.eachSubnode(function(elem) {
7229          if(opt.plotSubtree(node, elem) && elem.exist && elem.drawn) {
7230              var adj = node.getAdjacency(elem.id);
7231              !animating && opt.onBeforePlotLine(adj);
7232              ctx.globalAlpha = Math.min(nodeAlpha, elem.getData('alpha'));
7233              that.plotLine(adj, canvas, animating);
7234              !animating && opt.onAfterPlotLine(adj);
7235              that.plotTree(elem, opt, animating);
7236          }
7237        });
7238        if(node.drawn) {
7239            !animating && opt.onBeforePlotNode(node);
7240            this.plotNode(node, canvas, animating);
7241            !animating && opt.onAfterPlotNode(node);
7242            if(!opt.hideLabels && opt.withLabels && nodeAlpha >= 0.95) 
7243                this.labels.plotLabel(canvas, node, opt);
7244            else 
7245                this.labels.hideLabel(node, false);
7246        } else {
7247            this.labels.hideLabel(node, true);
7248        }
7249    },
7250
7251   /*
7252        Method: plotNode
7253     
7254        Plots a <Graph.Node>.
7255
7256        Parameters:
7257        
7258        node - (object) A <Graph.Node>.
7259        canvas - (object) A <Canvas> element.
7260
7261     */
7262     plotNode: function(node, canvas, animating) {
7263         var f = node.getData('type'), 
7264             ctxObj = this.node.CanvasStyles;
7265         if(f != 'none') {
7266           var width = node.getData('lineWidth'),
7267               color = node.getData('color'),
7268               alpha = node.getData('alpha'),
7269               ctx = canvas.getCtx();
7270           
7271           ctx.lineWidth = width;
7272           ctx.fillStyle = ctx.strokeStyle = color;
7273           ctx.globalAlpha = alpha;
7274           
7275           for(var s in ctxObj) {
7276             ctx[s] = node.getCanvasStyle(s);
7277           }
7278
7279           this.nodeTypes[f].render.call(this, node, canvas, animating);
7280         }
7281     },
7282     
7283     /*
7284        Method: plotLine
7285     
7286        Plots a <Graph.Adjacence>.
7287
7288        Parameters:
7289
7290        adj - (object) A <Graph.Adjacence>.
7291        canvas - (object) A <Canvas> instance.
7292
7293     */
7294     plotLine: function(adj, canvas, animating) {
7295       var f = adj.getData('type'),
7296           ctxObj = this.edge.CanvasStyles;
7297       if(f != 'none') {
7298         var width = adj.getData('lineWidth'),
7299             color = adj.getData('color'),
7300             ctx = canvas.getCtx();
7301         
7302         ctx.lineWidth = width;
7303         ctx.fillStyle = ctx.strokeStyle = color;
7304         
7305         for(var s in ctxObj) {
7306           ctx[s] = adj.getCanvasStyle(s);
7307         }
7308
7309         this.edgeTypes[f].render.call(this, adj, canvas, animating);
7310       }
7311     }    
7312   
7313 };
7314
7315
7316
7317 /*
7318  * File: Graph.Label.js
7319  *
7320 */
7321
7322 /*
7323    Object: Graph.Label
7324
7325    An interface for plotting/hiding/showing labels.
7326
7327    Description:
7328
7329    This is a generic interface for plotting/hiding/showing labels.
7330    The <Graph.Label> interface is implemented in multiple ways to provide
7331    different label types.
7332
7333    For example, the Graph.Label interface is implemented as <Graph.Label.HTML> to provide
7334    HTML label elements. Also we provide the <Graph.Label.SVG> interface for SVG type labels. 
7335    The <Graph.Label.Native> interface implements these methods with the native Canvas text rendering functions.
7336    
7337    All subclasses (<Graph.Label.HTML>, <Graph.Label.SVG> and <Graph.Label.Native>) implement the method plotLabel.
7338 */
7339
7340 Graph.Label = {};
7341
7342 /*
7343    Class: Graph.Label.Native
7344
7345    Implements labels natively, using the Canvas text API.
7346 */
7347 Graph.Label.Native = new Class({
7348     /*
7349        Method: plotLabel
7350
7351        Plots a label for a given node.
7352
7353        Parameters:
7354
7355        canvas - (object) A <Canvas> instance.
7356        node - (object) A <Graph.Node>.
7357        controller - (object) A configuration object.
7358        
7359        Example:
7360        
7361        (start code js)
7362        var viz = new $jit.Viz(options);
7363        var node = viz.graph.getNode('nodeId');
7364        viz.labels.plotLabel(viz.canvas, node, viz.config);
7365        (end code)
7366     */
7367     plotLabel: function(canvas, node, controller) {
7368       var ctx = canvas.getCtx();
7369       var pos = node.pos.getc(true);
7370
7371       ctx.font = node.getLabelData('style') + ' ' + node.getLabelData('size') + 'px ' + node.getLabelData('family');
7372       ctx.textAlign = node.getLabelData('textAlign');
7373       ctx.fillStyle = ctx.strokeStyle = node.getLabelData('color');
7374       ctx.textBaseline = node.getLabelData('textBaseline');
7375
7376       this.renderLabel(canvas, node, controller);
7377     },
7378
7379     /*
7380        renderLabel
7381
7382        Does the actual rendering of the label in the canvas. The default
7383        implementation renders the label close to the position of the node, this
7384        method should be overriden to position the labels differently.
7385
7386        Parameters:
7387
7388        canvas - A <Canvas> instance.
7389        node - A <Graph.Node>.
7390        controller - A configuration object. See also <Hypertree>, <RGraph>, <ST>.
7391     */
7392     renderLabel: function(canvas, node, controller) {
7393       var ctx = canvas.getCtx();
7394       var pos = node.pos.getc(true);
7395       ctx.fillText(node.name, pos.x, pos.y + node.getData("height") / 2);
7396     },
7397
7398     hideLabel: $.empty,
7399     hideLabels: $.empty
7400 });
7401
7402 /*
7403    Class: Graph.Label.DOM
7404
7405    Abstract Class implementing some DOM label methods.
7406
7407    Implemented by:
7408
7409    <Graph.Label.HTML> and <Graph.Label.SVG>.
7410
7411 */
7412 Graph.Label.DOM = new Class({
7413     //A flag value indicating if node labels are being displayed or not.
7414     labelsHidden: false,
7415     //Label container
7416     labelContainer: false,
7417     //Label elements hash.
7418     labels: {},
7419
7420     /*
7421        Method: getLabelContainer
7422
7423        Lazy fetcher for the label container.
7424
7425        Returns:
7426
7427        The label container DOM element.
7428
7429        Example:
7430
7431       (start code js)
7432         var viz = new $jit.Viz(options);
7433         var labelContainer = viz.labels.getLabelContainer();
7434         alert(labelContainer.innerHTML);
7435       (end code)
7436     */
7437     getLabelContainer: function() {
7438       return this.labelContainer ?
7439         this.labelContainer :
7440         this.labelContainer = document.getElementById(this.viz.config.labelContainer);
7441     },
7442
7443     /*
7444        Method: getLabel
7445
7446        Lazy fetcher for the label element.
7447
7448        Parameters:
7449
7450        id - (string) The label id (which is also a <Graph.Node> id).
7451
7452        Returns:
7453
7454        The label element.
7455
7456        Example:
7457
7458       (start code js)
7459         var viz = new $jit.Viz(options);
7460         var label = viz.labels.getLabel('someid');
7461         alert(label.innerHTML);
7462       (end code)
7463
7464     */
7465     getLabel: function(id) {
7466       return (id in this.labels && this.labels[id] != null) ?
7467         this.labels[id] :
7468         this.labels[id] = document.getElementById(id);
7469     },
7470
7471     /*
7472        Method: hideLabels
7473
7474        Hides all labels (by hiding the label container).
7475
7476        Parameters:
7477
7478        hide - (boolean) A boolean value indicating if the label container must be hidden or not.
7479
7480        Example:
7481        (start code js)
7482         var viz = new $jit.Viz(options);
7483         rg.labels.hideLabels(true);
7484        (end code)
7485
7486     */
7487     hideLabels: function (hide) {
7488       var container = this.getLabelContainer();
7489       if(hide)
7490         container.style.display = 'none';
7491       else
7492         container.style.display = '';
7493       this.labelsHidden = hide;
7494     },
7495
7496     /*
7497        Method: clearLabels
7498
7499        Clears the label container.
7500
7501        Useful when using a new visualization with the same canvas element/widget.
7502
7503        Parameters:
7504
7505        force - (boolean) Forces deletion of all labels.
7506
7507        Example:
7508        (start code js)
7509         var viz = new $jit.Viz(options);
7510         viz.labels.clearLabels();
7511         (end code)
7512     */
7513     clearLabels: function(force) {
7514       for(var id in this.labels) {
7515         if (force || !this.viz.graph.hasNode(id)) {
7516           this.disposeLabel(id);
7517           delete this.labels[id];
7518         }
7519       }
7520     },
7521
7522     /*
7523        Method: disposeLabel
7524
7525        Removes a label.
7526
7527        Parameters:
7528
7529        id - (string) A label id (which generally is also a <Graph.Node> id).
7530
7531        Example:
7532        (start code js)
7533         var viz = new $jit.Viz(options);
7534         viz.labels.disposeLabel('labelid');
7535        (end code)
7536     */
7537     disposeLabel: function(id) {
7538       var elem = this.getLabel(id);
7539       if(elem && elem.parentNode) {
7540         elem.parentNode.removeChild(elem);
7541       }
7542     },
7543
7544     /*
7545        Method: hideLabel
7546
7547        Hides the corresponding <Graph.Node> label.
7548
7549        Parameters:
7550
7551        node - (object) A <Graph.Node>. Can also be an array of <Graph.Nodes>.
7552        show - (boolean) If *true*, nodes will be shown. Otherwise nodes will be hidden.
7553
7554        Example:
7555        (start code js)
7556         var rg = new $jit.Viz(options);
7557         viz.labels.hideLabel(viz.graph.getNode('someid'), false);
7558        (end code)
7559     */
7560     hideLabel: function(node, show) {
7561       node = $.splat(node);
7562       var st = show ? "" : "none", lab, that = this;
7563       $.each(node, function(n) {
7564         var lab = that.getLabel(n.id);
7565         if (lab) {
7566           lab.style.display = st;
7567         }
7568       });
7569     },
7570
7571     /*
7572        fitsInCanvas
7573
7574        Returns _true_ or _false_ if the label for the node is contained in the canvas dom element or not.
7575
7576        Parameters:
7577
7578        pos - A <Complex> instance (I'm doing duck typing here so any object with _x_ and _y_ parameters will do).
7579        canvas - A <Canvas> instance.
7580
7581        Returns:
7582
7583        A boolean value specifying if the label is contained in the <Canvas> DOM element or not.
7584
7585     */
7586     fitsInCanvas: function(pos, canvas) {
7587       var size = canvas.getSize();
7588       if(pos.x >= size.width || pos.x < 0
7589          || pos.y >= size.height || pos.y < 0) return false;
7590        return true;
7591     }
7592 });
7593
7594 /*
7595    Class: Graph.Label.HTML
7596
7597    Implements HTML labels.
7598
7599    Extends:
7600
7601    All <Graph.Label.DOM> methods.
7602
7603 */
7604 Graph.Label.HTML = new Class({
7605     Implements: Graph.Label.DOM,
7606
7607     /*
7608        Method: plotLabel
7609
7610        Plots a label for a given node.
7611
7612        Parameters:
7613
7614        canvas - (object) A <Canvas> instance.
7615        node - (object) A <Graph.Node>.
7616        controller - (object) A configuration object.
7617        
7618       Example:
7619        
7620        (start code js)
7621        var viz = new $jit.Viz(options);
7622        var node = viz.graph.getNode('nodeId');
7623        viz.labels.plotLabel(viz.canvas, node, viz.config);
7624        (end code)
7625
7626
7627     */
7628     plotLabel: function(canvas, node, controller) {
7629       var id = node.id, tag = this.getLabel(id);
7630
7631       if(!tag && !(tag = document.getElementById(id))) {
7632         tag = document.createElement('div');
7633         var container = this.getLabelContainer();
7634         tag.id = id;
7635         tag.className = 'node';
7636         tag.style.position = 'absolute';
7637         controller.onCreateLabel(tag, node);
7638         container.appendChild(tag);
7639         this.labels[node.id] = tag;
7640       }
7641
7642       this.placeLabel(tag, node, controller);
7643     }
7644 });
7645
7646 /*
7647    Class: Graph.Label.SVG
7648
7649    Implements SVG labels.
7650
7651    Extends:
7652
7653    All <Graph.Label.DOM> methods.
7654 */
7655 Graph.Label.SVG = new Class({
7656     Implements: Graph.Label.DOM,
7657
7658     /*
7659        Method: plotLabel
7660
7661        Plots a label for a given node.
7662
7663        Parameters:
7664
7665        canvas - (object) A <Canvas> instance.
7666        node - (object) A <Graph.Node>.
7667        controller - (object) A configuration object.
7668        
7669        Example:
7670        
7671        (start code js)
7672        var viz = new $jit.Viz(options);
7673        var node = viz.graph.getNode('nodeId');
7674        viz.labels.plotLabel(viz.canvas, node, viz.config);
7675        (end code)
7676
7677
7678     */
7679     plotLabel: function(canvas, node, controller) {
7680       var id = node.id, tag = this.getLabel(id);
7681       if(!tag && !(tag = document.getElementById(id))) {
7682         var ns = 'http://www.w3.org/2000/svg';
7683           tag = document.createElementNS(ns, 'svg:text');
7684         var tspan = document.createElementNS(ns, 'svg:tspan');
7685         tag.appendChild(tspan);
7686         var container = this.getLabelContainer();
7687         tag.setAttribute('id', id);
7688         tag.setAttribute('class', 'node');
7689         container.appendChild(tag);
7690         controller.onCreateLabel(tag, node);
7691         this.labels[node.id] = tag;
7692       }
7693       this.placeLabel(tag, node, controller);
7694     }
7695 });
7696
7697
7698
7699 Graph.Geom = new Class({
7700
7701   initialize: function(viz) {
7702     this.viz = viz;
7703     this.config = viz.config;
7704     this.node = viz.config.Node;
7705     this.edge = viz.config.Edge;
7706   },
7707   /*
7708     Applies a translation to the tree.
7709   
7710     Parameters:
7711   
7712     pos - A <Complex> number specifying translation vector.
7713     prop - A <Graph.Node> position property ('pos', 'start' or 'end').
7714   
7715     Example:
7716   
7717     (start code js)
7718       st.geom.translate(new Complex(300, 100), 'end');
7719     (end code)
7720   */  
7721   translate: function(pos, prop) {
7722      prop = $.splat(prop);
7723      this.viz.graph.eachNode(function(elem) {
7724          $.each(prop, function(p) { elem.getPos(p).$add(pos); });
7725      });
7726   },
7727   /*
7728     Hides levels of the tree until it properly fits in canvas.
7729   */  
7730   setRightLevelToShow: function(node, canvas, callback) {
7731      var level = this.getRightLevelToShow(node, canvas), 
7732          fx = this.viz.labels,
7733          opt = $.merge({
7734            execShow:true,
7735            execHide:true,
7736            onHide: $.empty,
7737            onShow: $.empty
7738          }, callback || {});
7739      node.eachLevel(0, this.config.levelsToShow, function(n) {
7740          var d = n._depth - node._depth;
7741          if(d > level) {
7742              opt.onHide(n);
7743              if(opt.execHide) {
7744                n.drawn = false; 
7745                n.exist = false;
7746                fx.hideLabel(n, false);
7747              }
7748          } else {
7749              opt.onShow(n);
7750              if(opt.execShow) {
7751                n.exist = true;
7752              }
7753          }
7754      });
7755      node.drawn= true;
7756   },
7757   /*
7758     Returns the right level to show for the current tree in order to fit in canvas.
7759   */  
7760   getRightLevelToShow: function(node, canvas) {
7761      var config = this.config;
7762      var level = config.levelsToShow;
7763      var constrained = config.constrained;
7764      if(!constrained) return level;
7765      while(!this.treeFitsInCanvas(node, canvas, level) && level > 1) { level-- ; }
7766      return level;
7767   }
7768 });
7769
7770 /*
7771  * File: Loader.js
7772  * 
7773  */
7774
7775 /*
7776    Object: Loader
7777
7778    Provides methods for loading and serving JSON data.
7779 */
7780 var Loader = {
7781      construct: function(json) {
7782         var isGraph = ($.type(json) == 'array');
7783         var ans = new Graph(this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
7784         if(!isGraph) 
7785             //make tree
7786             (function (ans, json) {
7787                 ans.addNode(json);
7788                 if(json.children) {
7789                   for(var i=0, ch = json.children; i<ch.length; i++) {
7790                     ans.addAdjacence(json, ch[i]);
7791                     arguments.callee(ans, ch[i]);
7792                   }
7793                 }
7794             })(ans, json);
7795         else
7796             //make graph
7797             (function (ans, json) {
7798                 var getNode = function(id) {
7799                   for(var i=0, l=json.length; i<l; i++) {
7800                     if(json[i].id == id) {
7801                       return json[i];
7802                     }
7803                   }
7804                   // The node was not defined in the JSON
7805                   // Let's create it
7806                   var newNode = {
7807                                 "id" : id,
7808                                 "name" : id
7809                         };
7810                   return ans.addNode(newNode);
7811                 };
7812
7813                 for(var i=0, l=json.length; i<l; i++) {
7814                   ans.addNode(json[i]);
7815                   var adj = json[i].adjacencies;
7816                   if (adj) {
7817                     for(var j=0, lj=adj.length; j<lj; j++) {
7818                       var node = adj[j], data = {};
7819                       if(typeof adj[j] != 'string') {
7820                         data = $.merge(node.data, {});
7821                         node = node.nodeTo;
7822                       }
7823                       ans.addAdjacence(json[i], getNode(node), data);
7824                     }
7825                   }
7826                 }
7827             })(ans, json);
7828
7829         return ans;
7830     },
7831
7832     /*
7833      Method: loadJSON
7834     
7835      Loads a JSON structure to the visualization. The JSON structure can be a JSON *tree* or *graph* structure.
7836      
7837       A JSON tree or graph structure consists of nodes, each having as properties
7838        
7839        id - (string) A unique identifier for the node
7840        name - (string) A node's name
7841        data - (object) The data optional property contains a hash (i.e {}) 
7842        where you can store all the information you want about this node.
7843         
7844       For JSON *Tree* structures, there's an extra optional property *children* of type Array which contains the node's children.
7845       
7846       Example:
7847
7848       (start code js)
7849         var json = {  
7850           "id": "aUniqueIdentifier",  
7851           "name": "usually a nodes name",  
7852           "data": {
7853             "some key": "some value",
7854             "some other key": "some other value"
7855            },  
7856           "children": [ *other nodes or empty* ]  
7857         };  
7858       (end code)
7859         
7860         JSON *Graph* structures consist of an array of nodes, each specifying the nodes to which the current node is connected. 
7861         For JSON *Graph* structures, the *children* property is replaced by the *adjacencies* property.
7862         
7863         There are two types of *Graph* structures, *simple* and *extended* graph structures.
7864         
7865         For *simple* Graph structures, the adjacencies property contains an array of strings, each specifying the 
7866         id of the node connected to the main node.
7867         
7868         Example:
7869         
7870         (start code js)
7871         var json = [  
7872           {  
7873             "id": "aUniqueIdentifier",  
7874             "name": "usually a nodes name",  
7875             "data": {
7876               "some key": "some value",
7877               "some other key": "some other value"
7878              },  
7879             "adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier", 'etc']  
7880           },
7881
7882           'other nodes go here...' 
7883         ];          
7884         (end code)
7885         
7886         For *extended Graph structures*, the adjacencies property contains an array of Adjacency objects that have as properties
7887         
7888         nodeTo - (string) The other node connected by this adjacency.
7889         data - (object) A data property, where we can store custom key/value information.
7890         
7891         Example:
7892         
7893         (start code js)
7894         var json = [  
7895           {  
7896             "id": "aUniqueIdentifier",  
7897             "name": "usually a nodes name",  
7898             "data": {
7899               "some key": "some value",
7900               "some other key": "some other value"
7901              },  
7902             "adjacencies": [  
7903             {  
7904               nodeTo:"aNodeId",  
7905               data: {} //put whatever you want here  
7906             },
7907             'other adjacencies go here...'  
7908           },
7909
7910           'other nodes go here...' 
7911         ];          
7912         (end code)
7913        
7914        About the data property:
7915        
7916        As described before, you can store custom data in the *data* property of JSON *nodes* and *adjacencies*. 
7917        You can use almost any string as key for the data object. Some keys though are reserved by the toolkit, and 
7918        have special meanings. This is the case for keys starting with a dollar sign, for example, *$width*.
7919        
7920        For JSON *node* objects, adding dollar prefixed properties that match the names of the options defined in 
7921        <Options.Node> will override the general value for that option with that particular value. For this to work 
7922        however, you do have to set *overridable = true* in <Options.Node>.
7923        
7924        The same thing is true for JSON adjacencies. Dollar prefixed data properties will alter values set in <Options.Edge> 
7925        if <Options.Edge> has *overridable = true*.
7926        
7927        When loading JSON data into TreeMaps, the *data* property must contain a value for the *$area* key, 
7928        since this is the value which will be taken into account when creating the layout. 
7929        The same thing goes for the *$color* parameter.
7930        
7931        In JSON Nodes you can use also *$label-* prefixed properties to refer to <Options.Label> properties. For example, 
7932        *$label-size* will refer to <Options.Label> size property. Also, in JSON nodes and adjacencies you can set 
7933        canvas specific properties individually by using the *$canvas-* prefix. For example, *$canvas-shadowBlur* will refer 
7934        to the *shadowBlur* property.
7935        
7936        These properties can also be accessed after loading the JSON data from <Graph.Nodes> and <Graph.Adjacences> 
7937        by using <Accessors>. For more information take a look at the <Graph> and <Accessors> documentation.
7938        
7939        Finally, these properties can also be used to create advanced animations like with <Options.NodeStyles>. For more 
7940        information about creating animations please take a look at the <Graph.Plot> and <Graph.Plot.animate> documentation.
7941        
7942        loadJSON Parameters:
7943     
7944         json - A JSON Tree or Graph structure.
7945         i - For Graph structures only. Sets the indexed node as root for the visualization.
7946
7947     */
7948     loadJSON: function(json, i) {
7949       this.json = json;
7950       //if they're canvas labels erase them.
7951       if(this.labels && this.labels.clearLabels) {
7952         this.labels.clearLabels(true);
7953       }
7954       this.graph = this.construct(json);
7955       if($.type(json) != 'array'){
7956         this.root = json.id;
7957       } else {
7958         this.root = json[i? i : 0].id;
7959       }
7960     },
7961     
7962     /*
7963       Method: toJSON
7964    
7965       Returns a JSON tree/graph structure from the visualization's <Graph>. 
7966       See <Loader.loadJSON> for the graph formats available.
7967       
7968       See also:
7969       
7970       <Loader.loadJSON>
7971       
7972       Parameters:
7973       
7974       type - (string) Default's "tree". The type of the JSON structure to be returned. 
7975       Possible options are "tree" or "graph".
7976     */    
7977     toJSON: function(type) {
7978       type = type || "tree";
7979       if(type == 'tree') {
7980         var ans = {};
7981         var rootNode = this.graph.getNode(this.root);
7982         var ans = (function recTree(node) {
7983           var ans = {};
7984           ans.id = node.id;
7985           ans.name = node.name;
7986           ans.data = node.data;
7987           var ch =[];
7988           node.eachSubnode(function(n) {
7989             ch.push(recTree(n));
7990           });
7991           ans.children = ch;
7992           return ans;
7993         })(rootNode);
7994         return ans;
7995       } else {
7996         var ans = [];
7997         var T = !!this.graph.getNode(this.root).visited;
7998         this.graph.eachNode(function(node) {
7999           var ansNode = {};
8000           ansNode.id = node.id;
8001           ansNode.name = node.name;
8002           ansNode.data = node.data;
8003           var adjs = [];
8004           node.eachAdjacency(function(adj) {
8005             var nodeTo = adj.nodeTo;
8006             if(!!nodeTo.visited === T) {
8007               var ansAdj = {};
8008               ansAdj.nodeTo = nodeTo.id;
8009               ansAdj.data = adj.data;
8010               adjs.push(ansAdj);
8011             }
8012           });
8013           ansNode.adjacencies = adjs;
8014           ans.push(ansNode);
8015           node.visited = !T;
8016         });
8017         return ans;
8018       }
8019     }
8020 };
8021
8022
8023
8024 /*
8025  * File: Layouts.js
8026  * 
8027  * Implements base Tree and Graph layouts.
8028  *
8029  * Description:
8030  *
8031  * Implements base Tree and Graph layouts like Radial, Tree, etc.
8032  * 
8033  */
8034
8035 /*
8036  * Object: Layouts
8037  * 
8038  * Parent object for common layouts.
8039  *
8040  */
8041 var Layouts = $jit.Layouts = {};
8042
8043
8044 //Some util shared layout functions are defined here.
8045 var NodeDim = {
8046   label: null,
8047   
8048   compute: function(graph, prop, opt) {
8049     this.initializeLabel(opt);
8050     var label = this.label, style = label.style;
8051     graph.eachNode(function(n) {
8052       var autoWidth  = n.getData('autoWidth'),
8053           autoHeight = n.getData('autoHeight');
8054       if(autoWidth || autoHeight) {
8055         //delete dimensions since these are
8056         //going to be overridden now.
8057         delete n.data.$width;
8058         delete n.data.$height;
8059         delete n.data.$dim;
8060         
8061         var width  = n.getData('width'),
8062             height = n.getData('height');
8063         //reset label dimensions
8064         style.width  = autoWidth? 'auto' : width + 'px';
8065         style.height = autoHeight? 'auto' : height + 'px';
8066         
8067         //TODO(nico) should let the user choose what to insert here.
8068         label.innerHTML = n.name;
8069         
8070         var offsetWidth  = label.offsetWidth,
8071             offsetHeight = label.offsetHeight;
8072         var type = n.getData('type');
8073         if($.indexOf(['circle', 'square', 'triangle', 'star'], type) === -1) {
8074           n.setData('width', offsetWidth);
8075           n.setData('height', offsetHeight);
8076         } else {
8077           var dim = offsetWidth > offsetHeight? offsetWidth : offsetHeight;
8078           n.setData('width', dim);
8079           n.setData('height', dim);
8080           n.setData('dim', dim); 
8081         }
8082       }
8083     });
8084   },
8085   
8086   initializeLabel: function(opt) {
8087     if(!this.label) {
8088       this.label = document.createElement('div');
8089       document.body.appendChild(this.label);
8090     }
8091     this.setLabelStyles(opt);
8092   },
8093   
8094   setLabelStyles: function(opt) {
8095     $.extend(this.label.style, {
8096       'visibility': 'hidden',
8097       'position': 'absolute',
8098       'width': 'auto',
8099       'height': 'auto'
8100     });
8101     this.label.className = 'jit-autoadjust-label';
8102   }
8103 };
8104
8105
8106 /*
8107  * Class: Layouts.Tree
8108  * 
8109  * Implements a Tree Layout.
8110  * 
8111  * Implemented By:
8112  * 
8113  * <ST>
8114  * 
8115  * Inspired by:
8116  * 
8117  * Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8118  * 
8119  */
8120 Layouts.Tree = (function() {
8121   //Layout functions
8122   var slice = Array.prototype.slice;
8123
8124   /*
8125      Calculates the max width and height nodes for a tree level
8126   */  
8127   function getBoundaries(graph, config, level, orn, prop) {
8128     var dim = config.Node;
8129     var multitree = config.multitree;
8130     if (dim.overridable) {
8131       var w = -1, h = -1;
8132       graph.eachNode(function(n) {
8133         if (n._depth == level
8134             && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8135           var dw = n.getData('width', prop);
8136           var dh = n.getData('height', prop);
8137           w = (w < dw) ? dw : w;
8138           h = (h < dh) ? dh : h;
8139         }
8140       });
8141       return {
8142         'width' : w < 0 ? dim.width : w,
8143         'height' : h < 0 ? dim.height : h
8144       };
8145     } else {
8146       return dim;
8147     }
8148   }
8149
8150
8151   function movetree(node, prop, val, orn) {
8152     var p = (orn == "left" || orn == "right") ? "y" : "x";
8153     node.getPos(prop)[p] += val;
8154   }
8155
8156
8157   function moveextent(extent, val) {
8158     var ans = [];
8159     $.each(extent, function(elem) {
8160       elem = slice.call(elem);
8161       elem[0] += val;
8162       elem[1] += val;
8163       ans.push(elem);
8164     });
8165     return ans;
8166   }
8167
8168
8169   function merge(ps, qs) {
8170     if (ps.length == 0)
8171       return qs;
8172     if (qs.length == 0)
8173       return ps;
8174     var p = ps.shift(), q = qs.shift();
8175     return [ [ p[0], q[1] ] ].concat(merge(ps, qs));
8176   }
8177
8178
8179   function mergelist(ls, def) {
8180     def = def || [];
8181     if (ls.length == 0)
8182       return def;
8183     var ps = ls.pop();
8184     return mergelist(ls, merge(ps, def));
8185   }
8186
8187
8188   function fit(ext1, ext2, subtreeOffset, siblingOffset, i) {
8189     if (ext1.length <= i || ext2.length <= i)
8190       return 0;
8191
8192     var p = ext1[i][1], q = ext2[i][0];
8193     return Math.max(fit(ext1, ext2, subtreeOffset, siblingOffset, ++i)
8194         + subtreeOffset, p - q + siblingOffset);
8195   }
8196
8197
8198   function fitlistl(es, subtreeOffset, siblingOffset) {
8199     function $fitlistl(acc, es, i) {
8200       if (es.length <= i)
8201         return [];
8202       var e = es[i], ans = fit(acc, e, subtreeOffset, siblingOffset, 0);
8203       return [ ans ].concat($fitlistl(merge(acc, moveextent(e, ans)), es, ++i));
8204     }
8205     ;
8206     return $fitlistl( [], es, 0);
8207   }
8208
8209
8210   function fitlistr(es, subtreeOffset, siblingOffset) {
8211     function $fitlistr(acc, es, i) {
8212       if (es.length <= i)
8213         return [];
8214       var e = es[i], ans = -fit(e, acc, subtreeOffset, siblingOffset, 0);
8215       return [ ans ].concat($fitlistr(merge(moveextent(e, ans), acc), es, ++i));
8216     }
8217     ;
8218     es = slice.call(es);
8219     var ans = $fitlistr( [], es.reverse(), 0);
8220     return ans.reverse();
8221   }
8222
8223
8224   function fitlist(es, subtreeOffset, siblingOffset, align) {
8225     var esl = fitlistl(es, subtreeOffset, siblingOffset), esr = fitlistr(es,
8226         subtreeOffset, siblingOffset);
8227
8228     if (align == "left")
8229       esr = esl;
8230     else if (align == "right")
8231       esl = esr;
8232
8233     for ( var i = 0, ans = []; i < esl.length; i++) {
8234       ans[i] = (esl[i] + esr[i]) / 2;
8235     }
8236     return ans;
8237   }
8238
8239
8240   function design(graph, node, prop, config, orn) {
8241     var multitree = config.multitree;
8242     var auxp = [ 'x', 'y' ], auxs = [ 'width', 'height' ];
8243     var ind = +(orn == "left" || orn == "right");
8244     var p = auxp[ind], notp = auxp[1 - ind];
8245
8246     var cnode = config.Node;
8247     var s = auxs[ind], nots = auxs[1 - ind];
8248
8249     var siblingOffset = config.siblingOffset;
8250     var subtreeOffset = config.subtreeOffset;
8251     var align = config.align;
8252
8253     function $design(node, maxsize, acum) {
8254       var sval = node.getData(s, prop);
8255       var notsval = maxsize
8256           || (node.getData(nots, prop));
8257
8258       var trees = [], extents = [], chmaxsize = false;
8259       var chacum = notsval + config.levelDistance;
8260       node.eachSubnode(function(n) {
8261             if (n.exist
8262                 && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8263
8264               if (!chmaxsize)
8265                 chmaxsize = getBoundaries(graph, config, n._depth, orn, prop);
8266
8267               var s = $design(n, chmaxsize[nots], acum + chacum);
8268               trees.push(s.tree);
8269               extents.push(s.extent);
8270             }
8271           });
8272       var positions = fitlist(extents, subtreeOffset, siblingOffset, align);
8273       for ( var i = 0, ptrees = [], pextents = []; i < trees.length; i++) {
8274         movetree(trees[i], prop, positions[i], orn);
8275         pextents.push(moveextent(extents[i], positions[i]));
8276       }
8277       var resultextent = [ [ -sval / 2, sval / 2 ] ]
8278           .concat(mergelist(pextents));
8279       node.getPos(prop)[p] = 0;
8280
8281       if (orn == "top" || orn == "left") {
8282         node.getPos(prop)[notp] = acum;
8283       } else {
8284         node.getPos(prop)[notp] = -acum;
8285       }
8286
8287       return {
8288         tree : node,
8289         extent : resultextent
8290       };
8291     }
8292
8293     $design(node, false, 0);
8294   }
8295
8296
8297   return new Class({
8298     /*
8299     Method: compute
8300     
8301     Computes nodes' positions.
8302
8303      */
8304     compute : function(property, computeLevels) {
8305       var prop = property || 'start';
8306       var node = this.graph.getNode(this.root);
8307       $.extend(node, {
8308         'drawn' : true,
8309         'exist' : true,
8310         'selected' : true
8311       });
8312       NodeDim.compute(this.graph, prop, this.config);
8313       if (!!computeLevels || !("_depth" in node)) {
8314         this.graph.computeLevels(this.root, 0, "ignore");
8315       }
8316       
8317       this.computePositions(node, prop);
8318     },
8319
8320     computePositions : function(node, prop) {
8321       var config = this.config;
8322       var multitree = config.multitree;
8323       var align = config.align;
8324       var indent = align !== 'center' && config.indent;
8325       var orn = config.orientation;
8326       var orns = multitree ? [ 'top', 'right', 'bottom', 'left' ] : [ orn ];
8327       var that = this;
8328       $.each(orns, function(orn) {
8329         //calculate layout
8330           design(that.graph, node, prop, that.config, orn, prop);
8331           var i = [ 'x', 'y' ][+(orn == "left" || orn == "right")];
8332           //absolutize
8333           (function red(node) {
8334             node.eachSubnode(function(n) {
8335               if (n.exist
8336                   && (!multitree || ('$orn' in n.data) && n.data.$orn == orn)) {
8337
8338                 n.getPos(prop)[i] += node.getPos(prop)[i];
8339                 if (indent) {
8340                   n.getPos(prop)[i] += align == 'left' ? indent : -indent;
8341                 }
8342                 red(n);
8343               }
8344             });
8345           })(node);
8346         });
8347     }
8348   });
8349   
8350 })();
8351
8352 /*
8353  * File: Spacetree.js
8354  */
8355
8356 /*
8357    Class: ST
8358    
8359   A Tree layout with advanced contraction and expansion animations.
8360      
8361   Inspired by:
8362  
8363   SpaceTree: Supporting Exploration in Large Node Link Tree, Design Evolution and Empirical Evaluation (Catherine Plaisant, Jesse Grosjean, Benjamin B. Bederson) 
8364   <http://hcil.cs.umd.edu/trs/2002-05/2002-05.pdf>
8365   
8366   Drawing Trees (Andrew J. Kennedy) <http://research.microsoft.com/en-us/um/people/akenn/fun/drawingtrees.pdf>
8367   
8368   Note:
8369  
8370   This visualization was built and engineered from scratch, taking only the papers as inspiration, and only shares some features with the visualization described in those papers.
8371  
8372   Implements:
8373   
8374   All <Loader> methods
8375   
8376   Constructor Options:
8377   
8378   Inherits options from
8379   
8380   - <Options.Canvas>
8381   - <Options.Controller>
8382   - <Options.Tree>
8383   - <Options.Node>
8384   - <Options.Edge>
8385   - <Options.Label>
8386   - <Options.Events>
8387   - <Options.Tips>
8388   - <Options.NodeStyles>
8389   - <Options.Navigation>
8390   
8391   Additionally, there are other parameters and some default values changed
8392   
8393   constrained - (boolean) Default's *true*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
8394   levelsToShow - (number) Default's *2*. The number of levels to show for a subtree. This number is relative to the selected node.
8395   levelDistance - (number) Default's *30*. The distance between two consecutive levels of the tree.
8396   Node.type - Described in <Options.Node>. Default's set to *rectangle*.
8397   offsetX - (number) Default's *0*. The x-offset distance from the selected node to the center of the canvas.
8398   offsetY - (number) Default's *0*. The y-offset distance from the selected node to the center of the canvas.
8399   duration - Described in <Options.Fx>. It's default value has been changed to *700*.
8400   
8401   Instance Properties:
8402   
8403   canvas - Access a <Canvas> instance.
8404   graph - Access a <Graph> instance.
8405   op - Access a <ST.Op> instance.
8406   fx - Access a <ST.Plot> instance.
8407   labels - Access a <ST.Label> interface implementation.
8408
8409  */
8410
8411 $jit.ST= (function() {
8412     // Define some private methods first...
8413     // Nodes in path
8414     var nodesInPath = [];
8415     // Nodes to contract
8416     function getNodesToHide(node) {
8417       node = node || this.clickedNode;
8418       if(!this.config.constrained) {
8419         return [];
8420       }
8421       var Geom = this.geom;
8422       var graph = this.graph;
8423       var canvas = this.canvas;
8424       var level = node._depth, nodeArray = [];
8425           graph.eachNode(function(n) {
8426           if(n.exist && !n.selected) {
8427               if(n.isDescendantOf(node.id)) {
8428                 if(n._depth <= level) nodeArray.push(n);
8429               } else {
8430                 nodeArray.push(n);
8431               }
8432           }
8433           });
8434           var leafLevel = Geom.getRightLevelToShow(node, canvas);
8435           node.eachLevel(leafLevel, leafLevel, function(n) {
8436           if(n.exist && !n.selected) nodeArray.push(n);
8437           });
8438             
8439           for (var i = 0; i < nodesInPath.length; i++) {
8440             var n = this.graph.getNode(nodesInPath[i]);
8441             if(!n.isDescendantOf(node.id)) {
8442               nodeArray.push(n);
8443             }
8444           } 
8445           return nodeArray;       
8446     };
8447     // Nodes to expand
8448      function getNodesToShow(node) {
8449         var nodeArray = [], config = this.config;
8450         node = node || this.clickedNode;
8451         this.clickedNode.eachLevel(0, config.levelsToShow, function(n) {
8452             if(config.multitree && !('$orn' in n.data) 
8453                         && n.anySubnode(function(ch){ return ch.exist && !ch.drawn; })) {
8454                 nodeArray.push(n);
8455             } else if(n.drawn && !n.anySubnode("drawn")) {
8456               nodeArray.push(n);
8457             }
8458         });
8459         return nodeArray;
8460      };
8461     // Now define the actual class.
8462     return new Class({
8463     
8464         Implements: [Loader, Extras, Layouts.Tree],
8465         
8466         initialize: function(controller) {            
8467           var $ST = $jit.ST;
8468           
8469           var config= {
8470                 levelsToShow: 2,
8471                 levelDistance: 30,
8472                 constrained: true,                
8473                 Node: {
8474                   type: 'rectangle'
8475                 },
8476                 duration: 700,
8477                 offsetX: 0,
8478                 offsetY: 0
8479             };
8480             
8481             this.controller = this.config = $.merge(
8482                 Options("Canvas", "Fx", "Tree", "Node", "Edge", "Controller", 
8483                     "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
8484
8485             var canvasConfig = this.config;
8486             if(canvasConfig.useCanvas) {
8487               this.canvas = canvasConfig.useCanvas;
8488               this.config.labelContainer = this.canvas.id + '-label';
8489             } else {
8490               if(canvasConfig.background) {
8491                 canvasConfig.background = $.merge({
8492                   type: 'Fade',
8493                   colorStop1: this.config.colorStop1,
8494                   colorStop2: this.config.colorStop2
8495                 }, canvasConfig.background);
8496               }
8497               this.canvas = new Canvas(this, canvasConfig);
8498               this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
8499             }
8500
8501             this.graphOptions = {
8502                 'complex': true
8503             };
8504             this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
8505             this.labels = new $ST.Label[canvasConfig.Label.type](this);
8506             this.fx = new $ST.Plot(this, $ST);
8507             this.op = new $ST.Op(this);
8508             this.group = new $ST.Group(this);
8509             this.geom = new $ST.Geom(this);
8510             this.clickedNode=  null;
8511             // initialize extras
8512             this.initializeExtras();
8513         },
8514     
8515         /*
8516          Method: plot
8517         
8518          Plots the <ST>. This is a shortcut to *fx.plot*.
8519
8520         */  
8521         plot: function() { this.fx.plot(this.controller); },
8522     
8523       
8524         /*
8525          Method: switchPosition
8526         
8527          Switches the tree orientation.
8528
8529          Parameters:
8530
8531         pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
8532         method - (string) Set this to "animate" if you want to animate the tree when switching its position. You can also set this parameter to "replot" to just replot the subtree.
8533         onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8534
8535          Example:
8536
8537          (start code js)
8538            st.switchPosition("right", "animate", {
8539             onComplete: function() {
8540               alert('completed!');
8541             } 
8542            });
8543          (end code)
8544         */  
8545         switchPosition: function(pos, method, onComplete) {
8546           var Geom = this.geom, Plot = this.fx, that = this;
8547           if(!Plot.busy) {
8548               Plot.busy = true;
8549               this.contract({
8550                   onComplete: function() {
8551                       Geom.switchOrientation(pos);
8552                       that.compute('end', false);
8553                       Plot.busy = false;
8554                       if(method == 'animate') {
8555                           that.onClick(that.clickedNode.id, onComplete);  
8556                       } else if(method == 'replot') {
8557                           that.select(that.clickedNode.id, onComplete);
8558                       }
8559                   }
8560               }, pos);
8561           }
8562         },
8563
8564         /*
8565         Method: switchAlignment
8566        
8567         Switches the tree alignment.
8568
8569         Parameters:
8570
8571        align - (string) The new tree alignment. Possible values are "left", "center" and "right".
8572        method - (string) Set this to "animate" if you want to animate the tree after aligning its position. You can also set this parameter to "replot" to just replot the subtree.
8573        onComplete - (optional|object) This callback is called once the "switching" animation is complete.
8574
8575         Example:
8576
8577         (start code js)
8578           st.switchAlignment("right", "animate", {
8579            onComplete: function() {
8580              alert('completed!');
8581            } 
8582           });
8583         (end code)
8584        */  
8585        switchAlignment: function(align, method, onComplete) {
8586         this.config.align = align;
8587         if(method == 'animate') {
8588                 this.select(this.clickedNode.id, onComplete);
8589         } else if(method == 'replot') {
8590                 this.onClick(this.clickedNode.id, onComplete);  
8591         }
8592        },
8593
8594        /*
8595         Method: addNodeInPath
8596        
8597         Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
8598         
8599
8600         Parameters:
8601
8602        id - (string) A <Graph.Node> id.
8603
8604         Example:
8605
8606         (start code js)
8607           st.addNodeInPath("nodeId");
8608         (end code)
8609        */  
8610        addNodeInPath: function(id) {
8611            nodesInPath.push(id);
8612            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8613        },       
8614
8615        /*
8616        Method: clearNodesInPath
8617       
8618        Removes all nodes tagged as selected by the <ST.addNodeInPath> method.
8619        
8620        See also:
8621        
8622        <ST.addNodeInPath>
8623      
8624        Example:
8625
8626        (start code js)
8627          st.clearNodesInPath();
8628        (end code)
8629       */  
8630        clearNodesInPath: function(id) {
8631            nodesInPath.length = 0;
8632            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8633        },
8634         
8635        /*
8636          Method: refresh
8637         
8638          Computes positions and plots the tree.
8639          
8640        */
8641        refresh: function() {
8642            this.reposition();
8643            this.select((this.clickedNode && this.clickedNode.id) || this.root);
8644        },    
8645
8646        reposition: function() {
8647             this.graph.computeLevels(this.root, 0, "ignore");
8648              this.geom.setRightLevelToShow(this.clickedNode, this.canvas);
8649             this.graph.eachNode(function(n) {
8650                 if(n.exist) n.drawn = true;
8651             });
8652             this.compute('end');
8653         },
8654         
8655         requestNodes: function(node, onComplete) {
8656           var handler = $.merge(this.controller, onComplete), 
8657           lev = this.config.levelsToShow;
8658           if(handler.request) {
8659               var leaves = [], d = node._depth;
8660               node.eachLevel(0, lev, function(n) {
8661                   if(n.drawn && 
8662                    !n.anySubnode()) {
8663                    leaves.push(n);
8664                    n._level = lev - (n._depth - d);
8665                   }
8666               });
8667               this.group.requestNodes(leaves, handler);
8668           }
8669             else
8670               handler.onComplete();
8671         },
8672      
8673         contract: function(onComplete, switched) {
8674           var orn  = this.config.orientation;
8675           var Geom = this.geom, Group = this.group;
8676           if(switched) Geom.switchOrientation(switched);
8677           var nodes = getNodesToHide.call(this);
8678           if(switched) Geom.switchOrientation(orn);
8679           Group.contract(nodes, $.merge(this.controller, onComplete));
8680         },
8681       
8682          move: function(node, onComplete) {
8683             this.compute('end', false);
8684             var move = onComplete.Move, offset = {
8685                 'x': move.offsetX,
8686                 'y': move.offsetY 
8687             };
8688             if(move.enable) {
8689                 this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
8690             }
8691             this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
8692          },
8693       
8694         expand: function (node, onComplete) {
8695             var nodeArray = getNodesToShow.call(this, node);
8696             this.group.expand(nodeArray, $.merge(this.controller, onComplete));
8697         },
8698     
8699         selectPath: function(node) {
8700           var that = this;
8701           this.graph.eachNode(function(n) { n.selected = false; }); 
8702           function path(node) {
8703               if(node == null || node.selected) return;
8704               node.selected = true;
8705               $.each(that.group.getSiblings([node])[node.id], 
8706               function(n) { 
8707                    n.exist = true; 
8708                    n.drawn = true; 
8709               });    
8710               var parents = node.getParents();
8711               parents = (parents.length > 0)? parents[0] : null;
8712               path(parents);
8713           };
8714           for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
8715               path(this.graph.getNode(ns[i]));
8716           }
8717         },
8718       
8719         /*
8720         Method: setRoot
8721      
8722          Switches the current root node. Changes the topology of the Tree.
8723      
8724         Parameters:
8725            id - (string) The id of the node to be set as root.
8726            method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8727            onComplete - (optional|object) An action to perform after the animation (if any).
8728  
8729         Example:
8730
8731         (start code js)
8732           st.setRoot('nodeId', 'animate', {
8733              onComplete: function() {
8734                alert('complete!');
8735              }
8736           });
8737         (end code)
8738      */
8739      setRoot: function(id, method, onComplete) {
8740                 if(this.busy) return;
8741                 this.busy = true;
8742           var that = this, canvas = this.canvas;
8743                 var rootNode = this.graph.getNode(this.root);
8744                 var clickedNode = this.graph.getNode(id);
8745                 function $setRoot() {
8746                 if(this.config.multitree && clickedNode.data.$orn) {
8747                         var orn = clickedNode.data.$orn;
8748                         var opp = {
8749                                         'left': 'right',
8750                                         'right': 'left',
8751                                         'top': 'bottom',
8752                                         'bottom': 'top'
8753                         }[orn];
8754                         rootNode.data.$orn = opp;
8755                         (function tag(rootNode) {
8756                                 rootNode.eachSubnode(function(n) {
8757                                         if(n.id != id) {
8758                                                 n.data.$orn = opp;
8759                                                 tag(n);
8760                                         }
8761                                 });
8762                         })(rootNode);
8763                         delete clickedNode.data.$orn;
8764                 }
8765                 this.root = id;
8766                 this.clickedNode = clickedNode;
8767                 this.graph.computeLevels(this.root, 0, "ignore");
8768                 this.geom.setRightLevelToShow(clickedNode, canvas, {
8769                   execHide: false,
8770                   onShow: function(node) {
8771                     if(!node.drawn) {
8772                     node.drawn = true;
8773                     node.setData('alpha', 1, 'end');
8774                     node.setData('alpha', 0);
8775                     node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
8776                     }
8777                   }
8778                 });
8779               this.compute('end');
8780               this.busy = true;
8781               this.fx.animate({
8782                 modes: ['linear', 'node-property:alpha'],
8783                 onComplete: function() {
8784                   that.busy = false;
8785                   that.onClick(id, {
8786                     onComplete: function() {
8787                       onComplete && onComplete.onComplete();
8788                     }
8789                   });
8790                 }
8791               });
8792                 }
8793
8794                 // delete previous orientations (if any)
8795                 delete rootNode.data.$orns;
8796
8797                 if(method == 'animate') {
8798                   $setRoot.call(this);
8799                   that.selectPath(clickedNode);
8800                 } else if(method == 'replot') {
8801                         $setRoot.call(this);
8802                         this.select(this.root);
8803                 }
8804      },
8805
8806      /*
8807            Method: addSubtree
8808         
8809             Adds a subtree.
8810         
8811            Parameters:
8812               subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
8813               method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
8814               onComplete - (optional|object) An action to perform after the animation (if any).
8815     
8816            Example:
8817
8818            (start code js)
8819              st.addSubtree(json, 'animate', {
8820                 onComplete: function() {
8821                   alert('complete!');
8822                 }
8823              });
8824            (end code)
8825         */
8826         addSubtree: function(subtree, method, onComplete) {
8827             if(method == 'replot') {
8828                 this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
8829             } else if (method == 'animate') {
8830                 this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
8831             }
8832         },
8833     
8834         /*
8835            Method: removeSubtree
8836         
8837             Removes a subtree.
8838         
8839            Parameters:
8840               id - (string) The _id_ of the subtree to be removed.
8841               removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
8842               method - (string) Set this to "animate" if you want to animate the tree after removing the subtree. You can also set this parameter to "replot" to just replot the subtree.
8843               onComplete - (optional|object) An action to perform after the animation (if any).
8844
8845           Example:
8846
8847           (start code js)
8848             st.removeSubtree('idOfSubtreeToBeRemoved', false, 'animate', {
8849               onComplete: function() {
8850                 alert('complete!');
8851               }
8852             });
8853           (end code)
8854     
8855         */
8856         removeSubtree: function(id, removeRoot, method, onComplete) {
8857             var node = this.graph.getNode(id), subids = [];
8858             node.eachLevel(+!removeRoot, false, function(n) {
8859                 subids.push(n.id);
8860             });
8861             if(method == 'replot') {
8862                 this.op.removeNode(subids, $.extend({ type: 'replot' }, onComplete || {}));
8863             } else if (method == 'animate') {
8864                 this.op.removeNode(subids, $.extend({ type: 'fade:seq'}, onComplete || {}));
8865             }
8866         },
8867     
8868         /*
8869            Method: select
8870         
8871             Selects a node in the <ST> without performing an animation. Useful when selecting 
8872             nodes which are currently hidden or deep inside the tree.
8873
8874           Parameters:
8875             id - (string) The id of the node to select.
8876             onComplete - (optional|object) an onComplete callback.
8877
8878           Example:
8879           (start code js)
8880             st.select('mynodeid', {
8881               onComplete: function() {
8882                 alert('complete!');
8883               }
8884             });
8885           (end code)
8886         */
8887         select: function(id, onComplete) {
8888             var group = this.group, geom = this.geom;
8889             var node=  this.graph.getNode(id), canvas = this.canvas;
8890             var root  = this.graph.getNode(this.root);
8891             var complete = $.merge(this.controller, onComplete);
8892             var that = this;
8893     
8894             complete.onBeforeCompute(node);
8895             this.selectPath(node);
8896             this.clickedNode= node;
8897             this.requestNodes(node, {
8898                 onComplete: function(){
8899                     group.hide(group.prepare(getNodesToHide.call(that)), complete);
8900                     geom.setRightLevelToShow(node, canvas);
8901                     that.compute("current");
8902                     that.graph.eachNode(function(n) { 
8903                         var pos = n.pos.getc(true);
8904                         n.startPos.setc(pos.x, pos.y);
8905                         n.endPos.setc(pos.x, pos.y);
8906                         n.visited = false; 
8907                     });
8908                     var offset = { x: complete.offsetX, y: complete.offsetY };
8909                     that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
8910                     group.show(getNodesToShow.call(that));              
8911                     that.plot();
8912                     complete.onAfterCompute(that.clickedNode);
8913                     complete.onComplete();
8914                 }
8915             });     
8916         },
8917     
8918       /*
8919          Method: onClick
8920     
8921         Animates the <ST> to center the node specified by *id*.
8922             
8923         Parameters:
8924         
8925         id - (string) A node id.
8926         options - (optional|object) A group of options and callbacks described below.
8927         onComplete - (object) An object callback called when the animation finishes.
8928         Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
8929
8930         Example:
8931
8932         (start code js)
8933           st.onClick('mynodeid', {
8934                   Move: {
8935                         enable: true,
8936                     offsetX: 30,
8937                     offsetY: 5
8938                   },
8939                   onComplete: function() {
8940                       alert('yay!');
8941                   }
8942           });
8943         (end code)
8944     
8945         */    
8946       onClick: function (id, options) {
8947         var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
8948         var innerController = {
8949             Move: {
8950                     enable: true,
8951               offsetX: config.offsetX || 0,
8952               offsetY: config.offsetY || 0  
8953             },
8954             setRightLevelToShowConfig: false,
8955             onBeforeRequest: $.empty,
8956             onBeforeContract: $.empty,
8957             onBeforeMove: $.empty,
8958             onBeforeExpand: $.empty
8959         };
8960         var complete = $.merge(this.controller, innerController, options);
8961         
8962         if(!this.busy) {
8963             this.busy = true;
8964             var node = this.graph.getNode(id);
8965             this.selectPath(node, this.clickedNode);
8966                 this.clickedNode = node;
8967             complete.onBeforeCompute(node);
8968             complete.onBeforeRequest(node);
8969             this.requestNodes(node, {
8970                 onComplete: function() {
8971                     complete.onBeforeContract(node);
8972                     that.contract({
8973                         onComplete: function() {
8974                             Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
8975                             complete.onBeforeMove(node);
8976                             that.move(node, {
8977                                 Move: complete.Move,
8978                                 onComplete: function() {
8979                                     complete.onBeforeExpand(node);
8980                                     that.expand(node, {
8981                                         onComplete: function() {
8982                                             that.busy = false;
8983                                             complete.onAfterCompute(id);
8984                                             complete.onComplete();
8985                                         }
8986                                     }); // expand
8987                                 }
8988                             }); // move
8989                         }
8990                     });// contract
8991                 }
8992             });// request
8993         }
8994       }
8995     });
8996
8997 })();
8998
8999 $jit.ST.$extend = true;
9000
9001 /*
9002    Class: ST.Op
9003     
9004    Custom extension of <Graph.Op>.
9005
9006    Extends:
9007
9008    All <Graph.Op> methods
9009    
9010    See also:
9011    
9012    <Graph.Op>
9013
9014 */
9015 $jit.ST.Op = new Class({
9016
9017   Implements: Graph.Op
9018     
9019 });
9020
9021 /*
9022     
9023      Performs operations on group of nodes.
9024
9025 */
9026 $jit.ST.Group = new Class({
9027     
9028     initialize: function(viz) {
9029         this.viz = viz;
9030         this.canvas = viz.canvas;
9031         this.config = viz.config;
9032         this.animation = new Animation;
9033         this.nodes = null;
9034     },
9035     
9036     /*
9037     
9038        Calls the request method on the controller to request a subtree for each node. 
9039     */
9040     requestNodes: function(nodes, controller) {
9041         var counter = 0, len = nodes.length, nodeSelected = {};
9042         var complete = function() { controller.onComplete(); };
9043         var viz = this.viz;
9044         if(len == 0) complete();
9045         for(var i=0; i<len; i++) {
9046             nodeSelected[nodes[i].id] = nodes[i];
9047             controller.request(nodes[i].id, nodes[i]._level, {
9048                 onComplete: function(nodeId, data) {
9049                     if(data && data.children) {
9050                         data.id = nodeId;
9051                         viz.op.sum(data, { type: 'nothing' });
9052                     }
9053                     if(++counter == len) {
9054                         viz.graph.computeLevels(viz.root, 0);
9055                         complete();
9056                     }
9057                 }
9058             });
9059         }
9060     },
9061     
9062     /*
9063     
9064        Collapses group of nodes. 
9065     */
9066     contract: function(nodes, controller) {
9067         var viz = this.viz;
9068         var that = this;
9069
9070         nodes = this.prepare(nodes);
9071         this.animation.setOptions($.merge(controller, {
9072             $animating: false,
9073             compute: function(delta) {
9074               if(delta == 1) delta = 0.99;
9075               that.plotStep(1 - delta, controller, this.$animating);
9076               this.$animating = 'contract';
9077             },
9078             
9079             complete: function() {
9080                 that.hide(nodes, controller);
9081             }       
9082         })).start();
9083     },
9084     
9085     hide: function(nodes, controller) {
9086         var viz = this.viz;
9087         for(var i=0; i<nodes.length; i++) {
9088             // TODO nodes are requested on demand, but not
9089             // deleted when hidden. Would that be a good feature?
9090             // Currently that feature is buggy, so I'll turn it off
9091             // Actually this feature is buggy because trimming should take
9092             // place onAfterCompute and not right after collapsing nodes.
9093             if (true || !controller || !controller.request) {
9094                 nodes[i].eachLevel(1, false, function(elem){
9095                     if (elem.exist) {
9096                         $.extend(elem, {
9097                             'drawn': false,
9098                             'exist': false
9099                         });
9100                     }
9101                 });
9102             } else {
9103                 var ids = [];
9104                 nodes[i].eachLevel(1, false, function(n) {
9105                     ids.push(n.id);
9106                 });
9107                 viz.op.removeNode(ids, { 'type': 'nothing' });
9108                 viz.labels.clearLabels();
9109             }
9110         }
9111         controller.onComplete();
9112     },    
9113     
9114
9115     /*
9116        Expands group of nodes. 
9117     */
9118     expand: function(nodes, controller) {
9119         var that = this;
9120         this.show(nodes);
9121         this.animation.setOptions($.merge(controller, {
9122             $animating: false,
9123             compute: function(delta) {
9124                 that.plotStep(delta, controller, this.$animating);
9125                 this.$animating = 'expand';
9126             },
9127             
9128             complete: function() {
9129                 that.plotStep(undefined, controller, false);
9130                 controller.onComplete();
9131             }       
9132         })).start();
9133         
9134     },
9135     
9136     show: function(nodes) {
9137         var config = this.config;
9138         this.prepare(nodes);
9139         $.each(nodes, function(n) {
9140                 // check for root nodes if multitree
9141                 if(config.multitree && !('$orn' in n.data)) {
9142                         delete n.data.$orns;
9143                         var orns = ' ';
9144                         n.eachSubnode(function(ch) {
9145                                 if(('$orn' in ch.data) 
9146                                                 && orns.indexOf(ch.data.$orn) < 0 
9147                                                 && ch.exist && !ch.drawn) {
9148                                         orns += ch.data.$orn + ' ';
9149                                 }
9150                         });
9151                         n.data.$orns = orns;
9152                 }
9153             n.eachLevel(0, config.levelsToShow, function(n) {
9154                 if(n.exist) n.drawn = true;
9155             });     
9156         });
9157     },
9158     
9159     prepare: function(nodes) {
9160         this.nodes = this.getNodesWithChildren(nodes);
9161         return this.nodes;
9162     },
9163     
9164     /*
9165        Filters an array of nodes leaving only nodes with children.
9166     */
9167     getNodesWithChildren: function(nodes) {
9168         var ans = [], config = this.config, root = this.viz.root;
9169         nodes.sort(function(a, b) { return (a._depth <= b._depth) - (a._depth >= b._depth); });
9170         for(var i=0; i<nodes.length; i++) {
9171             if(nodes[i].anySubnode("exist")) {
9172                 for (var j = i+1, desc = false; !desc && j < nodes.length; j++) {
9173                     if(!config.multitree || '$orn' in nodes[j].data) {
9174                                 desc = desc || nodes[i].isDescendantOf(nodes[j].id);                            
9175                     }
9176                 }
9177                 if(!desc) ans.push(nodes[i]);
9178             }
9179         }
9180         return ans;
9181     },
9182     
9183     plotStep: function(delta, controller, animating) {
9184         var viz = this.viz,
9185         config = this.config,
9186         canvas = viz.canvas, 
9187         ctx = canvas.getCtx(),
9188         nodes = this.nodes;
9189         var i, node;
9190         // hide nodes that are meant to be collapsed/expanded
9191         var nds = {};
9192         for(i=0; i<nodes.length; i++) {
9193           node = nodes[i];
9194           nds[node.id] = [];
9195           var root = config.multitree && !('$orn' in node.data);
9196           var orns = root && node.data.$orns;
9197           node.eachSubgraph(function(n) { 
9198             // TODO(nico): Cleanup
9199                   // special check for root node subnodes when
9200                   // multitree is checked.
9201                   if(root && orns && orns.indexOf(n.data.$orn) > 0 
9202                                   && n.drawn) {
9203                           n.drawn = false;
9204                   nds[node.id].push(n);
9205               } else if((!root || !orns) && n.drawn) {
9206                 n.drawn = false;
9207                 nds[node.id].push(n);
9208               }
9209             }); 
9210             node.drawn = true;
9211         }
9212         // plot the whole (non-scaled) tree
9213         if(nodes.length > 0) viz.fx.plot();
9214         // show nodes that were previously hidden
9215         for(i in nds) {
9216           $.each(nds[i], function(n) { n.drawn = true; });
9217         }
9218         // plot each scaled subtree
9219         for(i=0; i<nodes.length; i++) {
9220           node = nodes[i];
9221           ctx.save();
9222           viz.fx.plotSubtree(node, controller, delta, animating);                
9223           ctx.restore();
9224         }
9225       },
9226
9227       getSiblings: function(nodes) {
9228         var siblings = {};
9229         $.each(nodes, function(n) {
9230             var par = n.getParents();
9231             if (par.length == 0) {
9232                 siblings[n.id] = [n];
9233             } else {
9234                 var ans = [];
9235                 par[0].eachSubnode(function(sn) {
9236                     ans.push(sn);
9237                 });
9238                 siblings[n.id] = ans;
9239             }
9240         });
9241         return siblings;
9242     }
9243 });
9244
9245 /*
9246    ST.Geom
9247
9248    Performs low level geometrical computations.
9249
9250    Access:
9251
9252    This instance can be accessed with the _geom_ parameter of the st instance created.
9253
9254    Example:
9255
9256    (start code js)
9257     var st = new ST(canvas, config);
9258     st.geom.translate //or can also call any other <ST.Geom> method
9259    (end code)
9260
9261 */
9262
9263 $jit.ST.Geom = new Class({
9264     Implements: Graph.Geom,
9265     /*
9266        Changes the tree current orientation to the one specified.
9267
9268        You should usually use <ST.switchPosition> instead.
9269     */  
9270     switchOrientation: function(orn) {
9271         this.config.orientation = orn;
9272     },
9273
9274     /*
9275        Makes a value dispatch according to the current layout
9276        Works like a CSS property, either _top-right-bottom-left_ or _top|bottom - left|right_.
9277      */
9278     dispatch: function() {
9279           // TODO(nico) should store Array.prototype.slice.call somewhere.
9280         var args = Array.prototype.slice.call(arguments);
9281         var s = args.shift(), len = args.length;
9282         var val = function(a) { return typeof a == 'function'? a() : a; };
9283         if(len == 2) {
9284             return (s == "top" || s == "bottom")? val(args[0]) : val(args[1]);
9285         } else if(len == 4) {
9286             switch(s) {
9287                 case "top": return val(args[0]);
9288                 case "right": return val(args[1]);
9289                 case "bottom": return val(args[2]);
9290                 case "left": return val(args[3]);
9291             }
9292         }
9293         return undefined;
9294     },
9295
9296     /*
9297        Returns label height or with, depending on the tree current orientation.
9298     */  
9299     getSize: function(n, invert) {
9300         var data = n.data, config = this.config;
9301         var siblingOffset = config.siblingOffset;
9302         var s = (config.multitree 
9303                         && ('$orn' in data) 
9304                         && data.$orn) || config.orientation;
9305         var w = n.getData('width') + siblingOffset;
9306         var h = n.getData('height') + siblingOffset;
9307         if(!invert)
9308             return this.dispatch(s, h, w);
9309         else
9310             return this.dispatch(s, w, h);
9311     },
9312     
9313     /*
9314        Calculates a subtree base size. This is an utility function used by _getBaseSize_
9315     */  
9316     getTreeBaseSize: function(node, level, leaf) {
9317         var size = this.getSize(node, true), baseHeight = 0, that = this;
9318         if(leaf(level, node)) return size;
9319         if(level === 0) return 0;
9320         node.eachSubnode(function(elem) {
9321             baseHeight += that.getTreeBaseSize(elem, level -1, leaf);
9322         });
9323         return (size > baseHeight? size : baseHeight) + this.config.subtreeOffset;
9324     },
9325
9326
9327     /*
9328        getEdge
9329        
9330        Returns a Complex instance with the begin or end position of the edge to be plotted.
9331
9332        Parameters:
9333
9334        node - A <Graph.Node> that is connected to this edge.
9335        type - Returns the begin or end edge position. Possible values are 'begin' or 'end'.
9336
9337        Returns:
9338
9339        A <Complex> number specifying the begin or end position.
9340     */  
9341     getEdge: function(node, type, s) {
9342         var $C = function(a, b) { 
9343           return function(){
9344             return node.pos.add(new Complex(a, b));
9345           }; 
9346         };
9347         var dim = this.node;
9348         var w = node.getData('width');
9349         var h = node.getData('height');
9350
9351         if(type == 'begin') {
9352             if(dim.align == "center") {
9353                 return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
9354                                      $C(0, -h/2),$C(w/2, 0));
9355             } else if(dim.align == "left") {
9356                 return this.dispatch(s, $C(0, h), $C(0, 0),
9357                                      $C(0, 0), $C(w, 0));
9358             } else if(dim.align == "right") {
9359                 return this.dispatch(s, $C(0, 0), $C(-w, 0),
9360                                      $C(0, -h),$C(0, 0));
9361             } else throw "align: not implemented";
9362             
9363             
9364         } else if(type == 'end') {
9365             if(dim.align == "center") {
9366                 return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
9367                                      $C(0, h/2),  $C(-w/2, 0));
9368             } else if(dim.align == "left") {
9369                 return this.dispatch(s, $C(0, 0), $C(w, 0),
9370                                      $C(0, h), $C(0, 0));
9371             } else if(dim.align == "right") {
9372                 return this.dispatch(s, $C(0, -h),$C(0, 0),
9373                                      $C(0, 0), $C(-w, 0));
9374             } else throw "align: not implemented";
9375         }
9376     },
9377
9378     /*
9379        Adjusts the tree position due to canvas scaling or translation.
9380     */  
9381     getScaledTreePosition: function(node, scale) {
9382         var dim = this.node;
9383         var w = node.getData('width');
9384         var h = node.getData('height');
9385         var s = (this.config.multitree 
9386                         && ('$orn' in node.data) 
9387                         && node.data.$orn) || this.config.orientation;
9388
9389         var $C = function(a, b) { 
9390           return function(){
9391             return node.pos.add(new Complex(a, b)).$scale(1 - scale);
9392           }; 
9393         };
9394         if(dim.align == "left") {
9395             return this.dispatch(s, $C(0, h), $C(0, 0),
9396                                  $C(0, 0), $C(w, 0));
9397         } else if(dim.align == "center") {
9398             return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
9399                                  $C(0, -h / 2),$C(w / 2, 0));
9400         } else if(dim.align == "right") {
9401             return this.dispatch(s, $C(0, 0), $C(-w, 0),
9402                                  $C(0, -h),$C(0, 0));
9403         } else throw "align: not implemented";
9404     },
9405
9406     /*
9407        treeFitsInCanvas
9408        
9409        Returns a Boolean if the current subtree fits in canvas.
9410
9411        Parameters:
9412
9413        node - A <Graph.Node> which is the current root of the subtree.
9414        canvas - The <Canvas> object.
9415        level - The depth of the subtree to be considered.
9416     */  
9417     treeFitsInCanvas: function(node, canvas, level) {
9418         var csize = canvas.getSize();
9419         var s = (this.config.multitree 
9420                         && ('$orn' in node.data) 
9421                         && node.data.$orn) || this.config.orientation;
9422
9423         var size = this.dispatch(s, csize.width, csize.height);
9424         var baseSize = this.getTreeBaseSize(node, level, function(level, node) { 
9425           return level === 0 || !node.anySubnode();
9426         });
9427         return (baseSize < size);
9428     }
9429 });
9430
9431 /*
9432   Class: ST.Plot
9433   
9434   Custom extension of <Graph.Plot>.
9435
9436   Extends:
9437
9438   All <Graph.Plot> methods
9439   
9440   See also:
9441   
9442   <Graph.Plot>
9443
9444 */
9445 $jit.ST.Plot = new Class({
9446     
9447     Implements: Graph.Plot,
9448     
9449     /*
9450        Plots a subtree from the spacetree.
9451     */
9452     plotSubtree: function(node, opt, scale, animating) {
9453         var viz = this.viz, canvas = viz.canvas, config = viz.config;
9454         scale = Math.min(Math.max(0.001, scale), 1);
9455         if(scale >= 0) {
9456             node.drawn = false;     
9457             var ctx = canvas.getCtx();
9458             var diff = viz.geom.getScaledTreePosition(node, scale);
9459             ctx.translate(diff.x, diff.y);
9460             ctx.scale(scale, scale);
9461         }
9462         this.plotTree(node, $.merge(opt, {
9463           'withLabels': true,
9464           'hideLabels': !!scale,
9465           'plotSubtree': function(n, ch) {
9466             var root = config.multitree && !('$orn' in node.data);
9467             var orns = root && node.getData('orns');
9468             return !root || orns.indexOf(elem.getData('orn')) > -1;
9469           }
9470         }), animating);
9471         if(scale >= 0) node.drawn = true;
9472     },
9473
9474     /**
9475      * Return array with correct positions for each element
9476      *
9477      * @param {Array} dimArray
9478      * @param {Number} fontHeight
9479      * @return {Array}
9480      */
9481     positions: function(dimArray, fontHeight)
9482     {
9483         var group = [];
9484         var isLastElem = false;
9485         var i;
9486         var newArray = [];
9487         var position = 0;
9488         var currentState;
9489
9490
9491         for (i = 0; i < dimArray.length; i++)
9492         {
9493             currentState = {type: 'element', position: position, height: dimArray[i], font: fontHeight, filament: true};
9494             if (dimArray[i] <= fontHeight)
9495             {
9496                 if (isLastElem)
9497                 {
9498                     group = [];
9499                 }
9500                 group.push(currentState);
9501                 isLastElem = false;
9502             }
9503             else
9504             {
9505                 group.push(currentState);
9506                 newArray.push({type: 'group', val:group, groupHeight: 0, groupPosition: group[0].position});
9507                 group = [];
9508                 isLastElem = true;
9509             }
9510             position += dimArray[i];
9511         }
9512         if (group.length > 0)
9513         {
9514             newArray.push({type: 'group', val: group, groupHeight: 0, groupPosition: group[0].position});
9515             group = [];
9516         }
9517         var figureHeight = position;
9518
9519         for (i = 0; i < newArray.length; i++)
9520         {
9521             newArray[i] = this.pipelineGetHeight(newArray[i]);
9522         }
9523
9524         newArray = this.pipelineMoveBlocks(newArray, figureHeight, fontHeight);
9525
9526         var ret = [];
9527         for (i = 0; i < newArray.length; i++)
9528         {
9529             group = newArray[i].val;
9530             for (var k = 0; k < group.length; k++)
9531             {
9532                 ret.push(group[k]);
9533             }
9534         }
9535         return ret;
9536     },
9537
9538     /**
9539      * Return recalculation group height(groupHeight) and positions of elements
9540      *
9541      * @param {Array} group
9542      * @return {Array}
9543      */
9544     pipelineGetHeight: function(group)
9545     {
9546         var position = 0;
9547         var between = 3;
9548         var count = group.val.length;
9549         var fontHeight = group.val[0].font;
9550         var positionStart = group.val[0].position;
9551
9552         if (count == 1)
9553         {
9554             group.groupHeight = group.val[0].font;
9555             group.val[0].filament = false;
9556             return group;
9557         }
9558
9559         if (count == 2)
9560         {
9561             group.groupHeight = fontHeight * 2 + between;
9562             group.val[1].position = positionStart + fontHeight + between;
9563             group.val[0].filament = false;
9564             group.val[1].filament = false;
9565             return group;
9566         }
9567
9568         var even = true;
9569         for (var i = 0; i < group.val.length; i++)
9570         {
9571             group.val[i].position = positionStart + position;
9572             even = i % 2;
9573             position += between;
9574             if (even)
9575             {
9576                 group.val[i].filament = false;
9577                 position += fontHeight;
9578             }
9579             else
9580             {
9581                 group.val[i].filament = true;
9582             }
9583         }
9584         group.groupHeight = (group.val[group.val.length - 1].position - group.val[0].position) + fontHeight + between;
9585         return group;
9586     },
9587
9588     /**
9589      * Return array with new group and elements positions relation figure layout border
9590      *
9591      * @param {Array} block
9592      * @param {Number} figureheight
9593      * @param {Number} fontHeight
9594      * @return {Array}
9595      */
9596     pipelineMoveBlocks: function(block, figureheight, fontHeight)
9597     {
9598         var offset;
9599         var rebuild;
9600         if (block.length < 2)
9601         {
9602             return block;
9603         }
9604
9605         var lastValue = block[block.length - 1];
9606         var prelastValue;
9607         if ((lastValue.groupPosition + lastValue.groupHeight) > figureheight)
9608         {
9609             offset = (figureheight - lastValue.groupHeight) - lastValue.groupPosition;
9610             lastValue.groupPosition += offset;
9611             for (var li = 0; li < lastValue.val.length; li++)
9612             {
9613                 lastValue.val[li].position += offset;
9614             }
9615             prelastValue = block[block.length - 2];
9616             if (prelastValue.groupPosition + fontHeight > lastValue.groupPosition)
9617             {
9618                 block[block.length - 2] = this.pipelineMergeGroup(lastValue, prelastValue);
9619                 block.pop();
9620                 rebuild = true;
9621             }
9622             if (block.length < 3)
9623             {
9624                 return block;
9625             }
9626         }
9627         for (var i = 1; i < block.length; i++)
9628         {
9629             if ( (block[i - 1].groupPosition + block[i - 1].groupHeight) > block[i].groupPosition)
9630             {
9631                 block[i - 1] = this.pipelineMergeGroup(block[i], block[i - 1]);
9632                 block.splice(i, 1);
9633                 rebuild = true;
9634             }
9635         }
9636         if (rebuild)
9637         {
9638             block = this.pipelineMoveBlocks(block, figureheight, fontHeight);
9639         }
9640         return block;
9641     },
9642
9643     /**
9644      * Merge two groups
9645      * 
9646      * @param {Array} lastValue
9647      * @param {Array} prelastValue
9648      * @return {Array}
9649      */
9650     pipelineMergeGroup: function(lastValue, prelastValue)
9651     {
9652         var newGroup;
9653         newGroup = prelastValue;
9654         newGroup.val = newGroup.val.concat(lastValue.val);
9655         newGroup = this.pipelineGetHeight(newGroup);
9656         newGroup.groupPosition = prelastValue.groupPosition;
9657         return newGroup;
9658     },
9659
9660     /*
9661         Method: getAlignedPos
9662         
9663         Returns a *x, y* object with the position of the top/left corner of a <ST> node.
9664         
9665         Parameters:
9666         
9667         pos - (object) A <Graph.Node> position.
9668         width - (number) The width of the node.
9669         height - (number) The height of the node.
9670         
9671      */
9672     getAlignedPos: function(pos, width, height) {
9673         var nconfig = this.node;
9674         var square, orn;
9675         if(nconfig.align == "center") {
9676             square = {
9677                 x: pos.x - width / 2,
9678                 y: pos.y - height / 2
9679             };
9680         } else if (nconfig.align == "left") {
9681             orn = this.config.orientation;
9682             if(orn == "bottom" || orn == "top") {
9683                 square = {
9684                     x: pos.x - width / 2,
9685                     y: pos.y
9686                 };
9687             } else {
9688                 square = {
9689                     x: pos.x,
9690                     y: pos.y - height / 2
9691                 };
9692             }
9693         } else if(nconfig.align == "right") {
9694             orn = this.config.orientation;
9695             if(orn == "bottom" || orn == "top") {
9696                 square = {
9697                     x: pos.x - width / 2,
9698                     y: pos.y - height
9699                 };
9700             } else {
9701                 square = {
9702                     x: pos.x - width,
9703                     y: pos.y - height / 2
9704                 };
9705             }
9706         } else throw "align: not implemented";
9707         
9708         return square;
9709     },
9710     
9711     getOrientation: function(adj) {
9712         var config = this.config;
9713         var orn = config.orientation;
9714
9715         if(config.multitree) {
9716                 var nodeFrom = adj.nodeFrom;
9717                 var nodeTo = adj.nodeTo;
9718                 orn = (('$orn' in nodeFrom.data) 
9719                         && nodeFrom.data.$orn) 
9720                         || (('$orn' in nodeTo.data) 
9721                         && nodeTo.data.$orn);
9722         }
9723
9724         return orn; 
9725     }
9726 });
9727
9728 /*
9729   Class: ST.Label
9730
9731   Custom extension of <Graph.Label>. 
9732   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
9733
9734   Extends:
9735
9736   All <Graph.Label> methods and subclasses.
9737
9738   See also:
9739
9740   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
9741  */ 
9742 $jit.ST.Label = {};
9743
9744 /*
9745    ST.Label.Native
9746
9747    Custom extension of <Graph.Label.Native>.
9748
9749    Extends:
9750
9751    All <Graph.Label.Native> methods
9752
9753    See also:
9754
9755    <Graph.Label.Native>
9756 */
9757 $jit.ST.Label.Native = new Class({
9758   Implements: Graph.Label.Native,
9759
9760   renderLabel: function(canvas, node, controller) {
9761     var ctx = canvas.getCtx();
9762     var coord = node.pos.getc(true);
9763     ctx.fillText(node.name, coord.x, coord.y);
9764   }
9765 });
9766
9767 $jit.ST.Label.DOM = new Class({
9768   Implements: Graph.Label.DOM,
9769
9770   /* 
9771       placeLabel
9772
9773       Overrides abstract method placeLabel in <Graph.Plot>.
9774
9775       Parameters:
9776
9777       tag - A DOM label element.
9778       node - A <Graph.Node>.
9779       controller - A configuration/controller object passed to the visualization.
9780      
9781     */
9782     placeLabel: function(tag, node, controller) {
9783         var pos = node.pos.getc(true), 
9784             config = this.viz.config, 
9785             dim = config.Node, 
9786             canvas = this.viz.canvas,
9787             w = node.getData('width'),
9788             h = node.getData('height'),
9789             radius = canvas.getSize(),
9790             labelPos, orn;
9791         
9792         var ox = canvas.translateOffsetX,
9793             oy = canvas.translateOffsetY,
9794             sx = canvas.scaleOffsetX,
9795             sy = canvas.scaleOffsetY,
9796             posx = pos.x * sx + ox,
9797             posy = pos.y * sy + oy;
9798
9799         if(dim.align == "center") {
9800             labelPos= {
9801                 x: Math.round(posx - w / 2 + radius.width/2),
9802                 y: Math.round(posy - h / 2 + radius.height/2)
9803             };
9804         } else if (dim.align == "left") {
9805             orn = config.orientation;
9806             if(orn == "bottom" || orn == "top") {
9807                 labelPos= {
9808                     x: Math.round(posx - w / 2 + radius.width/2),
9809                     y: Math.round(posy + radius.height/2)
9810                 };
9811             } else {
9812                 labelPos= {
9813                     x: Math.round(posx + radius.width/2),
9814                     y: Math.round(posy - h / 2 + radius.height/2)
9815                 };
9816             }
9817         } else if(dim.align == "right") {
9818             orn = config.orientation;
9819             if(orn == "bottom" || orn == "top") {
9820                 labelPos= {
9821                     x: Math.round(posx - w / 2 + radius.width/2),
9822                     y: Math.round(posy - h + radius.height/2)
9823                 };
9824             } else {
9825                 labelPos= {
9826                     x: Math.round(posx - w + radius.width/2),
9827                     y: Math.round(posy - h / 2 + radius.height/2)
9828                 };
9829             }
9830         } else throw "align: not implemented";
9831
9832         var style = tag.style;
9833         style.left = labelPos.x + 'px';
9834         style.top  = labelPos.y + 'px';
9835         style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
9836         controller.onPlaceLabel(tag, node);
9837     }
9838 });
9839
9840 /*
9841   ST.Label.SVG
9842
9843   Custom extension of <Graph.Label.SVG>.
9844
9845   Extends:
9846
9847   All <Graph.Label.SVG> methods
9848
9849   See also:
9850
9851   <Graph.Label.SVG>
9852 */
9853 $jit.ST.Label.SVG = new Class({
9854   Implements: [$jit.ST.Label.DOM, Graph.Label.SVG],
9855
9856   initialize: function(viz) {
9857     this.viz = viz;
9858   }
9859 });
9860
9861 /*
9862    ST.Label.HTML
9863
9864    Custom extension of <Graph.Label.HTML>.
9865
9866    Extends:
9867
9868    All <Graph.Label.HTML> methods.
9869
9870    See also:
9871
9872    <Graph.Label.HTML>
9873
9874 */
9875 $jit.ST.Label.HTML = new Class({
9876   Implements: [$jit.ST.Label.DOM, Graph.Label.HTML],
9877
9878   initialize: function(viz) {
9879     this.viz = viz;
9880   }
9881 });
9882
9883
9884 /*
9885   Class: ST.Plot.NodeTypes
9886
9887   This class contains a list of <Graph.Node> built-in types. 
9888   Node types implemented are 'none', 'circle', 'rectangle', 'ellipse' and 'square'.
9889
9890   You can add your custom node types, customizing your visualization to the extreme.
9891
9892   Example:
9893
9894   (start code js)
9895     ST.Plot.NodeTypes.implement({
9896       'mySpecialType': {
9897         'render': function(node, canvas) {
9898           //print your custom node to canvas
9899         },
9900         //optional
9901         'contains': function(node, pos) {
9902           //return true if pos is inside the node or false otherwise
9903         }
9904       }
9905     });
9906   (end code)
9907
9908 */
9909 $jit.ST.Plot.NodeTypes = new Class({
9910   'none': {
9911     'render': $.empty,
9912     'contains': $.lambda(false)
9913   },
9914   'circle': {
9915     'render': function(node, canvas) {
9916       var dim  = node.getData('dim'),
9917           pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9918           dim2 = dim/2;
9919       this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9920     },
9921     'contains': function(node, pos) {
9922       var dim  = node.getData('dim'),
9923           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9924           dim2 = dim/2;
9925       this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9926     }
9927   },
9928   'square': {
9929     'render': function(node, canvas) {
9930       var dim  = node.getData('dim'),
9931           dim2 = dim/2,
9932           pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
9933       this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y+dim2}, dim2, canvas);
9934     },
9935     'contains': function(node, pos) {
9936       var dim  = node.getData('dim'),
9937           npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
9938           dim2 = dim/2;
9939       this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2}, dim2);
9940     }
9941   },
9942   'ellipse': {
9943     'render': function(node, canvas) {
9944       var width = node.getData('width'),
9945           height = node.getData('height'),
9946           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9947       this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9948     },
9949     'contains': function(node, pos) {
9950       var width = node.getData('width'),
9951           height = node.getData('height'),
9952           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9953       this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9954     }
9955   },
9956   'rectangle': {
9957     'render': function(node, canvas) {
9958       var width = node.getData('width'),
9959           height = node.getData('height'),
9960           pos = this.getAlignedPos(node.pos.getc(true), width, height);
9961       this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2, y:pos.y+height/2}, width, height, canvas);
9962     },
9963     'contains': function(node, pos) {
9964       var width = node.getData('width'),
9965           height = node.getData('height'),
9966           npos = this.getAlignedPos(node.pos.getc(true), width, height);
9967       this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y+height/2}, width, height, canvas);
9968     }
9969   }
9970 });
9971
9972 /*
9973   Class: ST.Plot.EdgeTypes
9974
9975   This class contains a list of <Graph.Adjacence> built-in types. 
9976   Edge types implemented are 'none', 'line', 'arrow', 'quadratic:begin', 'quadratic:end', 'bezier'.
9977
9978   You can add your custom edge types, customizing your visualization to the extreme.
9979
9980   Example:
9981
9982   (start code js)
9983     ST.Plot.EdgeTypes.implement({
9984       'mySpecialType': {
9985         'render': function(adj, canvas) {
9986           //print your custom edge to canvas
9987         },
9988         //optional
9989         'contains': function(adj, pos) {
9990           //return true if pos is inside the arc or false otherwise
9991         }
9992       }
9993     });
9994   (end code)
9995
9996 */
9997 $jit.ST.Plot.EdgeTypes = new Class({
9998     'none': $.empty,
9999     'line': {
10000       'render': function(adj, canvas) {
10001         var orn = this.getOrientation(adj),
10002             nodeFrom = adj.nodeFrom, 
10003             nodeTo = adj.nodeTo,
10004             rel = nodeFrom._depth < nodeTo._depth,
10005             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10006             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
10007         this.edgeHelper.line.render(from, to, canvas);
10008       },
10009       'contains': function(adj, pos) {
10010         var orn = this.getOrientation(adj),
10011             nodeFrom = adj.nodeFrom, 
10012             nodeTo = adj.nodeTo,
10013             rel = nodeFrom._depth < nodeTo._depth,
10014             from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10015             to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
10016         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
10017       }
10018     },
10019      'arrow': {
10020        'render': function(adj, canvas) {
10021          var orn = this.getOrientation(adj),
10022              node = adj.nodeFrom, 
10023              child = adj.nodeTo,
10024              dim = adj.getData('dim'),
10025              from = this.viz.geom.getEdge(node, 'begin', orn),
10026              to = this.viz.geom.getEdge(child, 'end', orn),
10027              direction = adj.data.$direction,
10028              inv = (direction && direction.length>1 && direction[0] != node.id);
10029          this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
10030        },
10031        'contains': function(adj, pos) {
10032          var orn = this.getOrientation(adj),
10033              nodeFrom = adj.nodeFrom, 
10034              nodeTo = adj.nodeTo,
10035              rel = nodeFrom._depth < nodeTo._depth,
10036              from = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10037              to =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn);
10038          return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
10039        }
10040      },
10041     'quadratic:begin': {
10042        'render': function(adj, canvas) {
10043           var orn = this.getOrientation(adj);
10044           var nodeFrom = adj.nodeFrom, 
10045               nodeTo = adj.nodeTo,
10046               rel = nodeFrom._depth < nodeTo._depth,
10047               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10048               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10049               dim = adj.getData('dim'),
10050               ctx = canvas.getCtx();
10051           ctx.beginPath();
10052           ctx.moveTo(begin.x, begin.y);
10053           switch(orn) {
10054             case "left":
10055               ctx.quadraticCurveTo(begin.x + dim, begin.y, end.x, end.y);
10056               break;
10057             case "right":
10058               ctx.quadraticCurveTo(begin.x - dim, begin.y, end.x, end.y);
10059               break;
10060             case "top":
10061               ctx.quadraticCurveTo(begin.x, begin.y + dim, end.x, end.y);
10062               break;
10063             case "bottom":
10064               ctx.quadraticCurveTo(begin.x, begin.y - dim, end.x, end.y);
10065               break;
10066           }
10067           ctx.stroke();
10068         }
10069      },
10070     'quadratic:end': {
10071        'render': function(adj, canvas) {
10072           var orn = this.getOrientation(adj);
10073           var nodeFrom = adj.nodeFrom, 
10074               nodeTo = adj.nodeTo,
10075               rel = nodeFrom._depth < nodeTo._depth,
10076               begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10077               end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10078               dim = adj.getData('dim'),
10079               ctx = canvas.getCtx();
10080           ctx.beginPath();
10081           ctx.moveTo(begin.x, begin.y);
10082           switch(orn) {
10083             case "left":
10084               ctx.quadraticCurveTo(end.x - dim, end.y, end.x, end.y);
10085               break;
10086             case "right":
10087               ctx.quadraticCurveTo(end.x + dim, end.y, end.x, end.y);
10088               break;
10089             case "top":
10090               ctx.quadraticCurveTo(end.x, end.y - dim, end.x, end.y);
10091               break;
10092             case "bottom":
10093               ctx.quadraticCurveTo(end.x, end.y + dim, end.x, end.y);
10094               break;
10095           }
10096           ctx.stroke();
10097        }
10098      },
10099     'bezier': {
10100        'render': function(adj, canvas) {
10101          var orn = this.getOrientation(adj),
10102              nodeFrom = adj.nodeFrom, 
10103              nodeTo = adj.nodeTo,
10104              rel = nodeFrom._depth < nodeTo._depth,
10105              begin = this.viz.geom.getEdge(rel? nodeFrom:nodeTo, 'begin', orn),
10106              end =  this.viz.geom.getEdge(rel? nodeTo:nodeFrom, 'end', orn),
10107              dim = adj.getData('dim'),
10108              ctx = canvas.getCtx();
10109          ctx.beginPath();
10110          ctx.moveTo(begin.x, begin.y);
10111          switch(orn) {
10112            case "left":
10113              ctx.bezierCurveTo(begin.x + dim, begin.y, end.x - dim, end.y, end.x, end.y);
10114              break;
10115            case "right":
10116              ctx.bezierCurveTo(begin.x - dim, begin.y, end.x + dim, end.y, end.x, end.y);
10117              break;
10118            case "top":
10119              ctx.bezierCurveTo(begin.x, begin.y + dim, end.x, end.y - dim, end.x, end.y);
10120              break;
10121            case "bottom":
10122              ctx.bezierCurveTo(begin.x, begin.y - dim, end.x, end.y + dim, end.x, end.y);
10123              break;
10124          }
10125          ctx.stroke();
10126        }
10127     }
10128 });
10129
10130
10131 Options.LineChart = {
10132   $extend: true,
10133
10134   animate: false,
10135   labelOffset: 3, // label offset
10136   type: 'basic', // gradient
10137   dataPointSize: 10,
10138   Tips: {
10139     enable: false,
10140     onShow: $.empty,
10141     onHide: $.empty
10142   },
10143   Ticks: {
10144         enable: false,
10145         segments: 4,
10146         color: '#000000'
10147   },
10148   Events: {
10149     enable: false,
10150     onClick: $.empty
10151   },
10152   selectOnHover: true,
10153   showAggregates: true,
10154   showLabels: true,
10155   filterOnClick: false,
10156   restoreOnRightClick: false
10157 };
10158
10159
10160 /*
10161  * File: LineChart.js
10162  *
10163 */
10164
10165 $jit.ST.Plot.NodeTypes.implement({
10166   'linechart-basic' : {
10167     'render' : function(node, canvas) {
10168       var pos = node.pos.getc(true), 
10169           width = node.getData('width'),
10170           height = node.getData('height'),
10171           algnPos = this.getAlignedPos(pos, width, height),
10172           x = algnPos.x + width/2 , y = algnPos.y,
10173           stringArray = node.getData('stringArray'),
10174           lastNode = node.getData('lastNode'),
10175           dimArray = node.getData('dimArray'),
10176           valArray = node.getData('valueArray'),
10177           colorArray = node.getData('colorArray'),
10178           colorLength = colorArray.length,
10179           config = node.getData('config'),
10180           gradient = node.getData('gradient'),
10181           showLabels = config.showLabels,
10182           aggregates = config.showAggregates,
10183           label = config.Label,
10184           prev = node.getData('prev'),
10185           dataPointSize = config.dataPointSize;
10186
10187       var ctx = canvas.getCtx(), border = node.getData('border');
10188       if (colorArray && dimArray && stringArray) {
10189         
10190                for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
10191                 ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
10192                         ctx.lineWidth = 4;
10193                         ctx.lineCap = "round";
10194                   if(!lastNode) {
10195
10196                           ctx.save();
10197                                   //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
10198                           ctx.beginPath();
10199                           ctx.moveTo(x, y  - dimArray[i][0]); 
10200                           ctx.lineTo(x + width, y - dimArray[i][1]);
10201                           ctx.stroke();
10202                           ctx.restore();
10203                   }
10204                   //render data point
10205                   ctx.fillRect(x - (dataPointSize/2), y  - dimArray[i][0] - (dataPointSize/2),dataPointSize,dataPointSize);
10206                 }
10207         
10208
10209           if(label.type == 'Native' && showLabels) {
10210           //bottom labels
10211           ctx.fillStyle = ctx.strokeStyle = label.color;
10212           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10213           ctx.textAlign = 'center';
10214           ctx.textBaseline = 'middle';
10215           ctx.fillText(node.name, x, y + label.size + config.labelOffset);
10216           }
10217           
10218
10219       }
10220     },
10221     'contains': function(node, mpos) {
10222       var pos = node.pos.getc(true), 
10223           width = node.getData('width'),
10224           height = node.getData('height'),
10225           config = node.getData('config'),
10226           dataPointSize = config.dataPointSize,
10227           dataPointMidPoint = dataPointSize/2,
10228           algnPos = this.getAlignedPos(pos, width, height),
10229           x = algnPos.x + width/2, y = algnPos.y,
10230           dimArray = node.getData('dimArray');
10231       //bounding box check
10232       if(mpos.x < x - dataPointMidPoint || mpos.x > x + dataPointMidPoint) {
10233         return false;
10234       }
10235       //deep check
10236       for(var i=0, l=dimArray.length; i<l; i++) {
10237         var dimi = dimArray[i];
10238                 var url = Url.decode(node.getData('linkArray')[i]);
10239           if(mpos.x >= x - dataPointMidPoint && mpos.x <= x + dataPointMidPoint && mpos.y >= y - dimi[0] - dataPointMidPoint && mpos.y <= y - dimi[0] + dataPointMidPoint) {
10240                 var valArrayCur = node.getData('valArrayCur');
10241           var results = array_match(valArrayCur[i],valArrayCur);
10242           var matches = results[0];
10243           var indexValues = results[1];
10244           if(matches > 1) {
10245                         var names = new Array(),
10246                                 values = new Array(),
10247                                 percentages = new Array(),
10248                                 linksArr = new Array();
10249                                 for(var j=0, il=indexValues.length; j<il; j++) {
10250                                         names[j] = node.getData('stringArray')[indexValues[j]];
10251                                         values[j] = valArrayCur[indexValues[j]];
10252                                         percentages[j] = ((valArrayCur[indexValues[j]]/node.getData('groupTotalValue')) * 100).toFixed(1);
10253                                         linksArr[j] = Url.decode(node.getData('linkArray')[j]);
10254                                         
10255                                 }       
10256                         return {
10257                             'name': names,
10258                             'color': node.getData('colorArray')[i],
10259                             'value': values,
10260                             'percentage': percentages,
10261                             'link': false,
10262                             'collision': true
10263                         };
10264                 }
10265           else {
10266                   return {
10267                     'name': node.getData('stringArray')[i],
10268                     'color': node.getData('colorArray')[i],
10269                     'value': node.getData('valueArray')[i][0],
10270         //            'value': node.getData('valueArray')[i][0] + " - mx:" + mpos.x + " x:" + x + " my:" + mpos.y + " y:" + y + " h:" + height + " w:" + width,
10271                     'percentage': ((node.getData('valueArray')[i][0]/node.getData('groupTotalValue')) * 100).toFixed(1),
10272                     'link': url,
10273                     'collision': false
10274                   };
10275           }
10276         }
10277       }
10278       return false;
10279     }
10280   }
10281 });
10282
10283 /*
10284   Class: Line
10285   
10286   A visualization that displays line charts.
10287   
10288   Constructor Options:
10289   
10290   See <Options.Line>.
10291
10292 */
10293 $jit.LineChart = new Class({
10294   st: null,
10295   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
10296   selected: {},
10297   busy: false,
10298   
10299   initialize: function(opt) {
10300     this.controller = this.config = 
10301       $.merge(Options("Canvas", "Margin", "Label", "LineChart"), {
10302         Label: { type: 'Native' }
10303       }, opt);
10304     //set functions for showLabels and showAggregates
10305     var showLabels = this.config.showLabels,
10306         typeLabels = $.type(showLabels),
10307         showAggregates = this.config.showAggregates,
10308         typeAggregates = $.type(showAggregates);
10309     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
10310     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
10311     Options.Fx.clearCanvas = false;
10312     this.initializeViz();
10313   },
10314   
10315   initializeViz: function() {
10316     var config = this.config,
10317         that = this,
10318         nodeType = config.type.split(":")[0],
10319         nodeLabels = {};
10320
10321     var st = new $jit.ST({
10322       injectInto: config.injectInto,
10323       orientation: "bottom",
10324       backgroundColor: config.backgroundColor,
10325       renderBackground: config.renderBackground,
10326       levelDistance: 0,
10327       siblingOffset: 0,
10328       subtreeOffset: 0,
10329       withLabels: config.Label.type != 'Native',
10330       useCanvas: config.useCanvas,
10331       Label: {
10332         type: config.Label.type
10333       },
10334       Node: {
10335         overridable: true,
10336         type: 'linechart-' + nodeType,
10337         align: 'left',
10338         width: 1,
10339         height: 1
10340       },
10341       Edge: {
10342         type: 'none'
10343       },
10344       Tips: {
10345         enable: config.Tips.enable,
10346         type: 'Native',
10347         force: true,
10348         onShow: function(tip, node, contains) {
10349           var elem = contains;
10350           config.Tips.onShow(tip, elem, node);
10351         }
10352       },
10353       Events: {
10354         enable: true,
10355         type: 'Native',
10356         onClick: function(node, eventInfo, evt) {
10357           if(!config.filterOnClick && !config.Events.enable) return;
10358           var elem = eventInfo.getContains();
10359           if(elem) config.filterOnClick && that.filter(elem.name);
10360           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
10361         },
10362         onRightClick: function(node, eventInfo, evt) {
10363           if(!config.restoreOnRightClick) return;
10364           that.restore();
10365         },
10366         onMouseMove: function(node, eventInfo, evt) {
10367           if(!config.selectOnHover) return;
10368           if(node) {
10369             var elem = eventInfo.getContains();
10370             that.select(node.id, elem.name, elem.index);
10371           } else {
10372             that.select(false, false, false);
10373           }
10374         }
10375       },
10376       onCreateLabel: function(domElement, node) {
10377         var labelConf = config.Label,
10378             valueArray = node.getData('valueArray'),
10379             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
10380             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
10381         if(node.getData('prev')) {
10382           var nlbs = {
10383             wrapper: document.createElement('div'),
10384             aggregate: document.createElement('div'),
10385             label: document.createElement('div')
10386           };
10387           var wrapper = nlbs.wrapper,
10388               label = nlbs.label,
10389               aggregate = nlbs.aggregate,
10390               wrapperStyle = wrapper.style,
10391               labelStyle = label.style,
10392               aggregateStyle = aggregate.style;
10393           //store node labels
10394           nodeLabels[node.id] = nlbs;
10395           //append labels
10396           wrapper.appendChild(label);
10397           wrapper.appendChild(aggregate);
10398           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
10399             label.style.display = 'none';
10400           }
10401           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
10402             aggregate.style.display = 'none';
10403           }
10404           wrapperStyle.position = 'relative';
10405           wrapperStyle.overflow = 'visible';
10406           wrapperStyle.fontSize = labelConf.size + 'px';
10407           wrapperStyle.fontFamily = labelConf.family;
10408           wrapperStyle.color = labelConf.color;
10409           wrapperStyle.textAlign = 'center';
10410           aggregateStyle.position = labelStyle.position = 'absolute';
10411           
10412           domElement.style.width = node.getData('width') + 'px';
10413           domElement.style.height = node.getData('height') + 'px';
10414           label.innerHTML = node.name;
10415           
10416           domElement.appendChild(wrapper);
10417         }
10418       },
10419       onPlaceLabel: function(domElement, node) {
10420         if(!node.getData('prev')) return;
10421         var labels = nodeLabels[node.id],
10422             wrapperStyle = labels.wrapper.style,
10423             labelStyle = labels.label.style,
10424             aggregateStyle = labels.aggregate.style,
10425             width = node.getData('width'),
10426             height = node.getData('height'),
10427             dimArray = node.getData('dimArray'),
10428             valArray = node.getData('valueArray'),
10429             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
10430             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
10431             font = parseInt(wrapperStyle.fontSize, 10),
10432             domStyle = domElement.style;
10433         
10434         if(dimArray && valArray) {
10435           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
10436             labelStyle.display = '';
10437           } else {
10438             labelStyle.display = 'none';
10439           }
10440           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
10441             aggregateStyle.display = '';
10442           } else {
10443             aggregateStyle.display = 'none';
10444           }
10445           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
10446           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
10447           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
10448             if(dimArray[i][0] > 0) {
10449               acum+= valArray[i][0];
10450               leftAcum+= dimArray[i][0];
10451             }
10452           }
10453           aggregateStyle.top = (-font - config.labelOffset) + 'px';
10454           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
10455           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
10456           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
10457           labels.aggregate.innerHTML = acum;
10458         }
10459       }
10460     });
10461     
10462     var size = st.canvas.getSize(),
10463         margin = config.Margin;
10464     st.config.offsetY = -size.height/2 + margin.bottom 
10465       + (config.showLabels && (config.labelOffset + config.Label.size));
10466     st.config.offsetX = (margin.right - margin.left - config.labelOffset - config.Label.size)/2;
10467     this.st = st;
10468     this.canvas = this.st.canvas;
10469   },
10470   
10471     renderTitle: function() {
10472         var canvas = this.canvas,
10473         size = canvas.getSize(),
10474         config = this.config,
10475         margin = config.Margin,
10476         label = config.Label,
10477         title = config.Title;
10478         ctx = canvas.getCtx();
10479         ctx.fillStyle = title.color;
10480         ctx.textAlign = 'left';
10481         ctx.textBaseline = 'top';
10482         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
10483         if(label.type == 'Native') {
10484                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
10485         }
10486   },  
10487   
10488     renderTicks: function() {
10489
10490         var canvas = this.canvas,
10491         size = canvas.getSize(),
10492         config = this.config,
10493         margin = config.Margin,
10494         ticks = config.Ticks,
10495         title = config.Title,
10496         subtitle = config.Subtitle,
10497         label = config.Label,
10498         maxValue = this.maxValue,
10499         maxTickValue = Math.ceil(maxValue*.1)*10;
10500         if(maxTickValue == maxValue) {
10501                 var length = maxTickValue.toString().length;
10502                 maxTickValue = maxTickValue + parseInt(pad(1,length));
10503         }
10504
10505
10506         labelValue = 0,
10507         labelIncrement = maxTickValue/ticks.segments,
10508         ctx = canvas.getCtx();
10509         ctx.strokeStyle = ticks.color;
10510     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
10511         ctx.textAlign = 'center';
10512         ctx.textBaseline = 'middle';
10513         
10514         idLabel = canvas.id + "-label";
10515         labelDim = 100;
10516         container = document.getElementById(idLabel);
10517                   
10518                   
10519                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10520                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
10521                 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)),
10522                 segmentLength = grid/ticks.segments;
10523                 ctx.fillStyle = ticks.color;
10524                 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));
10525
10526                 while(axis>=grid) {
10527                         ctx.save();
10528                         ctx.translate(-(size.width/2)+margin.left, Math.round(axis));
10529                         ctx.rotate(Math.PI / 2);
10530                         ctx.fillStyle = label.color;
10531                         if(config.showLabels) {
10532                                 if(label.type == 'Native') { 
10533                                         ctx.fillText(labelValue, 0, 0);
10534                                 } else {
10535                                         //html labels on y axis
10536                                         labelDiv = document.createElement('div');
10537                                         labelDiv.innerHTML = labelValue;
10538                                         labelDiv.className = "rotatedLabel";
10539 //                                      labelDiv.class = "rotatedLabel";
10540                                         labelDiv.style.top = (htmlOrigin - (labelDim/2)) + "px";
10541                                         labelDiv.style.left = margin.left + "px";
10542                                         labelDiv.style.width = labelDim + "px";
10543                                         labelDiv.style.height = labelDim + "px";
10544                                         labelDiv.style.textAlign = "center";
10545                                         labelDiv.style.verticalAlign = "middle";
10546                                         labelDiv.style.position = "absolute";
10547                                         container.appendChild(labelDiv);
10548                                 }
10549                         }
10550                         ctx.restore();
10551                         ctx.fillStyle = ticks.color;
10552                         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 );
10553                         htmlOrigin += segmentLength;
10554                         axis += segmentLength;
10555                         labelValue += labelIncrement;
10556                 }
10557         
10558
10559         
10560         
10561         
10562
10563   },
10564   
10565   renderBackground: function() {
10566                 var canvas = this.canvas,
10567                 config = this.config,
10568                 backgroundColor = config.backgroundColor,
10569                 size = canvas.getSize(),
10570                 ctx = canvas.getCtx();
10571                 //ctx.globalCompositeOperation = "destination-over";
10572             ctx.fillStyle = backgroundColor;
10573             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
10574   },
10575   clear: function() {
10576         var canvas = this.canvas;
10577         var ctx = canvas.getCtx(),
10578         size = canvas.getSize();
10579         ctx.fillStyle = "rgba(255,255,255,0)";
10580         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
10581         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
10582  },
10583   resizeGraph: function(json,width) {
10584         var canvas = this.canvas,
10585         size = canvas.getSize(),
10586             orgHeight = size.height;
10587
10588         canvas.resize(width,orgHeight);
10589         if(typeof FlashCanvas == "undefined") {
10590                 canvas.clear();
10591         } else {
10592                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
10593         }
10594         this.loadJSON(json);
10595
10596         },
10597  /*
10598   Method: loadJSON
10599  
10600   Loads JSON data into the visualization. 
10601   
10602   Parameters:
10603   
10604   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>.
10605   
10606   Example:
10607   (start code js)
10608   var areaChart = new $jit.AreaChart(options);
10609   areaChart.loadJSON(json);
10610   (end code)
10611  */  
10612   loadJSON: function(json) {
10613     var prefix = $.time(), 
10614         ch = [], 
10615         st = this.st,
10616         name = $.splat(json.label), 
10617         color = $.splat(json.color || this.colors),
10618         config = this.config,
10619         ticks = config.Ticks,
10620         renderBackground = config.renderBackground,
10621         gradient = !!config.type.split(":")[1],
10622         animate = config.animate,
10623         title = config.Title,
10624         groupTotalValue = 0;
10625     
10626     var valArrayAll = new Array();
10627
10628     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10629         var val = values[i];
10630         var valArray = $.splat(val.values);
10631         for (var j=0, len=valArray.length; j<len; j++) {
10632                 valArrayAll.push(parseInt(valArray[j]));
10633         }
10634         groupTotalValue += parseInt(valArray.sum());
10635     }
10636     
10637     this.maxValue =  Math.max.apply(null, valArrayAll);
10638     
10639     for(var i=0, values=json.values, l=values.length; i<l; i++) {
10640       var val = values[i], prev = values[i-1];
10641
10642       var next = (i+1 < l) ? values[i+1] : 0;
10643       var valLeft = $.splat(values[i].values);
10644       var valRight = (i+1 < l) ? $.splat(values[i+1].values) : 0;
10645       var valArray = $.zip(valLeft, valRight);
10646       var valArrayCur = $.splat(values[i].values);
10647       var linkArray = $.splat(values[i].links);
10648       var acumLeft = 0, acumRight = 0;
10649       var lastNode = (l-1 == i) ? true : false; 
10650       ch.push({
10651         'id': prefix + val.label,
10652         'name': val.label,
10653         'data': {
10654           'value': valArray,
10655           '$valueArray': valArray,
10656           '$valArrayCur': valArrayCur,
10657           '$colorArray': color,
10658           '$linkArray': linkArray,
10659           '$stringArray': name,
10660           '$next': next? next.label:false,
10661           '$prev': prev? prev.label:false,
10662           '$config': config,
10663           '$lastNode': lastNode,
10664           '$groupTotalValue': groupTotalValue,
10665           '$gradient': gradient
10666         },
10667         'children': []
10668       });
10669     }
10670     var root = {
10671       'id': prefix + '$root',
10672       'name': '',
10673       'data': {
10674         '$type': 'none',
10675         '$width': 1,
10676         '$height': 1
10677       },
10678       'children': ch
10679     };
10680     st.loadJSON(root);
10681     
10682     this.normalizeDims();
10683     
10684     if(renderBackground) {
10685         this.renderBackground();        
10686     }
10687     
10688     if(!animate && ticks.enable) {
10689                 this.renderTicks();
10690         }
10691         
10692         
10693         if(title.text) {
10694                 this.renderTitle();     
10695         }
10696         
10697     st.compute();
10698     st.select(st.root);
10699     if(animate) {
10700       st.fx.animate({
10701         modes: ['node-property:height:dimArray'],
10702         duration:1500
10703       });
10704     }
10705   },
10706   
10707  /*
10708   Method: updateJSON
10709  
10710   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.
10711   
10712   Parameters:
10713   
10714   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
10715   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
10716   
10717   Example:
10718   
10719   (start code js)
10720   areaChart.updateJSON(json, {
10721     onComplete: function() {
10722       alert('update complete!');
10723     }
10724   });
10725   (end code)
10726  */  
10727   updateJSON: function(json, onComplete) {
10728     if(this.busy) return;
10729     this.busy = true;
10730     
10731     var st = this.st,
10732         graph = st.graph,
10733         labels = json.label && $.splat(json.label),
10734         values = json.values,
10735         animate = this.config.animate,
10736         that = this;
10737     $.each(values, function(v) {
10738       var n = graph.getByName(v.label);
10739       if(n) {
10740         v.values = $.splat(v.values);
10741         var stringArray = n.getData('stringArray'),
10742             valArray = n.getData('valueArray');
10743         $.each(valArray, function(a, i) {
10744           a[0] = v.values[i];
10745           if(labels) stringArray[i] = labels[i];
10746         });
10747         n.setData('valueArray', valArray);
10748         var prev = n.getData('prev'),
10749             next = n.getData('next'),
10750             nextNode = graph.getByName(next);
10751         if(prev) {
10752           var p = graph.getByName(prev);
10753           if(p) {
10754             var valArray = p.getData('valueArray');
10755             $.each(valArray, function(a, i) {
10756               a[1] = v.values[i];
10757             });
10758           }
10759         }
10760         if(!nextNode) {
10761           var valArray = n.getData('valueArray');
10762           $.each(valArray, function(a, i) {
10763             a[1] = v.values[i];
10764           });
10765         }
10766       }
10767     });
10768     this.normalizeDims();
10769     st.compute();
10770     
10771     st.select(st.root);
10772     if(animate) {
10773       st.fx.animate({
10774         modes: ['node-property:height:dimArray'],
10775         duration:1500,
10776         onComplete: function() {
10777           that.busy = false;
10778           onComplete && onComplete.onComplete();
10779         }
10780       });
10781     }
10782   },
10783   
10784 /*
10785   Method: filter
10786  
10787   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
10788   
10789   Parameters:
10790   
10791   Variable strings arguments with the name of the stacks.
10792   
10793   Example:
10794   
10795   (start code js)
10796   areaChart.filter('label A', 'label C');
10797   (end code)
10798   
10799   See also:
10800   
10801   <AreaChart.restore>.
10802  */  
10803   filter: function() {
10804     if(this.busy) return;
10805     this.busy = true;
10806     if(this.config.Tips.enable) this.st.tips.hide();
10807     this.select(false, false, false);
10808     var args = Array.prototype.slice.call(arguments);
10809     var rt = this.st.graph.getNode(this.st.root);
10810     var that = this;
10811     rt.eachAdjacency(function(adj) {
10812       var n = adj.nodeTo, 
10813           dimArray = n.getData('dimArray'),
10814           stringArray = n.getData('stringArray');
10815       n.setData('dimArray', $.map(dimArray, function(d, i) {
10816         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
10817       }), 'end');
10818     });
10819     this.st.fx.animate({
10820       modes: ['node-property:dimArray'],
10821       duration:1500,
10822       onComplete: function() {
10823         that.busy = false;
10824       }
10825     });
10826   },
10827   
10828   /*
10829   Method: restore
10830  
10831   Sets all stacks that could have been filtered visible.
10832   
10833   Example:
10834   
10835   (start code js)
10836   areaChart.restore();
10837   (end code)
10838   
10839   See also:
10840   
10841   <AreaChart.filter>.
10842  */  
10843   restore: function() {
10844     if(this.busy) return;
10845     this.busy = true;
10846     if(this.config.Tips.enable) this.st.tips.hide();
10847     this.select(false, false, false);
10848     this.normalizeDims();
10849     var that = this;
10850     this.st.fx.animate({
10851       modes: ['node-property:height:dimArray'],
10852       duration:1500,
10853       onComplete: function() {
10854         that.busy = false;
10855       }
10856     });
10857   },
10858   //adds the little brown bar when hovering the node
10859   select: function(id, name, index) {
10860     if(!this.config.selectOnHover) return;
10861     var s = this.selected;
10862     if(s.id != id || s.name != name 
10863         || s.index != index) {
10864       s.id = id;
10865       s.name = name;
10866       s.index = index;
10867       this.st.graph.eachNode(function(n) {
10868         n.setData('border', false);
10869       });
10870       if(id) {
10871         var n = this.st.graph.getNode(id);
10872         n.setData('border', s);
10873         var link = index === 0? 'prev':'next';
10874         link = n.getData(link);
10875         if(link) {
10876           n = this.st.graph.getByName(link);
10877           if(n) {
10878             n.setData('border', {
10879               name: name,
10880               index: 1-index
10881             });
10882           }
10883         }
10884       }
10885       this.st.plot();
10886     }
10887   },
10888   
10889   /*
10890     Method: getLegend
10891    
10892     Returns an object containing as keys the legend names and as values hex strings with color values.
10893     
10894     Example:
10895     
10896     (start code js)
10897     var legend = areaChart.getLegend();
10898     (end code)
10899  */  
10900   getLegend: function() {
10901     var legend = new Array();
10902     var name = new Array();
10903     var color = new Array();
10904     var n;
10905     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
10906       n = adj.nodeTo;
10907     });
10908     var colors = n.getData('colorArray'),
10909         len = colors.length;
10910     $.each(n.getData('stringArray'), function(s, i) {
10911       color[i] = colors[i % len];
10912       name[i] = s;
10913     });
10914         legend['name'] = name;
10915         legend['color'] = color;
10916     return legend;
10917   },
10918   
10919   /*
10920     Method: getMaxValue
10921    
10922     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
10923     
10924     Example:
10925     
10926     (start code js)
10927     var ans = areaChart.getMaxValue();
10928     (end code)
10929     
10930     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
10931     
10932     Example:
10933     
10934     (start code js)
10935     //will return 100 for all AreaChart instances,
10936     //displaying all of them with the same scale
10937     $jit.AreaChart.implement({
10938       'getMaxValue': function() {
10939         return 100;
10940       }
10941     });
10942     (end code)
10943     
10944 */  
10945
10946   normalizeDims: function() {
10947     //number of elements
10948     var root = this.st.graph.getNode(this.st.root), l=0;
10949     root.eachAdjacency(function() {
10950       l++;
10951     });
10952     
10953
10954     var maxValue = this.maxValue || 1,
10955         size = this.st.canvas.getSize(),
10956         config = this.config,
10957         margin = config.Margin,
10958         labelOffset = config.labelOffset + config.Label.size,
10959         fixedDim = (size.width - (margin.left + margin.right + labelOffset )) / (l-1),
10960         animate = config.animate,
10961         ticks = config.Ticks,
10962         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
10963           - (config.showLabels && labelOffset);
10964           
10965           
10966         var maxTickValue = Math.ceil(maxValue*.1)*10;
10967                 if(maxTickValue == maxValue) {
10968                         var length = maxTickValue.toString().length;
10969                         maxTickValue = maxTickValue + parseInt(pad(1,length));
10970                 }
10971                 
10972                 
10973                 
10974     this.st.graph.eachNode(function(n) {
10975       var acumLeft = 0, acumRight = 0, animateValue = [];
10976       $.each(n.getData('valueArray'), function(v) {
10977         acumLeft += +v[0];
10978         acumRight += +v[1];
10979         animateValue.push([0, 0]);
10980       });
10981       var acum = acumRight>acumLeft? acumRight:acumLeft;
10982       
10983       n.setData('width', fixedDim);
10984       if(animate) {
10985         n.setData('height', acum * height / maxValue, 'end');
10986         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10987           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
10988         }), 'end');
10989         var dimArray = n.getData('dimArray');
10990         if(!dimArray) {
10991           n.setData('dimArray', animateValue);
10992         }
10993       } else {
10994         
10995         if(ticks.enable) {
10996                 n.setData('height', acum * height / maxValue);
10997                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
10998                   return [n[0] * height / maxTickValue, n[1] * height / maxTickValue]; 
10999                 }));
11000         } else {
11001                 n.setData('height', acum * height / maxValue);
11002                 n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11003                   return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11004                 }));
11005         }
11006         
11007         
11008       }
11009     });
11010   }
11011 });
11012
11013
11014
11015
11016
11017 /*
11018  * File: AreaChart.js
11019  *
11020 */
11021
11022 $jit.ST.Plot.NodeTypes.implement({
11023   'areachart-stacked' : {
11024     'render' : function(node, canvas) {
11025       var pos = node.pos.getc(true), 
11026           width = node.getData('width'),
11027           height = node.getData('height'),
11028           algnPos = this.getAlignedPos(pos, width, height),
11029           x = algnPos.x, y = algnPos.y,
11030           stringArray = node.getData('stringArray'),
11031           dimArray = node.getData('dimArray'),
11032           valArray = node.getData('valueArray'),
11033           valLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11034           valRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11035           colorArray = node.getData('colorArray'),
11036           colorLength = colorArray.length,
11037           config = node.getData('config'),
11038           gradient = node.getData('gradient'),
11039           showLabels = config.showLabels,
11040           aggregates = config.showAggregates,
11041           label = config.Label,
11042           prev = node.getData('prev');
11043
11044       var ctx = canvas.getCtx(), border = node.getData('border');
11045       if (colorArray && dimArray && stringArray) {
11046         for (var i=0, l=dimArray.length, acumLeft=0, acumRight=0, valAcum=0; i<l; i++) {
11047           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11048           ctx.save();
11049           if(gradient && (dimArray[i][0] > 0 || dimArray[i][1] > 0)) {
11050             var h1 = acumLeft + dimArray[i][0],
11051                 h2 = acumRight + dimArray[i][1],
11052                 alpha = Math.atan((h2 - h1) / width),
11053                 delta = 55;
11054             var linear = ctx.createLinearGradient(x + width/2, 
11055                 y - (h1 + h2)/2,
11056                 x + width/2 + delta * Math.sin(alpha),
11057                 y - (h1 + h2)/2 + delta * Math.cos(alpha));
11058             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11059                 function(v) { return (v * 0.85) >> 0; }));
11060             linear.addColorStop(0, colorArray[i % colorLength]);
11061             linear.addColorStop(1, color);
11062             ctx.fillStyle = linear;
11063           }
11064           ctx.beginPath();
11065           ctx.moveTo(x, y - acumLeft);
11066           ctx.lineTo(x + width, y - acumRight);
11067           ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
11068           ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
11069           ctx.lineTo(x, y - acumLeft);
11070           ctx.fill();
11071           ctx.restore();
11072           if(border) {
11073             var strong = border.name == stringArray[i];
11074             var perc = strong? 0.7 : 0.8;
11075             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11076                 function(v) { return (v * perc) >> 0; }));
11077             ctx.strokeStyle = color;
11078             ctx.lineWidth = strong? 4 : 1;
11079             ctx.save();
11080             ctx.beginPath();
11081             if(border.index === 0) {
11082               ctx.moveTo(x, y - acumLeft);
11083               ctx.lineTo(x, y - acumLeft - dimArray[i][0]);
11084             } else {
11085               ctx.moveTo(x + width, y - acumRight);
11086               ctx.lineTo(x + width, y - acumRight - dimArray[i][1]);
11087             }
11088             ctx.stroke();
11089             ctx.restore();
11090           }
11091           acumLeft += (dimArray[i][0] || 0);
11092           acumRight += (dimArray[i][1] || 0);
11093           
11094           if(dimArray[i][0] > 0)
11095             valAcum += (valArray[i][0] || 0);
11096         }
11097         if(prev && label.type == 'Native') {
11098           ctx.save();
11099           ctx.beginPath();
11100           ctx.fillStyle = ctx.strokeStyle = label.color;
11101           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11102           ctx.textAlign = 'center';
11103           ctx.textBaseline = 'middle';
11104           if(aggregates(node.name, valLeft, valRight, node)) {
11105             ctx.fillText(valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
11106           }
11107           if(showLabels(node.name, valLeft, valRight, node)) {
11108             ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
11109           }
11110           ctx.restore();
11111         }
11112       }
11113     },
11114     'contains': function(node, mpos) {
11115       var pos = node.pos.getc(true), 
11116           width = node.getData('width'),
11117           height = node.getData('height'),
11118           algnPos = this.getAlignedPos(pos, width, height),
11119           x = algnPos.x, y = algnPos.y,
11120           dimArray = node.getData('dimArray'),
11121           rx = mpos.x - x;
11122       //bounding box check
11123       if(mpos.x < x || mpos.x > x + width
11124         || mpos.y > y || mpos.y < y - height) {
11125         return false;
11126       }
11127       //deep check
11128       for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
11129         var dimi = dimArray[i];
11130         lAcum -= dimi[0];
11131         rAcum -= dimi[1];
11132         var intersec = lAcum + (rAcum - lAcum) * rx / width;
11133         if(mpos.y >= intersec) {
11134           var index = +(rx > width/2);
11135           return {
11136             'name': node.getData('stringArray')[i],
11137             'color': node.getData('colorArray')[i],
11138             'value': node.getData('valueArray')[i][index],
11139             'index': index
11140           };
11141         }
11142       }
11143       return false;
11144     }
11145   }
11146 });
11147
11148 /*
11149   Class: AreaChart
11150   
11151   A visualization that displays stacked area charts.
11152   
11153   Constructor Options:
11154   
11155   See <Options.AreaChart>.
11156
11157 */
11158 $jit.AreaChart = new Class({
11159   st: null,
11160   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
11161   selected: {},
11162   busy: false,
11163   
11164   initialize: function(opt) {
11165     this.controller = this.config = 
11166       $.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
11167         Label: { type: 'Native' }
11168       }, opt);
11169     //set functions for showLabels and showAggregates
11170     var showLabels = this.config.showLabels,
11171         typeLabels = $.type(showLabels),
11172         showAggregates = this.config.showAggregates,
11173         typeAggregates = $.type(showAggregates);
11174     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
11175     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
11176     
11177     this.initializeViz();
11178   },
11179   
11180   initializeViz: function() {
11181     var config = this.config,
11182         that = this,
11183         nodeType = config.type.split(":")[0],
11184         nodeLabels = {};
11185
11186     var st = new $jit.ST({
11187       injectInto: config.injectInto,
11188       orientation: "bottom",
11189       levelDistance: 0,
11190       siblingOffset: 0,
11191       subtreeOffset: 0,
11192       withLabels: config.Label.type != 'Native',
11193       useCanvas: config.useCanvas,
11194       Label: {
11195         type: config.Label.type
11196       },
11197       Node: {
11198         overridable: true,
11199         type: 'areachart-' + nodeType,
11200         align: 'left',
11201         width: 1,
11202         height: 1
11203       },
11204       Edge: {
11205         type: 'none'
11206       },
11207       Tips: {
11208         enable: config.Tips.enable,
11209         type: 'Native',
11210         force: true,
11211         onShow: function(tip, node, contains) {
11212           var elem = contains;
11213           config.Tips.onShow(tip, elem, node);
11214         }
11215       },
11216       Events: {
11217         enable: true,
11218         type: 'Native',
11219         onClick: function(node, eventInfo, evt) {
11220           if(!config.filterOnClick && !config.Events.enable) return;
11221           var elem = eventInfo.getContains();
11222           if(elem) config.filterOnClick && that.filter(elem.name);
11223           config.Events.enable && config.Events.onClick(elem, eventInfo, evt);
11224         },
11225         onRightClick: function(node, eventInfo, evt) {
11226           if(!config.restoreOnRightClick) return;
11227           that.restore();
11228         },
11229         onMouseMove: function(node, eventInfo, evt) {
11230           if(!config.selectOnHover) return;
11231           if(node) {
11232             var elem = eventInfo.getContains();
11233             that.select(node.id, elem.name, elem.index);
11234           } else {
11235             that.select(false, false, false);
11236           }
11237         }
11238       },
11239       onCreateLabel: function(domElement, node) {
11240         var labelConf = config.Label,
11241             valueArray = node.getData('valueArray'),
11242             acumLeft = $.reduce(valueArray, function(x, y) { return x + y[0]; }, 0),
11243             acumRight = $.reduce(valueArray, function(x, y) { return x + y[1]; }, 0);
11244         if(node.getData('prev')) {
11245           var nlbs = {
11246             wrapper: document.createElement('div'),
11247             aggregate: document.createElement('div'),
11248             label: document.createElement('div')
11249           };
11250           var wrapper = nlbs.wrapper,
11251               label = nlbs.label,
11252               aggregate = nlbs.aggregate,
11253               wrapperStyle = wrapper.style,
11254               labelStyle = label.style,
11255               aggregateStyle = aggregate.style;
11256           //store node labels
11257           nodeLabels[node.id] = nlbs;
11258           //append labels
11259           wrapper.appendChild(label);
11260           wrapper.appendChild(aggregate);
11261           if(!config.showLabels(node.name, acumLeft, acumRight, node)) {
11262             label.style.display = 'none';
11263           }
11264           if(!config.showAggregates(node.name, acumLeft, acumRight, node)) {
11265             aggregate.style.display = 'none';
11266           }
11267           wrapperStyle.position = 'relative';
11268           wrapperStyle.overflow = 'visible';
11269           wrapperStyle.fontSize = labelConf.size + 'px';
11270           wrapperStyle.fontFamily = labelConf.family;
11271           wrapperStyle.color = labelConf.color;
11272           wrapperStyle.textAlign = 'center';
11273           aggregateStyle.position = labelStyle.position = 'absolute';
11274           
11275           domElement.style.width = node.getData('width') + 'px';
11276           domElement.style.height = node.getData('height') + 'px';
11277           label.innerHTML = node.name;
11278           
11279           domElement.appendChild(wrapper);
11280         }
11281       },
11282       onPlaceLabel: function(domElement, node) {
11283         if(!node.getData('prev')) return;
11284         var labels = nodeLabels[node.id],
11285             wrapperStyle = labels.wrapper.style,
11286             labelStyle = labels.label.style,
11287             aggregateStyle = labels.aggregate.style,
11288             width = node.getData('width'),
11289             height = node.getData('height'),
11290             dimArray = node.getData('dimArray'),
11291             valArray = node.getData('valueArray'),
11292             acumLeft = $.reduce(valArray, function(x, y) { return x + y[0]; }, 0),
11293             acumRight = $.reduce(valArray, function(x, y) { return x + y[1]; }, 0),
11294             font = parseInt(wrapperStyle.fontSize, 10),
11295             domStyle = domElement.style;
11296         
11297         if(dimArray && valArray) {
11298           if(config.showLabels(node.name, acumLeft, acumRight, node)) {
11299             labelStyle.display = '';
11300           } else {
11301             labelStyle.display = 'none';
11302           }
11303           if(config.showAggregates(node.name, acumLeft, acumRight, node)) {
11304             aggregateStyle.display = '';
11305           } else {
11306             aggregateStyle.display = 'none';
11307           }
11308           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
11309           aggregateStyle.left = labelStyle.left = -width/2 + 'px';
11310           for(var i=0, l=valArray.length, acum=0, leftAcum=0; i<l; i++) {
11311             if(dimArray[i][0] > 0) {
11312               acum+= valArray[i][0];
11313               leftAcum+= dimArray[i][0];
11314             }
11315           }
11316           aggregateStyle.top = (-font - config.labelOffset) + 'px';
11317           labelStyle.top = (config.labelOffset + leftAcum) + 'px';
11318           domElement.style.top = parseInt(domElement.style.top, 10) - leftAcum + 'px';
11319           domElement.style.height = wrapperStyle.height = leftAcum + 'px';
11320           labels.aggregate.innerHTML = acum;
11321         }
11322       }
11323     });
11324     
11325     var size = st.canvas.getSize(),
11326         margin = config.Margin;
11327     st.config.offsetY = -size.height/2 + margin.bottom 
11328       + (config.showLabels && (config.labelOffset + config.Label.size));
11329     st.config.offsetX = (margin.right - margin.left)/2;
11330     this.st = st;
11331     this.canvas = this.st.canvas;
11332   },
11333   
11334  /*
11335   Method: loadJSON
11336  
11337   Loads JSON data into the visualization. 
11338   
11339   Parameters:
11340   
11341   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>.
11342   
11343   Example:
11344   (start code js)
11345   var areaChart = new $jit.AreaChart(options);
11346   areaChart.loadJSON(json);
11347   (end code)
11348  */  
11349   loadJSON: function(json) {
11350     var prefix = $.time(), 
11351         ch = [], 
11352         st = this.st,
11353         name = $.splat(json.label), 
11354         color = $.splat(json.color || this.colors),
11355         config = this.config,
11356         gradient = !!config.type.split(":")[1],
11357         animate = config.animate;
11358     
11359     for(var i=0, values=json.values, l=values.length; i<l-1; i++) {
11360       var val = values[i], prev = values[i-1], next = values[i+1];
11361       var valLeft = $.splat(values[i].values), valRight = $.splat(values[i+1].values);
11362       var valArray = $.zip(valLeft, valRight);
11363       var acumLeft = 0, acumRight = 0;
11364       ch.push({
11365         'id': prefix + val.label,
11366         'name': val.label,
11367         'data': {
11368           'value': valArray,
11369           '$valueArray': valArray,
11370           '$colorArray': color,
11371           '$stringArray': name,
11372           '$next': next.label,
11373           '$prev': prev? prev.label:false,
11374           '$config': config,
11375           '$gradient': gradient
11376         },
11377         'children': []
11378       });
11379     }
11380     var root = {
11381       'id': prefix + '$root',
11382       'name': '',
11383       'data': {
11384         '$type': 'none',
11385         '$width': 1,
11386         '$height': 1
11387       },
11388       'children': ch
11389     };
11390     st.loadJSON(root);
11391     
11392     this.normalizeDims();
11393     st.compute();
11394     st.select(st.root);
11395     if(animate) {
11396       st.fx.animate({
11397         modes: ['node-property:height:dimArray'],
11398         duration:1500
11399       });
11400     }
11401   },
11402   
11403  /*
11404   Method: updateJSON
11405  
11406   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.
11407   
11408   Parameters:
11409   
11410   json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
11411   onComplete - (object) A callback object to be called when the animation transition when updating the data end.
11412   
11413   Example:
11414   
11415   (start code js)
11416   areaChart.updateJSON(json, {
11417     onComplete: function() {
11418       alert('update complete!');
11419     }
11420   });
11421   (end code)
11422  */  
11423   updateJSON: function(json, onComplete) {
11424     if(this.busy) return;
11425     this.busy = true;
11426     
11427     var st = this.st,
11428         graph = st.graph,
11429         labels = json.label && $.splat(json.label),
11430         values = json.values,
11431         animate = this.config.animate,
11432         that = this;
11433     $.each(values, function(v) {
11434       var n = graph.getByName(v.label);
11435       if(n) {
11436         v.values = $.splat(v.values);
11437         var stringArray = n.getData('stringArray'),
11438             valArray = n.getData('valueArray');
11439         $.each(valArray, function(a, i) {
11440           a[0] = v.values[i];
11441           if(labels) stringArray[i] = labels[i];
11442         });
11443         n.setData('valueArray', valArray);
11444         var prev = n.getData('prev'),
11445             next = n.getData('next'),
11446             nextNode = graph.getByName(next);
11447         if(prev) {
11448           var p = graph.getByName(prev);
11449           if(p) {
11450             var valArray = p.getData('valueArray');
11451             $.each(valArray, function(a, i) {
11452               a[1] = v.values[i];
11453             });
11454           }
11455         }
11456         if(!nextNode) {
11457           var valArray = n.getData('valueArray');
11458           $.each(valArray, function(a, i) {
11459             a[1] = v.values[i];
11460           });
11461         }
11462       }
11463     });
11464     this.normalizeDims();
11465     st.compute();
11466     st.select(st.root);
11467     if(animate) {
11468       st.fx.animate({
11469         modes: ['node-property:height:dimArray'],
11470         duration:1500,
11471         onComplete: function() {
11472           that.busy = false;
11473           onComplete && onComplete.onComplete();
11474         }
11475       });
11476     }
11477   },
11478   
11479 /*
11480   Method: filter
11481  
11482   Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
11483   
11484   Parameters:
11485   
11486   Variable strings arguments with the name of the stacks.
11487   
11488   Example:
11489   
11490   (start code js)
11491   areaChart.filter('label A', 'label C');
11492   (end code)
11493   
11494   See also:
11495   
11496   <AreaChart.restore>.
11497  */  
11498   filter: function() {
11499     if(this.busy) return;
11500     this.busy = true;
11501     if(this.config.Tips.enable) this.st.tips.hide();
11502     this.select(false, false, false);
11503     var args = Array.prototype.slice.call(arguments);
11504     var rt = this.st.graph.getNode(this.st.root);
11505     var that = this;
11506     rt.eachAdjacency(function(adj) {
11507       var n = adj.nodeTo, 
11508           dimArray = n.getData('dimArray'),
11509           stringArray = n.getData('stringArray');
11510       n.setData('dimArray', $.map(dimArray, function(d, i) {
11511         return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
11512       }), 'end');
11513     });
11514     this.st.fx.animate({
11515       modes: ['node-property:dimArray'],
11516       duration:1500,
11517       onComplete: function() {
11518         that.busy = false;
11519       }
11520     });
11521   },
11522   
11523   /*
11524   Method: restore
11525  
11526   Sets all stacks that could have been filtered visible.
11527   
11528   Example:
11529   
11530   (start code js)
11531   areaChart.restore();
11532   (end code)
11533   
11534   See also:
11535   
11536   <AreaChart.filter>.
11537  */  
11538   restore: function() {
11539     if(this.busy) return;
11540     this.busy = true;
11541     if(this.config.Tips.enable) this.st.tips.hide();
11542     this.select(false, false, false);
11543     this.normalizeDims();
11544     var that = this;
11545     this.st.fx.animate({
11546       modes: ['node-property:height:dimArray'],
11547       duration:1500,
11548       onComplete: function() {
11549         that.busy = false;
11550       }
11551     });
11552   },
11553   //adds the little brown bar when hovering the node
11554   select: function(id, name, index) {
11555     if(!this.config.selectOnHover) return;
11556     var s = this.selected;
11557     if(s.id != id || s.name != name 
11558         || s.index != index) {
11559       s.id = id;
11560       s.name = name;
11561       s.index = index;
11562       this.st.graph.eachNode(function(n) {
11563         n.setData('border', false);
11564       });
11565       if(id) {
11566         var n = this.st.graph.getNode(id);
11567         n.setData('border', s);
11568         var link = index === 0? 'prev':'next';
11569         link = n.getData(link);
11570         if(link) {
11571           n = this.st.graph.getByName(link);
11572           if(n) {
11573             n.setData('border', {
11574               name: name,
11575               index: 1-index
11576             });
11577           }
11578         }
11579       }
11580       this.st.plot();
11581     }
11582   },
11583   
11584   /*
11585     Method: getLegend
11586    
11587     Returns an object containing as keys the legend names and as values hex strings with color values.
11588     
11589     Example:
11590     
11591     (start code js)
11592     var legend = areaChart.getLegend();
11593     (end code)
11594  */  
11595   getLegend: function() {
11596     var legend = {};
11597     var n;
11598     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
11599       n = adj.nodeTo;
11600     });
11601     var colors = n.getData('colorArray'),
11602         len = colors.length;
11603     $.each(n.getData('stringArray'), function(s, i) {
11604       legend[s] = colors[i % len];
11605     });
11606     return legend;
11607   },
11608   
11609   /*
11610     Method: getMaxValue
11611    
11612     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
11613     
11614     Example:
11615     
11616     (start code js)
11617     var ans = areaChart.getMaxValue();
11618     (end code)
11619     
11620     In some cases it could be useful to override this method to normalize heights for a group of AreaCharts, like when doing small multiples.
11621     
11622     Example:
11623     
11624     (start code js)
11625     //will return 100 for all AreaChart instances,
11626     //displaying all of them with the same scale
11627     $jit.AreaChart.implement({
11628       'getMaxValue': function() {
11629         return 100;
11630       }
11631     });
11632     (end code)
11633     
11634 */  
11635   getMaxValue: function() {
11636     var maxValue = 0;
11637     this.st.graph.eachNode(function(n) {
11638       var valArray = n.getData('valueArray'),
11639           acumLeft = 0, acumRight = 0;
11640       $.each(valArray, function(v) { 
11641         acumLeft += +v[0];
11642         acumRight += +v[1];
11643       });
11644       var acum = acumRight>acumLeft? acumRight:acumLeft;
11645       maxValue = maxValue>acum? maxValue:acum;
11646     });
11647     return maxValue;
11648   },
11649   
11650   normalizeDims: function() {
11651     //number of elements
11652     var root = this.st.graph.getNode(this.st.root), l=0;
11653     root.eachAdjacency(function() {
11654       l++;
11655     });
11656     var maxValue = this.getMaxValue() || 1,
11657         size = this.st.canvas.getSize(),
11658         config = this.config,
11659         margin = config.Margin,
11660         labelOffset = config.labelOffset + config.Label.size,
11661         fixedDim = (size.width - (margin.left + margin.right)) / l,
11662         animate = config.animate,
11663         height = size.height - (margin.top + margin.bottom) - (config.showAggregates && labelOffset) 
11664           - (config.showLabels && labelOffset);
11665     this.st.graph.eachNode(function(n) {
11666       var acumLeft = 0, acumRight = 0, animateValue = [];
11667       $.each(n.getData('valueArray'), function(v) {
11668         acumLeft += +v[0];
11669         acumRight += +v[1];
11670         animateValue.push([0, 0]);
11671       });
11672       var acum = acumRight>acumLeft? acumRight:acumLeft;
11673       n.setData('width', fixedDim);
11674       if(animate) {
11675         n.setData('height', acum * height / maxValue, 'end');
11676         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11677           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11678         }), 'end');
11679         var dimArray = n.getData('dimArray');
11680         if(!dimArray) {
11681           n.setData('dimArray', animateValue);
11682         }
11683       } else {
11684         n.setData('height', acum * height / maxValue);
11685         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
11686           return [n[0] * height / maxValue, n[1] * height / maxValue]; 
11687         }));
11688       }
11689     });
11690   }
11691 });
11692
11693 /*
11694  * File: Options.BarChart.js
11695  *
11696 */
11697
11698 /*
11699   Object: Options.BarChart
11700   
11701   <BarChart> options. 
11702   Other options included in the BarChart are <Options.Canvas>, <Options.Label>, <Options.Margin>, <Options.Tips> and <Options.Events>.
11703   
11704   Syntax:
11705   
11706   (start code js)
11707
11708   Options.BarChart = {
11709     animate: true,
11710     labelOffset: 3,
11711     barsOffset: 0,
11712     type: 'stacked',
11713     hoveredColor: '#9fd4ff',
11714     orientation: 'horizontal',
11715     showAggregates: true,
11716     showLabels: true
11717   };
11718   
11719   (end code)
11720   
11721   Example:
11722   
11723   (start code js)
11724
11725   var barChart = new $jit.BarChart({
11726     animate: true,
11727     barsOffset: 10,
11728     type: 'stacked:gradient'
11729   });
11730   
11731   (end code)
11732
11733   Parameters:
11734   
11735   animate - (boolean) Default's *true*. Whether to add animated transitions when filtering/restoring stacks.
11736   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
11737   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
11738   barsOffset - (number) Default's *0*. Separation between bars.
11739   type - (string) Default's *'stacked'*. Stack or grouped styles. Posible values are 'stacked', 'grouped', 'stacked:gradient', 'grouped:gradient' to add gradients.
11740   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered bar stack.
11741   orientation - (string) Default's 'horizontal'. Sets the direction of the bars. Possible options are 'vertical' or 'horizontal'.
11742   showAggregates - (boolean) Default's *true*. Display the sum of the values of the different stacks.
11743   showLabels - (boolean) Default's *true*. Display the name of the slots.
11744   
11745 */
11746
11747 Options.BarChart = {
11748   $extend: true,
11749   
11750   animate: true,
11751   type: 'stacked', //stacked, grouped, : gradient
11752   labelOffset: 3, //label offset
11753   barsOffset: 0, //distance between bars
11754   nodeCount: 0, //number of bars
11755   hoveredColor: '#9fd4ff',
11756   background: false,
11757   renderBackground: false,
11758   orientation: 'horizontal',
11759   showAggregates: true,
11760   showLabels: true,
11761   Ticks: {
11762         enable: false,
11763         segments: 4,
11764         color: '#000000'
11765   },
11766   Tips: {
11767     enable: false,
11768     onShow: $.empty,
11769     onHide: $.empty
11770   },
11771   Events: {
11772     enable: false,
11773     onClick: $.empty
11774   }
11775 };
11776
11777 /*
11778  * File: BarChart.js
11779  *
11780 */
11781
11782 $jit.ST.Plot.NodeTypes.implement({
11783   'barchart-stacked' : {
11784     'render' : function(node, canvas) {
11785       var pos = node.pos.getc(true), 
11786           width = node.getData('width'),
11787           height = node.getData('height'),
11788           algnPos = this.getAlignedPos(pos, width, height),
11789           x = algnPos.x, y = algnPos.y,
11790           dimArray = node.getData('dimArray'),
11791           valueArray = node.getData('valueArray'),
11792           stringArray = node.getData('stringArray'),
11793           linkArray = node.getData('linkArray'),
11794           gvl = node.getData('gvl'),
11795           colorArray = node.getData('colorArray'),
11796           colorLength = colorArray.length,
11797           nodeCount = node.getData('nodeCount');
11798       var ctx = canvas.getCtx(),
11799           canvasSize = canvas.getSize(),
11800           opt = {},
11801           border = node.getData('border'),
11802           gradient = node.getData('gradient'),
11803           config = node.getData('config'),
11804           horz = config.orientation == 'horizontal',
11805           aggregates = config.showAggregates,
11806           showLabels = config.showLabels,
11807           label = config.Label,
11808           margin = config.Margin;
11809           
11810           
11811       if (colorArray && dimArray && stringArray) {
11812         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11813                 acum += (dimArray[i] || 0);
11814         }
11815       }
11816       
11817        //drop shadow 
11818        if(config.shadow.enable) {
11819        shadowThickness = config.shadow.size;
11820        ctx.fillStyle = "rgba(0,0,0,.2)";
11821           if(horz) {
11822             ctx.fillRect(x, y - shadowThickness, acum + shadowThickness, height + (shadowThickness*2));
11823           } else {
11824             ctx.fillRect(x - shadowThickness, y - acum - shadowThickness, width + (shadowThickness*2), acum + shadowThickness);
11825           }
11826        }
11827        
11828       if (colorArray && dimArray && stringArray) {
11829         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
11830           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
11831           if(gradient) {
11832             var linear;
11833             
11834
11835           
11836             if(horz) {
11837               linear = ctx.createLinearGradient(x + acum + dimArray[i]/2, y, 
11838                   x + acum + dimArray[i]/2, y + height);
11839             } else {
11840               linear = ctx.createLinearGradient(x, y - acum - dimArray[i]/2, 
11841                   x + width, y - acum- dimArray[i]/2);
11842             }
11843             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
11844                 function(v) { return (v * 0.8) >> 0; }));
11845             linear.addColorStop(0, color);
11846             linear.addColorStop(0.3, colorArray[i % colorLength]);
11847             linear.addColorStop(0.7, colorArray[i % colorLength]);
11848             linear.addColorStop(1, color);
11849             ctx.fillStyle = linear;
11850           }
11851
11852           if (horz)
11853           {
11854               yCoord = y;
11855               xCoord = x + acum;
11856               chartBarWidth = dimArray[i];
11857               chartBarHeight = height;
11858           }
11859           else
11860           {
11861               xCoord = x;
11862               yCoord = y - acum - dimArray[i];
11863               chartBarWidth = width;
11864               chartBarHeight = dimArray[i];
11865           }
11866           ctx.fillRect(xCoord, yCoord, chartBarWidth, chartBarHeight);
11867
11868           // add label
11869           if (chartBarHeight > 0)
11870           {
11871               ctx.font = label.style + ' ' + (label.size - 2) + 'px ' + label.family;
11872               labelText = valueArray[i].toString();
11873               mtxt = ctx.measureText(labelText);
11874
11875               labelTextPaddingX = 10;
11876               labelTextPaddingY = 6;
11877
11878               labelBoxWidth = mtxt.width + labelTextPaddingX;
11879               labelBoxHeight = label.size + labelTextPaddingY;
11880
11881               // do NOT draw label if label box is smaller than chartBarHeight
11882               if ((horz && (labelBoxWidth < chartBarWidth)) || (!horz && (labelBoxHeight < chartBarHeight)))
11883               {
11884                   labelBoxX = xCoord + chartBarWidth/2 - mtxt.width/2 - labelTextPaddingX/2;
11885                   labelBoxY = yCoord + chartBarHeight/2 - labelBoxHeight/2;
11886
11887                   ctx.fillStyle = "rgba(255,255,255,.2)";
11888                   $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "fill");
11889                   ctx.fillStyle = "rgba(0,0,0,.8)";
11890                   $.roundedRect(ctx, labelBoxX, labelBoxY, labelBoxWidth, labelBoxHeight, 4, "stroke");
11891                   ctx.textAlign = 'center';
11892                   ctx.fillStyle = "rgba(255,255,255,.6)";
11893                   ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2, labelBoxY + labelBoxHeight/2);
11894                   ctx.fillStyle = "rgba(0,0,0,.6)";
11895                   ctx.fillText(labelText, labelBoxX + mtxt.width/2 + labelTextPaddingX/2 + 1, labelBoxY + labelBoxHeight/2 + 1);
11896               }
11897           }
11898
11899           if(border && border.name == stringArray[i]) {
11900             opt.acum = acum;
11901             opt.dimValue = dimArray[i];
11902           }
11903           acum += (dimArray[i] || 0);
11904           valAcum += (valueArray[i] || 0);
11905         }
11906         if(border) {
11907           ctx.save();
11908           ctx.lineWidth = 2;
11909           ctx.strokeStyle = border.color;
11910           if(horz) {
11911             ctx.strokeRect(x + opt.acum + 1, y + 1, opt.dimValue -2, height - 2);
11912           } else {
11913             ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, width -2, opt.dimValue -2);
11914           }
11915           ctx.restore();
11916         }
11917         if(label.type == 'Native') {
11918           ctx.save();
11919           ctx.fillStyle = ctx.strokeStyle = label.color;
11920           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11921           ctx.textBaseline = 'middle';
11922                         if(gvl) {
11923                                 acumValueLabel = gvl;
11924                         } else {
11925                                 acumValueLabel = valAcum;
11926                         }
11927           if(aggregates(node.name, valAcum)) {
11928             if(!horz) {
11929                           ctx.textAlign = 'center';
11930                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11931                           //background box
11932                           ctx.save();
11933                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
11934                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
11935                                  (label ? label.size + config.labelOffset : 0));
11936                           mtxt = ctx.measureText(acumValueLabel);
11937                           boxWidth = mtxt.width+10;
11938                           inset = 10;
11939                           boxHeight = label.size+6;
11940                           
11941                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11942                                 bottomPadding = acum - config.labelOffset - boxHeight;
11943                           } else {
11944                                 bottomPadding = acum + config.labelOffset + inset;
11945                           }
11946                         
11947                         
11948                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
11949                           cornerRadius = 4;     
11950                           boxX = -inset/2;
11951                           boxY = -boxHeight/2;
11952                           
11953                           ctx.rotate(0 * Math.PI / 180);
11954                           ctx.fillStyle = "rgba(255,255,255,.8)";
11955                           if(boxHeight + acum + config.labelOffset > gridHeight) {
11956                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11957                           }
11958                           //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11959                           ctx.fillStyle = ctx.strokeStyle = label.color;
11960                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
11961                           ctx.restore();
11962
11963             }
11964           }
11965           if(showLabels(node.name, valAcum, node)) {
11966             if(horz) {
11967
11968
11969                 //background box
11970                 ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
11971                                 inset = 10;
11972                                 
11973                                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
11974                 mtxt = ctx.measureText(node.name + ": " + acumValueLabel);
11975                 boxWidth = mtxt.width+10;
11976                 inset = 10;
11977                 
11978                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
11979                         leftPadding = acum - config.labelOffset - boxWidth - inset;
11980                 } else {
11981                         leftPadding = acum + config.labelOffset;
11982                 }
11983                 
11984                 
11985                                 ctx.textAlign = 'left';
11986                                 ctx.translate(x + inset + leftPadding, y + height/2);
11987                                 boxHeight = label.size+6;
11988                                 boxX = -inset/2;
11989                                 boxY = -boxHeight/2;
11990                                 ctx.fillStyle = "rgba(255,255,255,.8)";
11991                                 cornerRadius = 4;
11992                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {  
11993                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
11994                                 }
11995                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
11996                                 
11997                           ctx.fillStyle = label.color;
11998               ctx.rotate(0 * Math.PI / 180);
11999               ctx.fillText(node.name + ": " + acumValueLabel, 0, 0);
12000
12001
12002             } else {
12003               //if the number of nodes greater than 8 rotate labels 45 degrees
12004               if(nodeCount > 8) {
12005                                 ctx.textAlign = 'left';
12006                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12007                                 ctx.rotate(45* Math.PI / 180);
12008                                 ctx.fillText(node.name, 0, 0);
12009                           } else {
12010                                 ctx.textAlign = 'center';
12011                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12012                           }
12013             }
12014           }
12015           ctx.restore();
12016         }
12017       }
12018     },
12019     'contains': function(node, mpos) {
12020       var pos = node.pos.getc(true), 
12021           width = node.getData('width'),
12022           height = node.getData('height'),
12023           algnPos = this.getAlignedPos(pos, width, height),
12024           x = algnPos.x, y = algnPos.y,
12025           dimArray = node.getData('dimArray'),
12026           config = node.getData('config'),
12027           rx = mpos.x - x,
12028           horz = config.orientation == 'horizontal';
12029       //bounding box check
12030       if(horz) {
12031         if(mpos.x < x || mpos.x > x + width
12032             || mpos.y > y + height || mpos.y < y) {
12033             return false;
12034           }
12035       } else {
12036         if(mpos.x < x || mpos.x > x + width
12037             || mpos.y > y || mpos.y < y - height) {
12038             return false;
12039           }
12040       }
12041       //deep check
12042       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
12043         var dimi = dimArray[i];
12044                 var url = Url.decode(node.getData('linkArray')[i]);
12045         if(horz) {
12046           acum += dimi;
12047           var intersec = acum;
12048           if(mpos.x <= intersec) {
12049             return {
12050               'name': node.getData('stringArray')[i],
12051               'color': node.getData('colorArray')[i],
12052               'value': node.getData('valueArray')[i],
12053               'valuelabel': node.getData('valuelabelArray')[i],
12054                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12055                           'link': url,
12056               'label': node.name
12057             };
12058           }
12059         } else {
12060           acum -= dimi;
12061           var intersec = acum;
12062           if(mpos.y >= intersec) {
12063             return {
12064               'name': node.getData('stringArray')[i],
12065               'color': node.getData('colorArray')[i],
12066               'value': node.getData('valueArray')[i],
12067                           'valuelabel': node.getData('valuelabelArray')[i],
12068                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12069               'link': url,
12070               'label': node.name
12071             };
12072           }
12073         }
12074       }
12075       return false;
12076     }
12077   },
12078   'barchart-grouped' : {
12079     'render' : function(node, canvas) {
12080       var pos = node.pos.getc(true), 
12081           width = node.getData('width'),
12082           height = node.getData('height'),
12083           algnPos = this.getAlignedPos(pos, width, height),
12084           x = algnPos.x, y = algnPos.y,
12085           dimArray = node.getData('dimArray'),
12086           valueArray = node.getData('valueArray'),
12087           valuelabelArray = node.getData('valuelabelArray'),
12088           linkArray = node.getData('linkArray'),
12089           valueLength = valueArray.length,
12090           colorArray = node.getData('colorArray'),
12091           colorLength = colorArray.length,
12092           stringArray = node.getData('stringArray'); 
12093
12094       var ctx = canvas.getCtx(),
12095           canvasSize = canvas.getSize(),
12096           opt = {},
12097           border = node.getData('border'),
12098           gradient = node.getData('gradient'),
12099           config = node.getData('config'),
12100           horz = config.orientation == 'horizontal',
12101           aggregates = config.showAggregates,
12102           showLabels = config.showLabels,
12103           label = config.Label,
12104           shadow = config.shadow,
12105           margin = config.Margin,
12106           fixedDim = (horz? height : width) / valueLength;
12107       
12108       //drop shadow
12109       
12110        maxValue = Math.max.apply(null, dimArray);
12111        
12112        
12113           
12114            ctx.fillStyle = "rgba(0,0,0,.2)";
12115       if (colorArray && dimArray && stringArray && shadow.enable) {
12116                  shadowThickness = shadow.size;
12117
12118         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12119                 nextBar = (dimArray[i+1]) ? dimArray[i+1] : false;
12120                 prevBar = (dimArray[i-1]) ? dimArray[i-1] : false;
12121                 if(horz) {
12122                                     
12123                         ctx.fillRect(x , y - shadowThickness + (fixedDim * i), dimArray[i]+ shadowThickness, fixedDim + shadowThickness*2);
12124                                         
12125                 } else {
12126                         
12127                         if(i == 0) {
12128                                 if(nextBar && nextBar > dimArray[i]) {
12129                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
12130                                 } else if (nextBar && nextBar < dimArray[i]){
12131                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
12132                                 } else {
12133                                         ctx.fillRect((x - shadowThickness) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
12134                                 }
12135                         } else if (i> 0 && i<l-1) {
12136                                 if(nextBar && nextBar > dimArray[i]) {
12137                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim, dimArray[i]+ shadowThickness);   
12138                                 } else if (nextBar && nextBar < dimArray[i]){
12139                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
12140                                 } else {
12141                                         ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness, dimArray[i]+ shadowThickness);
12142                                 }
12143                         } else if (i == l-1) {
12144                                 ctx.fillRect((x - ((prevBar < dimArray[i]) ? shadowThickness : 0)) + fixedDim * i, y - dimArray[i] - shadowThickness, fixedDim + shadowThickness*2, dimArray[i]+ shadowThickness);
12145                         }
12146                         
12147                         
12148                 }
12149         }
12150
12151       } 
12152                         
12153       
12154       if (colorArray && dimArray && stringArray) {
12155         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12156           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12157           if(gradient) {
12158             var linear;
12159             if(horz) {
12160               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
12161                   x + dimArray[i]/2, y + fixedDim * (i + 1));
12162             } else {
12163               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
12164                   x + fixedDim * (i + 1), y - dimArray[i]/2);
12165             }
12166             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
12167                 function(v) { return (v * 0.8) >> 0; }));
12168             linear.addColorStop(0, color);
12169             linear.addColorStop(0.3, colorArray[i % colorLength]);
12170             linear.addColorStop(0.7, colorArray[i % colorLength]);
12171             linear.addColorStop(1, color);
12172             ctx.fillStyle = linear;
12173           }
12174           if(horz) {
12175             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12176           } else {
12177             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12178           }
12179           if(border && border.name == stringArray[i]) {
12180             opt.acum = fixedDim * i;
12181             opt.dimValue = dimArray[i];
12182           }
12183           acum += (dimArray[i] || 0);
12184           valAcum += (valueArray[i] || 0);
12185                   ctx.fillStyle = ctx.strokeStyle = label.color;
12186           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12187           
12188           inset = 10;
12189                   if(aggregates(node.name, valAcum) && label.type == 'Native') {
12190                                 if(valuelabelArray[i]) {
12191                                         acumValueLabel = valuelabelArray[i];
12192                                 } else {
12193                                         acumValueLabel = valueArray[i];
12194                                 }
12195                            if(horz) {
12196                                   ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12197                                   ctx.textAlign = 'left';
12198                                   ctx.textBaseline = 'top';
12199                                   ctx.fillStyle = "rgba(255,255,255,.8)";
12200                                   //background box
12201                                   gridWidth = canvasSize.width - (margin.left + margin.right + config.labeloffset + label.size);
12202                                   mtxt = ctx.measureText(acumValueLabel);
12203                                   boxWidth = mtxt.width+10;
12204                                   
12205                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12206                                         leftPadding = dimArray[i] - config.labelOffset - boxWidth - inset;
12207                                   } else {
12208                                         leftPadding = dimArray[i] + config.labelOffset + inset;
12209                                   }
12210                               boxHeight = label.size+6;
12211                                   boxX = x + leftPadding;
12212                                   boxY = y + i*fixedDim + (fixedDim/2) - boxHeight/2;
12213                                   cornerRadius = 4;     
12214                                   
12215                                   
12216                                   if(boxWidth + dimArray[i] + config.labelOffset > gridWidth) {
12217                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12218                                   }
12219                                 //  $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12220                                   
12221                                   ctx.fillStyle = ctx.strokeStyle = label.color;
12222                                   ctx.fillText(acumValueLabel, x + inset/2 + leftPadding, y + i*fixedDim + (fixedDim/2) - (label.size/2));
12223                                   
12224
12225                                         
12226                                         
12227                                 } else {
12228                                   
12229                                         ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12230                                         ctx.save();
12231                                         ctx.textAlign = 'center';
12232                                         
12233                                         //background box
12234                                         gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12235                                          (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12236                                          (label ? label.size + config.labelOffset : 0));
12237                                         
12238                                         mtxt = ctx.measureText(acumValueLabel);
12239                                         boxWidth = mtxt.width+10;
12240                                         boxHeight = label.size+6;
12241                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12242                                                 bottomPadding = dimArray[i] - config.labelOffset - boxHeight - inset;
12243                                         } else {
12244                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12245                                         }
12246                                                                                                 
12247                                         
12248                                         ctx.translate(x + (i*fixedDim) + (fixedDim/2) , y - bottomPadding);
12249                                         
12250                                         boxX = -boxWidth/2;
12251                                         boxY = -boxHeight/2;
12252                                         ctx.fillStyle = "rgba(255,255,255,.8)";
12253                                         
12254                                         cornerRadius = 4;       
12255
12256                                         //ctx.rotate(270* Math.PI / 180);
12257                                         if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12258                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12259                                         }
12260                                         //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12261                                         
12262                                         ctx.fillStyle = ctx.strokeStyle = label.color;
12263                                         ctx.fillText(acumValueLabel, 0,0);
12264                                         ctx.restore();
12265
12266                                 }
12267                         }
12268         }
12269         if(border) {
12270           ctx.save();
12271           ctx.lineWidth = 2;
12272           ctx.strokeStyle = border.color;
12273           if(horz) {
12274             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12275           } else {
12276             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12277           }
12278           ctx.restore();
12279         }
12280         if(label.type == 'Native') {
12281           ctx.save();
12282           ctx.fillStyle = ctx.strokeStyle = label.color;
12283           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12284           ctx.textBaseline = 'middle';
12285
12286           if(showLabels(node.name, valAcum, node)) {
12287             if(horz) {
12288               ctx.textAlign = 'center';
12289               ctx.translate(x - config.labelOffset - label.size/2, y + height/2);
12290               ctx.rotate(Math.PI / 2);
12291               ctx.fillText(node.name, 0, 0);
12292             } else {
12293               ctx.textAlign = 'center';
12294               ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12295             }
12296           }
12297           ctx.restore();
12298         }
12299       }
12300     },
12301     'contains': function(node, mpos) {
12302       var pos = node.pos.getc(true), 
12303           width = node.getData('width'),
12304           height = node.getData('height'),
12305           algnPos = this.getAlignedPos(pos, width, height),
12306           x = algnPos.x, y = algnPos.y,
12307           dimArray = node.getData('dimArray'),
12308           len = dimArray.length,
12309           config = node.getData('config'),
12310           rx = mpos.x - x,
12311           horz = config.orientation == 'horizontal',
12312           fixedDim = (horz? height : width) / len;
12313       //bounding box check
12314       if(horz) {
12315         if(mpos.x < x || mpos.x > x + width
12316             || mpos.y > y + height || mpos.y < y) {
12317             return false;
12318           }
12319       } else {
12320         if(mpos.x < x || mpos.x > x + width
12321             || mpos.y > y || mpos.y < y - height) {
12322             return false;
12323           }
12324       }
12325       //deep check
12326       for(var i=0, l=dimArray.length; i<l; i++) {
12327         var dimi = dimArray[i];
12328                 var url = Url.decode(node.getData('linkArray')[i]);
12329         if(horz) {
12330           var limit = y + fixedDim * i;
12331           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12332             return {
12333               'name': node.getData('stringArray')[i],
12334               'color': node.getData('colorArray')[i],
12335               'value': node.getData('valueArray')[i],
12336                           'valuelabel': node.getData('valuelabelArray')[i],
12337               'title': node.getData('titleArray')[i],
12338                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12339               'link': url,
12340               'label': node.name
12341             };
12342           }
12343         } else {
12344           var limit = x + fixedDim * i;
12345           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12346             return {
12347               'name': node.getData('stringArray')[i],
12348               'color': node.getData('colorArray')[i],
12349               'value': node.getData('valueArray')[i],
12350                           'valuelabel': node.getData('valuelabelArray')[i],
12351               'title': node.getData('titleArray')[i],
12352                           'percentage': ((node.getData('valueArray')[i]/node.getData('barTotalValue')) * 100).toFixed(1),
12353               'link': url,
12354               'label': node.name
12355             };
12356           }
12357         }
12358       }
12359       return false;
12360     }
12361   },
12362   'barchart-basic' : {
12363     'render' : function(node, canvas) {
12364       var pos = node.pos.getc(true), 
12365           width = node.getData('width'),
12366           height = node.getData('height'),
12367           algnPos = this.getAlignedPos(pos, width, height),
12368           x = algnPos.x, y = algnPos.y,
12369           dimArray = node.getData('dimArray'),
12370           valueArray = node.getData('valueArray'),
12371                   valuelabelArray = node.getData('valuelabelArray'),
12372           linkArray = node.getData('linkArray'),
12373           valueLength = valueArray.length,
12374           colorArray = node.getData('colorMono'),
12375           colorLength = colorArray.length,
12376           stringArray = node.getData('stringArray'); 
12377
12378       var ctx = canvas.getCtx(),
12379           canvasSize = canvas.getSize(),
12380           opt = {},
12381           border = node.getData('border'),
12382           gradient = node.getData('gradient'),
12383           config = node.getData('config'),
12384           horz = config.orientation == 'horizontal',
12385           aggregates = config.showAggregates,
12386           showLabels = config.showLabels,
12387           label = config.Label,
12388           fixedDim = (horz? height : width) / valueLength,
12389           margin = config.Margin;
12390       
12391       if (colorArray && dimArray && stringArray) {
12392         for (var i=0, l=valueLength, acum=0, valAcum=0; i<l; i++) {
12393           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
12394
12395           if(gradient) {
12396             var linear;
12397             if(horz) {
12398               linear = ctx.createLinearGradient(x + dimArray[i]/2, y + fixedDim * i, 
12399                   x + dimArray[i]/2, y + fixedDim * (i + 1));
12400             } else {
12401               linear = ctx.createLinearGradient(x + fixedDim * i, y - dimArray[i]/2, 
12402                   x + fixedDim * (i + 1), y - dimArray[i]/2);
12403             }
12404             //drop shadow 
12405            if(config.shadow.size) {
12406                   shadowThickness = config.shadow.size;
12407                   ctx.fillStyle = "rgba(0,0,0,.2)";
12408                   if(horz) {
12409                     ctx.fillRect(x, y + fixedDim * i - (shadowThickness), dimArray[i] + shadowThickness, fixedDim + (shadowThickness*2));
12410                   } else {
12411                     ctx.fillRect(x + fixedDim * i - (shadowThickness), y - dimArray[i] - shadowThickness, fixedDim + (shadowThickness*2), dimArray[i] + shadowThickness);
12412                   }
12413           }
12414           
12415             var color = $.rgbToHex($.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
12416                 function(v) { return (v * 0.8) >> 0; }));
12417             linear.addColorStop(0, color);
12418             linear.addColorStop(0.3, colorArray[i % colorLength]);
12419             linear.addColorStop(0.7, colorArray[i % colorLength]);
12420             linear.addColorStop(1, color);
12421             ctx.fillStyle = linear;
12422           }
12423           if(horz) {
12424             ctx.fillRect(x, y + fixedDim * i, dimArray[i], fixedDim);
12425           } else {
12426             ctx.fillRect(x + fixedDim * i, y - dimArray[i], fixedDim, dimArray[i]);
12427           }
12428           if(border && border.name == stringArray[i]) {
12429             opt.acum = fixedDim * i;
12430             opt.dimValue = dimArray[i];
12431           }
12432           acum += (dimArray[i] || 0);
12433           valAcum += (valueArray[i] || 0);
12434                   
12435               if(label.type == 'Native') {
12436                                  ctx.fillStyle = ctx.strokeStyle = label.color;
12437                                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12438                                  if(aggregates(node.name, valAcum)) {
12439                                         if(valuelabelArray[i]) {
12440                                                 acumValueLabel = valuelabelArray[i];
12441                                           } else {
12442                                                 acumValueLabel = valueArray[i];
12443                                           }
12444                                          if(!horz) {
12445                                           ctx.textAlign = 'center';
12446                                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12447                                           //background box
12448                                           ctx.save();
12449                                           gridHeight = canvasSize.height - (margin.top + margin.bottom + (config.Title.text ? config.Title.size + config.Title.offset : 0) +
12450                                                  (config.Subtitle.text ? config.Subtitle.size + config.Subtitle.offset : 0) +
12451                                                  (label ? label.size + config.labelOffset : 0));
12452                           mtxt = ctx.measureText(acumValueLabel);
12453                                           boxWidth = mtxt.width+10;
12454                                           inset = 10;
12455                                           boxHeight = label.size+6;
12456                                           
12457                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12458                                                 bottomPadding = dimArray[i] - config.labelOffset  - inset;
12459                                           } else {
12460                                                 bottomPadding = dimArray[i] + config.labelOffset + inset;
12461                                           }
12462                                         
12463                                         
12464                                           ctx.translate(x + width/2 - (mtxt.width/2) , y - bottomPadding);
12465                                           cornerRadius = 4;     
12466                                           boxX = -inset/2;
12467                                           boxY = -boxHeight/2;
12468                                           
12469                                           //ctx.rotate(270* Math.PI / 180);
12470                                           ctx.fillStyle = "rgba(255,255,255,.6)";
12471                                           if(boxHeight + dimArray[i] + config.labelOffset > gridHeight) {
12472                                                 $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12473                                           }
12474                                          // $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12475                                           ctx.fillStyle = ctx.strokeStyle = label.color;
12476                                           ctx.fillText(acumValueLabel, mtxt.width/2, 0);
12477                                           ctx.restore();
12478                                         }
12479                                 }
12480                 }
12481         }
12482         if(border) {
12483           ctx.save();
12484           ctx.lineWidth = 2;
12485           ctx.strokeStyle = border.color;
12486           if(horz) {
12487             ctx.strokeRect(x + 1, y + opt.acum + 1, opt.dimValue -2, fixedDim - 2);
12488           } else {
12489             ctx.strokeRect(x + opt.acum + 1, y - opt.dimValue + 1, fixedDim -2, opt.dimValue -2);
12490           }
12491           ctx.restore();
12492         }
12493         if(label.type == 'Native') {
12494           ctx.save();
12495           ctx.fillStyle = ctx.strokeStyle = label.color;
12496           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12497           ctx.textBaseline = 'middle';
12498           if(showLabels(node.name, valAcum, node)) {
12499             if(horz) {
12500                 
12501                 //background box
12502                 gridWidth = canvasSize.width - (config.Margin.left + config.Margin.right);
12503                 mtxt = ctx.measureText(node.name + ": " + valAcum);
12504                 boxWidth = mtxt.width+10;
12505                 inset = 10;
12506                 
12507                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12508                         leftPadding = acum - config.labelOffset - boxWidth - inset;
12509                 } else {
12510                         leftPadding = acum + config.labelOffset;
12511                 }
12512                 
12513                                 
12514                                 ctx.textAlign = 'left';
12515                                 ctx.translate(x + inset + leftPadding, y + height/2);
12516                                 boxHeight = label.size+6;
12517                                 boxX = -inset/2;
12518                                 boxY = -boxHeight/2;
12519                                 ctx.fillStyle = "rgba(255,255,255,.8)";
12520                                 
12521                                 cornerRadius = 4;       
12522                                 if(acum + boxWidth + config.labelOffset + inset > gridWidth) {
12523                                         $.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"fill");
12524                                 }
12525                                 //$.roundedRect(ctx,boxX,boxY,boxWidth,boxHeight,cornerRadius,"stroke");
12526                 
12527                                 
12528                                 ctx.fillStyle = label.color;
12529                                 ctx.fillText(node.name + ": " + valAcum, 0, 0);
12530
12531             } else {
12532               
12533                           if(stringArray.length > 8) {
12534                                 ctx.textAlign = 'left';
12535                                 ctx.translate(x + width/2, y + label.size/2 + config.labelOffset);
12536                                 ctx.rotate(45* Math.PI / 180);
12537                                 ctx.fillText(node.name, 0, 0);
12538                           } else {
12539                                 ctx.textAlign = 'center';
12540                                 ctx.fillText(node.name, x + width/2, y + label.size/2 + config.labelOffset);
12541                           }
12542               
12543             }
12544           }
12545           ctx.restore();
12546         }
12547       }
12548     },
12549     'contains': function(node, mpos) {
12550       var pos = node.pos.getc(true), 
12551           width = node.getData('width'),
12552           height = node.getData('height'),
12553           config = node.getData('config'),
12554           algnPos = this.getAlignedPos(pos, width, height),
12555           x = algnPos.x, y = algnPos.y ,
12556           dimArray = node.getData('dimArray'),
12557           len = dimArray.length,
12558           rx = mpos.x - x,
12559           horz = config.orientation == 'horizontal',
12560           fixedDim = (horz? height : width) / len;
12561
12562       //bounding box check
12563       if(horz) {
12564         if(mpos.x < x || mpos.x > x + width
12565             || mpos.y > y + height || mpos.y < y) {
12566             return false;
12567           }
12568       } else {
12569         if(mpos.x < x || mpos.x > x + width
12570             || mpos.y > y || mpos.y < y - height) {
12571             return false;
12572           }
12573       }
12574       //deep check
12575       for(var i=0, l=dimArray.length; i<l; i++) {
12576         var dimi = dimArray[i];
12577                 var url = Url.decode(node.getData('linkArray')[i]);
12578         if(horz) {
12579           var limit = y + fixedDim * i;
12580           if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
12581             return {
12582               'name': node.getData('stringArray')[i],
12583               'color': node.getData('colorArray')[i],
12584               'value': node.getData('valueArray')[i],
12585                           'valuelabel': node.getData('valuelabelArray')[i],
12586                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12587               'link': url,
12588               'label': node.name
12589             };
12590           }
12591         } else {
12592           var limit = x + fixedDim * i;
12593           if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
12594             return {
12595               'name': node.getData('stringArray')[i],
12596               'color': node.getData('colorArray')[i],
12597               'value': node.getData('valueArray')[i],
12598                           'valuelabel': node.getData('valuelabelArray')[i],
12599                           'percentage': ((node.getData('valueArray')[i]/node.getData('groupTotalValue')) * 100).toFixed(1),
12600               'link': url,
12601               'label': node.name
12602             };
12603           }
12604         }
12605       }
12606       return false;
12607     }
12608   }
12609 });
12610
12611 /*
12612   Class: BarChart
12613   
12614   A visualization that displays stacked bar charts.
12615   
12616   Constructor Options:
12617   
12618   See <Options.BarChart>.
12619
12620 */
12621 $jit.BarChart = new Class({
12622   st: null,
12623   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
12624   selected: {},
12625   busy: false,
12626   
12627   initialize: function(opt) {
12628     this.controller = this.config = 
12629       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
12630         Label: { type: 'Native' }
12631       }, opt);
12632     //set functions for showLabels and showAggregates
12633     var showLabels = this.config.showLabels,
12634         typeLabels = $.type(showLabels),
12635         showAggregates = this.config.showAggregates,
12636         typeAggregates = $.type(showAggregates);
12637     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
12638     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
12639     Options.Fx.clearCanvas = false;
12640     this.initializeViz();
12641   },
12642   
12643   initializeViz: function() {
12644     var config = this.config, that = this;
12645     var nodeType = config.type.split(":")[0],
12646         horz = config.orientation == 'horizontal',
12647         nodeLabels = {};
12648     var st = new $jit.ST({
12649       injectInto: config.injectInto,
12650       orientation: horz? 'left' : 'bottom',
12651       background: config.background,
12652       renderBackground: config.renderBackground,
12653       backgroundColor: config.backgroundColor,
12654       colorStop1: config.colorStop1,
12655       colorStop2: config.colorStop2,
12656       levelDistance: 0,
12657       nodeCount: config.nodeCount,
12658       siblingOffset: config.barsOffset,
12659       subtreeOffset: 0,
12660       withLabels: config.Label.type != 'Native',      
12661       useCanvas: config.useCanvas,
12662       Label: {
12663         type: config.Label.type
12664       },
12665       Node: {
12666         overridable: true,
12667         type: 'barchart-' + nodeType,
12668         align: 'left',
12669         width: 1,
12670         height: 1
12671       },
12672       Edge: {
12673         type: 'none'
12674       },
12675       Tips: {
12676         enable: config.Tips.enable,
12677         type: 'Native',
12678         force: true,
12679         onShow: function(tip, node, contains) {
12680           var elem = contains;
12681           config.Tips.onShow(tip, elem, node);
12682                           if(elem.link != 'undefined' && elem.link != '') {
12683                                 document.body.style.cursor = 'pointer';
12684                           }
12685         },
12686                 onHide: function(call) {
12687                         document.body.style.cursor = 'default';
12688
12689         }
12690       },
12691       Events: {
12692         enable: true,
12693         type: 'Native',
12694         onClick: function(node, eventInfo, evt) {
12695           if(!config.Events.enable) return;
12696           var elem = eventInfo.getContains();
12697           config.Events.onClick(elem, eventInfo, evt);
12698         },
12699         onMouseMove: function(node, eventInfo, evt) {
12700           if(!config.hoveredColor) return;
12701           if(node) {
12702             var elem = eventInfo.getContains();
12703             that.select(node.id, elem.name, elem.index);
12704           } else {
12705             that.select(false, false, false);
12706           }
12707         }
12708       },
12709       onCreateLabel: function(domElement, node) {
12710         var labelConf = config.Label,
12711             valueArray = node.getData('valueArray'),
12712             acum = $.reduce(valueArray, function(x, y) { return x + y; }, 0),
12713             grouped = config.type.split(':')[0] == 'grouped',
12714             horz = config.orientation == 'horizontal';
12715         var nlbs = {
12716           wrapper: document.createElement('div'),
12717           aggregate: document.createElement('div'),
12718           label: document.createElement('div')
12719         };
12720         
12721         var wrapper = nlbs.wrapper,
12722             label = nlbs.label,
12723             aggregate = nlbs.aggregate,
12724             wrapperStyle = wrapper.style,
12725             labelStyle = label.style,
12726             aggregateStyle = aggregate.style;
12727         //store node labels
12728         nodeLabels[node.id] = nlbs;
12729         //append labels
12730         wrapper.appendChild(label);
12731         wrapper.appendChild(aggregate);
12732         if(!config.showLabels(node.name, acum, node)) {
12733           labelStyle.display = 'none';
12734         }
12735         if(!config.showAggregates(node.name, acum, node)) {
12736           aggregateStyle.display = 'none';
12737         }
12738         wrapperStyle.position = 'relative';
12739         wrapperStyle.overflow = 'visible';
12740         wrapperStyle.fontSize = labelConf.size + 'px';
12741         wrapperStyle.fontFamily = labelConf.family;
12742         wrapperStyle.color = labelConf.color;
12743         wrapperStyle.textAlign = 'center';
12744         aggregateStyle.position = labelStyle.position = 'absolute';
12745         
12746         domElement.style.width = node.getData('width') + 'px';
12747         domElement.style.height = node.getData('height') + 'px';
12748         aggregateStyle.left = "0px";
12749         labelStyle.left =  config.labelOffset + 'px';
12750         labelStyle.whiteSpace =  "nowrap";
12751                 label.innerHTML = node.name;       
12752         
12753         domElement.appendChild(wrapper);
12754       },
12755       onPlaceLabel: function(domElement, node) {
12756         if(!nodeLabels[node.id]) return;
12757         var labels = nodeLabels[node.id],
12758             wrapperStyle = labels.wrapper.style,
12759             labelStyle = labels.label.style,
12760             aggregateStyle = labels.aggregate.style,
12761             grouped = config.type.split(':')[0] == 'grouped',
12762             horz = config.orientation == 'horizontal',
12763             dimArray = node.getData('dimArray'),
12764             valArray = node.getData('valueArray'),
12765             nodeCount = node.getData('nodeCount'),
12766             valueLength = valArray.length;
12767             valuelabelArray = node.getData('valuelabelArray'),
12768             stringArray = node.getData('stringArray'),
12769             width = (grouped && horz)? Math.max.apply(null, dimArray) : node.getData('width'),
12770             height = (grouped && !horz)? Math.max.apply(null, dimArray) : node.getData('height'),
12771             font = parseInt(wrapperStyle.fontSize, 10),
12772             domStyle = domElement.style,
12773             fixedDim = (horz? height : width) / valueLength;
12774             
12775         
12776         if(dimArray && valArray) {
12777           wrapperStyle.width = aggregateStyle.width = labelStyle.width = domElement.style.width = width + 'px';
12778           
12779           aggregateStyle.width = width  - config.labelOffset + "px";
12780           for(var i=0, l=valArray.length, acum=0; i<l; i++) {
12781             if(dimArray[i] > 0) {
12782               acum+= valArray[i];
12783             }
12784           }
12785           if(config.showLabels(node.name, acum, node)) {
12786             labelStyle.display = '';
12787           } else {
12788             labelStyle.display = 'none';
12789           }
12790           if(config.showAggregates(node.name, acum, node)) {
12791             aggregateStyle.display = '';
12792           } else {
12793             aggregateStyle.display = 'none';
12794           }
12795           if(config.orientation == 'horizontal') {
12796             aggregateStyle.textAlign = 'right';
12797             labelStyle.textAlign = 'left';
12798             labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
12799             aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
12800             domElement.style.height = wrapperStyle.height = height + 'px';
12801           } else {
12802             aggregateStyle.top = (-font - config.labelOffset) + 'px';
12803             labelStyle.top = (config.labelOffset + height) + 'px';
12804             domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
12805             domElement.style.height = wrapperStyle.height = height + 'px';
12806             if(stringArray.length > 8) {
12807                 labels.label.className = "rotatedLabelReverse";
12808                 labelStyle.textAlign = "left";
12809                 labelStyle.top = config.labelOffset + height + width/2 + "px";
12810             }
12811           }
12812           
12813           if(horz) {
12814
12815                         labels.label.innerHTML = labels.label.innerHTML + ": " + acum;
12816                         labels.aggregate.innerHTML = "";
12817
12818           } else {
12819                 
12820                         if(grouped) {
12821                                 maxValue = Math.max.apply(null,dimArray);
12822                                 for (var i=0, l=valArray.length, acum=0, valAcum=0; i<l; i++) {
12823                                         valueLabelDim = 50;
12824                                         valueLabel = document.createElement('div');
12825                                         valueLabel.innerHTML =  valuelabelArray[i];
12826 //                                      valueLabel.class = "rotatedLabel";
12827                                         valueLabel.className = "rotatedLabel";
12828                                         valueLabel.style.position = "absolute";
12829                                                 valueLabel.style.textAlign = "left";
12830                                                 valueLabel.style.verticalAlign = "middle";
12831                                         valueLabel.style.height = valueLabelDim + "px";
12832                                         valueLabel.style.width = valueLabelDim + "px";
12833                                         valueLabel.style.top =  (maxValue - dimArray[i]) - valueLabelDim - config.labelOffset + "px";
12834                                         valueLabel.style.left = (fixedDim * i) + "px";
12835                                         labels.wrapper.appendChild(valueLabel);
12836                                 }
12837                         } else {
12838                                 labels.aggregate.innerHTML = acum;
12839                         }
12840           }
12841         }
12842       }
12843     });
12844
12845     var size = st.canvas.getSize(),
12846         l = config.nodeCount,
12847         margin = config.Margin;
12848         title = config.Title;
12849         subtitle = config.Subtitle,
12850         grouped = config.type.split(':')[0] == 'grouped',
12851         margin = config.Margin,
12852         ticks = config.Ticks,
12853         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
12854         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
12855         horz = config.orientation == 'horizontal',
12856         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
12857         fixedDim = (fixedDim > 40) ? 40 : fixedDim;
12858         whiteSpace = size.width - (marginWidth + (fixedDim * l));
12859         //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
12860         if(!horz && typeof FlashCanvas != "undefined" && size.width < 250)
12861         location.reload();
12862         //if not a grouped chart and is a vertical chart, adjust bar spacing to fix canvas width.
12863         if(!grouped && !horz) {
12864                 st.config.siblingOffset = whiteSpace/(l+1);
12865         }
12866         
12867         
12868         
12869         //Bars offset
12870     if(horz) {
12871       st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);    
12872           if(config.Ticks.enable)       {
12873                 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;
12874           } else {
12875                 st.config.offsetY = (margin.bottom - margin.top - (title.text? title.size+title.offset:0) - (subtitle.text? subtitle.size+subtitle.offset:0))/2;
12876           }
12877     } else {
12878       st.config.offsetY = -size.height/2 + margin.bottom 
12879         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
12880           if(config.Ticks.enable)       {
12881                 st.config.offsetX = ((margin.right-config.Label.size-config.labelOffset) - margin.left)/2;
12882           } else {
12883                 st.config.offsetX = (margin.right - margin.left)/2;
12884           }
12885     }
12886     this.st = st;
12887     this.canvas = this.st.canvas;
12888   },
12889   
12890  
12891   
12892   renderTitle: function() {
12893         var canvas = this.canvas,
12894         size = canvas.getSize(),
12895         config = this.config,
12896         margin = config.Margin,
12897         label = config.Label,
12898         title = config.Title;
12899         ctx = canvas.getCtx();
12900         ctx.fillStyle = title.color;
12901         ctx.textAlign = 'left';
12902         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
12903         if(label.type == 'Native') {
12904                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
12905         }
12906   },  
12907   
12908   renderSubtitle: function() {
12909         var canvas = this.canvas,
12910         size = canvas.getSize(),
12911         config = this.config,
12912         margin = config.Margin,
12913         label = config.Label,
12914         subtitle = config.Subtitle,
12915         nodeCount = config.nodeCount,
12916         horz = config.orientation == 'horizontal' ? true : false,
12917         ctx = canvas.getCtx();
12918         ctx.fillStyle = title.color;
12919         ctx.textAlign = 'left';
12920         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
12921         if(label.type == 'Native') {
12922                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-(!horz && nodeCount > 8 ? 20 : margin.bottom)-subtitle.size);
12923         }
12924   },
12925   
12926   renderScrollNote: function() {
12927         var canvas = this.canvas,
12928         size = canvas.getSize(),
12929         config = this.config,
12930         margin = config.Margin,
12931         label = config.Label,
12932         note = config.ScrollNote;
12933         ctx = canvas.getCtx();
12934         ctx.fillStyle = title.color;
12935         title = config.Title;
12936         ctx.textAlign = 'center';
12937         ctx.font = label.style + ' bold ' +' ' + note.size + 'px ' + label.family;
12938         if(label.type == 'Native') {
12939                 ctx.fillText(note.text, 0, -size.height/2+margin.top+title.size);
12940         }
12941   },  
12942   
12943   renderTicks: function() {
12944
12945         var canvas = this.canvas,
12946         size = canvas.getSize(),
12947         config = this.config,
12948         margin = config.Margin,
12949         ticks = config.Ticks,
12950         title = config.Title,
12951         subtitle = config.Subtitle,
12952         label = config.Label,
12953         shadow = config.shadow;
12954         horz = config.orientation == 'horizontal',
12955         grouped = config.type.split(':')[0] == 'grouped',
12956         ctx = canvas.getCtx();
12957         ctx.strokeStyle = ticks.color;
12958     ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
12959
12960         ctx.textAlign = 'center';
12961         ctx.textBaseline = 'middle';
12962         
12963         idLabel = canvas.id + "-label";
12964         labelDim = 100;
12965         container = document.getElementById(idLabel);
12966                   
12967                   
12968         if(horz) {
12969                 var axis = -(size.width/2)+margin.left + (grouped && config.Label ? config.labelOffset + label.size : 0),
12970                 grid = size.width-(margin.left + margin.right + (grouped && config.Label ? config.labelOffset + label.size : 0)),
12971                 segmentLength = grid/ticks.segments;
12972                 ctx.fillStyle = ticks.color;
12973
12974         // Main horizontal line
12975         var xTop = axis;
12976         var yTop = size.height / 2 - margin.bottom - config.labelOffset - label.size - (subtitle.text ? subtitle.size + subtitle.offset : 0) + (shadow.enable ? shadow.size : 0);
12977         var xLength = size.width - margin.left - margin.right - (grouped && config.Label ? config.labelOffset + label.size : 0);
12978         var yLength = 1;
12979                 ctx.fillRect(xTop, yTop, xLength, yLength);
12980
12981         maxTickValue = config.Ticks.maxValue;
12982         var humanNumber = config.Ticks.humanNumber;
12983         var segments = config.Ticks.segments;
12984         var tempHumanNumber = humanNumber;
12985         var humanNumberPow = 0;
12986         // Tries to find pow of humanNumber if it is less than 1. For 0.001 humanNumberPos will be 3. it means 0.001*10^3 = 1.
12987         // humanNumberPow is required for work with number less than 1.
12988         while (tempHumanNumber % 1 != 0)
12989         {
12990             tempHumanNumber = tempHumanNumber * 10;
12991             humanNumberPow ++;
12992         }
12993
12994         // Tries convert length of line to steps in pixels. 4 steps, 160 length - 40 pixels per step
12995         var pixelsPerStep = xLength / maxTickValue;
12996         var lineHeight = size.height - margin.bottom - margin.top - config.labelOffset - label.size - (title.text ? title.size+title.offset : 0) - (subtitle.text ? subtitle.size + subtitle.offset : 0);
12997         for (var i = 0; i <= segments; i++)
12998         {
12999             var iX = Math.round(xTop + i * pixelsPerStep * humanNumber);
13000             ctx.save();
13001             ctx.translate(iX, yTop + yLength + margin.top);
13002             ctx.rotate(0 * Math.PI / 2 * 3);
13003             ctx.fillStyle = label.color;
13004             // Float numbers fix (0.45 can be displayed as 0.44466666)
13005             var labelText = humanNumber * Math.pow(10, humanNumberPow) * i;
13006             labelText = labelText * Math.pow(10, -humanNumberPow);
13007             if (config.showLabels)
13008             {
13009                 // Filling Text through canvas or html elements
13010                 if (label.type == 'Native')
13011                 {
13012                     ctx.fillText(labelText, 0, 0); // For coords see ctx.translate above
13013                 }
13014                 else
13015                 {
13016                     //html labels on y axis
13017                     labelDiv = document.createElement('div');
13018                     labelDiv.innerHTML = labelText;
13019                     labelDiv.style.top = Math.round(size.height - margin.bottom - config.labelOffset) + "px";
13020                     labelDiv.style.left = Math.round(margin.left - labelDim / 2 + i * pixelsPerStep * humanNumber) + "px";
13021                     labelDiv.style.width = labelDim + "px";
13022                     labelDiv.style.height = labelDim + "px";
13023                     labelDiv.style.textAlign = "center";
13024                     labelDiv.style.verticalAlign = "middle";
13025                     labelDiv.style.position = "absolute";
13026                     labelDiv.style.background = '1px solid red';
13027                     container.appendChild(labelDiv);
13028                 }
13029             }
13030             ctx.restore();
13031             ctx.fillStyle = ticks.color;
13032             // Drawing line
13033             ctx.fillRect(Math.round(axis) + i * pixelsPerStep * humanNumber, -size.height / 2 + margin.top + (title.text ? title.size + title.offset : 0) - (shadow.enable ? shadow.size : 0), 1, lineHeight + (shadow.enable ? shadow.size * 2: 0));
13034         }
13035         } else {
13036         
13037                 var axis = (size.height/2)-(margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
13038                 htmlOrigin = size.height - (margin.bottom+config.labelOffset+label.size+(subtitle.text? subtitle.size+subtitle.offset:0)),
13039                 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)),
13040                 segmentLength = grid/ticks.segments;
13041                 ctx.fillStyle = ticks.color;
13042
13043         // Main horizontal line
13044         var xTop = -size.width / 2 + margin.left + config.labelOffset + label.size - 1;
13045         var yTop = -size.height / 2 + margin.top + (title.text ? title.size + title.offset : 0);
13046         var xLength = 1;
13047         var yLength = size.height - margin.top - margin.bottom - label.size - config.labelOffset - (title.text ? title.size + title.offset : 0) - (subtitle.text ? subtitle.size + subtitle.offset : 0);
13048                 ctx.fillRect(xTop, yTop, xLength, yLength);
13049
13050         maxTickValue = config.Ticks.maxValue;
13051         var humanNumber = config.Ticks.humanNumber;
13052         var segments = config.Ticks.segments;
13053         var tempHumanNumber = humanNumber;
13054         var humanNumberPow = 0;
13055         // Tries to find pow of humanNumber if it is less than 1. For 0.001 humanNumberPos will be 3. it means 0.001*10^3 = 1.
13056         // humanNumberPow is required for work with number less than 1.
13057         while (tempHumanNumber % 1 != 0)
13058         {
13059             tempHumanNumber = tempHumanNumber * 10;
13060             humanNumberPow ++;
13061         }
13062
13063         // Tries convert length of line to steps in pixels. 4 steps, 160 length - 40 pixels per step
13064         var pixelsPerStep = yLength / maxTickValue;
13065         for (var i = 0; i <= segments; i++)
13066         {
13067             var iY = Math.round(yTop + yLength - i * pixelsPerStep * humanNumber);
13068             ctx.save();
13069                         ctx.translate(-size.width / 2 + margin.left, iY);
13070             ctx.rotate(0 * Math.PI / 2 * 3);
13071             ctx.fillStyle = label.color;
13072             // Float numbers fix (0.45 can be displayed as 0.44466666)
13073             var labelText = humanNumber * Math.pow(10, humanNumberPow) * i;
13074             labelText = labelText * Math.pow(10, -humanNumberPow);
13075             if (config.showLabels)
13076             {
13077                 // Filling Text through canvas or html elements
13078                 if (label.type == 'Native')
13079                 {
13080                     ctx.fillText(labelText, 0, 0); // For coords see ctx.translate above
13081                 }
13082                 else
13083                 {
13084                     //html labels on y axis
13085                     labelDiv = document.createElement('div');
13086                     labelDiv.innerHTML = labelText;
13087                     labelDiv.className = "rotatedLabel";
13088 //                                      labelDiv.class = "rotatedLabel";
13089                     labelDiv.style.top = Math.round(htmlOrigin - labelDim / 2 - i * pixelsPerStep * humanNumber) + "px";
13090                     labelDiv.style.left = margin.left + "px";
13091                     labelDiv.style.width = labelDim + "px";
13092                     labelDiv.style.height = labelDim + "px";
13093                     labelDiv.style.textAlign = "center";
13094                     labelDiv.style.verticalAlign = "middle";
13095                     labelDiv.style.position = "absolute";
13096                     container.appendChild(labelDiv);
13097                 }
13098             }
13099             ctx.restore();
13100                         ctx.fillStyle = ticks.color;
13101                         ctx.fillRect(-size.width / 2 + margin.left + config.labelOffset + label.size, iY, size.width - margin.right - margin.left - config.labelOffset - label.size, 1);
13102         }
13103         }
13104         
13105         
13106         
13107
13108   },
13109   
13110   renderBackground: function() {
13111                 var canvas = this.canvas,
13112                 config = this.config,
13113                 backgroundColor = config.backgroundColor,
13114                 size = canvas.getSize(),
13115                 ctx = canvas.getCtx();
13116             ctx.fillStyle = backgroundColor;
13117             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
13118   },
13119   
13120   clear: function() {
13121         var canvas = this.canvas;
13122         var ctx = canvas.getCtx(),
13123         size = canvas.getSize();
13124         ctx.fillStyle = "rgba(255,255,255,0)";
13125         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
13126         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
13127  },
13128   resizeGraph: function(json,width) {
13129         var canvas = this.canvas,
13130         size = canvas.getSize(),
13131         config = this.config,
13132         orgHeight = size.height,
13133         margin = config.Margin,
13134         st = this.st,
13135         grouped = config.type.split(':')[0] == 'grouped',
13136         horz = config.orientation == 'horizontal';
13137         
13138         canvas.resize(width,orgHeight);
13139         if(typeof FlashCanvas == "undefined") {
13140                 canvas.clear();
13141         } else {
13142                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
13143         }
13144         if(horz) {
13145                 st.config.offsetX = size.width/2 - margin.left - (grouped && config.Label ? config.labelOffset + config.Label.size : 0);
13146         }
13147         
13148         this.loadJSON(json);
13149
13150         
13151         },
13152   /*
13153     Method: loadJSON
13154    
13155     Loads JSON data into the visualization. 
13156     
13157     Parameters:
13158     
13159     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>.
13160     
13161     Example:
13162     (start code js)
13163     var barChart = new $jit.BarChart(options);
13164     barChart.loadJSON(json);
13165     (end code)
13166  */  
13167   loadJSON: function(json) {
13168     if(this.busy) return;
13169     this.busy = true;
13170     
13171     var prefix = $.time(), 
13172         ch = [], 
13173         st = this.st,
13174         name = $.splat(json.label), 
13175         color = $.splat(json.color || this.colors),
13176         config = this.config,
13177         gradient = !!config.type.split(":")[1],
13178         renderBackground = config.renderBackground,
13179         animate = config.animate,
13180         ticks = config.Ticks,
13181         title = config.Title,
13182         note = config.ScrollNote,
13183         subtitle = config.Subtitle,
13184         horz = config.orientation == 'horizontal',
13185         that = this,
13186                 colorLength = color.length,
13187                 nameLength = name.length;
13188         groupTotalValue = 0;
13189     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13190         var val = values[i];
13191         var valArray = $.splat(val.values);
13192         groupTotalValue += parseFloat(valArray.sum());
13193     }
13194
13195     for(var i=0, values=json.values, l=values.length; i<l; i++) {
13196       var val = values[i];
13197       var valArray = $.splat(values[i].values);
13198       var valuelabelArray = $.splat(values[i].valuelabels);
13199       var linkArray = $.splat(values[i].links);
13200       var titleArray = $.splat(values[i].titles);
13201       var barTotalValue = valArray.sum();
13202       var acum = 0;
13203       ch.push({
13204         'id': prefix + val.label,
13205         'name': val.label,
13206         
13207         'data': {
13208           'value': valArray,
13209           '$linkArray': linkArray,
13210                   '$gvl': val.gvaluelabel,
13211           '$titleArray': titleArray,
13212           '$valueArray': valArray,
13213           '$valuelabelArray': valuelabelArray,
13214           '$colorArray': color,
13215           '$colorMono': $.splat(color[i % colorLength]),
13216           '$stringArray': name,
13217           '$barTotalValue': barTotalValue,
13218           '$groupTotalValue': groupTotalValue,
13219           '$nodeCount': values.length,
13220           '$gradient': gradient,
13221           '$config': config
13222         },
13223         'children': []
13224       });
13225     }
13226     var root = {
13227       'id': prefix + '$root',
13228       'name': '',
13229       'data': {
13230         '$type': 'none',
13231         '$width': 1,
13232         '$height': 1
13233       },
13234       'children': ch
13235     };
13236     st.loadJSON(root);
13237     
13238     this.normalizeDims();
13239     
13240     if(renderBackground) {
13241                 this.renderBackground();
13242     }
13243         
13244         if(!animate && ticks.enable) {
13245                 this.renderTicks();
13246         }
13247         if(!animate && note.text) {
13248                 this.renderScrollNote();
13249         }
13250         if(!animate && title.text) {
13251                 this.renderTitle();
13252         }
13253         if(!animate && subtitle.text) {
13254                 this.renderSubtitle();
13255         }
13256
13257     st.compute();
13258     st.select(st.root);
13259     if(animate) {
13260       if(horz) {
13261         st.fx.animate({
13262           modes: ['node-property:width:dimArray'],
13263           duration:1500,
13264           onComplete: function() {
13265             that.busy = false;
13266           }
13267         });
13268       } else {
13269         st.fx.animate({
13270           modes: ['node-property:height:dimArray'],
13271           duration:1500,
13272           onComplete: function() {
13273             that.busy = false;
13274           }
13275         });
13276       }
13277     } else {
13278       this.busy = false;
13279     }
13280   },
13281   
13282   /*
13283     Method: updateJSON
13284    
13285     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.
13286     
13287     Parameters:
13288     
13289     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13290     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13291     
13292     Example:
13293     
13294     (start code js)
13295     barChart.updateJSON(json, {
13296       onComplete: function() {
13297         alert('update complete!');
13298       }
13299     });
13300     (end code)
13301  */  
13302   updateJSON: function(json, onComplete) {
13303     if(this.busy) return;
13304     this.busy = true;
13305     
13306     var st = this.st;
13307     var graph = st.graph;
13308     var values = json.values;
13309     var animate = this.config.animate;
13310     var that = this;
13311     var horz = this.config.orientation == 'horizontal';
13312     $.each(values, function(v) {
13313       var n = graph.getByName(v.label);
13314       if(n) {
13315         n.setData('valueArray', $.splat(v.values));
13316         if(json.label) {
13317           n.setData('stringArray', $.splat(json.label));
13318         }
13319       }
13320     });
13321     this.normalizeDims();
13322     st.compute();
13323     st.select(st.root);
13324     if(animate) {
13325       if(horz) {
13326         st.fx.animate({
13327           modes: ['node-property:width:dimArray'],
13328           duration:1500,
13329           onComplete: function() {
13330             that.busy = false;
13331             onComplete && onComplete.onComplete();
13332           }
13333         });
13334       } else {
13335         st.fx.animate({
13336           modes: ['node-property:height:dimArray'],
13337           duration:1500,
13338           onComplete: function() {
13339             that.busy = false;
13340             onComplete && onComplete.onComplete();
13341           }
13342         });
13343       }
13344     }
13345   },
13346   
13347   //adds the little brown bar when hovering the node
13348   select: function(id, name) {
13349
13350     if(!this.config.hoveredColor) return;
13351     var s = this.selected;
13352     if(s.id != id || s.name != name) {
13353       s.id = id;
13354       s.name = name;
13355       s.color = this.config.hoveredColor;
13356       this.st.graph.eachNode(function(n) {
13357         if(id == n.id) {
13358           n.setData('border', s);
13359         } else {
13360           n.setData('border', false);
13361         }
13362       });
13363       this.st.plot();
13364     }
13365   },
13366   
13367   /*
13368     Method: getLegend
13369    
13370     Returns an object containing as keys the legend names and as values hex strings with color values.
13371     
13372     Example:
13373     
13374     (start code js)
13375     var legend = barChart.getLegend();
13376     (end code)
13377   */  
13378   getLegend: function() {
13379     var legend = new Array();
13380     var name = new Array();
13381     var color = new Array();
13382     var n;
13383     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13384       n = adj.nodeTo;
13385     });
13386     var colors = n.getData('colorArray'),
13387         len = colors.length;
13388     $.each(n.getData('stringArray'), function(s, i) {
13389       color[i] = colors[i % len];
13390       name[i] = s;
13391     });
13392         legend['name'] = name;
13393         legend['color'] = color;
13394     return legend;
13395   },
13396   
13397   /*
13398     Method: getMaxValue
13399    
13400     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13401     
13402     Example:
13403     
13404     (start code js)
13405     var ans = barChart.getMaxValue();
13406     (end code)
13407     
13408     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13409     
13410     Example:
13411     
13412     (start code js)
13413     //will return 100 for all BarChart instances,
13414     //displaying all of them with the same scale
13415     $jit.BarChart.implement({
13416       'getMaxValue': function() {
13417         return 100;
13418       }
13419     });
13420     (end code)
13421     
13422   */  
13423   getMaxValue: function() {
13424     var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13425     this.st.graph.eachNode(function(n) {
13426       var valArray = n.getData('valueArray'),
13427           acum = 0;
13428       if(!valArray) return;
13429       if(stacked) {
13430         $.each(valArray, function(v) { 
13431           acum += +v;
13432         });
13433       } else {
13434         acum = Math.max.apply(null, valArray);
13435       }
13436       maxValue = maxValue>acum? maxValue:acum;
13437     });
13438     return maxValue;
13439   },
13440   
13441   setBarType: function(type) {
13442     this.config.type = type;
13443     this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13444   },
13445   
13446   normalizeDims: function() {
13447     //number of elements
13448     var root = this.st.graph.getNode(this.st.root), l=0;
13449     root.eachAdjacency(function() {
13450       l++;
13451     });
13452     var maxValue = this.getMaxValue() || 1,
13453         size = this.st.canvas.getSize(),
13454         config = this.config,
13455         margin = config.Margin,
13456         ticks = config.Ticks,
13457         title = config.Title,
13458         subtitle = config.Subtitle,
13459         grouped = config.type.split(':')[0] == 'grouped',
13460         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13461         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13462         horz = config.orientation == 'horizontal',
13463         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13464         animate = config.animate,
13465         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13466
13467           - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13468         dim1 = horz? 'height':'width',
13469         dim2 = horz? 'width':'height',
13470         basic = config.type.split(':')[0] == 'basic';
13471
13472         // Bug #47147 Correct detection of maxTickValue and step size for asix labels
13473         var iDirection = 10; // We need this var for convert value. 10^2 = 100
13474         var zeroCount = 0; // Pow for iDirection for detection of size of human step.
13475         var iNumber = maxValue; // This var will store two first digits from maxValue. For 1265848 it will be 12. For 0.0453 it will be 45.
13476         // Tries to get two first digits from maxValue
13477         if (iNumber >= 0)
13478         {
13479             // Tries to calculate zeroCount
13480             // if iNumber = 100 we will get zeroCount = 2, iNumber = 0.1
13481             while (iNumber >= 1)
13482             {
13483                 zeroCount ++;
13484                 iNumber = iNumber / 10;
13485             }
13486             iNumber = Math.floor(iNumber * 100); // We need to increase iNumber by 100 to get two first digits. for 0.1 it will be 0.1*100 = 10
13487         }
13488         else
13489         {
13490             iDirection = 0.1; // if iNumber is less than 1 we should change iDirection. 0.1^2 = 0.01
13491             // Tries to calculate zeroCount
13492             // if iNumber = 0.01 we will get zeroCount = 2, iNumber = 1
13493             while (iNumber < 1)
13494             {
13495                 zeroCount ++;
13496                 iNumber = iNumber * 10;
13497             }
13498             iNumber = Math.floor(iNumber * 10); // We need to increase iNumber by 10 to get two first digits. for 1 it will be 1*10 = 10
13499         }
13500         var humanNumber = 0;
13501         var iNumberTemp = iNumber + 1; // We need to add 1 for correct tick size detection. It means that tick always will be great than max value of chart.
13502         // 5 is human step. And we try to detect max value of tick. if maxValue is 1234567 it means iNumber = 12, iNumberTemp = 13 and as result we will get iNumberTemp = 15.
13503         while (iNumberTemp % 5 != 0)
13504         {
13505             iNumberTemp ++;
13506         }
13507         var isFound = false;
13508         zeroCount --; // We need to reduce zeroCount because of increase by 10 or 100 in steps above. It means iNumber = 10, zeroCount = 2 - 1 = 1. 10 * 10^1 = 100. 100 is original value of iNumber
13509         // Tries to find humanNumber. Our step is 5. ticks.segments is number of lines = 4 (for example)
13510         // iNumberTemp = 15 (for example). 15 % 4 = 3. It means that we should add 5 to iNumberTemp till division will equal 0. 20 % 4 = 0. Our result is iNumberTemp = 20
13511         while (isFound == false)
13512         {
13513             if (iNumberTemp % ticks.segments == 0)
13514             {
13515                 humanNumber = iNumberTemp / ticks.segments;
13516                 isFound = true;
13517                 break;
13518             }
13519             iNumberTemp = iNumberTemp + 5;
13520         }
13521         // Getting real values
13522         var maxTickValue = config.Ticks.maxValue = maxTickValue = iNumberTemp * Math.pow(iDirection, zeroCount - 1);
13523         config.Ticks.humanNumber = humanNumber = humanNumber * Math.pow(iDirection, zeroCount - 1);
13524         config.Ticks.segments = Math.floor(maxTickValue / humanNumber);
13525
13526                 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13527
13528                 
13529     this.st.graph.eachNode(function(n) {
13530       var acum = 0, animateValue = [];
13531       $.each(n.getData('valueArray'), function(v) {
13532         acum += +v;
13533         animateValue.push(0);
13534       });
13535       
13536       if(grouped) {
13537         fixedDim = animateValue.length * 40;
13538       }
13539       n.setData(dim1, fixedDim);
13540       
13541       
13542       if(animate) {
13543         n.setData(dim2, acum * height / maxValue, 'end');
13544         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13545           return n * height / maxValue; 
13546         }), 'end');
13547         var dimArray = n.getData('dimArray');
13548         if(!dimArray) {
13549           n.setData('dimArray', animateValue);
13550         }
13551       } else {
13552         
13553
13554                 if(ticks.enable) {
13555                         n.setData(dim2, acum * height / maxTickValue);
13556                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13557                           return n * height / maxTickValue; 
13558                         }));
13559                 } else {
13560                         n.setData(dim2, acum * height / maxValue);
13561                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13562                           return n * height / maxValue; 
13563                         }));
13564                 }
13565       }
13566     });
13567   }
13568 });
13569
13570 //funnel chart options
13571
13572
13573 Options.FunnelChart = {
13574   $extend: true,
13575   
13576   animate: true,
13577   type: 'stacked', //stacked, grouped, : gradient
13578   labelOffset: 3, //label offset
13579   barsOffset: 0, //distance between bars
13580   hoveredColor: '#9fd4ff',
13581   orientation: 'vertical',
13582   showAggregates: true,
13583   showLabels: true,
13584   Tips: {
13585     enable: false,
13586     onShow: $.empty,
13587     onHide: $.empty
13588   },
13589   Events: {
13590     enable: false,
13591     onClick: $.empty
13592   }
13593 };
13594
13595 $jit.ST.Plot.NodeTypes.implement({
13596   'funnelchart-basic' : {
13597     'render' : function(node, canvas) {
13598       var pos = node.pos.getc(true), 
13599           width  = node.getData('width'),
13600           height = node.getData('height'),
13601           algnPos = this.getAlignedPos(pos, width, height),
13602           x = algnPos.x, y = algnPos.y,
13603           dimArray = node.getData('dimArray'),
13604           valueArray = node.getData('valueArray'),
13605           valuelabelArray = node.getData('valuelabelArray'),
13606           linkArray = node.getData('linkArray'),
13607           colorArray = node.getData('colorArray'),
13608           colorLength = colorArray.length,
13609           stringArray = node.getData('stringArray');
13610       var ctx = canvas.getCtx(),
13611           opt = {},
13612           border = node.getData('border'),
13613           gradient = node.getData('gradient'),
13614           config = node.getData('config'),
13615           horz = config.orientation == 'horizontal',
13616           aggregates = config.showAggregates,
13617           showLabels = config.showLabels,
13618           label = config.Label,
13619           size = canvas.getSize(),
13620           labelOffset = config.labelOffset + 10;
13621           minWidth =  width * .25;
13622           ratio = .65;
13623
13624       if (colorArray && dimArray && stringArray) {
13625           var newDimArray = this.positions(dimArray, label.size);
13626         
13627         // horizontal lines
13628         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13629         ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13630
13631         if(label.type == 'Native') {      
13632        if(showLabels(node.name, valAcum, node)) {
13633                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13634                  var stringValue = stringArray[i];
13635                  var valueLabel = String(valuelabelArray[i]);
13636              var mV = ctx.measureText(stringValue);
13637              var mVL = ctx.measureText(valueLabel);
13638            var next_mVL = 0;
13639            var next_mV = 0;
13640            if ((i + 1) < l)
13641            {
13642                next_mV = ctx.measureText(stringArray[i + 1]);
13643                next_mVL = ctx.measureText(String(valuelabelArray[i + 1]));
13644            }
13645            else
13646            {
13647                next_mV = mV;
13648                next_mVL = mVL;
13649            }
13650                  var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13651                          var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13652                          var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13653                  var bottomWidth = minWidth + ((acum) * ratio);
13654            var bottomWidthLabel = minWidth + (newDimArray[i].position * ratio);
13655            var labelOffsetRight = (newDimArray[i].filament) ? (next_mV.width + 25) : 0;
13656            var labelOffsetLeft = (newDimArray[i].filament) ? (next_mVL.width + 25) : 0;
13657 //             ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13658
13659                         //right lines
13660                         ctx.beginPath();
13661                         ctx.moveTo(bottomWidth/2,y - acum); //
13662            ctx.lineTo(bottomWidthLabel / 2 + (labelOffset - 10), y - newDimArray[i].position);  // top right
13663            ctx.lineTo(bottomWidthLabel / 2 + (labelOffset) + labelOffsetRight + mV.width, y - newDimArray[i].position);  // bottom right
13664                         ctx.stroke();
13665                         //left lines
13666                         ctx.beginPath();
13667                         ctx.moveTo(-bottomWidth/2,y - acum); //
13668            ctx.lineTo( - bottomWidthLabel / 2 - (labelOffset - 10), y - newDimArray[i].position);  // top right
13669            ctx.lineTo( - bottomWidthLabel / 2 - (labelOffset) - labelOffsetLeft - mVL.width, y - newDimArray[i].position);  // bottom right
13670                         ctx.stroke();
13671        }
13672         }
13673
13674                 acum += (dimArray[i] || 0);
13675           valAcum += (valueArray[i] || 0);
13676           
13677           
13678                 }
13679                 
13680  
13681   
13682         //funnel segments and labels
13683         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13684           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13685                           var colori = colorArray[i % colorLength];
13686                           if(label.type == 'Native') { 
13687                                   var stringValue = stringArray[i];
13688                           var valueLabel = String(valuelabelArray[i]);
13689                               var mV = ctx.measureText(stringValue);
13690                       var mVL = ctx.measureText(valueLabel);
13691                           } else {
13692                                   var mV = 10;
13693                       var mVL = 10;     
13694                           }
13695             if ((i + 1) < l)
13696             {
13697                 next_mV = ctx.measureText(stringArray[i + 1]);
13698                 next_mVL = ctx.measureText(String(valuelabelArray[i + 1]));
13699             }
13700             else
13701             {
13702                 next_mV = mV;
13703                 next_mVL = mVL;
13704             }
13705                       var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13706                       var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13707             var labelOffsetRight = (newDimArray[i].filament) ? (next_mV.width + 20) : 0;
13708             var labelOffsetLeft = (newDimArray[i].filament) ? (next_mVL.width + 20) : 0;
13709                       
13710           var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13711           var bottomWidth = minWidth + ((acum) * ratio);
13712             var bottomWidthLabel = minWidth + (newDimArray[i].position * ratio);
13713           
13714
13715           if(gradient) {
13716             var linear;
13717               linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13718                         var colorRgb = $.hexToRgb(colori);
13719             var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
13720                 function(v) { return (v * .5) >> 0; });
13721             linear.addColorStop(0, 'rgba('+color+',1)');
13722             linear.addColorStop(0.5,  'rgba('+colorRgb+',1)');
13723             linear.addColorStop(1, 'rgba('+color+',1)');
13724             ctx.fillStyle = linear;
13725           }
13726           
13727                         ctx.beginPath();
13728                         ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13729                         ctx.lineTo(topWidth/2,y - acum - dimArray[i]);  // top right
13730                         ctx.lineTo(bottomWidth/2,y - acum);  // bottom right
13731                         ctx.lineTo(-bottomWidth/2,y - acum);  // bottom left
13732                         ctx.closePath(); 
13733                         ctx.fill();
13734                 
13735           
13736           if(border && border.name == stringArray[i]) {
13737             opt.acum = acum;
13738             opt.dimValue = dimArray[i];
13739           }
13740           
13741           
13742         if(border) {
13743           ctx.save();
13744           ctx.lineWidth = 2;
13745           ctx.strokeStyle = border.color;
13746
13747             //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13748          
13749           ctx.restore();
13750         }
13751         if(label.type == 'Native') {
13752           ctx.save();
13753           ctx.fillStyle = ctx.strokeStyle = label.color;
13754           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13755           ctx.textBaseline = 'middle';
13756
13757                                 acumValueLabel = valAcum;
13758
13759           if(showLabels(node.name, valAcum, node)) {
13760
13761                       
13762               ctx.textAlign = 'left';
13763               ctx.fillText(stringArray[i], (bottomWidthLabel / 2) + labelOffset + labelOffsetRight, y - newDimArray[i].position - label.size / 2);
13764               ctx.textAlign = 'right';
13765               ctx.fillText(valuelabelArray[i], (- bottomWidthLabel / 2) - labelOffset - labelOffsetLeft, y - newDimArray[i].position - label.size / 2);
13766               }
13767           ctx.restore();
13768         }
13769
13770           acum += (dimArray[i] || 0);
13771           valAcum += (valueArray[i] || 0);
13772           
13773         }
13774
13775       }
13776     },
13777     'contains': function(node, mpos) {
13778       var pos = node.pos.getc(true), 
13779           width = node.getData('width'),
13780           height = node.getData('height'),
13781           algnPos = this.getAlignedPos(pos, width, height),
13782           x = algnPos.x, y = algnPos.y,
13783           dimArray = node.getData('dimArray'),
13784           config = node.getData('config'),
13785           st = node.getData('st'),
13786           rx = mpos.x - x,
13787           horz = config.orientation == 'horizontal',
13788            minWidth =  width * .25;
13789           ratio = .65,
13790           canvas = node.getData('canvas'),
13791           size = canvas.getSize(),
13792           offsetY = st.config.offsetY;
13793       //bounding box check
13794
13795         if(mpos.y > y || mpos.y < y - height) {
13796             return false;
13797           }
13798           
13799          var newY = Math.abs(mpos.y + offsetY);
13800         var bound = minWidth + (newY * ratio);
13801         var boundLeft = -bound/2;
13802         var boundRight = bound/2;
13803          if(mpos.x < boundLeft || mpos.x > boundRight ) {
13804             return false;
13805           }
13806
13807       
13808       //deep check
13809       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13810         var dimi = dimArray[i];
13811
13812           
13813           
13814                 var url = Url.decode(node.getData('linkArray')[i]);
13815           acum -= dimi;  
13816           var intersec = acum;
13817           if(mpos.y >= intersec) {
13818             return {
13819               'name': node.getData('stringArray')[i],
13820               'color': node.getData('colorArray')[i],
13821               'value': node.getData('valueArray')[i],
13822               'percentage': node.getData('percentageArray')[i],
13823                           'valuelabel': node.getData('valuelabelArray')[i],
13824               'link': url,
13825               'label': node.name
13826             };
13827           }
13828         
13829       }
13830       return false;
13831     }
13832   }
13833 });
13834
13835 /*
13836   Class: FunnelChart
13837   
13838   A visualization that displays funnel charts.
13839   
13840   Constructor Options:
13841   
13842   See <Options.FunnelChart>.
13843
13844 */
13845 $jit.FunnelChart = new Class({
13846   st: null,
13847   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13848   selected: {},
13849   busy: false,
13850   
13851   initialize: function(opt) {
13852     this.controller = this.config = 
13853       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13854         Label: { type: 'Native' }
13855       }, opt);
13856     //set functions for showLabels and showAggregates
13857     var showLabels = this.config.showLabels,
13858         typeLabels = $.type(showLabels),
13859         showAggregates = this.config.showAggregates,
13860         typeAggregates = $.type(showAggregates);
13861     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13862     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13863     Options.Fx.clearCanvas = false;
13864     this.initializeViz();
13865   },
13866   
13867   initializeViz: function() {
13868     var config = this.config, that = this;
13869     var nodeType = config.type.split(":")[0],
13870         horz = config.orientation == 'horizontal',
13871         nodeLabels = {};
13872     var st = new $jit.ST({
13873       injectInto: config.injectInto,
13874       orientation: horz? 'left' : 'bottom',
13875       levelDistance: 0,
13876       background: config.background,
13877       renderBackground: config.renderBackground,
13878       backgroundColor: config.backgroundColor,
13879       colorStop1: config.colorStop1,
13880       colorStop2: config.colorStop2,
13881       siblingOffset: config.segmentOffset,
13882       subtreeOffset: 0,
13883       withLabels: config.Label.type != 'Native',      
13884       useCanvas: config.useCanvas,
13885       Label: {
13886         type: config.Label.type
13887       },
13888       Node: {
13889         overridable: true,
13890         type: 'funnelchart-' + nodeType,
13891         align: 'left',
13892         width: 1,
13893         height: 1
13894       },
13895       Edge: {
13896         type: 'none'
13897       },
13898       Tips: {
13899         enable: config.Tips.enable,
13900         type: 'Native',
13901         force: true,
13902         onShow: function(tip, node, contains) {
13903           var elem = contains;
13904           config.Tips.onShow(tip, elem, node);
13905                           if(elem.link != 'undefined' && elem.link != '') {
13906                                 document.body.style.cursor = 'pointer';
13907                           }
13908         },
13909                 onHide: function(call) {
13910                         document.body.style.cursor = 'default';
13911
13912         }
13913       },
13914       Events: {
13915         enable: true,
13916         type: 'Native',
13917         onClick: function(node, eventInfo, evt) {
13918           if(!config.Events.enable) return;
13919           var elem = eventInfo.getContains();
13920           config.Events.onClick(elem, eventInfo, evt);
13921         },
13922         onMouseMove: function(node, eventInfo, evt) {
13923           if(!config.hoveredColor) return;
13924           if(node) {
13925             var elem = eventInfo.getContains();
13926             that.select(node.id, elem.name, elem.index);
13927           } else {
13928             that.select(false, false, false);
13929           }
13930         }
13931       },
13932       onCreateLabel: function(domElement, node) {
13933         var labelConf = config.Label,
13934             valueArray = node.getData('valueArray'),
13935             idArray = node.getData('idArray'),
13936             valuelabelArray = node.getData('valuelabelArray'),
13937             stringArray = node.getData('stringArray');
13938             size = st.canvas.getSize()
13939             prefix = $.time();
13940                 
13941                 for(var i=0, l=valueArray.length; i<l; i++) {
13942         var nlbs = {
13943           wrapper: document.createElement('div'),
13944           valueLabel: document.createElement('div'),
13945           label: document.createElement('div')
13946         };
13947         var wrapper = nlbs.wrapper,
13948             label = nlbs.label,
13949             valueLabel = nlbs.valueLabel,
13950             wrapperStyle = wrapper.style,
13951             labelStyle = label.style,
13952             valueLabelStyle = valueLabel.style;
13953         //store node labels
13954         nodeLabels[idArray[i]] = nlbs;
13955         //append labels
13956         wrapper.appendChild(label);
13957         wrapper.appendChild(valueLabel);
13958
13959         wrapperStyle.position = 'relative';
13960         wrapperStyle.overflow = 'visible';
13961         wrapperStyle.fontSize = labelConf.size + 'px';
13962         wrapperStyle.fontFamily = labelConf.family;
13963         wrapperStyle.color = labelConf.color;
13964         wrapperStyle.textAlign = 'center';
13965         wrapperStyle.width = size.width + 'px';
13966         valueLabelStyle.position = labelStyle.position = 'absolute';
13967         valueLabelStyle.left = labelStyle.left =  '0px';
13968                 valueLabelStyle.width = (size.width/3) + 'px';
13969                 valueLabelStyle.textAlign = 'right';
13970         label.innerHTML = stringArray[i];
13971         valueLabel.innerHTML = valuelabelArray[i];
13972         domElement.id = prefix+'funnel';
13973         domElement.style.width = size.width + 'px';
13974         
13975                 domElement.appendChild(wrapper);
13976                 }
13977
13978       },
13979       onPlaceLabel: function(domElement, node) {
13980
13981             var dimArray = node.getData('dimArray'),
13982             idArray = node.getData('idArray'),
13983             valueArray = node.getData('valueArray'),
13984             valuelabelArray = node.getData('valuelabelArray'),
13985             stringArray = node.getData('stringArray');
13986             size = st.canvas.getSize(),
13987             pos = node.pos.getc(true),
13988              domElement.style.left = "0px",
13989              domElement.style.top = "0px",
13990              minWidth = node.getData('width') * .25,
13991              ratio = .65,
13992              pos = node.pos.getc(true),
13993              labelConf = config.Label;
13994              
13995              
13996                 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
13997
13998         var labels = nodeLabels[idArray[i]],
13999             wrapperStyle = labels.wrapper.style,
14000             labelStyle = labels.label.style,
14001             valueLabelStyle = labels.valueLabel.style;
14002                 var bottomWidth = minWidth + (acum * ratio); 
14003                 
14004             font = parseInt(wrapperStyle.fontSize, 10),
14005             domStyle = domElement.style;
14006            
14007                 
14008        
14009                         wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
14010             valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
14011             labelStyle.left =  (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
14012
14013                         acum += (dimArray[i] || 0);
14014
14015                 }
14016
14017       }
14018
14019     });
14020
14021     var size = st.canvas.getSize(),
14022         margin = config.Margin;
14023         title = config.Title;
14024         subtitle = config.Subtitle;
14025         //y offset
14026
14027       st.config.offsetY = -size.height/2 + margin.bottom 
14028         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
14029
14030                 st.config.offsetX = (margin.right - margin.left)/2;
14031           
14032     
14033     this.st = st;
14034     this.canvas = this.st.canvas;
14035   },
14036   
14037   renderTitle: function() {
14038         var canvas = this.canvas,
14039         size = canvas.getSize(),
14040         config = this.config,
14041         margin = config.Margin,
14042         label = config.Label,
14043         title = config.Title;
14044         ctx = canvas.getCtx();
14045         ctx.fillStyle = title.color;
14046         ctx.textAlign = 'left';
14047         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
14048         if(label.type == 'Native') {
14049                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
14050         }
14051   },  
14052   
14053   renderSubtitle: function() {
14054         var canvas = this.canvas,
14055         size = canvas.getSize(),
14056         config = this.config,
14057         margin = config.Margin,
14058         label = config.Label,
14059         subtitle = config.Subtitle;
14060         ctx = canvas.getCtx();
14061         ctx.fillStyle = title.color;
14062         ctx.textAlign = 'left';
14063         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
14064         if(label.type == 'Native') {
14065                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
14066         }
14067   },
14068   
14069   
14070   renderDropShadow: function() {
14071         var canvas = this.canvas,
14072         size = canvas.getSize(),
14073         config = this.config,
14074         margin = config.Margin,
14075         horz = config.orientation == 'horizontal',
14076         label = config.Label,
14077         title = config.Title,
14078         shadowThickness = 4,
14079         subtitle = config.Subtitle,
14080         ctx = canvas.getCtx(),
14081         minwidth = (size.width/8) * .25,
14082         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14083         topMargin = (title.text? title.size + title.offset : 0)  + margin.top,
14084     height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14085           - (config.showLabels && (config.Label.size + config.labelOffset)),
14086     ratio = .65,
14087         topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
14088         topY = (-size.height/2) + topMargin - shadowThickness;
14089         bottomY = (-size.height/2) + topMargin + height + shadowThickness;
14090         bottomWidth = minwidth + shadowThickness;
14091         ctx.beginPath();
14092         ctx.fillStyle = "rgba(0,0,0,.2)";
14093         ctx.moveTo(0,topY);
14094         ctx.lineTo(-topWidth/2,topY); //top left
14095         ctx.lineTo(-bottomWidth/2,bottomY);  // bottom left
14096         ctx.lineTo(bottomWidth/2,bottomY);  // bottom right
14097         ctx.lineTo(topWidth/2,topY);  // top right
14098         ctx.closePath(); 
14099         ctx.fill();
14100                         
14101                         
14102   },
14103
14104    renderBackground: function() {
14105                 var canvas = this.canvas,
14106                 config = this.config,
14107                 backgroundColor = config.backgroundColor,
14108                 size = canvas.getSize(),
14109                 ctx = canvas.getCtx();
14110                 //ctx.globalCompositeOperation = "destination-over";
14111             ctx.fillStyle = backgroundColor;
14112             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
14113   },
14114   clear: function() {
14115         var canvas = this.canvas;
14116         var ctx = canvas.getCtx(),
14117         size = canvas.getSize();
14118         ctx.fillStyle = "rgba(255,255,255,0)";
14119         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
14120         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
14121   },
14122    resizeGraph: function(json,width) {
14123         var canvas = this.canvas,
14124         size = canvas.getSize(),
14125         config = this.config,
14126         orgHeight = size.height;
14127         
14128
14129         canvas.resize(width,orgHeight);
14130
14131         if(typeof FlashCanvas == "undefined") {
14132                 canvas.clear();
14133         } else {
14134                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
14135         }
14136         this.loadJSON(json);
14137
14138         },
14139         
14140   loadJSON: function(json) {
14141     if(this.busy) return;
14142     this.busy = true;
14143     var prefix = $.time(), 
14144         ch = [], 
14145         st = this.st,
14146         name = $.splat(json.label), 
14147         color = $.splat(json.color || this.colors),
14148         config = this.config,
14149         canvas = this.canvas,
14150         gradient = !!config.type.split(":")[1],
14151         animate = config.animate,
14152         title = config.Title,
14153         subtitle = config.Subtitle,
14154         renderBackground = config.renderBackground,
14155         horz = config.orientation == 'horizontal',
14156         that = this,
14157                 colorLength = color.length,
14158                 nameLength = name.length,
14159                 totalValue = 0;
14160     for(var i=0, values=json.values, l=values.length; i<l; i++) {
14161         var val = values[i];
14162         var valArray = $.splat(val.values);
14163         totalValue += parseFloat(valArray.sum());
14164     }
14165     
14166     
14167     var nameArray = new Array();
14168     var idArray = new Array();
14169     var valArray = new Array();
14170     var valuelabelArray = new Array();
14171     var linkArray = new Array();
14172     var titleArray = new Array();
14173     var percentageArray = new Array();
14174     
14175     for(var i=0, values=json.values, l=values.length; i<l; i++) {
14176       var val = values[i];
14177       nameArray[i] = $.splat(val.label);
14178       idArray[i] = $.splat(prefix + val.label);
14179       valArray[i] = $.splat(val.values);
14180       valuelabelArray[i] = $.splat(val.valuelabels);
14181       linkArray[i] = $.splat(val.links);
14182       titleArray[i] = $.splat(val.titles);
14183       percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
14184       var acum = 0;
14185     }
14186     
14187
14188     nameArray.reverse();
14189     valArray.reverse();
14190     valuelabelArray.reverse();
14191     linkArray.reverse();
14192     titleArray.reverse();
14193     percentageArray.reverse();
14194     
14195       ch.push({
14196         'id': prefix + val.label,
14197         'name': val.label,
14198         
14199         'data': {
14200           'value': valArray,
14201           '$idArray': idArray,
14202           '$linkArray': linkArray,
14203           '$titleArray': titleArray,
14204           '$valueArray': valArray,
14205           '$valuelabelArray': valuelabelArray,
14206           '$colorArray': color,
14207           '$colorMono': $.splat(color[i % colorLength]),
14208           '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
14209           '$gradient': gradient,
14210           '$config': config,
14211           '$percentageArray' : percentageArray,
14212           '$canvas': canvas,
14213           '$st': st
14214         },
14215         'children': []
14216       });
14217     
14218     var root = {
14219       'id': prefix + '$root',
14220       'name': '',
14221       'data': {
14222         '$type': 'none',
14223         '$width': 1,
14224         '$height': 1
14225       },
14226       'children': ch
14227     };
14228     st.loadJSON(root);
14229     
14230     this.normalizeDims();
14231         
14232         if(renderBackground) {
14233                 this.renderBackground();        
14234         }
14235         if(!animate && title.text) {
14236                 this.renderTitle();
14237         }
14238         if(!animate && subtitle.text) {
14239                 this.renderSubtitle();
14240         }
14241         if(typeof FlashCanvas == "undefined") {
14242                 this.renderDropShadow();
14243         }
14244     st.compute();
14245     st.select(st.root);
14246     if(animate) {
14247       if(horz) {
14248         st.fx.animate({
14249           modes: ['node-property:width:dimArray'],
14250           duration:1500,
14251           onComplete: function() {
14252             that.busy = false;
14253           }
14254         });
14255       } else {
14256         st.fx.animate({
14257           modes: ['node-property:height:dimArray'],
14258           duration:1500,
14259           onComplete: function() {
14260             that.busy = false;
14261           }
14262         });
14263       }
14264     } else {
14265       this.busy = false;
14266     }
14267   },
14268   
14269   /*
14270     Method: updateJSON
14271    
14272     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.
14273     
14274     Parameters:
14275     
14276     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
14277     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
14278     
14279     Example:
14280     
14281     (start code js)
14282     barChart.updateJSON(json, {
14283       onComplete: function() {
14284         alert('update complete!');
14285       }
14286     });
14287     (end code)
14288  */  
14289   updateJSON: function(json, onComplete) {
14290     if(this.busy) return;
14291     this.busy = true;
14292     
14293     var st = this.st;
14294     var graph = st.graph;
14295     var values = json.values;
14296     var animate = this.config.animate;
14297     var that = this;
14298     var horz = this.config.orientation == 'horizontal';
14299     $.each(values, function(v) {
14300       var n = graph.getByName(v.label);
14301       if(n) {
14302         n.setData('valueArray', $.splat(v.values));
14303         if(json.label) {
14304           n.setData('stringArray', $.splat(json.label));
14305         }
14306       }
14307     });
14308     this.normalizeDims();
14309     st.compute();
14310     st.select(st.root);
14311     if(animate) {
14312       if(horz) {
14313         st.fx.animate({
14314           modes: ['node-property:width:dimArray'],
14315           duration:1500,
14316           onComplete: function() {
14317             that.busy = false;
14318             onComplete && onComplete.onComplete();
14319           }
14320         });
14321       } else {
14322         st.fx.animate({
14323           modes: ['node-property:height:dimArray'],
14324           duration:1500,
14325           onComplete: function() {
14326             that.busy = false;
14327             onComplete && onComplete.onComplete();
14328           }
14329         });
14330       }
14331     }
14332   },
14333   
14334   //adds the little brown bar when hovering the node
14335   select: function(id, name) {
14336
14337     if(!this.config.hoveredColor) return;
14338     var s = this.selected;
14339     if(s.id != id || s.name != name) {
14340       s.id = id;
14341       s.name = name;
14342       s.color = this.config.hoveredColor;
14343       this.st.graph.eachNode(function(n) {
14344         if(id == n.id) {
14345           n.setData('border', s);
14346         } else {
14347           n.setData('border', false);
14348         }
14349       });
14350       this.st.plot();
14351     }
14352   },
14353   
14354   /*
14355     Method: getLegend
14356    
14357     Returns an object containing as keys the legend names and as values hex strings with color values.
14358     
14359     Example:
14360     
14361     (start code js)
14362     var legend = barChart.getLegend();
14363     (end code)
14364   */  
14365   getLegend: function() {
14366     var legend = new Array();
14367     var name = new Array();
14368     var color = new Array();
14369     var n;
14370     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14371       n = adj.nodeTo;
14372     });
14373     var colors = n.getData('colorArray'),
14374         len = colors.length;
14375     $.each(n.getData('stringArray'), function(s, i) {
14376       color[i] = colors[i % len];
14377       name[i] = s;
14378     });
14379         legend['name'] = name;
14380         legend['color'] = color;
14381     return legend;
14382   },
14383   
14384   /*
14385     Method: getMaxValue
14386    
14387     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14388     
14389     Example:
14390     
14391     (start code js)
14392     var ans = barChart.getMaxValue();
14393     (end code)
14394     
14395     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14396     
14397     Example:
14398     
14399     (start code js)
14400     //will return 100 for all BarChart instances,
14401     //displaying all of them with the same scale
14402     $jit.BarChart.implement({
14403       'getMaxValue': function() {
14404         return 100;
14405       }
14406     });
14407     (end code)
14408     
14409   */  
14410   getMaxValue: function() {
14411     var maxValue = 0, stacked = true;
14412     this.st.graph.eachNode(function(n) {
14413       var valArray = n.getData('valueArray'),
14414           acum = 0;
14415       if(!valArray) return;
14416       if(stacked) {
14417         $.each(valArray, function(v) { 
14418           acum += +v;
14419         });
14420       } else {
14421         acum = Math.max.apply(null, valArray);
14422       }
14423       maxValue = maxValue>acum? maxValue:acum;
14424     });
14425     return maxValue;
14426   },
14427   
14428   setBarType: function(type) {
14429     this.config.type = type;
14430     this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14431   },
14432   
14433   normalizeDims: function() {
14434     //number of elements
14435     var root = this.st.graph.getNode(this.st.root), l=0;
14436     root.eachAdjacency(function() {
14437       l++;
14438     });
14439     var maxValue = this.getMaxValue() || 1,
14440         size = this.st.canvas.getSize(),
14441         config = this.config,
14442         margin = config.Margin,
14443         title = config.Title,
14444         subtitle = config.Subtitle,
14445         marginWidth = margin.left + margin.right,
14446         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14447         horz = config.orientation == 'horizontal',
14448         animate = config.animate,
14449         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14450
14451           - (config.showLabels && (config.Label.size + config.labelOffset)),
14452         dim1 = horz? 'height':'width',
14453         dim2 = horz? 'width':'height';
14454         
14455
14456         minWidth = size.width/8;
14457         
14458
14459
14460     this.st.graph.eachNode(function(n) {
14461       var acum = 0, animateValue = [];
14462       $.each(n.getData('valueArray'), function(v) {
14463         acum += +v;
14464         animateValue.push(0);
14465       });
14466       n.setData(dim1, minWidth);
14467             
14468       if(animate) {
14469         n.setData(dim2, acum * height / maxValue, 'end');
14470         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14471           return n * height / maxValue; 
14472         }), 'end');
14473         var dimArray = n.getData('dimArray');
14474         if(!dimArray) {
14475           n.setData('dimArray', animateValue);
14476         }
14477       } else {
14478                         n.setData(dim2, acum * height / maxValue);
14479                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14480                           return n * height / maxValue; 
14481                         }));
14482       }
14483
14484     });
14485   }
14486 });
14487
14488
14489
14490 /*
14491  * File: Options.PieChart.js
14492  *
14493 */
14494 /*
14495   Object: Options.PieChart
14496   
14497   <PieChart> options. 
14498   Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14499   
14500   Syntax:
14501   
14502   (start code js)
14503
14504   Options.PieChart = {
14505     animate: true,
14506     offset: 25,
14507     sliceOffset:0,
14508     labelOffset: 3,
14509     type: 'stacked',
14510     hoveredColor: '#9fd4ff',
14511     showLabels: true,
14512     resizeLabels: false,
14513     updateHeights: false
14514   };  
14515
14516   (end code)
14517   
14518   Example:
14519   
14520   (start code js)
14521
14522   var pie = new $jit.PieChart({
14523     animate: true,
14524     sliceOffset: 5,
14525     type: 'stacked:gradient'
14526   });  
14527
14528   (end code)
14529   
14530   Parameters:
14531   
14532   animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14533   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14534   sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14535   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14536   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14537   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14538   showLabels - (boolean) Default's *true*. Display the name of the slots.
14539   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.
14540   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.
14541
14542 */
14543 Options.PieChart = {
14544   $extend: true,
14545
14546   animate: true,
14547   offset: 25, // page offset
14548   sliceOffset:0,
14549   labelOffset: 3, // label offset
14550   type: 'stacked', // gradient
14551   labelType: 'name',
14552   hoveredColor: '#9fd4ff',
14553   Events: {
14554     enable: false,
14555     onClick: $.empty
14556   },
14557   Tips: {
14558     enable: false,
14559     onShow: $.empty,
14560     onHide: $.empty
14561   },
14562   showLabels: true,
14563   resizeLabels: false,
14564   
14565   //only valid for mono-valued datasets
14566   updateHeights: false
14567 };
14568
14569 /*
14570  * Class: Layouts.Radial
14571  * 
14572  * Implements a Radial Layout.
14573  * 
14574  * Implemented By:
14575  * 
14576  * <RGraph>, <Hypertree>
14577  * 
14578  */
14579 Layouts.Radial = new Class({
14580
14581   /*
14582    * Method: compute
14583    * 
14584    * Computes nodes' positions.
14585    * 
14586    * Parameters:
14587    * 
14588    * property - _optional_ A <Graph.Node> position property to store the new
14589    * positions. Possible values are 'pos', 'end' or 'start'.
14590    * 
14591    */
14592   compute : function(property) {
14593     var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14594     NodeDim.compute(this.graph, prop, this.config);
14595     this.graph.computeLevels(this.root, 0, "ignore");
14596     var lengthFunc = this.createLevelDistanceFunc(); 
14597     this.computeAngularWidths(prop);
14598     this.computePositions(prop, lengthFunc);
14599   },
14600
14601   /*
14602    * computePositions
14603    * 
14604    * Performs the main algorithm for computing node positions.
14605    */
14606   computePositions : function(property, getLength) {
14607     var propArray = property;
14608     var graph = this.graph;
14609     var root = graph.getNode(this.root);
14610     var parent = this.parent;
14611     var config = this.config;
14612
14613     for ( var i=0, l=propArray.length; i < l; i++) {
14614       var pi = propArray[i];
14615       root.setPos($P(0, 0), pi);
14616       root.setData('span', Math.PI * 2, pi);
14617     }
14618
14619     root.angleSpan = {
14620       begin : 0,
14621       end : 2 * Math.PI
14622     };
14623
14624     graph.eachBFS(this.root, function(elem) {
14625       var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14626       var angleInit = elem.angleSpan.begin;
14627       var len = getLength(elem);
14628       //Calculate the sum of all angular widths
14629       var totalAngularWidths = 0, subnodes = [], maxDim = {};
14630       elem.eachSubnode(function(sib) {
14631         totalAngularWidths += sib._treeAngularWidth;
14632         //get max dim
14633         for ( var i=0, l=propArray.length; i < l; i++) {
14634           var pi = propArray[i], dim = sib.getData('dim', pi);
14635           maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14636         }
14637         subnodes.push(sib);
14638       }, "ignore");
14639       //Maintain children order
14640       //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14641       if (parent && parent.id == elem.id && subnodes.length > 0
14642           && subnodes[0].dist) {
14643         subnodes.sort(function(a, b) {
14644           return (a.dist >= b.dist) - (a.dist <= b.dist);
14645         });
14646       }
14647       //Calculate nodes positions.
14648       for (var k = 0, ls=subnodes.length; k < ls; k++) {
14649         var child = subnodes[k];
14650         if (!child._flag) {
14651           var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14652           var theta = angleInit + angleProportion / 2;
14653
14654           for ( var i=0, l=propArray.length; i < l; i++) {
14655             var pi = propArray[i];
14656             child.setPos($P(theta, len), pi);
14657             child.setData('span', angleProportion, pi);
14658             child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14659           }
14660
14661           child.angleSpan = {
14662             begin : angleInit,
14663             end : angleInit + angleProportion
14664           };
14665           angleInit += angleProportion;
14666         }
14667       }
14668     }, "ignore");
14669   },
14670
14671   /*
14672    * Method: setAngularWidthForNodes
14673    * 
14674    * Sets nodes angular widths.
14675    */
14676   setAngularWidthForNodes : function(prop) {
14677     this.graph.eachBFS(this.root, function(elem, i) {
14678       var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14679       elem._angularWidth = diamValue / i;
14680     }, "ignore");
14681   },
14682
14683   /*
14684    * Method: setSubtreesAngularWidth
14685    * 
14686    * Sets subtrees angular widths.
14687    */
14688   setSubtreesAngularWidth : function() {
14689     var that = this;
14690     this.graph.eachNode(function(elem) {
14691       that.setSubtreeAngularWidth(elem);
14692     }, "ignore");
14693   },
14694
14695   /*
14696    * Method: setSubtreeAngularWidth
14697    * 
14698    * Sets the angular width for a subtree.
14699    */
14700   setSubtreeAngularWidth : function(elem) {
14701     var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14702     elem.eachSubnode(function(child) {
14703       that.setSubtreeAngularWidth(child);
14704       sumAW += child._treeAngularWidth;
14705     }, "ignore");
14706     elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14707   },
14708
14709   /*
14710    * Method: computeAngularWidths
14711    * 
14712    * Computes nodes and subtrees angular widths.
14713    */
14714   computeAngularWidths : function(prop) {
14715     this.setAngularWidthForNodes(prop);
14716     this.setSubtreesAngularWidth();
14717   }
14718
14719 });
14720
14721
14722 /*
14723  * File: Sunburst.js
14724  */
14725
14726 /*
14727    Class: Sunburst
14728       
14729    A radial space filling tree visualization.
14730    
14731    Inspired by:
14732  
14733    Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14734    
14735    Note:
14736    
14737    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.
14738    
14739   Implements:
14740   
14741   All <Loader> methods
14742   
14743    Constructor Options:
14744    
14745    Inherits options from
14746    
14747    - <Options.Canvas>
14748    - <Options.Controller>
14749    - <Options.Node>
14750    - <Options.Edge>
14751    - <Options.Label>
14752    - <Options.Events>
14753    - <Options.Tips>
14754    - <Options.NodeStyles>
14755    - <Options.Navigation>
14756    
14757    Additionally, there are other parameters and some default values changed
14758    
14759    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14760    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
14761    Node.type - Described in <Options.Node>. Default's to *multipie*.
14762    Node.height - Described in <Options.Node>. Default's *0*.
14763    Edge.type - Described in <Options.Edge>. Default's *none*.
14764    Label.textAlign - Described in <Options.Label>. Default's *start*.
14765    Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14766      
14767    Instance Properties:
14768
14769    canvas - Access a <Canvas> instance.
14770    graph - Access a <Graph> instance.
14771    op - Access a <Sunburst.Op> instance.
14772    fx - Access a <Sunburst.Plot> instance.
14773    labels - Access a <Sunburst.Label> interface implementation.   
14774
14775 */
14776
14777 $jit.Sunburst = new Class({
14778
14779   Implements: [ Loader, Extras, Layouts.Radial ],
14780
14781   initialize: function(controller) {
14782     var $Sunburst = $jit.Sunburst;
14783
14784     var config = {
14785       interpolation: 'linear',
14786       levelDistance: 100,
14787       Node: {
14788         'type': 'multipie',
14789         'height':0
14790       },
14791       Edge: {
14792         'type': 'none'
14793       },
14794       Label: {
14795         textAlign: 'start',
14796         textBaseline: 'middle'
14797       }
14798     };
14799
14800     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14801         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14802
14803     var canvasConfig = this.config;
14804     if(canvasConfig.useCanvas) {
14805       this.canvas = canvasConfig.useCanvas;
14806       this.config.labelContainer = this.canvas.id + '-label';
14807     } else {
14808       if(canvasConfig.background) {
14809         canvasConfig.background = $.merge({
14810           type: 'Fade',
14811           colorStop1: this.config.colorStop1,
14812           colorStop2: this.config.colorStop2
14813         }, canvasConfig.background);
14814       }
14815       this.canvas = new Canvas(this, canvasConfig);
14816       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14817     }
14818
14819     this.graphOptions = {
14820       'complex': false,
14821       'Node': {
14822         'selected': false,
14823         'exist': true,
14824         'drawn': true
14825       }
14826     };
14827     this.graph = new Graph(this.graphOptions, this.config.Node,
14828         this.config.Edge);
14829     this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14830     this.fx = new $Sunburst.Plot(this, $Sunburst);
14831     this.op = new $Sunburst.Op(this);
14832     this.json = null;
14833     this.root = null;
14834     this.rotated = null;
14835     this.busy = false;
14836     // initialize extras
14837     this.initializeExtras();
14838   },
14839
14840   /* 
14841   
14842     createLevelDistanceFunc 
14843   
14844     Returns the levelDistance function used for calculating a node distance 
14845     to its origin. This function returns a function that is computed 
14846     per level and not per node, such that all nodes with the same depth will have the 
14847     same distance to the origin. The resulting function gets the 
14848     parent node as parameter and returns a float.
14849
14850    */
14851   createLevelDistanceFunc: function() {
14852     var ld = this.config.levelDistance;
14853     return function(elem) {
14854       return (elem._depth + 1) * ld;
14855     };
14856   },
14857
14858   /* 
14859      Method: refresh 
14860      
14861      Computes positions and plots the tree.
14862
14863    */
14864   refresh: function() {
14865     this.compute();
14866     this.plot();
14867   },
14868
14869   /*
14870    reposition
14871   
14872    An alias for computing new positions to _endPos_
14873
14874    See also:
14875
14876    <Sunburst.compute>
14877    
14878   */
14879   reposition: function() {
14880     this.compute('end');
14881   },
14882
14883   /*
14884   Method: rotate
14885   
14886   Rotates the graph so that the selected node is horizontal on the right.
14887
14888   Parameters:
14889   
14890   node - (object) A <Graph.Node>.
14891   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14892   opt - (object) Configuration options merged with this visualization configuration options.
14893   
14894   See also:
14895
14896   <Sunburst.rotateAngle>
14897   
14898   */
14899   rotate: function(node, method, opt) {
14900     var theta = node.getPos(opt.property || 'current').getp(true).theta;
14901     this.rotated = node;
14902     this.rotateAngle(-theta, method, opt);
14903   },
14904
14905   /*
14906   Method: rotateAngle
14907   
14908   Rotates the graph of an angle theta.
14909   
14910    Parameters:
14911    
14912    node - (object) A <Graph.Node>.
14913    method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14914    opt - (object) Configuration options merged with this visualization configuration options.
14915    
14916    See also:
14917
14918    <Sunburst.rotate>
14919   
14920   */
14921   rotateAngle: function(theta, method, opt) {
14922     var that = this;
14923     var options = $.merge(this.config, opt || {}, {
14924       modes: [ 'polar' ]
14925     });
14926     var prop = opt.property || (method === "animate" ? 'end' : 'current');
14927     if(method === 'animate') {
14928       this.fx.animation.pause();
14929     }
14930     this.graph.eachNode(function(n) {
14931       var p = n.getPos(prop);
14932       p.theta += theta;
14933       if (p.theta < 0) {
14934         p.theta += Math.PI * 2;
14935       }
14936     });
14937     if (method == 'animate') {
14938       this.fx.animate(options);
14939     } else if (method == 'replot') {
14940       this.fx.plot();
14941       this.busy = false;
14942     }
14943   },
14944
14945   /*
14946    Method: plot
14947   
14948    Plots the Sunburst. This is a shortcut to *fx.plot*.
14949   */
14950   plot: function() {
14951     this.fx.plot();
14952   }
14953 });
14954
14955 $jit.Sunburst.$extend = true;
14956
14957 (function(Sunburst) {
14958
14959   /*
14960      Class: Sunburst.Op
14961
14962      Custom extension of <Graph.Op>.
14963
14964      Extends:
14965
14966      All <Graph.Op> methods
14967      
14968      See also:
14969      
14970      <Graph.Op>
14971
14972   */
14973   Sunburst.Op = new Class( {
14974
14975     Implements: Graph.Op
14976
14977   });
14978
14979   /*
14980      Class: Sunburst.Plot
14981
14982     Custom extension of <Graph.Plot>.
14983   
14984     Extends:
14985   
14986     All <Graph.Plot> methods
14987     
14988     See also:
14989     
14990     <Graph.Plot>
14991   
14992   */
14993   Sunburst.Plot = new Class( {
14994
14995     Implements: Graph.Plot
14996
14997   });
14998
14999   /*
15000     Class: Sunburst.Label
15001
15002     Custom extension of <Graph.Label>. 
15003     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
15004   
15005     Extends:
15006   
15007     All <Graph.Label> methods and subclasses.
15008   
15009     See also:
15010   
15011     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
15012   
15013    */
15014   Sunburst.Label = {};
15015
15016   /*
15017      Sunburst.Label.Native
15018
15019      Custom extension of <Graph.Label.Native>.
15020
15021      Extends:
15022
15023      All <Graph.Label.Native> methods
15024
15025      See also:
15026
15027      <Graph.Label.Native>
15028   */
15029   Sunburst.Label.Native = new Class( {
15030     Implements: Graph.Label.Native,
15031
15032     initialize: function(viz) {
15033       this.viz = viz;
15034       this.label = viz.config.Label;
15035       this.config = viz.config;
15036     },
15037
15038     renderLabel: function(canvas, node, controller) {
15039       var span = node.getData('span');
15040       if(span < Math.PI /2 && Math.tan(span) * 
15041           this.config.levelDistance * node._depth < 10) {
15042         return;
15043       }
15044       var ctx = canvas.getCtx();
15045       var measure = ctx.measureText(node.name);
15046       if (node.id == this.viz.root) {
15047         var x = -measure.width / 2, y = 0, thetap = 0;
15048         var ld = 0;
15049       } else {
15050         var indent = 5;
15051         var ld = controller.levelDistance - indent;
15052         var clone = node.pos.clone();
15053         clone.rho += indent;
15054         var p = clone.getp(true);
15055         var ct = clone.getc(true);
15056         var x = ct.x, y = ct.y;
15057         // get angle in degrees
15058         var pi = Math.PI;
15059         var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
15060         var thetap = cond ? p.theta + pi : p.theta;
15061         if (cond) {
15062           x -= Math.abs(Math.cos(p.theta) * measure.width);
15063           y += Math.sin(p.theta) * measure.width;
15064         } else if (node.id == this.viz.root) {
15065           x -= measure.width / 2;
15066         }
15067       }
15068       ctx.save();
15069       ctx.translate(x, y);
15070       ctx.rotate(thetap);
15071       ctx.fillText(node.name, 0, 0);
15072       ctx.restore();
15073     }
15074   });
15075
15076   /*
15077      Sunburst.Label.SVG
15078
15079     Custom extension of <Graph.Label.SVG>.
15080   
15081     Extends:
15082   
15083     All <Graph.Label.SVG> methods
15084   
15085     See also:
15086   
15087     <Graph.Label.SVG>
15088   
15089   */
15090   Sunburst.Label.SVG = new Class( {
15091     Implements: Graph.Label.SVG,
15092
15093     initialize: function(viz) {
15094       this.viz = viz;
15095     },
15096
15097     /* 
15098        placeLabel
15099
15100        Overrides abstract method placeLabel in <Graph.Plot>.
15101
15102        Parameters:
15103
15104        tag - A DOM label element.
15105        node - A <Graph.Node>.
15106        controller - A configuration/controller object passed to the visualization.
15107       
15108      */
15109     placeLabel: function(tag, node, controller) {
15110       var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
15111       var radius = canvas.getSize();
15112       var labelPos = {
15113         x: Math.round(pos.x + radius.width / 2),
15114         y: Math.round(pos.y + radius.height / 2)
15115       };
15116       tag.setAttribute('x', labelPos.x);
15117       tag.setAttribute('y', labelPos.y);
15118
15119       var bb = tag.getBBox();
15120       if (bb) {
15121         // center the label
15122     var x = tag.getAttribute('x');
15123     var y = tag.getAttribute('y');
15124     // get polar coordinates
15125     var p = node.pos.getp(true);
15126     // get angle in degrees
15127     var pi = Math.PI;
15128     var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
15129     if (cond) {
15130       tag.setAttribute('x', x - bb.width);
15131       tag.setAttribute('y', y - bb.height);
15132     } else if (node.id == viz.root) {
15133       tag.setAttribute('x', x - bb.width / 2);
15134     }
15135
15136     var thetap = cond ? p.theta + pi : p.theta;
15137     if(node._depth)
15138       tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
15139           + ' ' + y + ')');
15140   }
15141
15142   controller.onPlaceLabel(tag, node);
15143 }
15144   });
15145
15146   /*
15147      Sunburst.Label.HTML
15148
15149      Custom extension of <Graph.Label.HTML>.
15150
15151      Extends:
15152
15153      All <Graph.Label.HTML> methods.
15154
15155      See also:
15156
15157      <Graph.Label.HTML>
15158
15159   */
15160   Sunburst.Label.HTML = new Class( {
15161     Implements: Graph.Label.HTML,
15162
15163     initialize: function(viz) {
15164       this.viz = viz;
15165     },
15166     /* 
15167        placeLabel
15168
15169        Overrides abstract method placeLabel in <Graph.Plot>.
15170
15171        Parameters:
15172
15173        tag - A DOM label element.
15174        node - A <Graph.Node>.
15175        controller - A configuration/controller object passed to the visualization.
15176       
15177      */
15178     placeLabel: function(tag, node, controller) {
15179       var pos = node.pos.clone(), 
15180           canvas = this.viz.canvas,
15181           height = node.getData('height'),
15182           ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
15183           radius = canvas.getSize();
15184       pos.rho += ldist;
15185       pos = pos.getc(true);
15186       
15187       var labelPos = {
15188         x: Math.round(pos.x + radius.width / 2),
15189         y: Math.round(pos.y + radius.height / 2)
15190       };
15191
15192       var style = tag.style;
15193       style.left = labelPos.x + 'px';
15194       style.top = labelPos.y + 'px';
15195       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
15196
15197       controller.onPlaceLabel(tag, node);
15198     }
15199   });
15200
15201   /*
15202     Class: Sunburst.Plot.NodeTypes
15203
15204     This class contains a list of <Graph.Node> built-in types. 
15205     Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
15206
15207     You can add your custom node types, customizing your visualization to the extreme.
15208
15209     Example:
15210
15211     (start code js)
15212       Sunburst.Plot.NodeTypes.implement({
15213         'mySpecialType': {
15214           'render': function(node, canvas) {
15215             //print your custom node to canvas
15216           },
15217           //optional
15218           'contains': function(node, pos) {
15219             //return true if pos is inside the node or false otherwise
15220           }
15221         }
15222       });
15223     (end code)
15224
15225   */
15226   Sunburst.Plot.NodeTypes = new Class( {
15227     'none': {
15228       'render': $.empty,
15229       'contains': $.lambda(false),
15230       'anglecontains': function(node, pos) {
15231         var span = node.getData('span') / 2, theta = node.pos.theta;
15232         var begin = theta - span, end = theta + span;
15233         if (begin < 0)
15234           begin += Math.PI * 2;
15235         var atan = Math.atan2(pos.y, pos.x);
15236         if (atan < 0)
15237           atan += Math.PI * 2;
15238         if (begin > end) {
15239           return (atan > begin && atan <= Math.PI * 2) || atan < end;
15240         } else {
15241           return atan > begin && atan < end;
15242         }
15243       },
15244           'anglecontainsgauge': function(node, pos) {
15245         var span = node.getData('span') / 2, theta = node.pos.theta;
15246                 var config = node.getData('config');
15247         var ld = this.config.levelDistance;
15248                 var yOffset = pos.y-(ld/2);
15249                 var begin = ((theta - span)/2)+Math.PI,
15250         end = ((theta + span)/2)+Math.PI;
15251                 
15252         if (begin < 0)
15253           begin += Math.PI * 2;
15254         var atan = Math.atan2(yOffset, pos.x);
15255
15256                 
15257         if (atan < 0)
15258           atan += Math.PI * 2;
15259                   
15260                   
15261         if (begin > end) {
15262           return (atan > begin && atan <= Math.PI * 2) || atan < end;
15263         } else {
15264           return atan > begin && atan < end;
15265         }
15266       }
15267     },
15268
15269     'pie': {
15270       'render': function(node, canvas) {
15271         var span = node.getData('span') / 2, theta = node.pos.theta;
15272         var begin = theta - span, end = theta + span;
15273         var polarNode = node.pos.getp(true);
15274         var polar = new Polar(polarNode.rho, begin);
15275         var p1coord = polar.getc(true);
15276         polar.theta = end;
15277         var p2coord = polar.getc(true);
15278
15279         var ctx = canvas.getCtx();
15280         ctx.beginPath();
15281         ctx.moveTo(0, 0);
15282         ctx.lineTo(p1coord.x, p1coord.y);
15283         ctx.moveTo(0, 0);
15284         ctx.lineTo(p2coord.x, p2coord.y);
15285         ctx.moveTo(0, 0);
15286         ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
15287             false);
15288         ctx.fill();
15289       },
15290       'contains': function(node, pos) {
15291         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15292           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15293           var ld = this.config.levelDistance, d = node._depth;
15294           return (rho <= ld * d);
15295         }
15296         return false;
15297       }
15298     },
15299     'multipie': {
15300       'render': function(node, canvas) {
15301         var height = node.getData('height');
15302         var ldist = height? height : this.config.levelDistance;
15303         var span = node.getData('span') / 2, theta = node.pos.theta;
15304         var begin = theta - span, end = theta + span;
15305         var polarNode = node.pos.getp(true);
15306
15307         var polar = new Polar(polarNode.rho, begin);
15308         var p1coord = polar.getc(true);
15309
15310         polar.theta = end;
15311         var p2coord = polar.getc(true);
15312
15313         polar.rho += ldist;
15314         var p3coord = polar.getc(true);
15315
15316         polar.theta = begin;
15317         var p4coord = polar.getc(true);
15318
15319         var ctx = canvas.getCtx();
15320         ctx.moveTo(0, 0);
15321         ctx.beginPath();
15322         ctx.arc(0, 0, polarNode.rho, begin, end, false);
15323         ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15324         ctx.moveTo(p1coord.x, p1coord.y);
15325         ctx.lineTo(p4coord.x, p4coord.y);
15326         ctx.moveTo(p2coord.x, p2coord.y);
15327         ctx.lineTo(p3coord.x, p3coord.y);
15328         ctx.fill();
15329
15330         if (node.collapsed) {
15331           ctx.save();
15332           ctx.lineWidth = 2;
15333           ctx.moveTo(0, 0);
15334           ctx.beginPath();
15335           ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15336               true);
15337           ctx.stroke();
15338           ctx.restore();
15339         }
15340       },
15341       'contains': function(node, pos) {
15342         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15343           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15344           var height = node.getData('height');
15345           var ldist = height? height : this.config.levelDistance;
15346           var ld = this.config.levelDistance, d = node._depth;
15347           return (rho >= ld * d) && (rho <= (ld * d + ldist));
15348         }
15349         return false;
15350       }
15351     },
15352
15353     'gradient-multipie': {
15354       'render': function(node, canvas) {
15355         var ctx = canvas.getCtx();
15356         var height = node.getData('height');
15357         var ldist = height? height : this.config.levelDistance;
15358         var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15359             0, 0, node.getPos().rho + ldist);
15360
15361         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15362         $.each(colorArray, function(i) {
15363           ans.push(parseInt(i * 0.5, 10));
15364         });
15365         var endColor = $.rgbToHex(ans);
15366         radialGradient.addColorStop(0, endColor);
15367         radialGradient.addColorStop(1, node.getData('color'));
15368         ctx.fillStyle = radialGradient;
15369         this.nodeTypes['multipie'].render.call(this, node, canvas);
15370       },
15371       'contains': function(node, pos) {
15372         return this.nodeTypes['multipie'].contains.call(this, node, pos);
15373       }
15374     },
15375
15376     'gradient-pie': {
15377       'render': function(node, canvas) {
15378         var ctx = canvas.getCtx();
15379         var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15380             .getPos().rho);
15381
15382         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15383         $.each(colorArray, function(i) {
15384           ans.push(parseInt(i * 0.5, 10));
15385         });
15386         var endColor = $.rgbToHex(ans);
15387         radialGradient.addColorStop(1, endColor);
15388         radialGradient.addColorStop(0, node.getData('color'));
15389         ctx.fillStyle = radialGradient;
15390         this.nodeTypes['pie'].render.call(this, node, canvas);
15391       },
15392       'contains': function(node, pos) {
15393         return this.nodeTypes['pie'].contains.call(this, node, pos);
15394       }
15395     }
15396   });
15397
15398   /*
15399     Class: Sunburst.Plot.EdgeTypes
15400
15401     This class contains a list of <Graph.Adjacence> built-in types. 
15402     Edge types implemented are 'none', 'line' and 'arrow'.
15403   
15404     You can add your custom edge types, customizing your visualization to the extreme.
15405   
15406     Example:
15407   
15408     (start code js)
15409       Sunburst.Plot.EdgeTypes.implement({
15410         'mySpecialType': {
15411           'render': function(adj, canvas) {
15412             //print your custom edge to canvas
15413           },
15414           //optional
15415           'contains': function(adj, pos) {
15416             //return true if pos is inside the arc or false otherwise
15417           }
15418         }
15419       });
15420     (end code)
15421   
15422   */
15423   Sunburst.Plot.EdgeTypes = new Class({
15424     'none': $.empty,
15425     'line': {
15426       'render': function(adj, canvas) {
15427         var from = adj.nodeFrom.pos.getc(true),
15428             to = adj.nodeTo.pos.getc(true);
15429         this.edgeHelper.line.render(from, to, canvas);
15430       },
15431       'contains': function(adj, pos) {
15432         var from = adj.nodeFrom.pos.getc(true),
15433             to = adj.nodeTo.pos.getc(true);
15434         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15435       }
15436     },
15437     'arrow': {
15438       'render': function(adj, canvas) {
15439         var from = adj.nodeFrom.pos.getc(true),
15440             to = adj.nodeTo.pos.getc(true),
15441             dim = adj.getData('dim'),
15442             direction = adj.data.$direction,
15443             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15444         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15445       },
15446       'contains': function(adj, pos) {
15447         var from = adj.nodeFrom.pos.getc(true),
15448             to = adj.nodeTo.pos.getc(true);
15449         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15450       }
15451     },
15452     'hyperline': {
15453       'render': function(adj, canvas) {
15454         var from = adj.nodeFrom.pos.getc(),
15455             to = adj.nodeTo.pos.getc(),
15456             dim = Math.max(from.norm(), to.norm());
15457         this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15458       },
15459       'contains': $.lambda(false) //TODO(nico): Implement this!
15460     }
15461   });
15462
15463 })($jit.Sunburst);
15464
15465
15466 /*
15467  * File: PieChart.js
15468  *
15469 */
15470
15471 $jit.Sunburst.Plot.NodeTypes.implement({
15472   'piechart-stacked' : {
15473     'render' : function(node, canvas) {
15474       var pos = node.pos.getp(true),
15475           dimArray = node.getData('dimArray'),
15476           valueArray = node.getData('valueArray'),
15477           colorArray = node.getData('colorArray'),
15478           colorLength = colorArray.length,
15479           stringArray = node.getData('stringArray'),
15480           span = node.getData('span') / 2,
15481           theta = node.pos.theta,
15482           begin = theta - span,
15483           end = theta + span,
15484           polar = new Polar;
15485     
15486       var ctx = canvas.getCtx(), 
15487           opt = {},
15488           gradient = node.getData('gradient'),
15489           border = node.getData('border'),
15490           config = node.getData('config'),
15491           showLabels = config.showLabels,
15492           resizeLabels = config.resizeLabels,
15493           label = config.Label;
15494
15495       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15496       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15497
15498       if (colorArray && dimArray && stringArray) {
15499         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15500           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15501           if(dimi <= 0) continue;
15502           ctx.fillStyle = ctx.strokeStyle = colori;
15503           if(gradient && dimi) {
15504             var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15505                 xpos, ypos, acum + dimi + config.sliceOffset);
15506             var colorRgb = $.hexToRgb(colori), 
15507                 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15508                 endColor = $.rgbToHex(ans);
15509
15510             radialGradient.addColorStop(0, colori);
15511             radialGradient.addColorStop(0.5, colori);
15512             radialGradient.addColorStop(1, endColor);
15513             ctx.fillStyle = radialGradient;
15514           }
15515           
15516           polar.rho = acum + config.sliceOffset;
15517           polar.theta = begin;
15518           var p1coord = polar.getc(true);
15519           polar.theta = end;
15520           var p2coord = polar.getc(true);
15521           polar.rho += dimi;
15522           var p3coord = polar.getc(true);
15523           polar.theta = begin;
15524           var p4coord = polar.getc(true);
15525
15526           ctx.beginPath();
15527           //fixing FF arc method + fill
15528           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15529           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15530           ctx.fill();
15531           if(border && border.name == stringArray[i]) {
15532             opt.acum = acum;
15533             opt.dimValue = dimArray[i];
15534             opt.begin = begin;
15535             opt.end = end;
15536           }
15537           acum += (dimi || 0);
15538           valAcum += (valueArray[i] || 0);
15539         }
15540         if(border) {
15541           ctx.save();
15542           ctx.globalCompositeOperation = "source-over";
15543           ctx.lineWidth = 2;
15544           ctx.strokeStyle = border.color;
15545           var s = begin < end? 1 : -1;
15546           ctx.beginPath();
15547           //fixing FF arc method + fill
15548           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15549           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15550           ctx.closePath();
15551           ctx.stroke();
15552           ctx.restore();
15553         }
15554         if(showLabels && label.type == 'Native') {
15555           ctx.save();
15556           ctx.fillStyle = ctx.strokeStyle = label.color;
15557           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15558               fontSize = (label.size * scale) >> 0;
15559           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15560           
15561           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15562           ctx.textBaseline = 'middle';
15563           ctx.textAlign = 'center';
15564           
15565           polar.rho = acum + config.labelOffset + config.sliceOffset;
15566           polar.theta = node.pos.theta;
15567           var cart = polar.getc(true);
15568           
15569           ctx.fillText(node.name, cart.x, cart.y);
15570           ctx.restore();
15571         }
15572       }
15573     },
15574     'contains': function(node, pos) {
15575       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15576         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15577         var ld = this.config.levelDistance, d = node._depth;
15578         var config = node.getData('config');
15579         if(rho <=ld * d + config.sliceOffset) {
15580           var dimArray = node.getData('dimArray');
15581           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15582             var dimi = dimArray[i];
15583             if(rho >= acum && rho <= acum + dimi) {
15584               return {
15585                 name: node.getData('stringArray')[i],
15586                 color: node.getData('colorArray')[i],
15587                 value: node.getData('valueArray')[i],
15588                 label: node.name
15589               };
15590             }
15591             acum += dimi;
15592           }
15593         }
15594         return false;
15595         
15596       }
15597       return false;
15598     }
15599   },
15600     'piechart-basic' : {
15601     'render' : function(node, canvas) {
15602       var pos = node.pos.getp(true),
15603           dimArray = node.getData('dimArray'),
15604           valueArray = node.getData('valueArray'),
15605           colorArray = node.getData('colorMono'),
15606           colorLength = colorArray.length,
15607           stringArray = node.getData('stringArray'),
15608                   percentage = node.getData('percentage'),
15609                   iteration = node.getData('iteration'),
15610           span = node.getData('span') / 2,
15611           theta = node.pos.theta,
15612           begin = theta - span,
15613           end = theta + span,
15614           polar = new Polar;
15615     
15616       var ctx = canvas.getCtx(), 
15617           opt = {},
15618           gradient = node.getData('gradient'),
15619           border = node.getData('border'),
15620           config = node.getData('config'),
15621           renderSubtitle = node.getData('renderSubtitle'),
15622           renderBackground = config.renderBackground,
15623           showLabels = config.showLabels,
15624           resizeLabels = config.resizeLabels,
15625           label = config.Label;
15626
15627       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15628       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15629       //background rendering for IE
15630                 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15631                         backgroundColor = config.backgroundColor,
15632                         size = canvas.getSize();
15633                         ctx.save();
15634                     ctx.fillStyle = backgroundColor;
15635                     ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15636                     
15637                     //subtitle
15638
15639                         var margin = config.Margin,
15640                         title = config.Title,
15641                         subtitle = config.Subtitle;
15642                         ctx.fillStyle = title.color;
15643                         ctx.textAlign = 'left';
15644                         
15645                         if(title.text != "") {
15646                                 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15647                                 ctx.moveTo(0,0);
15648                                 if(label.type == 'Native') {
15649                                         ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15650                                 }
15651                         }       
15652         
15653                         if(subtitle.text != "") {
15654                                 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15655                                 if(label.type == 'Native') {
15656                                         ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15657                                 } 
15658                         }
15659                         ctx.restore();          
15660                 }
15661       if (colorArray && dimArray && stringArray) {
15662         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15663           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15664           if(dimi <= 0) continue;
15665           ctx.fillStyle = ctx.strokeStyle = colori;
15666           
15667           polar.rho = acum + config.sliceOffset;
15668           polar.theta = begin;
15669           var p1coord = polar.getc(true);
15670           polar.theta = end;
15671           var p2coord = polar.getc(true);
15672           polar.rho += dimi;
15673           var p3coord = polar.getc(true);
15674           polar.theta = begin;
15675           var p4coord = polar.getc(true);
15676           
15677           if(typeof FlashCanvas == "undefined") {
15678                   //drop shadow
15679                   ctx.beginPath();
15680                   ctx.fillStyle = "rgba(0,0,0,.2)";
15681                   ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15682                   ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);    
15683                   ctx.fill();
15684                   
15685                   if(gradient && dimi) {
15686                     var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15687                         xpos, ypos, acum + dimi + config.sliceOffset);
15688                     var colorRgb = $.hexToRgb(colori), 
15689                         endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15690                         endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15691         
15692                     radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15693                     radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15694                                 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15695                     radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15696                     ctx.fillStyle = radialGradient;
15697                   }
15698           }
15699
15700           
15701           //fixing FF arc method + fill
15702           ctx.beginPath();
15703           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15704           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15705           ctx.fill();
15706           if(border && border.name == stringArray[i]) {
15707             opt.acum = acum;
15708             opt.dimValue = dimArray[i];
15709             opt.begin = begin;
15710             opt.end = end;
15711             opt.sliceValue = valueArray[i];
15712           }
15713           acum += (dimi || 0);
15714           valAcum += (valueArray[i] || 0);
15715         }
15716         if(border) {
15717           ctx.save();
15718           ctx.globalCompositeOperation = "source-over";
15719           ctx.lineWidth = 2;
15720           ctx.strokeStyle = border.color;
15721           var s = begin < end? 1 : -1;
15722           ctx.beginPath();
15723           //fixing FF arc method + fill
15724           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15725           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15726           ctx.closePath();
15727           ctx.stroke();
15728           ctx.restore();
15729         }
15730         if(showLabels && label.type == 'Native') {
15731           ctx.save();
15732           ctx.fillStyle = ctx.strokeStyle = label.color;
15733           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15734               fontSize = (label.size * scale) >> 0;
15735           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15736           
15737           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15738           ctx.textBaseline = 'middle';
15739           ctx.textAlign = 'center';
15740           pi = Math.PI;
15741           angle = theta * 360 / (2 * pi);
15742           polar.rho = acum + config.labelOffset + config.sliceOffset;
15743           polar.theta = node.pos.theta;
15744           var cart = polar.getc(true);
15745           if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15746                 
15747                 } else {
15748                   if(config.labelType == 'name') {
15749                                 ctx.fillText(node.name, cart.x, cart.y);
15750                           } else {
15751                                 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15752                           }
15753           }
15754           ctx.restore();
15755         }
15756       }
15757     },
15758     'contains': function(node, pos) {
15759       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15760         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15761         var ld = this.config.levelDistance, d = node._depth;
15762         var config = node.getData('config');
15763
15764         if(rho <=ld * d + config.sliceOffset) {
15765           var dimArray = node.getData('dimArray');
15766           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15767             var dimi = dimArray[i];
15768             if(rho >= acum && rho <= acum + dimi) {
15769                           var url = Url.decode(node.getData('linkArray')[i]);
15770               return {
15771                 name: node.getData('stringArray')[i],
15772                 link: url,
15773                 color: node.getData('colorArray')[i],
15774                 value: node.getData('valueArray')[i],
15775                 percentage: node.getData('percentage'),
15776                 valuelabel: node.getData('valuelabelsArray')[i],
15777                 label: node.name
15778               };
15779             }
15780             acum += dimi;
15781           }
15782         }
15783         return false;
15784         
15785       }
15786       return false;
15787     }
15788   }
15789 });
15790
15791 /*
15792   Class: PieChart
15793   
15794   A visualization that displays stacked bar charts.
15795   
15796   Constructor Options:
15797   
15798   See <Options.PieChart>.
15799
15800 */
15801 $jit.PieChart = new Class({
15802   sb: null,
15803   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15804   selected: {},
15805   busy: false,
15806   
15807   initialize: function(opt) {
15808     this.controller = this.config = 
15809       $.merge(Options("Canvas", "PieChart", "Label"), {
15810         Label: { type: 'Native' }
15811       }, opt);
15812     this.initializeViz();
15813   },
15814   
15815   initializeViz: function() {
15816     var config = this.config, that = this;
15817     var nodeType = config.type.split(":")[0];
15818     var sb = new $jit.Sunburst({
15819       injectInto: config.injectInto,
15820       useCanvas: config.useCanvas,
15821       withLabels: config.Label.type != 'Native',
15822       background: config.background,
15823       renderBackground: config.renderBackground,
15824       backgroundColor: config.backgroundColor,
15825       colorStop1: config.colorStop1,
15826       colorStop2: config.colorStop2,
15827       Label: {
15828         type: config.Label.type
15829       },
15830       Node: {
15831         overridable: true,
15832         type: 'piechart-' + nodeType,
15833         width: 1,
15834         height: 1
15835       },
15836       Edge: {
15837         type: 'none'
15838       },
15839       Tips: {
15840         enable: config.Tips.enable,
15841         type: 'Native',
15842         force: true,
15843         onShow: function(tip, node, contains) {
15844           var elem = contains;
15845           config.Tips.onShow(tip, elem, node);
15846                           if(elem.link != 'undefined' && elem.link != '') {
15847                                 document.body.style.cursor = 'pointer';
15848                           }
15849         },
15850                 onHide: function() {
15851                                 document.body.style.cursor = 'default';
15852         }
15853       },
15854       Events: {
15855         enable: true,
15856         type: 'Native',
15857         onClick: function(node, eventInfo, evt) {
15858           if(!config.Events.enable) return;
15859           var elem = eventInfo.getContains();
15860           config.Events.onClick(elem, eventInfo, evt);
15861         },
15862         onMouseMove: function(node, eventInfo, evt) {
15863           if(!config.hoveredColor) return;
15864           if(node) {
15865             var elem = eventInfo.getContains();
15866             that.select(node.id, elem.name, elem.index);
15867           } else {
15868             that.select(false, false, false);
15869           }
15870         }
15871       },
15872       onCreateLabel: function(domElement, node) {
15873         var labelConf = config.Label;
15874         if(config.showLabels) {
15875           var style = domElement.style;
15876           style.fontSize = labelConf.size + 'px';
15877           style.fontFamily = labelConf.family;
15878           style.color = labelConf.color;
15879           style.textAlign = 'center';
15880           if(config.labelType == 'name') {
15881                 domElement.innerHTML = node.name;
15882           } else {
15883                 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15884           }
15885           domElement.style.width = '400px';
15886         }
15887       },
15888       onPlaceLabel: function(domElement, node) {
15889         if(!config.showLabels) return;
15890         var pos = node.pos.getp(true),
15891             dimArray = node.getData('dimArray'),
15892             span = node.getData('span') / 2,
15893             theta = node.pos.theta,
15894             begin = theta - span,
15895             end = theta + span,
15896             polar = new Polar;
15897
15898         var showLabels = config.showLabels,
15899             resizeLabels = config.resizeLabels,
15900             label = config.Label;
15901         
15902         if (dimArray) {
15903           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15904             acum += dimArray[i];
15905           }
15906           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15907               fontSize = (label.size * scale) >> 0;
15908           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15909           domElement.style.fontSize = fontSize + 'px';
15910           polar.rho = acum + config.labelOffset + config.sliceOffset;
15911           polar.theta = (begin + end) / 2;
15912           var pos = polar.getc(true);
15913           var radius = that.canvas.getSize();
15914           var labelPos = {
15915             x: Math.round(pos.x + radius.width / 2),
15916             y: Math.round(pos.y + radius.height / 2)
15917           };
15918           domElement.style.left = (labelPos.x - 200) + 'px';
15919           domElement.style.top = labelPos.y + 'px';
15920         }
15921       }
15922     });
15923     
15924     var size = sb.canvas.getSize(),
15925         min = Math.min;
15926     sb.config.levelDistance = min(size.width, size.height)/2 
15927       - config.offset - config.sliceOffset;
15928     this.sb = sb;
15929     this.canvas = this.sb.canvas;
15930     this.canvas.getCtx().globalCompositeOperation = 'lighter';
15931   },
15932     renderBackground: function() {
15933                 var canvas = this.canvas,
15934                 config = this.config,
15935                 backgroundColor = config.backgroundColor,
15936                 size = canvas.getSize(),
15937                 ctx = canvas.getCtx();
15938                 ctx.globalCompositeOperation = "destination-over";
15939             ctx.fillStyle = backgroundColor;
15940             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
15941   },
15942    renderTitle: function() {
15943         var canvas = this.canvas,
15944         size = canvas.getSize(),
15945         config = this.config,
15946         margin = config.Margin,
15947         radius = this.sb.config.levelDistance,
15948         title = config.Title,
15949         label = config.Label,
15950         subtitle = config.Subtitle;
15951         ctx = canvas.getCtx();
15952         ctx.fillStyle = title.color;
15953         ctx.textAlign = 'left';
15954         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15955         ctx.moveTo(0,0);
15956         if(label.type == 'Native') {
15957                 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15958         }       
15959   },
15960   renderSubtitle: function() {
15961         var canvas = this.canvas,
15962         size = canvas.getSize(),
15963         config = this.config,
15964         margin = config.Margin,
15965         radius = this.sb.config.levelDistance,
15966         title = config.Title,
15967         label = config.Label,
15968         subtitle = config.Subtitle;
15969         ctx = canvas.getCtx();
15970         ctx.fillStyle = title.color;
15971         ctx.textAlign = 'left';
15972         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15973         ctx.moveTo(0,0);
15974         if(label.type == 'Native') {
15975                 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15976         }       
15977   },
15978   clear: function() {
15979         var canvas = this.canvas;
15980         var ctx = canvas.getCtx(),
15981         size = canvas.getSize();
15982         ctx.fillStyle = "rgba(255,255,255,0)";
15983         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15984         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15985   },
15986   resizeGraph: function(json,width) {
15987         var canvas = this.canvas,
15988         size = canvas.getSize(),
15989         config = this.config,
15990         orgHeight = size.height;
15991         
15992         canvas.resize(width,orgHeight);
15993         if(typeof FlashCanvas == "undefined") {
15994                 canvas.clear();
15995         } else {
15996                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
15997         }
15998         this.loadJSON(json);
15999
16000         },
16001   /*
16002     Method: loadJSON
16003    
16004     Loads JSON data into the visualization. 
16005     
16006     Parameters:
16007     
16008     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>.
16009     
16010     Example:
16011     (start code js)
16012     var pieChart = new $jit.PieChart(options);
16013     pieChart.loadJSON(json);
16014     (end code)
16015   */  
16016   loadJSON: function(json) {
16017     var prefix = $.time(), 
16018         ch = [], 
16019         sb = this.sb,
16020         name = $.splat(json.label),
16021         nameLength = name.length,
16022         color = $.splat(json.color || this.colors),
16023         colorLength = color.length,
16024         config = this.config,
16025         renderBackground = config.renderBackground,
16026         title = config.Title,
16027                 subtitle = config.Subtitle,
16028         gradient = !!config.type.split(":")[1],
16029         animate = config.animate,
16030         mono = nameLength == 1;
16031         totalValue = 0;
16032     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16033         var val = values[i];
16034         var valArray = $.splat(val.values);
16035         totalValue += parseFloat(valArray.sum());
16036     }
16037
16038     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16039       var val = values[i];
16040       var valArray = $.splat(val.values);
16041           var percentage = (valArray.sum()/totalValue) * 100;
16042
16043       var linkArray = $.splat(val.links);
16044       var valuelabelsArray = $.splat(val.valuelabels);
16045       
16046  
16047       ch.push({
16048         'id': prefix + val.label,
16049         'name': val.label,
16050         'data': {
16051           'value': valArray,
16052           'valuelabel': valuelabelsArray,
16053           '$linkArray': linkArray,
16054           '$valuelabelsArray': valuelabelsArray,
16055           '$valueArray': valArray,
16056           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16057           '$colorMono': $.splat(color[i % colorLength]),
16058           '$stringArray': name,
16059           '$gradient': gradient,
16060           '$config': config,
16061           '$iteration': i,
16062           '$percentage': percentage.toFixed(1),
16063           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16064         },
16065         'children': []
16066       });
16067     }
16068     var root = {
16069       'id': prefix + '$root',
16070       'name': '',
16071       'data': {
16072         '$type': 'none',
16073         '$width': 1,
16074         '$height': 1
16075       },
16076       'children': ch
16077     };
16078     sb.loadJSON(root);
16079     
16080     
16081     this.normalizeDims();
16082
16083     
16084     sb.refresh();
16085     if(title.text != "") {
16086         this.renderTitle();
16087     }
16088        
16089     if(subtitle.text != "") {
16090         this.renderSubtitle();
16091     }
16092      if(renderBackground && typeof FlashCanvas == "undefined") {
16093         this.renderBackground();        
16094     }
16095     
16096     if(animate) {
16097       sb.fx.animate({
16098         modes: ['node-property:dimArray'],
16099         duration:1500
16100       });
16101     }
16102   },
16103   
16104   /*
16105     Method: updateJSON
16106    
16107     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.
16108     
16109     Parameters:
16110     
16111     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
16112     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
16113     
16114     Example:
16115     
16116     (start code js)
16117     pieChart.updateJSON(json, {
16118       onComplete: function() {
16119         alert('update complete!');
16120       }
16121     });
16122     (end code)
16123   */  
16124   updateJSON: function(json, onComplete) {
16125     if(this.busy) return;
16126     this.busy = true;
16127     
16128     var sb = this.sb;
16129     var graph = sb.graph;
16130     var values = json.values;
16131     var animate = this.config.animate;
16132     var that = this;
16133     $.each(values, function(v) {
16134       var n = graph.getByName(v.label),
16135           vals = $.splat(v.values);
16136       if(n) {
16137         n.setData('valueArray', vals);
16138         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16139         if(json.label) {
16140           n.setData('stringArray', $.splat(json.label));
16141         }
16142       }
16143     });
16144     this.normalizeDims();
16145     if(animate) {
16146       sb.compute('end');
16147       sb.fx.animate({
16148         modes: ['node-property:dimArray:span', 'linear'],
16149         duration:1500,
16150         onComplete: function() {
16151           that.busy = false;
16152           onComplete && onComplete.onComplete();
16153         }
16154       });
16155     } else {
16156       sb.refresh();
16157     }
16158   },
16159     
16160   //adds the little brown bar when hovering the node
16161   select: function(id, name) {
16162     if(!this.config.hoveredColor) return;
16163     var s = this.selected;
16164     if(s.id != id || s.name != name) {
16165       s.id = id;
16166       s.name = name;
16167       s.color = this.config.hoveredColor;
16168       this.sb.graph.eachNode(function(n) {
16169         if(id == n.id) {
16170           n.setData('border', s);
16171         } else {
16172           n.setData('border', false);
16173         }
16174       });
16175       this.sb.plot();
16176     }
16177   },
16178   
16179   /*
16180     Method: getLegend
16181    
16182     Returns an object containing as keys the legend names and as values hex strings with color values.
16183     
16184     Example:
16185     
16186     (start code js)
16187     var legend = pieChart.getLegend();
16188     (end code)
16189   */  
16190   getLegend: function() {
16191     var legend = new Array();
16192     var name = new Array();
16193     var color = new Array();
16194     var n;
16195     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16196       n = adj.nodeTo;
16197     });
16198     var colors = n.getData('colorArray'),
16199         len = colors.length;
16200     $.each(n.getData('stringArray'), function(s, i) {
16201       color[i] = colors[i % len];
16202       name[i] = s;
16203     });
16204         legend['name'] = name;
16205         legend['color'] = color;
16206     return legend;
16207   },
16208   
16209   /*
16210     Method: getMaxValue
16211    
16212     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16213     
16214     Example:
16215     
16216     (start code js)
16217     var ans = pieChart.getMaxValue();
16218     (end code)
16219     
16220     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16221     
16222     Example:
16223     
16224     (start code js)
16225     //will return 100 for all PieChart instances,
16226     //displaying all of them with the same scale
16227     $jit.PieChart.implement({
16228       'getMaxValue': function() {
16229         return 100;
16230       }
16231     });
16232     (end code)
16233     
16234   */  
16235   getMaxValue: function() {
16236     var maxValue = 0;
16237     this.sb.graph.eachNode(function(n) {
16238       var valArray = n.getData('valueArray'),
16239           acum = 0;
16240       $.each(valArray, function(v) { 
16241         acum += +v;
16242       });
16243       maxValue = maxValue>acum? maxValue:acum;
16244     });
16245     return maxValue;
16246   },
16247   
16248   normalizeDims: function() {
16249     //number of elements
16250     var root = this.sb.graph.getNode(this.sb.root), l=0;
16251     root.eachAdjacency(function() {
16252       l++;
16253     });
16254     var maxValue = this.getMaxValue() || 1,
16255         config = this.config,
16256         animate = config.animate,
16257         rho = this.sb.config.levelDistance;
16258     this.sb.graph.eachNode(function(n) {
16259       var acum = 0, animateValue = [];
16260       $.each(n.getData('valueArray'), function(v) {
16261         acum += +v;
16262         animateValue.push(1);
16263       });
16264       var stat = (animateValue.length == 1) && !config.updateHeights;
16265       if(animate) {
16266         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16267           return stat? rho: (n * rho / maxValue); 
16268         }), 'end');
16269         var dimArray = n.getData('dimArray');
16270         if(!dimArray) {
16271           n.setData('dimArray', animateValue);
16272         }
16273       } else {
16274         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16275           return stat? rho : (n * rho / maxValue); 
16276         }));
16277       }
16278       n.setData('normalizedDim', acum / maxValue);
16279     });
16280   }
16281 });
16282
16283
16284 //Gauge Chart
16285
16286 Options.GaugeChart = {
16287   $extend: true,
16288
16289   animate: true,
16290   offset: 25, // page offset
16291   sliceOffset:0,
16292   labelOffset: 3, // label offset
16293   type: 'stacked', // gradient
16294   labelType: 'name',
16295   hoveredColor: '#9fd4ff',
16296   Events: {
16297     enable: false,
16298     onClick: $.empty
16299   },
16300   Tips: {
16301     enable: false,
16302     onShow: $.empty,
16303     onHide: $.empty
16304   },
16305   showLabels: true,
16306   resizeLabels: false,
16307   
16308   //only valid for mono-valued datasets
16309   updateHeights: false
16310 };
16311
16312
16313
16314 $jit.Sunburst.Plot.NodeTypes.implement({
16315     'gaugechart-basic' : {
16316     'render' : function(node, canvas) {
16317       var pos = node.pos.getp(true),
16318           dimArray = node.getData('dimArray'),
16319           valueArray = node.getData('valueArray'),
16320           valuelabelsArray = node.getData('valuelabelsArray'),
16321           gaugeTarget = node.getData('gaugeTarget'),
16322           nodeIteration = node.getData('nodeIteration'),
16323           nodeLength = node.getData('nodeLength'),
16324           colorArray = node.getData('colorMono'),
16325           colorLength = colorArray.length,
16326           stringArray = node.getData('stringArray'),
16327           span = node.getData('span') / 2,
16328           theta = node.pos.theta,
16329           begin = ((theta - span)/2)+Math.PI,
16330           end = ((theta + span)/2)+Math.PI,
16331           polar = new Polar;
16332
16333   
16334       var ctx = canvas.getCtx(), 
16335           opt = {},
16336           gradient = node.getData('gradient'),
16337           border = node.getData('border'),
16338           config = node.getData('config'),
16339           showLabels = config.showLabels,
16340           resizeLabels = config.resizeLabels,
16341           label = config.Label;
16342
16343       var xpos = Math.cos((begin + end) /2);
16344       var ypos = Math.sin((begin + end) /2);
16345
16346       if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16347         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16348           var dimi = dimArray[i], colori = colorArray[i % colorLength];
16349           if(dimi <= 0) continue;
16350           ctx.fillStyle = ctx.strokeStyle = colori;
16351           if(gradient && dimi) {
16352             var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16353                 xpos, (ypos + dimi/2), acum + dimi);
16354             var colorRgb = $.hexToRgb(colori), 
16355                 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16356                 endColor = $.rgbToHex(ans);
16357
16358             radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16359             radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16360             radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16361             radialGradient.addColorStop(1,  'rgba('+ans+',1)');
16362             ctx.fillStyle = radialGradient;
16363           }
16364           
16365           polar.rho = acum;
16366           polar.theta = begin;
16367           var p1coord = polar.getc(true);
16368           polar.theta = end;
16369           var p2coord = polar.getc(true);
16370           polar.rho += dimi;
16371           var p3coord = polar.getc(true);
16372           polar.theta = begin;
16373           var p4coord = polar.getc(true);
16374
16375                   
16376           ctx.beginPath();
16377           //fixing FF arc method + fill
16378           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16379           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16380           ctx.fill();
16381                   
16382
16383           acum += (dimi || 0);
16384           valAcum += (valueArray[i] || 0);                
16385         }
16386                 
16387                 if(showLabels && label.type == 'Native') {
16388                           ctx.save();
16389                           ctx.fillStyle = ctx.strokeStyle = label.color;
16390
16391                           
16392                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16393                           ctx.textBaseline = 'bottom';
16394                           ctx.textAlign = 'center';
16395
16396                           polar.rho = acum * .65;
16397                           polar.theta = begin;
16398                           var cart = polar.getc(true);
16399                           
16400                           //changes y pos of first label
16401                           if(nodeIteration == 1) {
16402                                 textY = cart.y - (label.size/2) + acum /2;
16403                           } else {
16404                                 textY = cart.y + acum/2;
16405                           }
16406                           
16407                           if(config.labelType == 'name') {
16408                                 ctx.fillText(node.name, cart.x, textY);
16409                           } else {
16410                                 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16411                           }
16412                           
16413                           //adds final label
16414                           if(nodeIteration == nodeLength) {
16415                                 polar.theta = end;
16416                                 var cart = polar.getc(true);
16417                                 if(config.labelType == 'name') {
16418                                         ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16419                                 } else {
16420                                         ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16421                                 }
16422                                 
16423                           }
16424                           ctx.restore();
16425                 }
16426
16427       }
16428       },
16429     'contains': function(node, pos) {
16430                 
16431                 
16432                 
16433       if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16434                 var config = node.getData('config');
16435         var ld = this.config.levelDistance , d = node._depth;
16436                 var yOffset = pos.y - (ld/2);
16437                 var xOffset = pos.x;
16438         var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16439         if(rho <=parseInt(ld * d)) {
16440           var dimArray = node.getData('dimArray');
16441           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16442                         var dimi = dimArray[i];
16443             if(rho >= ld * .8 && rho <= acum + dimi) {
16444                 
16445                           var url = Url.decode(node.getData('linkArray')[i]);
16446               return {
16447                 name: node.getData('stringArray')[i],
16448                 link: url,
16449                 color: node.getData('colorArray')[i],
16450                 value: node.getData('valueArray')[i],
16451                 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16452                 label: node.name
16453               };
16454             }
16455             acum += dimi;
16456                         
16457                         
16458           }
16459         }
16460         return false;
16461         
16462       }
16463       return false;
16464     }
16465   }
16466 });
16467
16468 /*
16469   Class: GaugeChart
16470   
16471   A visualization that displays gauge charts
16472   
16473   Constructor Options:
16474   
16475   See <Options.Gauge>.
16476
16477 */
16478 $jit.GaugeChart = new Class({
16479   sb: null,
16480   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16481   selected: {},
16482   busy: false,
16483   
16484   initialize: function(opt) {
16485     this.controller = this.config = 
16486       $.merge(Options("Canvas", "GaugeChart", "Label"), {
16487         Label: { type: 'Native' }
16488       }, opt);
16489     this.initializeViz();
16490   },
16491   
16492   initializeViz: function() {
16493     var config = this.config, that = this;
16494     var nodeType = config.type.split(":")[0];
16495     var sb = new $jit.Sunburst({
16496       injectInto: config.injectInto,
16497       useCanvas: config.useCanvas,
16498       withLabels: config.Label.type != 'Native',
16499       background: config.background,
16500       renderBackground: config.renderBackground,
16501       backgroundColor: config.backgroundColor,
16502       colorStop1: config.colorStop1,
16503       colorStop2: config.colorStop2,
16504       Label: {
16505         type: config.Label.type
16506       },
16507       Node: {
16508         overridable: true,
16509         type: 'gaugechart-' + nodeType,
16510         width: 1,
16511         height: 1
16512       },
16513       Edge: {
16514         type: 'none'
16515       },
16516       Tips: {
16517         enable: config.Tips.enable,
16518         type: 'Native',
16519         force: true,
16520         onShow: function(tip, node, contains) {
16521           var elem = contains;
16522           config.Tips.onShow(tip, elem, node);
16523                           if(elem.link != 'undefined' && elem.link != '') {
16524                                 document.body.style.cursor = 'pointer';
16525                           }
16526         },
16527                 onHide: function() {
16528                                 document.body.style.cursor = 'default';
16529         }
16530       },
16531       Events: {
16532         enable: true,
16533         type: 'Native',
16534         onClick: function(node, eventInfo, evt) {
16535           if(!config.Events.enable) return;
16536           var elem = eventInfo.getContains();
16537           config.Events.onClick(elem, eventInfo, evt);
16538         }
16539         },
16540       onCreateLabel: function(domElement, node) {
16541         var labelConf = config.Label;
16542         if(config.showLabels) {
16543           var style = domElement.style;
16544           style.fontSize = labelConf.size + 'px';
16545           style.fontFamily = labelConf.family;
16546           style.color = labelConf.color;
16547           style.textAlign = 'center';
16548           valuelabelsArray = node.getData('valuelabelsArray'),
16549           nodeIteration = node.getData('nodeIteration'),
16550           nodeLength = node.getData('nodeLength'),
16551           canvas = sb.canvas,
16552           prefix = $.time();
16553           
16554           if(config.labelType == 'name') {
16555                 domElement.innerHTML = node.name;
16556           } else {
16557                 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16558           }
16559           
16560           domElement.style.width = '400px';
16561           
16562           //adds final label
16563                   if(nodeIteration == nodeLength && nodeLength != 0) {
16564                   idLabel = canvas.id + "-label";
16565                   container = document.getElementById(idLabel);
16566                   finalLabel = document.createElement('div');
16567                   finalLabelStyle = finalLabel.style;
16568                   finalLabel.id = prefix + "finalLabel";
16569                   finalLabelStyle.position = "absolute";
16570                   finalLabelStyle.width = "400px";
16571                   finalLabelStyle.left = "0px";
16572                   container.appendChild(finalLabel);
16573                         if(config.labelType == 'name') {
16574                                 finalLabel.innerHTML = node.name;
16575                         } else {
16576                                 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16577                         }
16578                         
16579                   }
16580         }
16581       },
16582       onPlaceLabel: function(domElement, node) {
16583         if(!config.showLabels) return;
16584         var pos = node.pos.getp(true),
16585             dimArray = node.getData('dimArray'),
16586             nodeIteration = node.getData('nodeIteration'),
16587             nodeLength = node.getData('nodeLength'),
16588             span = node.getData('span') / 2,
16589             theta = node.pos.theta,
16590             begin = ((theta - span)/2)+Math.PI,
16591             end = ((theta + span)/2)+Math.PI,
16592             polar = new Polar;
16593
16594         var showLabels = config.showLabels,
16595             resizeLabels = config.resizeLabels,
16596             label = config.Label,
16597             radiusOffset = sb.config.levelDistance;
16598
16599         if (dimArray) {
16600           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16601             acum += dimArray[i];
16602           }
16603           var scale = resizeLabels? node.getData('normalizedDim') : 1,
16604               fontSize = (label.size * scale) >> 0;
16605           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16606           domElement.style.fontSize = fontSize + 'px';
16607           polar.rho = acum * .65;
16608           polar.theta = begin;
16609           var pos = polar.getc(true);
16610           var radius = that.canvas.getSize();
16611           var labelPos = {
16612             x: Math.round(pos.x + radius.width / 2),
16613             y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16614           };
16615           
16616
16617           
16618           domElement.style.left = (labelPos.x - 200) + 'px';
16619           domElement.style.top = labelPos.y + 'px';
16620           
16621           //reposition first label
16622           if(nodeIteration == 1) {
16623                  domElement.style.top = labelPos.y - label.size + 'px';
16624           }
16625           
16626           
16627           //position final label
16628                 if(nodeIteration == nodeLength && nodeLength != 0) {
16629                         polar.theta = end;
16630                         var final = polar.getc(true);
16631                         var finalPos = {
16632                                         x: Math.round(final.x + radius.width / 2),
16633                                 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16634                                 };
16635                         finalLabel.style.left = (finalPos.x - 200) + "px";
16636                         finalLabel.style.top = finalPos.y - label.size + "px";
16637                     }
16638           
16639         }
16640       }
16641    
16642     });
16643     this.sb = sb;
16644     this.canvas = this.sb.canvas;
16645     var size = sb.canvas.getSize(),
16646         min = Math.min;
16647         sb.config.levelDistance = min(size.width, size.height)/2 
16648       - config.offset - config.sliceOffset;
16649
16650
16651   },
16652         
16653   renderBackground: function() {
16654         var canvas = this.sb.canvas,
16655         config = this.config,
16656         style = config.gaugeStyle,
16657         ctx = canvas.getCtx(),
16658         size = canvas.getSize(),
16659         radius = this.sb.config.levelDistance,
16660         startAngle = (Math.PI/180)*1,
16661         endAngle = (Math.PI/180)*179;
16662         
16663
16664         //background border
16665         ctx.fillStyle = style.borderColor;      
16666         ctx.beginPath();
16667         ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true); 
16668         ctx.fill(); 
16669         
16670         
16671         var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16672         radialGradient.addColorStop(0, '#ffffff');  
16673         radialGradient.addColorStop(0.3, style.backgroundColor);  
16674         radialGradient.addColorStop(0.6, style.backgroundColor);  
16675         radialGradient.addColorStop(1, '#FFFFFF'); 
16676         ctx.fillStyle = radialGradient;
16677         
16678         //background
16679         startAngle = (Math.PI/180)*0;
16680         endAngle = (Math.PI/180)*180;
16681         ctx.beginPath();
16682         ctx.arc(0,radius/2,radius,startAngle,endAngle, true); 
16683         ctx.fill();     
16684         
16685         
16686  
16687   },
16688   
16689   
16690   renderNeedle: function(gaugePosition,target) {
16691         var canvas = this.sb.canvas,
16692         config = this.config,
16693         style = config.gaugeStyle,
16694         ctx = canvas.getCtx(),
16695         size = canvas.getSize(),
16696         radius = this.sb.config.levelDistance;
16697         gaugeCenter = (radius/2);
16698         startAngle = 0;
16699         endAngle = (Math.PI/180)*180;
16700         
16701         
16702         // needle
16703         ctx.fillStyle = style.needleColor;
16704         var segments = 180/target;
16705         needleAngle = gaugePosition * segments;
16706         ctx.translate(0, gaugeCenter);
16707         ctx.save();
16708         ctx.rotate(needleAngle * Math.PI / 180);  
16709         ctx.beginPath();
16710         ctx.moveTo(0,0); 
16711         ctx.lineTo(0,-4);  
16712         ctx.lineTo(-radius*.9,-1);  
16713         ctx.lineTo(-radius*.9,1);  
16714         ctx.lineTo(0,4);  
16715         ctx.lineTo(0,0);  
16716         ctx.closePath(); 
16717         ctx.fill();
16718         ctx.restore(); 
16719         
16720         
16721         // stroke needle
16722         ctx.lineWidth = 1;
16723         ctx.strokeStyle = '#aa0000';
16724         ctx.save();
16725         ctx.rotate(needleAngle * Math.PI / 180);  
16726         ctx.beginPath();
16727         ctx.moveTo(0,0); 
16728         ctx.lineTo(0,-4);  
16729         ctx.lineTo(-radius*.8,-1);  
16730         ctx.lineTo(-radius*.8,1);  
16731         ctx.lineTo(0,4);  
16732         ctx.lineTo(0,0);  
16733         ctx.closePath(); 
16734         ctx.stroke();
16735         ctx.restore(); 
16736
16737         //needle cap
16738         ctx.fillStyle = "#000000";
16739         ctx.lineWidth = style.borderSize;
16740     ctx.strokeStyle = style.borderColor;
16741         var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16742         radialGradient.addColorStop(0, '#666666');  
16743         radialGradient.addColorStop(0.8, '#444444');  
16744         radialGradient.addColorStop(1, 'rgba(0,0,0,0)'); 
16745         ctx.fillStyle = radialGradient;
16746         ctx.translate(0,5);
16747         ctx.save();
16748         ctx.beginPath();
16749         ctx.arc(0,0,radius*.2,startAngle,endAngle, true); 
16750         ctx.fill();     
16751         ctx.restore();
16752
16753         
16754   },
16755   
16756   renderTicks: function(values) {
16757         var canvas = this.sb.canvas,
16758         config = this.config,
16759         style = config.gaugeStyle,
16760         ctx = canvas.getCtx(),
16761         size = canvas.getSize(),
16762         radius = this.sb.config.levelDistance,
16763         gaugeCenter = (radius/2);
16764         
16765         
16766         ctx.strokeStyle = style.borderColor;
16767         ctx.lineWidth = 5;
16768         ctx.lineCap = "round";
16769                 for(var i=0, total = 0, l=values.length; i<l; i++) {
16770                         var val = values[i];
16771                         if(val.label != 'GaugePosition') {
16772                         total += (parseInt(val.values) || 0);
16773                         }
16774                 }
16775         
16776                 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16777                         var val = values[i];
16778                         if(val.label != 'GaugePosition') {
16779                         acum += (parseInt(val.values) || 0);
16780
16781                            var segments = 180/total;
16782                         angle = acum * segments;
16783
16784                           //alert(acum);
16785                                  ctx.save();
16786                                  ctx.translate(0, gaugeCenter);
16787                                  ctx.beginPath();
16788                                 ctx.rotate(angle * (Math.PI/180));
16789                                 ctx.moveTo(-radius,0);
16790                                 ctx.lineTo(-radius*.75,0);
16791                                 ctx.stroke();
16792                                  ctx.restore();
16793                         
16794                         }
16795                 }
16796         },
16797         
16798         renderPositionLabel: function(position) {
16799                 var canvas = this.sb.canvas,
16800                 config = this.config,
16801                 label = config.Label,
16802                 style = config.gaugeStyle,
16803                 ctx = canvas.getCtx(),
16804                 size = canvas.getSize(),
16805                 radius = this.sb.config.levelDistance,
16806                 gaugeCenter = (radius/2);
16807                 ctx.textBaseline = 'middle';
16808                 ctx.textAlign = 'center';
16809                 ctx.font = style.positionFontSize + 'px ' + label.family;
16810                 ctx.fillStyle = "#ffffff";
16811                 ctx.lineWidth = 2;
16812                 height = style.positionFontSize + 10,
16813                 cornerRadius = 8,
16814                 idLabel = canvas.id + "-label";
16815                 container = document.getElementById(idLabel);
16816                 if(label.type == 'Native') {
16817                         var m = ctx.measureText(position),
16818                         width = m.width + 40;
16819                 } else {
16820                         var width = 70;
16821                 }
16822                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16823                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16824                 if(label.type == 'Native') {
16825                         ctx.fillStyle = label.color;
16826                         ctx.fillText(position, 0, (height/2) + style.positionOffset);
16827                 } else {
16828                         var labelDiv =  document.createElement('div');
16829                         labelDivStyle = labelDiv.style;
16830                         labelDivStyle.color =  label.color;
16831                         labelDivStyle.fontSize =  style.positionFontSize + "px";
16832                         labelDivStyle.position = "absolute";
16833                         labelDivStyle.width = width + "px";
16834                         labelDivStyle.left = (size.width/2) - (width/2) + "px";
16835                         labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16836                         labelDiv.innerHTML = position;
16837                         container.appendChild(labelDiv);
16838                 }
16839         
16840     },
16841     
16842    renderSubtitle: function() {
16843         var canvas = this.canvas,
16844         size = canvas.getSize(),
16845         config = this.config,
16846         margin = config.Margin,
16847         radius = this.sb.config.levelDistance,
16848         title = config.Title,
16849         label = config.Label,
16850         subtitle = config.Subtitle;
16851         ctx = canvas.getCtx();
16852         ctx.fillStyle = title.color;
16853         ctx.textAlign = 'left';
16854         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16855         ctx.moveTo(0,0);
16856         if(label.type == 'Native') {
16857                 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2)); 
16858         }
16859   },
16860   
16861   renderChartBackground: function() {
16862                 var canvas = this.canvas,
16863                 config = this.config,
16864                 backgroundColor = config.backgroundColor,
16865                 size = canvas.getSize(),
16866                 ctx = canvas.getCtx();
16867                 //ctx.globalCompositeOperation = "destination-over";
16868             ctx.fillStyle = backgroundColor;
16869             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
16870   },
16871   clear: function() {
16872         var canvas = this.canvas;
16873         var ctx = canvas.getCtx(),
16874         size = canvas.getSize();
16875         ctx.fillStyle = "rgba(255,255,255,0)";
16876         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16877         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16878  },
16879   resizeGraph: function(json,width) {
16880         var canvas = this.canvas,
16881         size = canvas.getSize(),
16882             orgHeight = size.height;
16883         
16884         canvas.resize(width,orgHeight);
16885         if(typeof FlashCanvas == "undefined") {
16886                 canvas.clear();
16887         } else {
16888                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16889         }
16890         this.loadJSON(json);
16891
16892         },
16893   loadJSON: function(json) {
16894   
16895      var prefix = $.time(), 
16896         ch = [], 
16897         sb = this.sb,
16898         name = $.splat(json.label),
16899         nameLength = name.length,
16900         color = $.splat(json.color || this.colors),
16901         colorLength = color.length,
16902         config = this.config,
16903         renderBackground = config.renderBackground,
16904         gradient = !!config.type.split(":")[1],
16905         animate = config.animate,
16906         mono = nameLength == 1;
16907                 var props = $.splat(json.properties)[0];
16908
16909     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16910         
16911       var val = values[i];
16912           if(val.label != 'GaugePosition') {
16913                   var valArray = $.splat(val.values);
16914                   var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16915                   var valuelabelsArray = $.splat(val.valuelabels);
16916
16917                   ch.push({
16918                         'id': prefix + val.label,
16919                         'name': val.label,
16920                         'data': {
16921                           'value': valArray,
16922                           'valuelabel': valuelabelsArray,
16923                           '$linkArray': linkArray,
16924                           '$valuelabelsArray': valuelabelsArray,
16925                           '$valueArray': valArray,
16926                           '$nodeIteration': i,
16927                           '$nodeLength': l-1,
16928                           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16929                           '$colorMono': $.splat(color[i % colorLength]),
16930                           '$stringArray': name,
16931                           '$gradient': gradient,
16932                           '$config': config,
16933                           '$gaugeTarget': props['gaugeTarget'],
16934                           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16935                         },
16936                         'children': []
16937                   });
16938           } else {
16939                 var gaugePosition = val.gvalue;
16940                 var gaugePositionLabel = val.gvaluelabel;
16941           }
16942     }
16943     var root = {
16944       'id': prefix + '$root',
16945       'name': '',
16946       'data': {
16947         '$type': 'none',
16948         '$width': 1,
16949         '$height': 1
16950       },
16951       'children': ch
16952     };
16953         
16954         
16955     sb.loadJSON(root);
16956     
16957     if(renderBackground) {
16958         this.renderChartBackground();   
16959     }
16960     
16961     this.renderBackground();
16962     this.renderSubtitle();
16963     
16964     this.normalizeDims();
16965         
16966     sb.refresh();
16967     if(animate) {
16968       sb.fx.animate({
16969         modes: ['node-property:dimArray'],
16970         duration:1500
16971       });
16972     }
16973         
16974
16975         this.renderPositionLabel(gaugePositionLabel);
16976         if (props['gaugeTarget'] != 0) {
16977                 this.renderTicks(json.values);
16978                 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16979         }
16980         
16981         
16982
16983   },
16984   
16985   updateJSON: function(json, onComplete) {
16986     if(this.busy) return;
16987     this.busy = true;
16988     
16989     var sb = this.sb;
16990     var graph = sb.graph;
16991     var values = json.values;
16992     var animate = this.config.animate;
16993     var that = this;
16994     $.each(values, function(v) {
16995       var n = graph.getByName(v.label),
16996           vals = $.splat(v.values);
16997       if(n) {
16998         n.setData('valueArray', vals);
16999         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
17000         if(json.label) {
17001           n.setData('stringArray', $.splat(json.label));
17002         }
17003       }
17004     });
17005     this.normalizeDims();
17006     if(animate) {
17007       sb.compute('end');
17008       sb.fx.animate({
17009         modes: ['node-property:dimArray:span', 'linear'],
17010         duration:1500,
17011         onComplete: function() {
17012           that.busy = false;
17013           onComplete && onComplete.onComplete();
17014         }
17015       });
17016     } else {
17017       sb.refresh();
17018     }
17019   },
17020     
17021   //adds the little brown bar when hovering the node
17022   select: function(id, name) {
17023     if(!this.config.hoveredColor) return;
17024     var s = this.selected;
17025     if(s.id != id || s.name != name) {
17026       s.id = id;
17027       s.name = name;
17028       s.color = this.config.hoveredColor;
17029       this.sb.graph.eachNode(function(n) {
17030         if(id == n.id) {
17031           n.setData('border', s);
17032         } else {
17033           n.setData('border', false);
17034         }
17035       });
17036       this.sb.plot();
17037     }
17038   },
17039   
17040   /*
17041     Method: getLegend
17042    
17043     Returns an object containing as keys the legend names and as values hex strings with color values.
17044     
17045     Example:
17046     
17047     (start code js)
17048     var legend = pieChart.getLegend();
17049     (end code)
17050   */  
17051   getLegend: function() {
17052     var legend = new Array();
17053     var name = new Array();
17054     var color = new Array();
17055     var n;
17056     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
17057       n = adj.nodeTo;
17058     });
17059     var colors = n.getData('colorArray'),
17060         len = colors.length;
17061     $.each(n.getData('stringArray'), function(s, i) {
17062       color[i] = colors[i % len];
17063       name[i] = s;
17064     });
17065         legend['name'] = name;
17066         legend['color'] = color;
17067     return legend;
17068   },
17069   
17070   /*
17071     Method: getMaxValue
17072    
17073     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
17074     
17075     Example:
17076     
17077     (start code js)
17078     var ans = pieChart.getMaxValue();
17079     (end code)
17080     
17081     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
17082     
17083     Example:
17084     
17085     (start code js)
17086     //will return 100 for all PieChart instances,
17087     //displaying all of them with the same scale
17088     $jit.PieChart.implement({
17089       'getMaxValue': function() {
17090         return 100;
17091       }
17092     });
17093     (end code)
17094     
17095   */  
17096   getMaxValue: function() {
17097     var maxValue = 0;
17098     this.sb.graph.eachNode(function(n) {
17099       var valArray = n.getData('valueArray'),
17100           acum = 0;
17101       $.each(valArray, function(v) { 
17102         acum += +v;
17103       });
17104       maxValue = maxValue>acum? maxValue:acum;
17105     });
17106     return maxValue;
17107   },
17108   
17109   normalizeDims: function() {
17110     //number of elements
17111     var root = this.sb.graph.getNode(this.sb.root), l=0;
17112     root.eachAdjacency(function() {
17113       l++;
17114     });
17115     var maxValue = this.getMaxValue() || 1,
17116         config = this.config,
17117         animate = config.animate,
17118         rho = this.sb.config.levelDistance;
17119     this.sb.graph.eachNode(function(n) {
17120       var acum = 0, animateValue = [];
17121       $.each(n.getData('valueArray'), function(v) {
17122         acum += +v;
17123         animateValue.push(1);
17124       });
17125       var stat = (animateValue.length == 1) && !config.updateHeights;
17126       if(animate) {
17127         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
17128           return stat? rho: (n * rho / maxValue); 
17129         }), 'end');
17130         var dimArray = n.getData('dimArray');
17131         if(!dimArray) {
17132           n.setData('dimArray', animateValue);
17133         }
17134       } else {
17135         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
17136           return stat? rho : (n * rho / maxValue); 
17137         }));
17138       }
17139       n.setData('normalizedDim', acum / maxValue);
17140     });
17141   }
17142 });
17143
17144
17145 /*
17146  * Class: Layouts.TM
17147  * 
17148  * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
17149  * 
17150  * Implemented By:
17151  * 
17152  * <TM>
17153  * 
17154  */
17155 Layouts.TM = {};
17156
17157 Layouts.TM.SliceAndDice = new Class({
17158   compute: function(prop) {
17159     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17160     this.controller.onBeforeCompute(root);
17161     var size = this.canvas.getSize(),
17162         config = this.config,
17163         width = size.width,
17164         height = size.height;
17165     this.graph.computeLevels(this.root, 0, "ignore");
17166     //set root position and dimensions
17167     root.getPos(prop).setc(-width/2, -height/2);
17168     root.setData('width', width, prop);
17169     root.setData('height', height + config.titleHeight, prop);
17170     this.computePositions(root, root, this.layout.orientation, prop);
17171     this.controller.onAfterCompute(root);
17172   },
17173   
17174   computePositions: function(par, ch, orn, prop) {
17175     //compute children areas
17176     var totalArea = 0;
17177     par.eachSubnode(function(n) {
17178       totalArea += n.getData('area', prop);
17179     });
17180     
17181     var config = this.config,
17182         offst = config.offset,
17183         width  = par.getData('width', prop),
17184         height = par.getData('height', prop) - config.titleHeight,
17185         fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
17186     
17187     var otherSize, size, dim, pos, pos2, posth, pos2th;
17188     var horizontal = (orn == "h");
17189     if(horizontal) {
17190       orn = 'v';    
17191       otherSize = height;
17192       size = width * fact;
17193       dim = 'height';
17194       pos = 'y';
17195       pos2 = 'x';
17196       posth = config.titleHeight;
17197       pos2th = 0;
17198     } else {
17199       orn = 'h';    
17200       otherSize = height * fact;
17201       size = width;
17202       dim = 'width';
17203       pos = 'x';
17204       pos2 = 'y';
17205       posth = 0;
17206       pos2th = config.titleHeight;
17207     }
17208     var cpos = ch.getPos(prop);
17209     ch.setData('width', size, prop);
17210     ch.setData('height', otherSize, prop);
17211     var offsetSize = 0, tm = this;
17212     ch.eachSubnode(function(n) {
17213       var p = n.getPos(prop);
17214       p[pos] = offsetSize + cpos[pos] + posth;
17215       p[pos2] = cpos[pos2] + pos2th;
17216       tm.computePositions(ch, n, orn, prop);
17217       offsetSize += n.getData(dim, prop);
17218     });
17219   }
17220
17221 });
17222
17223 Layouts.TM.Area = {
17224  /*
17225     Method: compute
17226  
17227    Called by loadJSON to calculate recursively all node positions and lay out the tree.
17228  
17229     Parameters:
17230
17231        json - A JSON tree. See also <Loader.loadJSON>.
17232        coord - A coordinates object specifying width, height, left and top style properties.
17233  */
17234  compute: function(prop) {
17235     prop = prop || "current";
17236     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17237     this.controller.onBeforeCompute(root);
17238     var config = this.config,
17239         size = this.canvas.getSize(),
17240         width = size.width,
17241         height = size.height,
17242         offst = config.offset,
17243         offwdth = width - offst,
17244         offhght = height - offst;
17245     this.graph.computeLevels(this.root, 0, "ignore");
17246     //set root position and dimensions
17247     root.getPos(prop).setc(-width/2, -height/2);
17248     root.setData('width', width, prop);
17249     root.setData('height', height, prop);
17250     //create a coordinates object
17251     var coord = {
17252         'top': -height/2 + config.titleHeight,
17253         'left': -width/2,
17254         'width': offwdth,
17255         'height': offhght - config.titleHeight
17256     };
17257     this.computePositions(root, coord, prop);
17258     this.controller.onAfterCompute(root);
17259  }, 
17260  
17261  /*
17262     Method: computeDim
17263  
17264    Computes dimensions and positions of a group of nodes
17265    according to a custom layout row condition. 
17266  
17267     Parameters:
17268
17269        tail - An array of nodes.  
17270        initElem - An array of nodes (containing the initial node to be laid).
17271        w - A fixed dimension where nodes will be layed out.
17272        coord - A coordinates object specifying width, height, left and top style properties.
17273        comp - A custom comparison function
17274  */
17275  computeDim: function(tail, initElem, w, coord, comp, prop) {
17276    if(tail.length + initElem.length == 1) {
17277      var l = (tail.length == 1)? tail : initElem;
17278      this.layoutLast(l, w, coord, prop);
17279      return;
17280    }
17281    if(tail.length >= 2 && initElem.length == 0) {
17282      initElem = [tail.shift()];
17283    }
17284    if(tail.length == 0) {
17285      if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17286      return;
17287    }
17288    var c = tail[0];
17289    if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17290      this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17291    } else {
17292      var newCoords = this.layoutRow(initElem, w, coord, prop);
17293      this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17294    }
17295  },
17296
17297  
17298  /*
17299     Method: worstAspectRatio
17300  
17301    Calculates the worst aspect ratio of a group of rectangles. 
17302        
17303     See also:
17304        
17305        <http://en.wikipedia.org/wiki/Aspect_ratio>
17306    
17307     Parameters:
17308
17309      ch - An array of nodes.  
17310      w  - The fixed dimension where rectangles are being laid out.
17311
17312     Returns:
17313  
17314         The worst aspect ratio.
17315
17316
17317  */
17318  worstAspectRatio: function(ch, w) {
17319    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17320    var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17321    for(var i=0, l=ch.length; i<l; i++) {
17322      var area = ch[i]._area;
17323      areaSum += area; 
17324      minArea = minArea < area? minArea : area;
17325      maxArea = maxArea > area? maxArea : area; 
17326    }
17327    var sqw = w * w, sqAreaSum = areaSum * areaSum;
17328    return Math.max(sqw * maxArea / sqAreaSum,
17329            sqAreaSum / (sqw * minArea));
17330  },
17331  
17332  /*
17333     Method: avgAspectRatio
17334  
17335    Calculates the average aspect ratio of a group of rectangles. 
17336        
17337        See also:
17338        
17339        <http://en.wikipedia.org/wiki/Aspect_ratio>
17340    
17341     Parameters:
17342
17343      ch - An array of nodes.  
17344        w - The fixed dimension where rectangles are being laid out.
17345
17346     Returns:
17347  
17348         The average aspect ratio.
17349
17350
17351  */
17352  avgAspectRatio: function(ch, w) {
17353    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17354    var arSum = 0;
17355    for(var i=0, l=ch.length; i<l; i++) {
17356      var area = ch[i]._area;
17357      var h = area / w;
17358      arSum += w > h? w / h : h / w;
17359    }
17360    return arSum / l;
17361  },
17362
17363  /*
17364     layoutLast
17365  
17366    Performs the layout of the last computed sibling.
17367  
17368     Parameters:
17369
17370        ch - An array of nodes.  
17371        w - A fixed dimension where nodes will be layed out.
17372      coord - A coordinates object specifying width, height, left and top style properties.
17373  */
17374  layoutLast: function(ch, w, coord, prop) {
17375    var child = ch[0];
17376    child.getPos(prop).setc(coord.left, coord.top);
17377    child.setData('width', coord.width, prop);
17378    child.setData('height', coord.height, prop);
17379  }
17380 };
17381
17382
17383 Layouts.TM.Squarified = new Class({
17384  Implements: Layouts.TM.Area,
17385  
17386  computePositions: function(node, coord, prop) {
17387    var config = this.config;
17388    
17389    if (coord.width >= coord.height) 
17390      this.layout.orientation = 'h';
17391    else
17392      this.layout.orientation = 'v';
17393    
17394    var ch = node.getSubnodes([1, 1], "ignore");
17395    if(ch.length > 0) {
17396      this.processChildrenLayout(node, ch, coord, prop);
17397      for(var i=0, l=ch.length; i<l; i++) {
17398        var chi = ch[i]; 
17399        var offst = config.offset,
17400            height = chi.getData('height', prop) - offst - config.titleHeight,
17401            width = chi.getData('width', prop) - offst;
17402        var chipos = chi.getPos(prop);
17403        coord = {
17404          'width': width,
17405          'height': height,
17406          'top': chipos.y + config.titleHeight,
17407          'left': chipos.x
17408        };
17409        this.computePositions(chi, coord, prop);
17410      }
17411    }
17412  },
17413
17414  /*
17415     Method: processChildrenLayout
17416  
17417    Computes children real areas and other useful parameters for performing the Squarified algorithm.
17418  
17419     Parameters:
17420
17421        par - The parent node of the json subtree.  
17422        ch - An Array of nodes
17423      coord - A coordinates object specifying width, height, left and top style properties.
17424  */
17425  processChildrenLayout: function(par, ch, coord, prop) {
17426    //compute children real areas
17427    var parentArea = coord.width * coord.height;
17428    var i, l=ch.length, totalChArea=0, chArea = [];
17429    for(i=0; i<l; i++) {
17430      chArea[i] = parseFloat(ch[i].getData('area', prop));
17431      totalChArea += chArea[i];
17432    }
17433    for(i=0; i<l; i++) {
17434      ch[i]._area = parentArea * chArea[i] / totalChArea;
17435    }
17436    var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17437    ch.sort(function(a, b) { 
17438      var diff = b._area - a._area; 
17439      return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1)); 
17440    });
17441    var initElem = [ch[0]];
17442    var tail = ch.slice(1);
17443    this.squarify(tail, initElem, minimumSideValue, coord, prop);
17444  },
17445
17446  /*
17447    Method: squarify
17448  
17449    Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17450  
17451     Parameters:
17452
17453        tail - An array of nodes.  
17454        initElem - An array of nodes, containing the initial node to be laid out.
17455        w - A fixed dimension where nodes will be laid out.
17456        coord - A coordinates object specifying width, height, left and top style properties.
17457  */
17458  squarify: function(tail, initElem, w, coord, prop) {
17459    this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17460  },
17461  
17462  /*
17463     Method: layoutRow
17464  
17465    Performs the layout of an array of nodes.
17466  
17467     Parameters:
17468
17469        ch - An array of nodes.  
17470        w - A fixed dimension where nodes will be laid out.
17471        coord - A coordinates object specifying width, height, left and top style properties.
17472  */
17473  layoutRow: function(ch, w, coord, prop) {
17474    if(this.layout.horizontal()) {
17475      return this.layoutV(ch, w, coord, prop);
17476    } else {
17477      return this.layoutH(ch, w, coord, prop);
17478    }
17479  },
17480  
17481  layoutV: function(ch, w, coord, prop) {
17482    var totalArea = 0, rnd = function(x) { return x; }; 
17483    $.each(ch, function(elem) { totalArea += elem._area; });
17484    var width = rnd(totalArea / w), top =  0; 
17485    for(var i=0, l=ch.length; i<l; i++) {
17486      var h = rnd(ch[i]._area / width);
17487      var chi = ch[i];
17488      chi.getPos(prop).setc(coord.left, coord.top + top);
17489      chi.setData('width', width, prop);
17490      chi.setData('height', h, prop);
17491      top += h;
17492    }
17493    var ans = {
17494      'height': coord.height,
17495      'width': coord.width - width,
17496      'top': coord.top,
17497      'left': coord.left + width
17498    };
17499    //take minimum side value.
17500    ans.dim = Math.min(ans.width, ans.height);
17501    if(ans.dim != ans.height) this.layout.change();
17502    return ans;
17503  },
17504  
17505  layoutH: function(ch, w, coord, prop) {
17506    var totalArea = 0; 
17507    $.each(ch, function(elem) { totalArea += elem._area; });
17508    var height = totalArea / w,
17509        top = coord.top, 
17510        left = 0;
17511    
17512    for(var i=0, l=ch.length; i<l; i++) {
17513      var chi = ch[i];
17514      var w = chi._area / height;
17515      chi.getPos(prop).setc(coord.left + left, top);
17516      chi.setData('width', w, prop);
17517      chi.setData('height', height, prop);
17518      left += w;
17519    }
17520    var ans = {
17521      'height': coord.height - height,
17522      'width': coord.width,
17523      'top': coord.top + height,
17524      'left': coord.left
17525    };
17526    ans.dim = Math.min(ans.width, ans.height);
17527    if(ans.dim != ans.width) this.layout.change();
17528    return ans;
17529  }
17530 });
17531
17532 Layouts.TM.Strip = new Class({
17533   Implements: Layouts.TM.Area,
17534
17535     /*
17536       Method: compute
17537     
17538      Called by loadJSON to calculate recursively all node positions and lay out the tree.
17539     
17540       Parameters:
17541     
17542          json - A JSON subtree. See also <Loader.loadJSON>. 
17543        coord - A coordinates object specifying width, height, left and top style properties.
17544     */
17545     computePositions: function(node, coord, prop) {
17546      var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17547      if(ch.length > 0) {
17548        this.processChildrenLayout(node, ch, coord, prop);
17549        for(var i=0, l=ch.length; i<l; i++) {
17550          var chi = ch[i];
17551          var offst = config.offset,
17552              height = chi.getData('height', prop) - offst - config.titleHeight,
17553              width  = chi.getData('width', prop)  - offst;
17554          var chipos = chi.getPos(prop);
17555          coord = {
17556            'width': width,
17557            'height': height,
17558            'top': chipos.y + config.titleHeight,
17559            'left': chipos.x
17560          };
17561          this.computePositions(chi, coord, prop);
17562        }
17563      }
17564     },
17565     
17566     /*
17567       Method: processChildrenLayout
17568     
17569      Computes children real areas and other useful parameters for performing the Strip algorithm.
17570     
17571       Parameters:
17572     
17573          par - The parent node of the json subtree.  
17574          ch - An Array of nodes
17575          coord - A coordinates object specifying width, height, left and top style properties.
17576     */
17577     processChildrenLayout: function(par, ch, coord, prop) {
17578      //compute children real areas
17579       var parentArea = coord.width * coord.height;
17580       var i, l=ch.length, totalChArea=0, chArea = [];
17581       for(i=0; i<l; i++) {
17582         chArea[i] = +ch[i].getData('area', prop);
17583         totalChArea += chArea[i];
17584       }
17585       for(i=0; i<l; i++) {
17586         ch[i]._area = parentArea * chArea[i] / totalChArea;
17587       }
17588      var side = this.layout.horizontal()? coord.width : coord.height;
17589      var initElem = [ch[0]];
17590      var tail = ch.slice(1);
17591      this.stripify(tail, initElem, side, coord, prop);
17592     },
17593     
17594     /*
17595       Method: stripify
17596     
17597      Performs an heuristic method to calculate div elements sizes in order to have 
17598      a good compromise between aspect ratio and order.
17599     
17600       Parameters:
17601     
17602          tail - An array of nodes.  
17603          initElem - An array of nodes.
17604          w - A fixed dimension where nodes will be layed out.
17605        coord - A coordinates object specifying width, height, left and top style properties.
17606     */
17607     stripify: function(tail, initElem, w, coord, prop) {
17608      this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17609     },
17610     
17611     /*
17612       Method: layoutRow
17613     
17614      Performs the layout of an array of nodes.
17615     
17616       Parameters:
17617     
17618          ch - An array of nodes.  
17619          w - A fixed dimension where nodes will be laid out.
17620          coord - A coordinates object specifying width, height, left and top style properties.
17621     */
17622     layoutRow: function(ch, w, coord, prop) {
17623      if(this.layout.horizontal()) {
17624        return this.layoutH(ch, w, coord, prop);
17625      } else {
17626        return this.layoutV(ch, w, coord, prop);
17627      }
17628     },
17629     
17630     layoutV: function(ch, w, coord, prop) {
17631      var totalArea = 0; 
17632      $.each(ch, function(elem) { totalArea += elem._area; });
17633      var width = totalArea / w, top =  0; 
17634      for(var i=0, l=ch.length; i<l; i++) {
17635        var chi = ch[i];
17636        var h = chi._area / width;
17637        chi.getPos(prop).setc(coord.left, 
17638            coord.top + (w - h - top));
17639        chi.setData('width', width, prop);
17640        chi.setData('height', h, prop);
17641        top += h;
17642      }
17643     
17644      return {
17645        'height': coord.height,
17646        'width': coord.width - width,
17647        'top': coord.top,
17648        'left': coord.left + width,
17649        'dim': w
17650      };
17651     },
17652     
17653     layoutH: function(ch, w, coord, prop) {
17654      var totalArea = 0; 
17655      $.each(ch, function(elem) { totalArea += elem._area; });
17656      var height = totalArea / w,
17657          top = coord.height - height, 
17658          left = 0;
17659      
17660      for(var i=0, l=ch.length; i<l; i++) {
17661        var chi = ch[i];
17662        var s = chi._area / height;
17663        chi.getPos(prop).setc(coord.left + left, coord.top + top);
17664        chi.setData('width', s, prop);
17665        chi.setData('height', height, prop);
17666        left += s;
17667      }
17668      return {
17669        'height': coord.height - height,
17670        'width': coord.width,
17671        'top': coord.top,
17672        'left': coord.left,
17673        'dim': w
17674      };
17675     }
17676  });
17677
17678 /*
17679  * Class: Layouts.Icicle
17680  *
17681  * Implements the icicle tree layout.
17682  *
17683  * Implemented By:
17684  *
17685  * <Icicle>
17686  *
17687  */
17688
17689 Layouts.Icicle = new Class({
17690  /*
17691   * Method: compute
17692   *
17693   * Called by loadJSON to calculate all node positions.
17694   *
17695   * Parameters:
17696   *
17697   * posType - The nodes' position to compute. Either "start", "end" or
17698   *            "current". Defaults to "current".
17699   */
17700   compute: function(posType) {
17701     posType = posType || "current";
17702     var root = this.graph.getNode(this.root),
17703         config = this.config,
17704         size = this.canvas.getSize(),
17705         width = size.width,
17706         height = size.height,
17707         offset = config.offset,
17708         levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17709
17710     this.controller.onBeforeCompute(root);
17711
17712     Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17713
17714     var treeDepth = 0;
17715
17716     Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17717
17718     var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17719     var maxDepth = Math.min(treeDepth, levelsToShow-1);
17720     var initialDepth = startNode._depth;
17721     if(this.layout.horizontal()) {
17722       this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17723     } else {
17724       this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17725     }
17726   },
17727
17728   computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17729     root.getPos(posType).setc(x, y);
17730     root.setData('width', width, posType);
17731     root.setData('height', height, posType);
17732
17733     var nodeLength, prevNodeLength = 0, totalDim = 0;
17734     var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17735
17736     if(!children.length)
17737       return;
17738
17739     $.each(children, function(e) { totalDim += e.getData('dim'); });
17740
17741     for(var i=0, l=children.length; i < l; i++) {
17742       if(this.layout.horizontal()) {
17743         nodeLength = height * children[i].getData('dim') / totalDim;
17744         this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17745         y += nodeLength;
17746       } else {
17747         nodeLength = width * children[i].getData('dim') / totalDim;
17748         this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17749         x += nodeLength;
17750       }
17751     }
17752   }
17753 });
17754
17755
17756
17757 /*
17758  * File: Icicle.js
17759  *
17760 */
17761
17762 /*
17763   Class: Icicle
17764   
17765   Icicle space filling visualization.
17766   
17767   Implements:
17768   
17769   All <Loader> methods
17770   
17771   Constructor Options:
17772   
17773   Inherits options from
17774   
17775   - <Options.Canvas>
17776   - <Options.Controller>
17777   - <Options.Node>
17778   - <Options.Edge>
17779   - <Options.Label>
17780   - <Options.Events>
17781   - <Options.Tips>
17782   - <Options.NodeStyles>
17783   - <Options.Navigation>
17784   
17785   Additionally, there are other parameters and some default values changed
17786
17787   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17788   offset - (number) Default's *2*. Boxes offset.
17789   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17790   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17791   animate - (boolean) Default's *false*. Whether to animate transitions.
17792   Node.type - Described in <Options.Node>. Default's *rectangle*.
17793   Label.type - Described in <Options.Label>. Default's *Native*.
17794   duration - Described in <Options.Fx>. Default's *700*.
17795   fps - Described in <Options.Fx>. Default's *45*.
17796   
17797   Instance Properties:
17798   
17799   canvas - Access a <Canvas> instance.
17800   graph - Access a <Graph> instance.
17801   op - Access a <Icicle.Op> instance.
17802   fx - Access a <Icicle.Plot> instance.
17803   labels - Access a <Icicle.Label> interface implementation.
17804
17805 */
17806
17807 $jit.Icicle = new Class({
17808   Implements: [ Loader, Extras, Layouts.Icicle ],
17809
17810   layout: {
17811     orientation: "h",
17812     vertical: function(){
17813       return this.orientation == "v";
17814     },
17815     horizontal: function(){
17816       return this.orientation == "h";
17817     },
17818     change: function(){
17819       this.orientation = this.vertical()? "h" : "v";
17820     }
17821   },
17822
17823   initialize: function(controller) {
17824     var config = {
17825       animate: false,
17826       orientation: "h",
17827       offset: 2,
17828       levelsToShow: Number.MAX_VALUE,
17829       constrained: false,
17830       Node: {
17831         type: 'rectangle',
17832         overridable: true
17833       },
17834       Edge: {
17835         type: 'none'
17836       },
17837       Label: {
17838         type: 'Native'
17839       },
17840       duration: 700,
17841       fps: 45
17842     };
17843
17844     var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17845                        "Events", "Navigation", "Controller", "Label");
17846     this.controller = this.config = $.merge(opts, config, controller);
17847     this.layout.orientation = this.config.orientation;
17848
17849     var canvasConfig = this.config;
17850     if (canvasConfig.useCanvas) {
17851       this.canvas = canvasConfig.useCanvas;
17852       this.config.labelContainer = this.canvas.id + '-label';
17853     } else {
17854       this.canvas = new Canvas(this, canvasConfig);
17855       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17856     }
17857
17858     this.graphOptions = {
17859       'complex': true,
17860       'Node': {
17861         'selected': false,
17862         'exist': true,
17863         'drawn': true
17864       }
17865     };
17866
17867     this.graph = new Graph(
17868       this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17869
17870     this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17871     this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17872     this.op = new $jit.Icicle.Op(this);
17873     this.group = new $jit.Icicle.Group(this);
17874     this.clickedNode = null;
17875
17876     this.initializeExtras();
17877   },
17878
17879   /* 
17880     Method: refresh 
17881     
17882     Computes positions and plots the tree.
17883   */
17884   refresh: function(){
17885     var labelType = this.config.Label.type;
17886     if(labelType != 'Native') {
17887       var that = this;
17888       this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17889     }
17890     this.compute();
17891     this.plot();
17892   },
17893
17894   /* 
17895     Method: plot 
17896     
17897     Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
17898   
17899    */
17900   plot: function(){
17901     this.fx.plot(this.config);
17902   },
17903
17904   /* 
17905     Method: enter 
17906     
17907     Sets the node as root.
17908     
17909      Parameters:
17910      
17911      node - (object) A <Graph.Node>.
17912   
17913    */
17914   enter: function (node) {
17915     if (this.busy)
17916       return;
17917     this.busy = true;
17918
17919     var that = this,
17920         config = this.config;
17921
17922     var callback = {
17923       onComplete: function() {
17924         //compute positions of newly inserted nodes
17925         if(config.request)
17926           that.compute();
17927
17928         if(config.animate) {
17929           that.graph.nodeList.setDataset(['current', 'end'], {
17930             'alpha': [1, 0] //fade nodes
17931           });
17932
17933           Graph.Util.eachSubgraph(node, function(n) {
17934             n.setData('alpha', 1, 'end');
17935           }, "ignore");
17936
17937           that.fx.animate({
17938             duration: 500,
17939             modes:['node-property:alpha'],
17940             onComplete: function() {
17941               that.clickedNode = node;
17942               that.compute('end');
17943
17944               that.fx.animate({
17945                 modes:['linear', 'node-property:width:height'],
17946                 duration: 1000,
17947                 onComplete: function() {
17948                   that.busy = false;
17949                   that.clickedNode = node;
17950                 }
17951               });
17952             }
17953           });
17954         } else {
17955           that.clickedNode = node;
17956           that.busy = false;
17957           that.refresh();
17958         }
17959       }
17960     };
17961
17962     if(config.request) {
17963       this.requestNodes(clickedNode, callback);
17964     } else {
17965       callback.onComplete();
17966     }
17967   },
17968
17969   /* 
17970     Method: out 
17971     
17972     Sets the parent node of the current selected node as root.
17973   
17974    */
17975   out: function(){
17976     if(this.busy)
17977       return;
17978
17979     var that = this,
17980         GUtil = Graph.Util,
17981         config = this.config,
17982         graph = this.graph,
17983         parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17984         parent = parents[0],
17985         clickedNode = parent,
17986         previousClickedNode = this.clickedNode;
17987
17988     this.busy = true;
17989     this.events.hoveredNode = false;
17990
17991     if(!parent) {
17992       this.busy = false;
17993       return;
17994     }
17995
17996     //final plot callback
17997     callback = {
17998       onComplete: function() {
17999         that.clickedNode = parent;
18000         if(config.request) {
18001           that.requestNodes(parent, {
18002             onComplete: function() {
18003               that.compute();
18004               that.plot();
18005               that.busy = false;
18006             }
18007           });
18008         } else {
18009           that.compute();
18010           that.plot();
18011           that.busy = false;
18012         }
18013       }
18014     };
18015
18016     //animate node positions
18017     if(config.animate) {
18018       this.clickedNode = clickedNode;
18019       this.compute('end');
18020       //animate the visible subtree only
18021       this.clickedNode = previousClickedNode;
18022       this.fx.animate({
18023         modes:['linear', 'node-property:width:height'],
18024         duration: 1000,
18025         onComplete: function() {
18026           //animate the parent subtree
18027           that.clickedNode = clickedNode;
18028           //change nodes alpha
18029           graph.nodeList.setDataset(['current', 'end'], {
18030             'alpha': [0, 1]
18031           });
18032           GUtil.eachSubgraph(previousClickedNode, function(node) {
18033             node.setData('alpha', 1);
18034           }, "ignore");
18035           that.fx.animate({
18036             duration: 500,
18037             modes:['node-property:alpha'],
18038             onComplete: function() {
18039               callback.onComplete();
18040             }
18041           });
18042         }
18043       });
18044     } else {
18045       callback.onComplete();
18046     }
18047   },
18048   requestNodes: function(node, onComplete){
18049     var handler = $.merge(this.controller, onComplete),
18050         levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
18051
18052     if (handler.request) {
18053       var leaves = [], d = node._depth;
18054       Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
18055         if (n.drawn && !Graph.Util.anySubnode(n)) {
18056           leaves.push(n);
18057           n._level = n._depth - d;
18058           if (this.config.constrained)
18059             n._level = levelsToShow - n._level;
18060
18061         }
18062       });
18063       this.group.requestNodes(leaves, handler);
18064     } else {
18065       handler.onComplete();
18066     }
18067   }
18068 });
18069
18070 /*
18071   Class: Icicle.Op
18072   
18073   Custom extension of <Graph.Op>.
18074   
18075   Extends:
18076   
18077   All <Graph.Op> methods
18078   
18079   See also:
18080   
18081   <Graph.Op>
18082   
18083   */
18084 $jit.Icicle.Op = new Class({
18085
18086   Implements: Graph.Op
18087
18088 });
18089
18090 /*
18091  * Performs operations on group of nodes.
18092  */
18093 $jit.Icicle.Group = new Class({
18094
18095   initialize: function(viz){
18096     this.viz = viz;
18097     this.canvas = viz.canvas;
18098     this.config = viz.config;
18099   },
18100
18101   /*
18102    * Calls the request method on the controller to request a subtree for each node.
18103    */
18104   requestNodes: function(nodes, controller){
18105     var counter = 0, len = nodes.length, nodeSelected = {};
18106     var complete = function(){
18107       controller.onComplete();
18108     };
18109     var viz = this.viz;
18110     if (len == 0)
18111       complete();
18112     for(var i = 0; i < len; i++) {
18113       nodeSelected[nodes[i].id] = nodes[i];
18114       controller.request(nodes[i].id, nodes[i]._level, {
18115         onComplete: function(nodeId, data){
18116           if (data && data.children) {
18117             data.id = nodeId;
18118             viz.op.sum(data, {
18119               type: 'nothing'
18120             });
18121           }
18122           if (++counter == len) {
18123             Graph.Util.computeLevels(viz.graph, viz.root, 0);
18124             complete();
18125           }
18126         }
18127       });
18128     }
18129   }
18130 });
18131
18132 /*
18133   Class: Icicle.Plot
18134   
18135   Custom extension of <Graph.Plot>.
18136   
18137   Extends:
18138   
18139   All <Graph.Plot> methods
18140   
18141   See also:
18142   
18143   <Graph.Plot>
18144   
18145   */
18146 $jit.Icicle.Plot = new Class({
18147   Implements: Graph.Plot,
18148
18149   plot: function(opt, animating){
18150     opt = opt || this.viz.controller;
18151     var viz = this.viz,
18152         graph = viz.graph,
18153         root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
18154         initialDepth = root._depth;
18155
18156     viz.canvas.clear();
18157     this.plotTree(root, $.merge(opt, {
18158       'withLabels': true,
18159       'hideLabels': false,
18160       'plotSubtree': function(root, node) {
18161         return !viz.config.constrained ||
18162                (node._depth - initialDepth < viz.config.levelsToShow);
18163       }
18164     }), animating);
18165   }
18166 });
18167
18168 /*
18169   Class: Icicle.Label
18170   
18171   Custom extension of <Graph.Label>. 
18172   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18173   
18174   Extends:
18175   
18176   All <Graph.Label> methods and subclasses.
18177   
18178   See also:
18179   
18180   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18181   
18182   */
18183 $jit.Icicle.Label = {};
18184
18185 /*
18186   Icicle.Label.Native
18187   
18188   Custom extension of <Graph.Label.Native>.
18189   
18190   Extends:
18191   
18192   All <Graph.Label.Native> methods
18193   
18194   See also:
18195   
18196   <Graph.Label.Native>
18197
18198   */
18199 $jit.Icicle.Label.Native = new Class({
18200   Implements: Graph.Label.Native,
18201
18202   renderLabel: function(canvas, node, controller) {
18203     var ctx = canvas.getCtx(),
18204         width = node.getData('width'),
18205         height = node.getData('height'),
18206         size = node.getLabelData('size'),
18207         m = ctx.measureText(node.name);
18208
18209     // Guess as much as possible if the label will fit in the node
18210     if(height < (size * 1.5) || width < m.width)
18211       return;
18212
18213     var pos = node.pos.getc(true);
18214     ctx.fillText(node.name,
18215                  pos.x + width / 2,
18216                  pos.y + height / 2);
18217   }
18218 });
18219
18220 /*
18221   Icicle.Label.SVG
18222   
18223   Custom extension of <Graph.Label.SVG>.
18224   
18225   Extends:
18226   
18227   All <Graph.Label.SVG> methods
18228   
18229   See also:
18230   
18231   <Graph.Label.SVG>
18232 */
18233 $jit.Icicle.Label.SVG = new Class( {
18234   Implements: Graph.Label.SVG,
18235
18236   initialize: function(viz){
18237     this.viz = viz;
18238   },
18239
18240   /*
18241     placeLabel
18242    
18243     Overrides abstract method placeLabel in <Graph.Plot>.
18244    
18245     Parameters:
18246    
18247     tag - A DOM label element.
18248     node - A <Graph.Node>.
18249     controller - A configuration/controller object passed to the visualization.
18250    */
18251   placeLabel: function(tag, node, controller){
18252     var pos = node.pos.getc(true), canvas = this.viz.canvas;
18253     var radius = canvas.getSize();
18254     var labelPos = {
18255       x: Math.round(pos.x + radius.width / 2),
18256       y: Math.round(pos.y + radius.height / 2)
18257     };
18258     tag.setAttribute('x', labelPos.x);
18259     tag.setAttribute('y', labelPos.y);
18260
18261     controller.onPlaceLabel(tag, node);
18262   }
18263 });
18264
18265 /*
18266   Icicle.Label.HTML
18267   
18268   Custom extension of <Graph.Label.HTML>.
18269   
18270   Extends:
18271   
18272   All <Graph.Label.HTML> methods.
18273   
18274   See also:
18275   
18276   <Graph.Label.HTML>
18277   
18278   */
18279 $jit.Icicle.Label.HTML = new Class( {
18280   Implements: Graph.Label.HTML,
18281
18282   initialize: function(viz){
18283     this.viz = viz;
18284   },
18285
18286   /*
18287     placeLabel
18288    
18289     Overrides abstract method placeLabel in <Graph.Plot>.
18290    
18291     Parameters:
18292    
18293     tag - A DOM label element.
18294     node - A <Graph.Node>.
18295     controller - A configuration/controller object passed to the visualization.
18296    */
18297   placeLabel: function(tag, node, controller){
18298     var pos = node.pos.getc(true), canvas = this.viz.canvas;
18299     var radius = canvas.getSize();
18300     var labelPos = {
18301       x: Math.round(pos.x + radius.width / 2),
18302       y: Math.round(pos.y + radius.height / 2)
18303     };
18304
18305     var style = tag.style;
18306     style.left = labelPos.x + 'px';
18307     style.top = labelPos.y + 'px';
18308     style.display = '';
18309
18310     controller.onPlaceLabel(tag, node);
18311   }
18312 });
18313
18314 /*
18315   Class: Icicle.Plot.NodeTypes
18316   
18317   This class contains a list of <Graph.Node> built-in types. 
18318   Node types implemented are 'none', 'rectangle'.
18319   
18320   You can add your custom node types, customizing your visualization to the extreme.
18321   
18322   Example:
18323   
18324   (start code js)
18325     Icicle.Plot.NodeTypes.implement({
18326       'mySpecialType': {
18327         'render': function(node, canvas) {
18328           //print your custom node to canvas
18329         },
18330         //optional
18331         'contains': function(node, pos) {
18332           //return true if pos is inside the node or false otherwise
18333         }
18334       }
18335     });
18336   (end code)
18337   
18338   */
18339 $jit.Icicle.Plot.NodeTypes = new Class( {
18340   'none': {
18341     'render': $.empty
18342   },
18343
18344   'rectangle': {
18345     'render': function(node, canvas, animating) {
18346       var config = this.viz.config;
18347       var offset = config.offset;
18348       var width = node.getData('width');
18349       var height = node.getData('height');
18350       var border = node.getData('border');
18351       var pos = node.pos.getc(true);
18352       var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18353       var ctx = canvas.getCtx();
18354       
18355       if(width - offset < 2 || height - offset < 2) return;
18356       
18357       if(config.cushion) {
18358         var color = node.getData('color');
18359         var lg = ctx.createRadialGradient(posx + (width - offset)/2, 
18360                                           posy + (height - offset)/2, 1, 
18361                                           posx + (width-offset)/2, posy + (height-offset)/2, 
18362                                           width < height? height : width);
18363         var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
18364             function(r) { return r * 0.3 >> 0; }));
18365         lg.addColorStop(0, color);
18366         lg.addColorStop(1, colorGrad);
18367         ctx.fillStyle = lg;
18368       }
18369
18370       if (border) {
18371         ctx.strokeStyle = border;
18372         ctx.lineWidth = 3;
18373       }
18374
18375       ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18376       border && ctx.strokeRect(pos.x, pos.y, width, height);
18377     },
18378
18379     'contains': function(node, pos) {
18380       if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18381       var npos = node.pos.getc(true),
18382           width = node.getData('width'),
18383           height = node.getData('height');
18384       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18385     }
18386   }
18387 });
18388
18389 $jit.Icicle.Plot.EdgeTypes = new Class( {
18390   'none': $.empty
18391 });
18392
18393
18394
18395 /*
18396  * File: Layouts.ForceDirected.js
18397  *
18398 */
18399
18400 /*
18401  * Class: Layouts.ForceDirected
18402  * 
18403  * Implements a Force Directed Layout.
18404  * 
18405  * Implemented By:
18406  * 
18407  * <ForceDirected>
18408  * 
18409  * Credits:
18410  * 
18411  * Marcus Cobden <http://marcuscobden.co.uk>
18412  * 
18413  */
18414 Layouts.ForceDirected = new Class({
18415
18416   getOptions: function(random) {
18417     var s = this.canvas.getSize();
18418     var w = s.width, h = s.height;
18419     //count nodes
18420     var count = 0;
18421     this.graph.eachNode(function(n) { 
18422       count++;
18423     });
18424     var k2 = w * h / count, k = Math.sqrt(k2);
18425     var l = this.config.levelDistance;
18426     
18427     return {
18428       width: w,
18429       height: h,
18430       tstart: w * 0.1,
18431       nodef: function(x) { return k2 / (x || 1); },
18432       edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18433     };
18434   },
18435   
18436   compute: function(property, incremental) {
18437     var prop = $.splat(property || ['current', 'start', 'end']);
18438     var opt = this.getOptions();
18439     NodeDim.compute(this.graph, prop, this.config);
18440     this.graph.computeLevels(this.root, 0, "ignore");
18441     this.graph.eachNode(function(n) {
18442       $.each(prop, function(p) {
18443         var pos = n.getPos(p);
18444         if(pos.equals(Complex.KER)) {
18445           pos.x = opt.width/5 * (Math.random() - 0.5);
18446           pos.y = opt.height/5 * (Math.random() - 0.5);
18447         }
18448         //initialize disp vector
18449         n.disp = {};
18450         $.each(prop, function(p) {
18451           n.disp[p] = $C(0, 0);
18452         });
18453       });
18454     });
18455     this.computePositions(prop, opt, incremental);
18456   },
18457   
18458   computePositions: function(property, opt, incremental) {
18459     var times = this.config.iterations, i = 0, that = this;
18460     if(incremental) {
18461       (function iter() {
18462         for(var total=incremental.iter, j=0; j<total; j++) {
18463           opt.t = opt.tstart * (1 - i++/(times -1));
18464           that.computePositionStep(property, opt);
18465           if(i >= times) {
18466             incremental.onComplete();
18467             return;
18468           }
18469         }
18470         incremental.onStep(Math.round(i / (times -1) * 100));
18471         setTimeout(iter, 1);
18472       })();
18473     } else {
18474       for(; i < times; i++) {
18475         opt.t = opt.tstart * (1 - i/(times -1));
18476         this.computePositionStep(property, opt);
18477       }
18478     }
18479   },
18480   
18481   computePositionStep: function(property, opt) {
18482     var graph = this.graph;
18483     var min = Math.min, max = Math.max;
18484     var dpos = $C(0, 0);
18485     //calculate repulsive forces
18486     graph.eachNode(function(v) {
18487       //initialize disp
18488       $.each(property, function(p) {
18489         v.disp[p].x = 0; v.disp[p].y = 0;
18490       });
18491       graph.eachNode(function(u) {
18492         if(u.id != v.id) {
18493           $.each(property, function(p) {
18494             var vp = v.getPos(p), up = u.getPos(p);
18495             dpos.x = vp.x - up.x;
18496             dpos.y = vp.y - up.y;
18497             var norm = dpos.norm() || 1;
18498             v.disp[p].$add(dpos
18499                 .$scale(opt.nodef(norm) / norm));
18500           });
18501         }
18502       });
18503     });
18504     //calculate attractive forces
18505     var T = !!graph.getNode(this.root).visited;
18506     graph.eachNode(function(node) {
18507       node.eachAdjacency(function(adj) {
18508         var nodeTo = adj.nodeTo;
18509         if(!!nodeTo.visited === T) {
18510           $.each(property, function(p) {
18511             var vp = node.getPos(p), up = nodeTo.getPos(p);
18512             dpos.x = vp.x - up.x;
18513             dpos.y = vp.y - up.y;
18514             var norm = dpos.norm() || 1;
18515             node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18516             nodeTo.disp[p].$add(dpos.$scale(-1));
18517           });
18518         }
18519       });
18520       node.visited = !T;
18521     });
18522     //arrange positions to fit the canvas
18523     var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18524     graph.eachNode(function(u) {
18525       $.each(property, function(p) {
18526         var disp = u.disp[p];
18527         var norm = disp.norm() || 1;
18528         var p = u.getPos(p);
18529         p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
18530             disp.y * min(Math.abs(disp.y), t) / norm));
18531         p.x = min(w2, max(-w2, p.x));
18532         p.y = min(h2, max(-h2, p.y));
18533       });
18534     });
18535   }
18536 });
18537
18538 /*
18539  * File: ForceDirected.js
18540  */
18541
18542 /*
18543    Class: ForceDirected
18544       
18545    A visualization that lays graphs using a Force-Directed layout algorithm.
18546    
18547    Inspired by:
18548   
18549    Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18550    
18551   Implements:
18552   
18553   All <Loader> methods
18554   
18555    Constructor Options:
18556    
18557    Inherits options from
18558    
18559    - <Options.Canvas>
18560    - <Options.Controller>
18561    - <Options.Node>
18562    - <Options.Edge>
18563    - <Options.Label>
18564    - <Options.Events>
18565    - <Options.Tips>
18566    - <Options.NodeStyles>
18567    - <Options.Navigation>
18568    
18569    Additionally, there are two parameters
18570    
18571    levelDistance - (number) Default's *50*. The natural length desired for the edges.
18572    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*. 
18573      
18574    Instance Properties:
18575
18576    canvas - Access a <Canvas> instance.
18577    graph - Access a <Graph> instance.
18578    op - Access a <ForceDirected.Op> instance.
18579    fx - Access a <ForceDirected.Plot> instance.
18580    labels - Access a <ForceDirected.Label> interface implementation.
18581
18582 */
18583
18584 $jit.ForceDirected = new Class( {
18585
18586   Implements: [ Loader, Extras, Layouts.ForceDirected ],
18587
18588   initialize: function(controller) {
18589     var $ForceDirected = $jit.ForceDirected;
18590
18591     var config = {
18592       iterations: 50,
18593       levelDistance: 50
18594     };
18595
18596     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18597         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18598
18599     var canvasConfig = this.config;
18600     if(canvasConfig.useCanvas) {
18601       this.canvas = canvasConfig.useCanvas;
18602       this.config.labelContainer = this.canvas.id + '-label';
18603     } else {
18604       if(canvasConfig.background) {
18605         canvasConfig.background = $.merge({
18606           type: 'Circles'
18607         }, canvasConfig.background);
18608       }
18609       this.canvas = new Canvas(this, canvasConfig);
18610       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18611     }
18612
18613     this.graphOptions = {
18614       'complex': true,
18615       'Node': {
18616         'selected': false,
18617         'exist': true,
18618         'drawn': true
18619       }
18620     };
18621     this.graph = new Graph(this.graphOptions, this.config.Node,
18622         this.config.Edge);
18623     this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18624     this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18625     this.op = new $ForceDirected.Op(this);
18626     this.json = null;
18627     this.busy = false;
18628     // initialize extras
18629     this.initializeExtras();
18630   },
18631
18632   /* 
18633     Method: refresh 
18634     
18635     Computes positions and plots the tree.
18636   */
18637   refresh: function() {
18638     this.compute();
18639     this.plot();
18640   },
18641
18642   reposition: function() {
18643     this.compute('end');
18644   },
18645
18646 /*
18647   Method: computeIncremental
18648   
18649   Performs the Force Directed algorithm incrementally.
18650   
18651   Description:
18652   
18653   ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
18654   This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
18655   avoiding browser messages such as "This script is taking too long to complete".
18656   
18657   Parameters:
18658   
18659   opt - (object) The object properties are described below
18660   
18661   iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
18662   of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18663   
18664   property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
18665   You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
18666   computations for final animation positions then you can just choose 'end'.
18667   
18668   onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
18669   parameter a percentage value.
18670   
18671   onComplete - A callback function called when the algorithm completed.
18672   
18673   Example:
18674   
18675   In this example I calculate the end positions and then animate the graph to those positions
18676   
18677   (start code js)
18678   var fd = new $jit.ForceDirected(...);
18679   fd.computeIncremental({
18680     iter: 20,
18681     property: 'end',
18682     onStep: function(perc) {
18683       Log.write("loading " + perc + "%");
18684     },
18685     onComplete: function() {
18686       Log.write("done");
18687       fd.animate();
18688     }
18689   });
18690   (end code)
18691   
18692   In this example I calculate all positions and (re)plot the graph
18693   
18694   (start code js)
18695   var fd = new ForceDirected(...);
18696   fd.computeIncremental({
18697     iter: 20,
18698     property: ['end', 'start', 'current'],
18699     onStep: function(perc) {
18700       Log.write("loading " + perc + "%");
18701     },
18702     onComplete: function() {
18703       Log.write("done");
18704       fd.plot();
18705     }
18706   });
18707   (end code)
18708   
18709   */
18710   computeIncremental: function(opt) {
18711     opt = $.merge( {
18712       iter: 20,
18713       property: 'end',
18714       onStep: $.empty,
18715       onComplete: $.empty
18716     }, opt || {});
18717
18718     this.config.onBeforeCompute(this.graph.getNode(this.root));
18719     this.compute(opt.property, opt);
18720   },
18721
18722   /*
18723     Method: plot
18724    
18725     Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18726    */
18727   plot: function() {
18728     this.fx.plot();
18729   },
18730
18731   /*
18732      Method: animate
18733     
18734      Animates the graph from the current positions to the 'end' node positions.
18735   */
18736   animate: function(opt) {
18737     this.fx.animate($.merge( {
18738       modes: [ 'linear' ]
18739     }, opt || {}));
18740   }
18741 });
18742
18743 $jit.ForceDirected.$extend = true;
18744
18745 (function(ForceDirected) {
18746
18747   /*
18748      Class: ForceDirected.Op
18749      
18750      Custom extension of <Graph.Op>.
18751
18752      Extends:
18753
18754      All <Graph.Op> methods
18755      
18756      See also:
18757      
18758      <Graph.Op>
18759
18760   */
18761   ForceDirected.Op = new Class( {
18762
18763     Implements: Graph.Op
18764
18765   });
18766
18767   /*
18768     Class: ForceDirected.Plot
18769     
18770     Custom extension of <Graph.Plot>.
18771   
18772     Extends:
18773   
18774     All <Graph.Plot> methods
18775     
18776     See also:
18777     
18778     <Graph.Plot>
18779   
18780   */
18781   ForceDirected.Plot = new Class( {
18782
18783     Implements: Graph.Plot
18784
18785   });
18786
18787   /*
18788     Class: ForceDirected.Label
18789     
18790     Custom extension of <Graph.Label>. 
18791     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18792   
18793     Extends:
18794   
18795     All <Graph.Label> methods and subclasses.
18796   
18797     See also:
18798   
18799     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18800   
18801   */
18802   ForceDirected.Label = {};
18803
18804   /*
18805      ForceDirected.Label.Native
18806      
18807      Custom extension of <Graph.Label.Native>.
18808
18809      Extends:
18810
18811      All <Graph.Label.Native> methods
18812
18813      See also:
18814
18815      <Graph.Label.Native>
18816
18817   */
18818   ForceDirected.Label.Native = new Class( {
18819     Implements: Graph.Label.Native
18820   });
18821
18822   /*
18823     ForceDirected.Label.SVG
18824     
18825     Custom extension of <Graph.Label.SVG>.
18826   
18827     Extends:
18828   
18829     All <Graph.Label.SVG> methods
18830   
18831     See also:
18832   
18833     <Graph.Label.SVG>
18834   
18835   */
18836   ForceDirected.Label.SVG = new Class( {
18837     Implements: Graph.Label.SVG,
18838
18839     initialize: function(viz) {
18840       this.viz = viz;
18841     },
18842
18843     /* 
18844        placeLabel
18845
18846        Overrides abstract method placeLabel in <Graph.Label>.
18847
18848        Parameters:
18849
18850        tag - A DOM label element.
18851        node - A <Graph.Node>.
18852        controller - A configuration/controller object passed to the visualization.
18853       
18854      */
18855     placeLabel: function(tag, node, controller) {
18856       var pos = node.pos.getc(true), 
18857           canvas = this.viz.canvas,
18858           ox = canvas.translateOffsetX,
18859           oy = canvas.translateOffsetY,
18860           sx = canvas.scaleOffsetX,
18861           sy = canvas.scaleOffsetY,
18862           radius = canvas.getSize();
18863       var labelPos = {
18864         x: Math.round(pos.x * sx + ox + radius.width / 2),
18865         y: Math.round(pos.y * sy + oy + radius.height / 2)
18866       };
18867       tag.setAttribute('x', labelPos.x);
18868       tag.setAttribute('y', labelPos.y);
18869
18870       controller.onPlaceLabel(tag, node);
18871     }
18872   });
18873
18874   /*
18875      ForceDirected.Label.HTML
18876      
18877      Custom extension of <Graph.Label.HTML>.
18878
18879      Extends:
18880
18881      All <Graph.Label.HTML> methods.
18882
18883      See also:
18884
18885      <Graph.Label.HTML>
18886
18887   */
18888   ForceDirected.Label.HTML = new Class( {
18889     Implements: Graph.Label.HTML,
18890
18891     initialize: function(viz) {
18892       this.viz = viz;
18893     },
18894     /* 
18895        placeLabel
18896
18897        Overrides abstract method placeLabel in <Graph.Plot>.
18898
18899        Parameters:
18900
18901        tag - A DOM label element.
18902        node - A <Graph.Node>.
18903        controller - A configuration/controller object passed to the visualization.
18904       
18905      */
18906     placeLabel: function(tag, node, controller) {
18907       var pos = node.pos.getc(true), 
18908           canvas = this.viz.canvas,
18909           ox = canvas.translateOffsetX,
18910           oy = canvas.translateOffsetY,
18911           sx = canvas.scaleOffsetX,
18912           sy = canvas.scaleOffsetY,
18913           radius = canvas.getSize();
18914       var labelPos = {
18915         x: Math.round(pos.x * sx + ox + radius.width / 2),
18916         y: Math.round(pos.y * sy + oy + radius.height / 2)
18917       };
18918       var style = tag.style;
18919       style.left = labelPos.x + 'px';
18920       style.top = labelPos.y + 'px';
18921       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18922
18923       controller.onPlaceLabel(tag, node);
18924     }
18925   });
18926
18927   /*
18928     Class: ForceDirected.Plot.NodeTypes
18929
18930     This class contains a list of <Graph.Node> built-in types. 
18931     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18932
18933     You can add your custom node types, customizing your visualization to the extreme.
18934
18935     Example:
18936
18937     (start code js)
18938       ForceDirected.Plot.NodeTypes.implement({
18939         'mySpecialType': {
18940           'render': function(node, canvas) {
18941             //print your custom node to canvas
18942           },
18943           //optional
18944           'contains': function(node, pos) {
18945             //return true if pos is inside the node or false otherwise
18946           }
18947         }
18948       });
18949     (end code)
18950
18951   */
18952   ForceDirected.Plot.NodeTypes = new Class({
18953     'none': {
18954       'render': $.empty,
18955       'contains': $.lambda(false)
18956     },
18957     'circle': {
18958       'render': function(node, canvas){
18959         var pos = node.pos.getc(true), 
18960             dim = node.getData('dim');
18961         this.nodeHelper.circle.render('fill', pos, dim, canvas);
18962       },
18963       'contains': function(node, pos){
18964         var npos = node.pos.getc(true), 
18965             dim = node.getData('dim');
18966         return this.nodeHelper.circle.contains(npos, pos, dim);
18967       }
18968     },
18969     'ellipse': {
18970       'render': function(node, canvas){
18971         var pos = node.pos.getc(true), 
18972             width = node.getData('width'), 
18973             height = node.getData('height');
18974         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18975         },
18976       // TODO(nico): be more precise...
18977       'contains': function(node, pos){
18978         var npos = node.pos.getc(true), 
18979             width = node.getData('width'), 
18980             height = node.getData('height');
18981         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18982       }
18983     },
18984     'square': {
18985       'render': function(node, canvas){
18986         var pos = node.pos.getc(true), 
18987             dim = node.getData('dim');
18988         this.nodeHelper.square.render('fill', pos, dim, canvas);
18989       },
18990       'contains': function(node, pos){
18991         var npos = node.pos.getc(true), 
18992             dim = node.getData('dim');
18993         return this.nodeHelper.square.contains(npos, pos, dim);
18994       }
18995     },
18996     'rectangle': {
18997       'render': function(node, canvas){
18998         var pos = node.pos.getc(true), 
18999             width = node.getData('width'), 
19000             height = node.getData('height');
19001         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
19002       },
19003       'contains': function(node, pos){
19004         var npos = node.pos.getc(true), 
19005             width = node.getData('width'), 
19006             height = node.getData('height');
19007         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
19008       }
19009     },
19010     'triangle': {
19011       'render': function(node, canvas){
19012         var pos = node.pos.getc(true), 
19013             dim = node.getData('dim');
19014         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
19015       },
19016       'contains': function(node, pos) {
19017         var npos = node.pos.getc(true), 
19018             dim = node.getData('dim');
19019         return this.nodeHelper.triangle.contains(npos, pos, dim);
19020       }
19021     },
19022     'star': {
19023       'render': function(node, canvas){
19024         var pos = node.pos.getc(true),
19025             dim = node.getData('dim');
19026         this.nodeHelper.star.render('fill', pos, dim, canvas);
19027       },
19028       'contains': function(node, pos) {
19029         var npos = node.pos.getc(true),
19030             dim = node.getData('dim');
19031         return this.nodeHelper.star.contains(npos, pos, dim);
19032       }
19033     }
19034   });
19035
19036   /*
19037     Class: ForceDirected.Plot.EdgeTypes
19038   
19039     This class contains a list of <Graph.Adjacence> built-in types. 
19040     Edge types implemented are 'none', 'line' and 'arrow'.
19041   
19042     You can add your custom edge types, customizing your visualization to the extreme.
19043   
19044     Example:
19045   
19046     (start code js)
19047       ForceDirected.Plot.EdgeTypes.implement({
19048         'mySpecialType': {
19049           'render': function(adj, canvas) {
19050             //print your custom edge to canvas
19051           },
19052           //optional
19053           'contains': function(adj, pos) {
19054             //return true if pos is inside the arc or false otherwise
19055           }
19056         }
19057       });
19058     (end code)
19059   
19060   */
19061   ForceDirected.Plot.EdgeTypes = new Class({
19062     'none': $.empty,
19063     'line': {
19064       'render': function(adj, canvas) {
19065         var from = adj.nodeFrom.pos.getc(true),
19066             to = adj.nodeTo.pos.getc(true);
19067         this.edgeHelper.line.render(from, to, canvas);
19068       },
19069       'contains': function(adj, pos) {
19070         var from = adj.nodeFrom.pos.getc(true),
19071             to = adj.nodeTo.pos.getc(true);
19072         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
19073       }
19074     },
19075     'arrow': {
19076       'render': function(adj, canvas) {
19077         var from = adj.nodeFrom.pos.getc(true),
19078             to = adj.nodeTo.pos.getc(true),
19079             dim = adj.getData('dim'),
19080             direction = adj.data.$direction,
19081             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
19082         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
19083       },
19084       'contains': function(adj, pos) {
19085         var from = adj.nodeFrom.pos.getc(true),
19086             to = adj.nodeTo.pos.getc(true);
19087         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
19088       }
19089     }
19090   });
19091
19092 })($jit.ForceDirected);
19093
19094
19095 /*
19096  * File: Treemap.js
19097  *
19098 */
19099
19100 $jit.TM = {};
19101
19102 var TM = $jit.TM;
19103
19104 $jit.TM.$extend = true;
19105
19106 /*
19107   Class: TM.Base
19108   
19109   Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
19110   
19111   Implements:
19112   
19113   All <Loader> methods
19114   
19115   Constructor Options:
19116   
19117   Inherits options from
19118   
19119   - <Options.Canvas>
19120   - <Options.Controller>
19121   - <Options.Node>
19122   - <Options.Edge>
19123   - <Options.Label>
19124   - <Options.Events>
19125   - <Options.Tips>
19126   - <Options.NodeStyles>
19127   - <Options.Navigation>
19128   
19129   Additionally, there are other parameters and some default values changed
19130
19131   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
19132   titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
19133   offset - (number) Default's *2*. Boxes offset.
19134   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
19135   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
19136   animate - (boolean) Default's *false*. Whether to animate transitions.
19137   Node.type - Described in <Options.Node>. Default's *rectangle*.
19138   duration - Described in <Options.Fx>. Default's *700*.
19139   fps - Described in <Options.Fx>. Default's *45*.
19140   
19141   Instance Properties:
19142   
19143   canvas - Access a <Canvas> instance.
19144   graph - Access a <Graph> instance.
19145   op - Access a <TM.Op> instance.
19146   fx - Access a <TM.Plot> instance.
19147   labels - Access a <TM.Label> interface implementation.
19148
19149   Inspired by:
19150   
19151   Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
19152   
19153   Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
19154   
19155    Note:
19156    
19157    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.
19158
19159 */
19160 TM.Base = {
19161   layout: {
19162     orientation: "h",
19163     vertical: function(){
19164       return this.orientation == "v";
19165     },
19166     horizontal: function(){
19167       return this.orientation == "h";
19168     },
19169     change: function(){
19170       this.orientation = this.vertical()? "h" : "v";
19171     }
19172   },
19173
19174   initialize: function(controller){
19175     var config = {
19176       orientation: "h",
19177       titleHeight: 13,
19178       offset: 2,
19179       levelsToShow: 0,
19180       constrained: false,
19181       animate: false,
19182       Node: {
19183         type: 'rectangle',
19184         overridable: true,
19185         //we all know why this is not zero,
19186         //right, Firefox?
19187         width: 3,
19188         height: 3,
19189         color: '#444'
19190       },
19191       Label: {
19192         textAlign: 'center',
19193         textBaseline: 'top'
19194       },
19195       Edge: {
19196         type: 'none'
19197       },
19198       duration: 700,
19199       fps: 45
19200     };
19201
19202     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19203         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19204     this.layout.orientation = this.config.orientation;
19205
19206     var canvasConfig = this.config;
19207     if (canvasConfig.useCanvas) {
19208       this.canvas = canvasConfig.useCanvas;
19209       this.config.labelContainer = this.canvas.id + '-label';
19210     } else {
19211       if(canvasConfig.background) {
19212         canvasConfig.background = $.merge({
19213           type: 'Circles'
19214         }, canvasConfig.background);
19215       }
19216       this.canvas = new Canvas(this, canvasConfig);
19217       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19218     }
19219
19220     this.graphOptions = {
19221       'complex': true,
19222       'Node': {
19223         'selected': false,
19224         'exist': true,
19225         'drawn': true
19226       }
19227     };
19228     this.graph = new Graph(this.graphOptions, this.config.Node,
19229         this.config.Edge);
19230     this.labels = new TM.Label[canvasConfig.Label.type](this);
19231     this.fx = new TM.Plot(this);
19232     this.op = new TM.Op(this);
19233     this.group = new TM.Group(this);
19234     this.geom = new TM.Geom(this);
19235     this.clickedNode = null;
19236     this.busy = false;
19237     // initialize extras
19238     this.initializeExtras();
19239   },
19240
19241   /* 
19242     Method: refresh 
19243     
19244     Computes positions and plots the tree.
19245   */
19246   refresh: function(){
19247     if(this.busy) return;
19248     this.busy = true;
19249     var that = this;
19250     if(this.config.animate) {
19251       this.compute('end');
19252       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
19253           && this.clickedNode.id || this.root));
19254       this.fx.animate($.merge(this.config, {
19255         modes: ['linear', 'node-property:width:height'],
19256         onComplete: function() {
19257           that.busy = false;
19258         }
19259       }));
19260     } else {
19261       var labelType = this.config.Label.type;
19262       if(labelType != 'Native') {
19263         var that = this;
19264         this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
19265       }
19266       this.busy = false;
19267       this.compute();
19268       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
19269           && this.clickedNode.id || this.root));
19270       this.plot();
19271     }
19272   },
19273
19274   /* 
19275     Method: plot 
19276     
19277     Plots the TreeMap. This is a shortcut to *fx.plot*. 
19278   
19279    */
19280   plot: function(){
19281     this.fx.plot();
19282   },
19283
19284   /* 
19285   Method: leaf 
19286   
19287   Returns whether the node is a leaf.
19288   
19289    Parameters:
19290    
19291    n - (object) A <Graph.Node>.
19292
19293  */
19294   leaf: function(n){
19295     return n.getSubnodes([
19296         1, 1
19297     ], "ignore").length == 0;
19298   },
19299   
19300   /* 
19301   Method: enter 
19302   
19303   Sets the node as root.
19304   
19305    Parameters:
19306    
19307    n - (object) A <Graph.Node>.
19308
19309  */
19310   enter: function(n){
19311     if(this.busy) return;
19312     this.busy = true;
19313     
19314     var that = this,
19315         config = this.config,
19316         graph = this.graph,
19317         clickedNode = n,
19318         previousClickedNode = this.clickedNode;
19319
19320     var callback = {
19321       onComplete: function() {
19322         //ensure that nodes are shown for that level
19323         if(config.levelsToShow > 0) {
19324           that.geom.setRightLevelToShow(n);
19325         }
19326         //compute positions of newly inserted nodes
19327         if(config.levelsToShow > 0 || config.request) that.compute();
19328         if(config.animate) {
19329           //fade nodes
19330           graph.nodeList.setData('alpha', 0, 'end');
19331           n.eachSubgraph(function(n) {
19332             n.setData('alpha', 1, 'end');
19333           }, "ignore");
19334           that.fx.animate({
19335             duration: 500,
19336             modes:['node-property:alpha'],
19337             onComplete: function() {
19338               //compute end positions
19339               that.clickedNode = clickedNode;
19340               that.compute('end');
19341               //animate positions
19342               //TODO(nico) commenting this line didn't seem to throw errors...
19343               that.clickedNode = previousClickedNode;
19344               that.fx.animate({
19345                 modes:['linear', 'node-property:width:height'],
19346                 duration: 1000,
19347                 onComplete: function() { 
19348                   that.busy = false;
19349                   //TODO(nico) check comment above
19350                   that.clickedNode = clickedNode;
19351                 }
19352               });
19353             }
19354           });
19355         } else {
19356           that.busy = false;
19357           that.clickedNode = n;
19358           that.refresh();
19359         }
19360       }
19361     };
19362     if(config.request) {
19363       this.requestNodes(clickedNode, callback);
19364     } else {
19365       callback.onComplete();
19366     }
19367   },
19368
19369   /* 
19370   Method: out 
19371   
19372   Sets the parent node of the current selected node as root.
19373
19374  */
19375   out: function(){
19376     if(this.busy) return;
19377     this.busy = true;
19378     this.events.hoveredNode = false;
19379     var that = this,
19380         config = this.config,
19381         graph = this.graph,
19382         parents = graph.getNode(this.clickedNode 
19383             && this.clickedNode.id || this.root).getParents(),
19384         parent = parents[0],
19385         clickedNode = parent,
19386         previousClickedNode = this.clickedNode;
19387     
19388     //if no parents return
19389     if(!parent) {
19390       this.busy = false;
19391       return;
19392     }
19393     //final plot callback
19394     callback = {
19395       onComplete: function() {
19396         that.clickedNode = parent;
19397         if(config.request) {
19398           that.requestNodes(parent, {
19399             onComplete: function() {
19400               that.compute();
19401               that.plot();
19402               that.busy = false;
19403             }
19404           });
19405         } else {
19406           that.compute();
19407           that.plot();
19408           that.busy = false;
19409         }
19410       }
19411     };
19412     //prune tree
19413     if (config.levelsToShow > 0)
19414       this.geom.setRightLevelToShow(parent);
19415     //animate node positions
19416     if(config.animate) {
19417       this.clickedNode = clickedNode;
19418       this.compute('end');
19419       //animate the visible subtree only
19420       this.clickedNode = previousClickedNode;
19421       this.fx.animate({
19422         modes:['linear', 'node-property:width:height'],
19423         duration: 1000,
19424         onComplete: function() {
19425           //animate the parent subtree
19426           that.clickedNode = clickedNode;
19427           //change nodes alpha
19428           graph.eachNode(function(n) {
19429             n.setDataset(['current', 'end'], {
19430               'alpha': [0, 1]
19431             });
19432           }, "ignore");
19433           previousClickedNode.eachSubgraph(function(node) {
19434             node.setData('alpha', 1);
19435           }, "ignore");
19436           that.fx.animate({
19437             duration: 500,
19438             modes:['node-property:alpha'],
19439             onComplete: function() {
19440               callback.onComplete();
19441             }
19442           });
19443         }
19444       });
19445     } else {
19446       callback.onComplete();
19447     }
19448   },
19449
19450   requestNodes: function(node, onComplete){
19451     var handler = $.merge(this.controller, onComplete), 
19452         lev = this.config.levelsToShow;
19453     if (handler.request) {
19454       var leaves = [], d = node._depth;
19455       node.eachLevel(0, lev, function(n){
19456         var nodeLevel = lev - (n._depth - d);
19457         if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19458           leaves.push(n);
19459           n._level = nodeLevel;
19460         }
19461       });
19462       this.group.requestNodes(leaves, handler);
19463     } else {
19464       handler.onComplete();
19465     }
19466   }
19467 };
19468
19469 /*
19470   Class: TM.Op
19471   
19472   Custom extension of <Graph.Op>.
19473   
19474   Extends:
19475   
19476   All <Graph.Op> methods
19477   
19478   See also:
19479   
19480   <Graph.Op>
19481   
19482   */
19483 TM.Op = new Class({
19484   Implements: Graph.Op,
19485
19486   initialize: function(viz){
19487     this.viz = viz;
19488   }
19489 });
19490
19491 //extend level methods of Graph.Geom
19492 TM.Geom = new Class({
19493   Implements: Graph.Geom,
19494   
19495   getRightLevelToShow: function() {
19496     return this.viz.config.levelsToShow;
19497   },
19498   
19499   setRightLevelToShow: function(node) {
19500     var level = this.getRightLevelToShow(), 
19501         fx = this.viz.labels;
19502     node.eachLevel(0, level+1, function(n) {
19503       var d = n._depth - node._depth;
19504       if(d > level) {
19505         n.drawn = false; 
19506         n.exist = false;
19507         n.ignore = true;
19508         fx.hideLabel(n, false);
19509       } else {
19510         n.drawn = true;
19511         n.exist = true;
19512         delete n.ignore;
19513       }
19514     });
19515     node.drawn = true;
19516     delete node.ignore;
19517   }
19518 });
19519
19520 /*
19521
19522 Performs operations on group of nodes.
19523
19524 */
19525 TM.Group = new Class( {
19526
19527   initialize: function(viz){
19528     this.viz = viz;
19529     this.canvas = viz.canvas;
19530     this.config = viz.config;
19531   },
19532
19533   /*
19534   
19535     Calls the request method on the controller to request a subtree for each node. 
19536   */
19537   requestNodes: function(nodes, controller){
19538     var counter = 0, len = nodes.length, nodeSelected = {};
19539     var complete = function(){
19540       controller.onComplete();
19541     };
19542     var viz = this.viz;
19543     if (len == 0)
19544       complete();
19545     for ( var i = 0; i < len; i++) {
19546       nodeSelected[nodes[i].id] = nodes[i];
19547       controller.request(nodes[i].id, nodes[i]._level, {
19548         onComplete: function(nodeId, data){
19549           if (data && data.children) {
19550             data.id = nodeId;
19551             viz.op.sum(data, {
19552               type: 'nothing'
19553             });
19554           }
19555           if (++counter == len) {
19556             viz.graph.computeLevels(viz.root, 0);
19557             complete();
19558           }
19559         }
19560       });
19561     }
19562   }
19563 });
19564
19565 /*
19566   Class: TM.Plot
19567   
19568   Custom extension of <Graph.Plot>.
19569   
19570   Extends:
19571   
19572   All <Graph.Plot> methods
19573   
19574   See also:
19575   
19576   <Graph.Plot>
19577   
19578   */
19579 TM.Plot = new Class({
19580
19581   Implements: Graph.Plot,
19582
19583   initialize: function(viz){
19584     this.viz = viz;
19585     this.config = viz.config;
19586     this.node = this.config.Node;
19587     this.edge = this.config.Edge;
19588     this.animation = new Animation;
19589     this.nodeTypes = new TM.Plot.NodeTypes;
19590     this.edgeTypes = new TM.Plot.EdgeTypes;
19591     this.labels = viz.labels;
19592   },
19593
19594   plot: function(opt, animating){
19595     var viz = this.viz, 
19596         graph = viz.graph;
19597     viz.canvas.clear();
19598     this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19599       'withLabels': true,
19600       'hideLabels': false,
19601       'plotSubtree': function(n, ch){
19602         return n.anySubnode("exist");
19603       }
19604     }), animating);
19605   }
19606 });
19607
19608 /*
19609   Class: TM.Label
19610   
19611   Custom extension of <Graph.Label>. 
19612   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19613
19614   Extends:
19615
19616   All <Graph.Label> methods and subclasses.
19617
19618   See also:
19619
19620   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19621   
19622 */
19623 TM.Label = {};
19624
19625 /*
19626  TM.Label.Native
19627
19628  Custom extension of <Graph.Label.Native>.
19629
19630  Extends:
19631
19632  All <Graph.Label.Native> methods
19633
19634  See also:
19635
19636  <Graph.Label.Native>
19637 */
19638 TM.Label.Native = new Class({
19639   Implements: Graph.Label.Native,
19640
19641   initialize: function(viz) {
19642     this.config = viz.config;
19643     this.leaf = viz.leaf;
19644   },
19645   
19646   renderLabel: function(canvas, node, controller){
19647     if(!this.leaf(node) && !this.config.titleHeight) return;
19648     var pos = node.pos.getc(true), 
19649         ctx = canvas.getCtx(),
19650         width = node.getData('width'),
19651         height = node.getData('height'),
19652         x = pos.x + width/2,
19653         y = pos.y;
19654         
19655     ctx.fillText(node.name, x, y, width);
19656   }
19657 });
19658
19659 /*
19660  TM.Label.SVG
19661
19662   Custom extension of <Graph.Label.SVG>.
19663
19664   Extends:
19665
19666   All <Graph.Label.SVG> methods
19667
19668   See also:
19669
19670   <Graph.Label.SVG>
19671 */
19672 TM.Label.SVG = new Class( {
19673   Implements: Graph.Label.SVG,
19674
19675   initialize: function(viz){
19676     this.viz = viz;
19677     this.leaf = viz.leaf;
19678     this.config = viz.config;
19679   },
19680
19681   /* 
19682   placeLabel
19683
19684   Overrides abstract method placeLabel in <Graph.Plot>.
19685
19686   Parameters:
19687
19688   tag - A DOM label element.
19689   node - A <Graph.Node>.
19690   controller - A configuration/controller object passed to the visualization.
19691   
19692   */
19693   placeLabel: function(tag, node, controller){
19694     var pos = node.pos.getc(true), 
19695         canvas = this.viz.canvas,
19696         ox = canvas.translateOffsetX,
19697         oy = canvas.translateOffsetY,
19698         sx = canvas.scaleOffsetX,
19699         sy = canvas.scaleOffsetY,
19700         radius = canvas.getSize();
19701     var labelPos = {
19702       x: Math.round(pos.x * sx + ox + radius.width / 2),
19703       y: Math.round(pos.y * sy + oy + radius.height / 2)
19704     };
19705     tag.setAttribute('x', labelPos.x);
19706     tag.setAttribute('y', labelPos.y);
19707
19708     if(!this.leaf(node) && !this.config.titleHeight) {
19709       tag.style.display = 'none';
19710     }
19711     controller.onPlaceLabel(tag, node);
19712   }
19713 });
19714
19715 /*
19716  TM.Label.HTML
19717
19718  Custom extension of <Graph.Label.HTML>.
19719
19720  Extends:
19721
19722  All <Graph.Label.HTML> methods.
19723
19724  See also:
19725
19726  <Graph.Label.HTML>
19727
19728 */
19729 TM.Label.HTML = new Class( {
19730   Implements: Graph.Label.HTML,
19731
19732   initialize: function(viz){
19733     this.viz = viz;
19734     this.leaf = viz.leaf;
19735     this.config = viz.config;
19736   },
19737
19738   /* 
19739     placeLabel
19740   
19741     Overrides abstract method placeLabel in <Graph.Plot>.
19742   
19743     Parameters:
19744   
19745     tag - A DOM label element.
19746     node - A <Graph.Node>.
19747     controller - A configuration/controller object passed to the visualization.
19748   
19749   */
19750   placeLabel: function(tag, node, controller){
19751     var pos = node.pos.getc(true), 
19752         canvas = this.viz.canvas,
19753         ox = canvas.translateOffsetX,
19754         oy = canvas.translateOffsetY,
19755         sx = canvas.scaleOffsetX,
19756         sy = canvas.scaleOffsetY,
19757         radius = canvas.getSize();
19758     var labelPos = {
19759       x: Math.round(pos.x * sx + ox + radius.width / 2),
19760       y: Math.round(pos.y * sy + oy + radius.height / 2)
19761     };
19762
19763     var style = tag.style;
19764     style.left = labelPos.x + 'px';
19765     style.top = labelPos.y + 'px';
19766     style.width = node.getData('width') * sx + 'px';
19767     style.height = node.getData('height') * sy + 'px';
19768     style.zIndex = node._depth * 100;
19769     style.display = '';
19770
19771     if(!this.leaf(node) && !this.config.titleHeight) {
19772       tag.style.display = 'none';
19773     }
19774     controller.onPlaceLabel(tag, node);
19775   }
19776 });
19777
19778 /*
19779   Class: TM.Plot.NodeTypes
19780
19781   This class contains a list of <Graph.Node> built-in types. 
19782   Node types implemented are 'none', 'rectangle'.
19783
19784   You can add your custom node types, customizing your visualization to the extreme.
19785
19786   Example:
19787
19788   (start code js)
19789     TM.Plot.NodeTypes.implement({
19790       'mySpecialType': {
19791         'render': function(node, canvas) {
19792           //print your custom node to canvas
19793         },
19794         //optional
19795         'contains': function(node, pos) {
19796           //return true if pos is inside the node or false otherwise
19797         }
19798       }
19799     });
19800   (end code)
19801
19802 */
19803 TM.Plot.NodeTypes = new Class( {
19804   'none': {
19805     'render': $.empty
19806   },
19807
19808   'rectangle': {
19809     'render': function(node, canvas, animating){
19810       var leaf = this.viz.leaf(node),
19811           config = this.config,
19812           offst = config.offset,
19813           titleHeight = config.titleHeight,
19814           pos = node.pos.getc(true),
19815           width = node.getData('width'),
19816           height = node.getData('height'),
19817           border = node.getData('border'),
19818           ctx = canvas.getCtx(),
19819           posx = pos.x + offst / 2, 
19820           posy = pos.y + offst / 2;
19821       if(width <= offst || height <= offst) return;
19822       if (leaf) {
19823         if(config.cushion) {
19824           var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1, 
19825               posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19826           var color = node.getData('color');
19827           var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
19828               function(r) { return r * 0.2 >> 0; }));
19829           lg.addColorStop(0, color);
19830           lg.addColorStop(1, colorGrad);
19831           ctx.fillStyle = lg;
19832         }
19833         ctx.fillRect(posx, posy, width - offst, height - offst);
19834         if(border) {
19835           ctx.save();
19836           ctx.strokeStyle = border;
19837           ctx.strokeRect(posx, posy, width - offst, height - offst);
19838           ctx.restore();
19839         }
19840       } else if(titleHeight > 0){
19841         ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19842             titleHeight - offst);
19843         if(border) {
19844           ctx.save();
19845           ctx.strokeStyle = border;
19846           ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19847               height - offst);
19848           ctx.restore();
19849         }
19850       }
19851     },
19852     'contains': function(node, pos) {
19853       if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19854       var npos = node.pos.getc(true),
19855           width = node.getData('width'), 
19856           leaf = this.viz.leaf(node),
19857           height = leaf? node.getData('height') : this.config.titleHeight;
19858       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19859     }
19860   }
19861 });
19862
19863 TM.Plot.EdgeTypes = new Class( {
19864   'none': $.empty
19865 });
19866
19867 /*
19868   Class: TM.SliceAndDice
19869   
19870   A slice and dice TreeMap visualization.
19871   
19872   Implements:
19873   
19874   All <TM.Base> methods and properties.
19875 */
19876 TM.SliceAndDice = new Class( {
19877   Implements: [
19878       Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19879   ]
19880 });
19881
19882 /*
19883   Class: TM.Squarified
19884   
19885   A squarified TreeMap visualization.
19886
19887   Implements:
19888   
19889   All <TM.Base> methods and properties.
19890 */
19891 TM.Squarified = new Class( {
19892   Implements: [
19893       Loader, Extras, TM.Base, Layouts.TM.Squarified
19894   ]
19895 });
19896
19897 /*
19898   Class: TM.Strip
19899   
19900   A strip TreeMap visualization.
19901
19902   Implements:
19903   
19904   All <TM.Base> methods and properties.
19905 */
19906 TM.Strip = new Class( {
19907   Implements: [
19908       Loader, Extras, TM.Base, Layouts.TM.Strip
19909   ]
19910 });
19911
19912
19913 /*
19914  * File: RGraph.js
19915  *
19916  */
19917
19918 /*
19919    Class: RGraph
19920    
19921    A radial graph visualization with advanced animations.
19922    
19923    Inspired by:
19924  
19925    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>
19926    
19927    Note:
19928    
19929    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.
19930    
19931   Implements:
19932   
19933   All <Loader> methods
19934   
19935    Constructor Options:
19936    
19937    Inherits options from
19938    
19939    - <Options.Canvas>
19940    - <Options.Controller>
19941    - <Options.Node>
19942    - <Options.Edge>
19943    - <Options.Label>
19944    - <Options.Events>
19945    - <Options.Tips>
19946    - <Options.NodeStyles>
19947    - <Options.Navigation>
19948    
19949    Additionally, there are other parameters and some default values changed
19950    
19951    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19952    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
19953      
19954    Instance Properties:
19955
19956    canvas - Access a <Canvas> instance.
19957    graph - Access a <Graph> instance.
19958    op - Access a <RGraph.Op> instance.
19959    fx - Access a <RGraph.Plot> instance.
19960    labels - Access a <RGraph.Label> interface implementation.   
19961 */
19962
19963 $jit.RGraph = new Class( {
19964
19965   Implements: [
19966       Loader, Extras, Layouts.Radial
19967   ],
19968
19969   initialize: function(controller){
19970     var $RGraph = $jit.RGraph;
19971
19972     var config = {
19973       interpolation: 'linear',
19974       levelDistance: 100
19975     };
19976
19977     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19978         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19979
19980     var canvasConfig = this.config;
19981     if(canvasConfig.useCanvas) {
19982       this.canvas = canvasConfig.useCanvas;
19983       this.config.labelContainer = this.canvas.id + '-label';
19984     } else {
19985       if(canvasConfig.background) {
19986         canvasConfig.background = $.merge({
19987           type: 'Circles'
19988         }, canvasConfig.background);
19989       }
19990       this.canvas = new Canvas(this, canvasConfig);
19991       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19992     }
19993
19994     this.graphOptions = {
19995       'complex': false,
19996       'Node': {
19997         'selected': false,
19998         'exist': true,
19999         'drawn': true
20000       }
20001     };
20002     this.graph = new Graph(this.graphOptions, this.config.Node,
20003         this.config.Edge);
20004     this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
20005     this.fx = new $RGraph.Plot(this, $RGraph);
20006     this.op = new $RGraph.Op(this);
20007     this.json = null;
20008     this.root = null;
20009     this.busy = false;
20010     this.parent = false;
20011     // initialize extras
20012     this.initializeExtras();
20013   },
20014
20015   /* 
20016   
20017     createLevelDistanceFunc 
20018   
20019     Returns the levelDistance function used for calculating a node distance 
20020     to its origin. This function returns a function that is computed 
20021     per level and not per node, such that all nodes with the same depth will have the 
20022     same distance to the origin. The resulting function gets the 
20023     parent node as parameter and returns a float.
20024
20025    */
20026   createLevelDistanceFunc: function(){
20027     var ld = this.config.levelDistance;
20028     return function(elem){
20029       return (elem._depth + 1) * ld;
20030     };
20031   },
20032
20033   /* 
20034      Method: refresh 
20035      
20036      Computes positions and plots the tree.
20037
20038    */
20039   refresh: function(){
20040     this.compute();
20041     this.plot();
20042   },
20043
20044   reposition: function(){
20045     this.compute('end');
20046   },
20047
20048   /*
20049    Method: plot
20050   
20051    Plots the RGraph. This is a shortcut to *fx.plot*.
20052   */
20053   plot: function(){
20054     this.fx.plot();
20055   },
20056   /*
20057    getNodeAndParentAngle
20058   
20059    Returns the _parent_ of the given node, also calculating its angle span.
20060   */
20061   getNodeAndParentAngle: function(id){
20062     var theta = false;
20063     var n = this.graph.getNode(id);
20064     var ps = n.getParents();
20065     var p = (ps.length > 0)? ps[0] : false;
20066     if (p) {
20067       var posParent = p.pos.getc(), posChild = n.pos.getc();
20068       var newPos = posParent.add(posChild.scale(-1));
20069       theta = Math.atan2(newPos.y, newPos.x);
20070       if (theta < 0)
20071         theta += 2 * Math.PI;
20072     }
20073     return {
20074       parent: p,
20075       theta: theta
20076     };
20077   },
20078   /*
20079    tagChildren
20080   
20081    Enumerates the children in order to maintain child ordering (second constraint of the paper).
20082   */
20083   tagChildren: function(par, id){
20084     if (par.angleSpan) {
20085       var adjs = [];
20086       par.eachAdjacency(function(elem){
20087         adjs.push(elem.nodeTo);
20088       }, "ignore");
20089       var len = adjs.length;
20090       for ( var i = 0; i < len && id != adjs[i].id; i++)
20091         ;
20092       for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
20093         adjs[j].dist = k++;
20094       }
20095     }
20096   },
20097   /* 
20098   Method: onClick 
20099   
20100   Animates the <RGraph> to center the node specified by *id*.
20101
20102    Parameters:
20103
20104    id - A <Graph.Node> id.
20105    opt - (optional|object) An object containing some extra properties described below
20106    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20107
20108    Example:
20109
20110    (start code js)
20111      rgraph.onClick('someid');
20112      //or also...
20113      rgraph.onClick('someid', {
20114       hideLabels: false
20115      });
20116     (end code)
20117     
20118   */
20119   onClick: function(id, opt){
20120     if (this.root != id && !this.busy) {
20121       this.busy = true;
20122       this.root = id;
20123       that = this;
20124       this.controller.onBeforeCompute(this.graph.getNode(id));
20125       var obj = this.getNodeAndParentAngle(id);
20126
20127       // second constraint
20128       this.tagChildren(obj.parent, id);
20129       this.parent = obj.parent;
20130       this.compute('end');
20131
20132       // first constraint
20133       var thetaDiff = obj.theta - obj.parent.endPos.theta;
20134       this.graph.eachNode(function(elem){
20135         elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
20136       });
20137
20138       var mode = this.config.interpolation;
20139       opt = $.merge( {
20140         onComplete: $.empty
20141       }, opt || {});
20142
20143       this.fx.animate($.merge( {
20144         hideLabels: true,
20145         modes: [
20146           mode
20147         ]
20148       }, opt, {
20149         onComplete: function(){
20150           that.busy = false;
20151           opt.onComplete();
20152         }
20153       }));
20154     }
20155   }
20156 });
20157
20158 $jit.RGraph.$extend = true;
20159
20160 (function(RGraph){
20161
20162   /*
20163      Class: RGraph.Op
20164      
20165      Custom extension of <Graph.Op>.
20166
20167      Extends:
20168
20169      All <Graph.Op> methods
20170      
20171      See also:
20172      
20173      <Graph.Op>
20174
20175   */
20176   RGraph.Op = new Class( {
20177
20178     Implements: Graph.Op
20179
20180   });
20181
20182   /*
20183      Class: RGraph.Plot
20184     
20185     Custom extension of <Graph.Plot>.
20186   
20187     Extends:
20188   
20189     All <Graph.Plot> methods
20190     
20191     See also:
20192     
20193     <Graph.Plot>
20194   
20195   */
20196   RGraph.Plot = new Class( {
20197
20198     Implements: Graph.Plot
20199
20200   });
20201
20202   /*
20203     Object: RGraph.Label
20204
20205     Custom extension of <Graph.Label>. 
20206     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20207   
20208     Extends:
20209   
20210     All <Graph.Label> methods and subclasses.
20211   
20212     See also:
20213   
20214     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20215   
20216    */
20217   RGraph.Label = {};
20218
20219   /*
20220      RGraph.Label.Native
20221
20222      Custom extension of <Graph.Label.Native>.
20223
20224      Extends:
20225
20226      All <Graph.Label.Native> methods
20227
20228      See also:
20229
20230      <Graph.Label.Native>
20231
20232   */
20233   RGraph.Label.Native = new Class( {
20234     Implements: Graph.Label.Native
20235   });
20236
20237   /*
20238      RGraph.Label.SVG
20239     
20240     Custom extension of <Graph.Label.SVG>.
20241   
20242     Extends:
20243   
20244     All <Graph.Label.SVG> methods
20245   
20246     See also:
20247   
20248     <Graph.Label.SVG>
20249   
20250   */
20251   RGraph.Label.SVG = new Class( {
20252     Implements: Graph.Label.SVG,
20253
20254     initialize: function(viz){
20255       this.viz = viz;
20256     },
20257
20258     /* 
20259        placeLabel
20260
20261        Overrides abstract method placeLabel in <Graph.Plot>.
20262
20263        Parameters:
20264
20265        tag - A DOM label element.
20266        node - A <Graph.Node>.
20267        controller - A configuration/controller object passed to the visualization.
20268       
20269      */
20270     placeLabel: function(tag, node, controller){
20271       var pos = node.pos.getc(true), 
20272           canvas = this.viz.canvas,
20273           ox = canvas.translateOffsetX,
20274           oy = canvas.translateOffsetY,
20275           sx = canvas.scaleOffsetX,
20276           sy = canvas.scaleOffsetY,
20277           radius = canvas.getSize();
20278       var labelPos = {
20279         x: Math.round(pos.x * sx + ox + radius.width / 2),
20280         y: Math.round(pos.y * sy + oy + radius.height / 2)
20281       };
20282       tag.setAttribute('x', labelPos.x);
20283       tag.setAttribute('y', labelPos.y);
20284
20285       controller.onPlaceLabel(tag, node);
20286     }
20287   });
20288
20289   /*
20290      RGraph.Label.HTML
20291
20292      Custom extension of <Graph.Label.HTML>.
20293
20294      Extends:
20295
20296      All <Graph.Label.HTML> methods.
20297
20298      See also:
20299
20300      <Graph.Label.HTML>
20301
20302   */
20303   RGraph.Label.HTML = new Class( {
20304     Implements: Graph.Label.HTML,
20305
20306     initialize: function(viz){
20307       this.viz = viz;
20308     },
20309     /* 
20310        placeLabel
20311
20312        Overrides abstract method placeLabel in <Graph.Plot>.
20313
20314        Parameters:
20315
20316        tag - A DOM label element.
20317        node - A <Graph.Node>.
20318        controller - A configuration/controller object passed to the visualization.
20319       
20320      */
20321     placeLabel: function(tag, node, controller){
20322       var pos = node.pos.getc(true), 
20323           canvas = this.viz.canvas,
20324           ox = canvas.translateOffsetX,
20325           oy = canvas.translateOffsetY,
20326           sx = canvas.scaleOffsetX,
20327           sy = canvas.scaleOffsetY,
20328           radius = canvas.getSize();
20329       var labelPos = {
20330         x: Math.round(pos.x * sx + ox + radius.width / 2),
20331         y: Math.round(pos.y * sy + oy + radius.height / 2)
20332       };
20333
20334       var style = tag.style;
20335       style.left = labelPos.x + 'px';
20336       style.top = labelPos.y + 'px';
20337       style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20338
20339       controller.onPlaceLabel(tag, node);
20340     }
20341   });
20342
20343   /*
20344     Class: RGraph.Plot.NodeTypes
20345
20346     This class contains a list of <Graph.Node> built-in types. 
20347     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20348
20349     You can add your custom node types, customizing your visualization to the extreme.
20350
20351     Example:
20352
20353     (start code js)
20354       RGraph.Plot.NodeTypes.implement({
20355         'mySpecialType': {
20356           'render': function(node, canvas) {
20357             //print your custom node to canvas
20358           },
20359           //optional
20360           'contains': function(node, pos) {
20361             //return true if pos is inside the node or false otherwise
20362           }
20363         }
20364       });
20365     (end code)
20366
20367   */
20368   RGraph.Plot.NodeTypes = new Class({
20369     'none': {
20370       'render': $.empty,
20371       'contains': $.lambda(false)
20372     },
20373     'circle': {
20374       'render': function(node, canvas){
20375         var pos = node.pos.getc(true), 
20376             dim = node.getData('dim');
20377         this.nodeHelper.circle.render('fill', pos, dim, canvas);
20378       },
20379       'contains': function(node, pos){
20380         var npos = node.pos.getc(true), 
20381             dim = node.getData('dim');
20382         return this.nodeHelper.circle.contains(npos, pos, dim);
20383       }
20384     },
20385     'ellipse': {
20386       'render': function(node, canvas){
20387         var pos = node.pos.getc(true), 
20388             width = node.getData('width'), 
20389             height = node.getData('height');
20390         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20391         },
20392       // TODO(nico): be more precise...
20393       'contains': function(node, pos){
20394         var npos = node.pos.getc(true), 
20395             width = node.getData('width'), 
20396             height = node.getData('height');
20397         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20398       }
20399     },
20400     'square': {
20401       'render': function(node, canvas){
20402         var pos = node.pos.getc(true), 
20403             dim = node.getData('dim');
20404         this.nodeHelper.square.render('fill', pos, dim, canvas);
20405       },
20406       'contains': function(node, pos){
20407         var npos = node.pos.getc(true), 
20408             dim = node.getData('dim');
20409         return this.nodeHelper.square.contains(npos, pos, dim);
20410       }
20411     },
20412     'rectangle': {
20413       'render': function(node, canvas){
20414         var pos = node.pos.getc(true), 
20415             width = node.getData('width'), 
20416             height = node.getData('height');
20417         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20418       },
20419       'contains': function(node, pos){
20420         var npos = node.pos.getc(true), 
20421             width = node.getData('width'), 
20422             height = node.getData('height');
20423         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20424       }
20425     },
20426     'triangle': {
20427       'render': function(node, canvas){
20428         var pos = node.pos.getc(true), 
20429             dim = node.getData('dim');
20430         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20431       },
20432       'contains': function(node, pos) {
20433         var npos = node.pos.getc(true), 
20434             dim = node.getData('dim');
20435         return this.nodeHelper.triangle.contains(npos, pos, dim);
20436       }
20437     },
20438     'star': {
20439       'render': function(node, canvas){
20440         var pos = node.pos.getc(true),
20441             dim = node.getData('dim');
20442         this.nodeHelper.star.render('fill', pos, dim, canvas);
20443       },
20444       'contains': function(node, pos) {
20445         var npos = node.pos.getc(true),
20446             dim = node.getData('dim');
20447         return this.nodeHelper.star.contains(npos, pos, dim);
20448       }
20449     }
20450   });
20451
20452   /*
20453     Class: RGraph.Plot.EdgeTypes
20454
20455     This class contains a list of <Graph.Adjacence> built-in types. 
20456     Edge types implemented are 'none', 'line' and 'arrow'.
20457   
20458     You can add your custom edge types, customizing your visualization to the extreme.
20459   
20460     Example:
20461   
20462     (start code js)
20463       RGraph.Plot.EdgeTypes.implement({
20464         'mySpecialType': {
20465           'render': function(adj, canvas) {
20466             //print your custom edge to canvas
20467           },
20468           //optional
20469           'contains': function(adj, pos) {
20470             //return true if pos is inside the arc or false otherwise
20471           }
20472         }
20473       });
20474     (end code)
20475   
20476   */
20477   RGraph.Plot.EdgeTypes = new Class({
20478     'none': $.empty,
20479     'line': {
20480       'render': function(adj, canvas) {
20481         var from = adj.nodeFrom.pos.getc(true),
20482             to = adj.nodeTo.pos.getc(true);
20483         this.edgeHelper.line.render(from, to, canvas);
20484       },
20485       'contains': function(adj, pos) {
20486         var from = adj.nodeFrom.pos.getc(true),
20487             to = adj.nodeTo.pos.getc(true);
20488         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20489       }
20490     },
20491     'arrow': {
20492       'render': function(adj, canvas) {
20493         var from = adj.nodeFrom.pos.getc(true),
20494             to = adj.nodeTo.pos.getc(true),
20495             dim = adj.getData('dim'),
20496             direction = adj.data.$direction,
20497             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20498         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20499       },
20500       'contains': function(adj, pos) {
20501         var from = adj.nodeFrom.pos.getc(true),
20502             to = adj.nodeTo.pos.getc(true);
20503         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20504       }
20505     }
20506   });
20507
20508 })($jit.RGraph);
20509
20510
20511 /*
20512  * File: Hypertree.js
20513  * 
20514 */
20515
20516 /* 
20517      Complex 
20518      
20519      A multi-purpose Complex Class with common methods. Extended for the Hypertree. 
20520  
20521 */
20522 /* 
20523    moebiusTransformation 
20524  
20525    Calculates a moebius transformation for this point / complex. 
20526     For more information go to: 
20527         http://en.wikipedia.org/wiki/Moebius_transformation. 
20528  
20529    Parameters: 
20530  
20531       c - An initialized Complex instance representing a translation Vector. 
20532 */
20533
20534 Complex.prototype.moebiusTransformation = function(c) {
20535   var num = this.add(c);
20536   var den = c.$conjugate().$prod(this);
20537   den.x++;
20538   return num.$div(den);
20539 };
20540
20541 /* 
20542     moebiusTransformation 
20543      
20544     Calculates a moebius transformation for the hyperbolic tree. 
20545      
20546     <http://en.wikipedia.org/wiki/Moebius_transformation> 
20547       
20548      Parameters: 
20549      
20550         graph - A <Graph> instance.
20551         pos - A <Complex>.
20552         prop - A property array.
20553         theta - Rotation angle. 
20554         startPos - _optional_ start position. 
20555 */
20556 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20557   this.eachNode(graph, function(elem) {
20558     for ( var i = 0; i < prop.length; i++) {
20559       var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20560       elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20561     }
20562   }, flags);
20563 };
20564
20565 /* 
20566    Class: Hypertree 
20567    
20568    A Hyperbolic Tree/Graph visualization.
20569    
20570    Inspired by:
20571  
20572    A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli). 
20573    <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20574  
20575   Note:
20576  
20577   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.
20578
20579   Implements:
20580   
20581   All <Loader> methods
20582   
20583   Constructor Options:
20584   
20585   Inherits options from
20586   
20587   - <Options.Canvas>
20588   - <Options.Controller>
20589   - <Options.Node>
20590   - <Options.Edge>
20591   - <Options.Label>
20592   - <Options.Events>
20593   - <Options.Tips>
20594   - <Options.NodeStyles>
20595   - <Options.Navigation>
20596   
20597   Additionally, there are other parameters and some default values changed
20598   
20599   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*.
20600   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.
20601   fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20602   duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20603   Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
20604   
20605   Instance Properties:
20606   
20607   canvas - Access a <Canvas> instance.
20608   graph - Access a <Graph> instance.
20609   op - Access a <Hypertree.Op> instance.
20610   fx - Access a <Hypertree.Plot> instance.
20611   labels - Access a <Hypertree.Label> interface implementation.
20612
20613 */
20614
20615 $jit.Hypertree = new Class( {
20616
20617   Implements: [ Loader, Extras, Layouts.Radial ],
20618
20619   initialize: function(controller) {
20620     var $Hypertree = $jit.Hypertree;
20621
20622     var config = {
20623       radius: "auto",
20624       offset: 0,
20625       Edge: {
20626         type: 'hyperline'
20627       },
20628       duration: 1500,
20629       fps: 35
20630     };
20631     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20632         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20633
20634     var canvasConfig = this.config;
20635     if(canvasConfig.useCanvas) {
20636       this.canvas = canvasConfig.useCanvas;
20637       this.config.labelContainer = this.canvas.id + '-label';
20638     } else {
20639       if(canvasConfig.background) {
20640         canvasConfig.background = $.merge({
20641           type: 'Circles'
20642         }, canvasConfig.background);
20643       }
20644       this.canvas = new Canvas(this, canvasConfig);
20645       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20646     }
20647
20648     this.graphOptions = {
20649       'complex': false,
20650       'Node': {
20651         'selected': false,
20652         'exist': true,
20653         'drawn': true
20654       }
20655     };
20656     this.graph = new Graph(this.graphOptions, this.config.Node,
20657         this.config.Edge);
20658     this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20659     this.fx = new $Hypertree.Plot(this, $Hypertree);
20660     this.op = new $Hypertree.Op(this);
20661     this.json = null;
20662     this.root = null;
20663     this.busy = false;
20664     // initialize extras
20665     this.initializeExtras();
20666   },
20667
20668   /* 
20669   
20670   createLevelDistanceFunc 
20671
20672   Returns the levelDistance function used for calculating a node distance 
20673   to its origin. This function returns a function that is computed 
20674   per level and not per node, such that all nodes with the same depth will have the 
20675   same distance to the origin. The resulting function gets the 
20676   parent node as parameter and returns a float.
20677
20678   */
20679   createLevelDistanceFunc: function() {
20680     // get max viz. length.
20681     var r = this.getRadius();
20682     // get max depth.
20683     var depth = 0, max = Math.max, config = this.config;
20684     this.graph.eachNode(function(node) {
20685       depth = max(node._depth, depth);
20686     }, "ignore");
20687     depth++;
20688     // node distance generator
20689     var genDistFunc = function(a) {
20690       return function(node) {
20691         node.scale = r;
20692         var d = node._depth + 1;
20693         var acum = 0, pow = Math.pow;
20694         while (d) {
20695           acum += pow(a, d--);
20696         }
20697         return acum - config.offset;
20698       };
20699     };
20700     // estimate better edge length.
20701     for ( var i = 0.51; i <= 1; i += 0.01) {
20702       var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20703       if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20704     }
20705     return genDistFunc(0.75);
20706   },
20707
20708   /* 
20709     Method: getRadius 
20710     
20711     Returns the current radius of the visualization. If *config.radius* is *auto* then it 
20712     calculates the radius by taking the smaller size of the <Canvas> widget.
20713     
20714     See also:
20715     
20716     <Canvas.getSize>
20717    
20718   */
20719   getRadius: function() {
20720     var rad = this.config.radius;
20721     if (rad !== "auto") { return rad; }
20722     var s = this.canvas.getSize();
20723     return Math.min(s.width, s.height) / 2;
20724   },
20725
20726   /* 
20727     Method: refresh 
20728     
20729     Computes positions and plots the tree.
20730
20731     Parameters:
20732
20733     reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20734
20735    */
20736   refresh: function(reposition) {
20737     if (reposition) {
20738       this.reposition();
20739       this.graph.eachNode(function(node) {
20740         node.startPos.rho = node.pos.rho = node.endPos.rho;
20741         node.startPos.theta = node.pos.theta = node.endPos.theta;
20742       });
20743     } else {
20744       this.compute();
20745     }
20746     this.plot();
20747   },
20748
20749   /* 
20750    reposition 
20751    
20752    Computes nodes' positions and restores the tree to its previous position.
20753
20754    For calculating nodes' positions the root must be placed on its origin. This method does this 
20755      and then attemps to restore the hypertree to its previous position.
20756     
20757   */
20758   reposition: function() {
20759     this.compute('end');
20760     var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20761     Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20762         'end', "ignore");
20763     this.graph.eachNode(function(node) {
20764       if (node.ignore) {
20765         node.endPos.rho = node.pos.rho;
20766         node.endPos.theta = node.pos.theta;
20767       }
20768     });
20769   },
20770
20771   /* 
20772    Method: plot 
20773    
20774    Plots the <Hypertree>. This is a shortcut to *fx.plot*. 
20775
20776   */
20777   plot: function() {
20778     this.fx.plot();
20779   },
20780
20781   /* 
20782    Method: onClick 
20783    
20784    Animates the <Hypertree> to center the node specified by *id*.
20785
20786    Parameters:
20787
20788    id - A <Graph.Node> id.
20789    opt - (optional|object) An object containing some extra properties described below
20790    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20791
20792    Example:
20793
20794    (start code js)
20795      ht.onClick('someid');
20796      //or also...
20797      ht.onClick('someid', {
20798       hideLabels: false
20799      });
20800     (end code)
20801     
20802   */
20803   onClick: function(id, opt) {
20804     var pos = this.graph.getNode(id).pos.getc(true);
20805     this.move(pos, opt);
20806   },
20807
20808   /* 
20809    Method: move 
20810
20811    Translates the tree to the given position. 
20812
20813    Parameters:
20814
20815    pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20816    opt - This object has been defined in <Hypertree.onClick>
20817    
20818    Example:
20819    
20820    (start code js)
20821      ht.move({ x: 0, y: 0.7 }, {
20822        hideLabels: false
20823      });
20824    (end code)
20825
20826   */
20827   move: function(pos, opt) {
20828     var versor = $C(pos.x, pos.y);
20829     if (this.busy === false && versor.norm() < 1) {
20830       this.busy = true;
20831       var root = this.graph.getClosestNodeToPos(versor), that = this;
20832       this.graph.computeLevels(root.id, 0);
20833       this.controller.onBeforeCompute(root);
20834       opt = $.merge( {
20835         onComplete: $.empty
20836       }, opt || {});
20837       this.fx.animate($.merge( {
20838         modes: [ 'moebius' ],
20839         hideLabels: true
20840       }, opt, {
20841         onComplete: function() {
20842           that.busy = false;
20843           opt.onComplete();
20844         }
20845       }), versor);
20846     }
20847   }
20848 });
20849
20850 $jit.Hypertree.$extend = true;
20851
20852 (function(Hypertree) {
20853
20854   /* 
20855      Class: Hypertree.Op 
20856    
20857      Custom extension of <Graph.Op>.
20858
20859      Extends:
20860
20861      All <Graph.Op> methods
20862      
20863      See also:
20864      
20865      <Graph.Op>
20866
20867   */
20868   Hypertree.Op = new Class( {
20869
20870     Implements: Graph.Op
20871
20872   });
20873
20874   /* 
20875      Class: Hypertree.Plot 
20876    
20877     Custom extension of <Graph.Plot>.
20878   
20879     Extends:
20880   
20881     All <Graph.Plot> methods
20882     
20883     See also:
20884     
20885     <Graph.Plot>
20886   
20887   */
20888   Hypertree.Plot = new Class( {
20889
20890     Implements: Graph.Plot
20891
20892   });
20893
20894   /*
20895     Object: Hypertree.Label
20896
20897     Custom extension of <Graph.Label>. 
20898     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20899   
20900     Extends:
20901   
20902     All <Graph.Label> methods and subclasses.
20903   
20904     See also:
20905   
20906     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20907
20908    */
20909   Hypertree.Label = {};
20910
20911   /*
20912      Hypertree.Label.Native
20913
20914      Custom extension of <Graph.Label.Native>.
20915
20916      Extends:
20917
20918      All <Graph.Label.Native> methods
20919
20920      See also:
20921
20922      <Graph.Label.Native>
20923
20924   */
20925   Hypertree.Label.Native = new Class( {
20926     Implements: Graph.Label.Native,
20927
20928     initialize: function(viz) {
20929       this.viz = viz;
20930     },
20931
20932     renderLabel: function(canvas, node, controller) {
20933       var ctx = canvas.getCtx();
20934       var coord = node.pos.getc(true);
20935       var s = this.viz.getRadius();
20936       ctx.fillText(node.name, coord.x * s, coord.y * s);
20937     }
20938   });
20939
20940   /*
20941      Hypertree.Label.SVG
20942
20943     Custom extension of <Graph.Label.SVG>.
20944   
20945     Extends:
20946   
20947     All <Graph.Label.SVG> methods
20948   
20949     See also:
20950   
20951     <Graph.Label.SVG>
20952   
20953   */
20954   Hypertree.Label.SVG = new Class( {
20955     Implements: Graph.Label.SVG,
20956
20957     initialize: function(viz) {
20958       this.viz = viz;
20959     },
20960
20961     /* 
20962        placeLabel
20963
20964        Overrides abstract method placeLabel in <Graph.Plot>.
20965
20966        Parameters:
20967
20968        tag - A DOM label element.
20969        node - A <Graph.Node>.
20970        controller - A configuration/controller object passed to the visualization.
20971       
20972      */
20973     placeLabel: function(tag, node, controller) {
20974       var pos = node.pos.getc(true), 
20975           canvas = this.viz.canvas,
20976           ox = canvas.translateOffsetX,
20977           oy = canvas.translateOffsetY,
20978           sx = canvas.scaleOffsetX,
20979           sy = canvas.scaleOffsetY,
20980           radius = canvas.getSize(),
20981           r = this.viz.getRadius();
20982       var labelPos = {
20983         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20984         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20985       };
20986       tag.setAttribute('x', labelPos.x);
20987       tag.setAttribute('y', labelPos.y);
20988       controller.onPlaceLabel(tag, node);
20989     }
20990   });
20991
20992   /*
20993      Hypertree.Label.HTML
20994
20995      Custom extension of <Graph.Label.HTML>.
20996
20997      Extends:
20998
20999      All <Graph.Label.HTML> methods.
21000
21001      See also:
21002
21003      <Graph.Label.HTML>
21004
21005   */
21006   Hypertree.Label.HTML = new Class( {
21007     Implements: Graph.Label.HTML,
21008
21009     initialize: function(viz) {
21010       this.viz = viz;
21011     },
21012     /* 
21013        placeLabel
21014
21015        Overrides abstract method placeLabel in <Graph.Plot>.
21016
21017        Parameters:
21018
21019        tag - A DOM label element.
21020        node - A <Graph.Node>.
21021        controller - A configuration/controller object passed to the visualization.
21022       
21023      */
21024     placeLabel: function(tag, node, controller) {
21025       var pos = node.pos.getc(true), 
21026           canvas = this.viz.canvas,
21027           ox = canvas.translateOffsetX,
21028           oy = canvas.translateOffsetY,
21029           sx = canvas.scaleOffsetX,
21030           sy = canvas.scaleOffsetY,
21031           radius = canvas.getSize(),
21032           r = this.viz.getRadius();
21033       var labelPos = {
21034         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
21035         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
21036       };
21037       var style = tag.style;
21038       style.left = labelPos.x + 'px';
21039       style.top = labelPos.y + 'px';
21040       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
21041
21042       controller.onPlaceLabel(tag, node);
21043     }
21044   });
21045
21046   /*
21047     Class: Hypertree.Plot.NodeTypes
21048
21049     This class contains a list of <Graph.Node> built-in types. 
21050     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
21051
21052     You can add your custom node types, customizing your visualization to the extreme.
21053
21054     Example:
21055
21056     (start code js)
21057       Hypertree.Plot.NodeTypes.implement({
21058         'mySpecialType': {
21059           'render': function(node, canvas) {
21060             //print your custom node to canvas
21061           },
21062           //optional
21063           'contains': function(node, pos) {
21064             //return true if pos is inside the node or false otherwise
21065           }
21066         }
21067       });
21068     (end code)
21069
21070   */
21071   Hypertree.Plot.NodeTypes = new Class({
21072     'none': {
21073       'render': $.empty,
21074       'contains': $.lambda(false)
21075     },
21076     'circle': {
21077       'render': function(node, canvas) {
21078         var nconfig = this.node,
21079             dim = node.getData('dim'),
21080             p = node.pos.getc();
21081         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21082         p.$scale(node.scale);
21083         if (dim > 0.2) {
21084           this.nodeHelper.circle.render('fill', p, dim, canvas);
21085         }
21086       },
21087       'contains': function(node, pos) {
21088         var dim = node.getData('dim'),
21089             npos = node.pos.getc().$scale(node.scale);
21090         return this.nodeHelper.circle.contains(npos, pos, dim);
21091       }
21092     },
21093     'ellipse': {
21094       'render': function(node, canvas) {
21095         var pos = node.pos.getc().$scale(node.scale),
21096             width = node.getData('width'),
21097             height = node.getData('height');
21098         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
21099       },
21100       'contains': function(node, pos) {
21101         var width = node.getData('width'),
21102             height = node.getData('height'),
21103             npos = node.pos.getc().$scale(node.scale);
21104         return this.nodeHelper.circle.contains(npos, pos, width, height);
21105       }
21106     },
21107     'square': {
21108       'render': function(node, canvas) {
21109         var nconfig = this.node,
21110             dim = node.getData('dim'),
21111             p = node.pos.getc();
21112         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21113         p.$scale(node.scale);
21114         if (dim > 0.2) {
21115           this.nodeHelper.square.render('fill', p, dim, canvas);
21116         }
21117       },
21118       'contains': function(node, pos) {
21119         var dim = node.getData('dim'),
21120             npos = node.pos.getc().$scale(node.scale);
21121         return this.nodeHelper.square.contains(npos, pos, dim);
21122       }
21123     },
21124     'rectangle': {
21125       'render': function(node, canvas) {
21126         var nconfig = this.node,
21127             width = node.getData('width'),
21128             height = node.getData('height'),
21129             pos = node.pos.getc();
21130         width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
21131         height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
21132         pos.$scale(node.scale);
21133         if (width > 0.2 && height > 0.2) {
21134           this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
21135         }
21136       },
21137       'contains': function(node, pos) {
21138         var width = node.getData('width'),
21139             height = node.getData('height'),
21140             npos = node.pos.getc().$scale(node.scale);
21141         return this.nodeHelper.square.contains(npos, pos, width, height);
21142       }
21143     },
21144     'triangle': {
21145       'render': function(node, canvas) {
21146         var nconfig = this.node,
21147             dim = node.getData('dim'),
21148             p = node.pos.getc();
21149         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21150         p.$scale(node.scale);
21151         if (dim > 0.2) {
21152           this.nodeHelper.triangle.render('fill', p, dim, canvas);
21153         }
21154       },
21155       'contains': function(node, pos) {
21156         var dim = node.getData('dim'),
21157             npos = node.pos.getc().$scale(node.scale);
21158         return this.nodeHelper.triangle.contains(npos, pos, dim);
21159       }
21160     },
21161     'star': {
21162       'render': function(node, canvas) {
21163         var nconfig = this.node,
21164             dim = node.getData('dim'),
21165             p = node.pos.getc();
21166         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21167         p.$scale(node.scale);
21168         if (dim > 0.2) {
21169           this.nodeHelper.star.render('fill', p, dim, canvas);
21170         }
21171       },
21172       'contains': function(node, pos) {
21173         var dim = node.getData('dim'),
21174             npos = node.pos.getc().$scale(node.scale);
21175         return this.nodeHelper.star.contains(npos, pos, dim);
21176       }
21177     }
21178   });
21179
21180   /*
21181    Class: Hypertree.Plot.EdgeTypes
21182
21183     This class contains a list of <Graph.Adjacence> built-in types. 
21184     Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
21185   
21186     You can add your custom edge types, customizing your visualization to the extreme.
21187   
21188     Example:
21189   
21190     (start code js)
21191       Hypertree.Plot.EdgeTypes.implement({
21192         'mySpecialType': {
21193           'render': function(adj, canvas) {
21194             //print your custom edge to canvas
21195           },
21196           //optional
21197           'contains': function(adj, pos) {
21198             //return true if pos is inside the arc or false otherwise
21199           }
21200         }
21201       });
21202     (end code)
21203   
21204   */
21205   Hypertree.Plot.EdgeTypes = new Class({
21206     'none': $.empty,
21207     'line': {
21208       'render': function(adj, canvas) {
21209         var from = adj.nodeFrom.pos.getc(true),
21210           to = adj.nodeTo.pos.getc(true),
21211           r = adj.nodeFrom.scale;
21212           this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
21213       },
21214       'contains': function(adj, pos) {
21215         var from = adj.nodeFrom.pos.getc(true),
21216             to = adj.nodeTo.pos.getc(true),
21217             r = adj.nodeFrom.scale;
21218             this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21219       }
21220     },
21221     'arrow': {
21222       'render': function(adj, canvas) {
21223         var from = adj.nodeFrom.pos.getc(true),
21224             to = adj.nodeTo.pos.getc(true),
21225             r = adj.nodeFrom.scale,
21226             dim = adj.getData('dim'),
21227             direction = adj.data.$direction,
21228             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
21229         this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
21230       },
21231       'contains': function(adj, pos) {
21232         var from = adj.nodeFrom.pos.getc(true),
21233             to = adj.nodeTo.pos.getc(true),
21234             r = adj.nodeFrom.scale;
21235         this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21236       }
21237     },
21238     'hyperline': {
21239       'render': function(adj, canvas) {
21240         var from = adj.nodeFrom.pos.getc(),
21241             to = adj.nodeTo.pos.getc(),
21242             dim = this.viz.getRadius();
21243         this.edgeHelper.hyperline.render(from, to, dim, canvas);
21244       },
21245       'contains': $.lambda(false)
21246     }
21247   });
21248
21249 })($jit.Hypertree);
21250
21251
21252
21253
21254  })();