]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/SugarCharts/Jit/js/Jit/jit.js
Release 6.5.16
[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       if (typeof val.id == 'undefined') {
13204         val.id = val.label;
13205       }
13206       ch.push({
13207         'id': prefix + val.id,
13208         'name': val.label,
13209         
13210         'data': {
13211           'value': valArray,
13212           '$linkArray': linkArray,
13213                   '$gvl': val.gvaluelabel,
13214           '$titleArray': titleArray,
13215           '$valueArray': valArray,
13216           '$valuelabelArray': valuelabelArray,
13217           '$colorArray': color,
13218           '$colorMono': $.splat(color[i % colorLength]),
13219           '$stringArray': name,
13220           '$barTotalValue': barTotalValue,
13221           '$groupTotalValue': groupTotalValue,
13222           '$nodeCount': values.length,
13223           '$gradient': gradient,
13224           '$config': config
13225         },
13226         'children': []
13227       });
13228     }
13229     var root = {
13230       'id': prefix + '$root',
13231       'name': '',
13232       'data': {
13233         '$type': 'none',
13234         '$width': 1,
13235         '$height': 1
13236       },
13237       'children': ch
13238     };
13239     st.loadJSON(root);
13240     
13241     this.normalizeDims();
13242     
13243     if(renderBackground) {
13244                 this.renderBackground();
13245     }
13246         
13247         if(!animate && ticks.enable) {
13248                 this.renderTicks();
13249         }
13250         if(!animate && note.text) {
13251                 this.renderScrollNote();
13252         }
13253         if(!animate && title.text) {
13254                 this.renderTitle();
13255         }
13256         if(!animate && subtitle.text) {
13257                 this.renderSubtitle();
13258         }
13259
13260     st.compute();
13261     st.select(st.root);
13262     if(animate) {
13263       if(horz) {
13264         st.fx.animate({
13265           modes: ['node-property:width:dimArray'],
13266           duration:1500,
13267           onComplete: function() {
13268             that.busy = false;
13269           }
13270         });
13271       } else {
13272         st.fx.animate({
13273           modes: ['node-property:height:dimArray'],
13274           duration:1500,
13275           onComplete: function() {
13276             that.busy = false;
13277           }
13278         });
13279       }
13280     } else {
13281       this.busy = false;
13282     }
13283   },
13284   
13285   /*
13286     Method: updateJSON
13287    
13288     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.
13289     
13290     Parameters:
13291     
13292     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
13293     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
13294     
13295     Example:
13296     
13297     (start code js)
13298     barChart.updateJSON(json, {
13299       onComplete: function() {
13300         alert('update complete!');
13301       }
13302     });
13303     (end code)
13304  */  
13305   updateJSON: function(json, onComplete) {
13306     if(this.busy) return;
13307     this.busy = true;
13308     
13309     var st = this.st;
13310     var graph = st.graph;
13311     var values = json.values;
13312     var animate = this.config.animate;
13313     var that = this;
13314     var horz = this.config.orientation == 'horizontal';
13315     $.each(values, function(v) {
13316       var n = graph.getByName(v.label);
13317       if(n) {
13318         n.setData('valueArray', $.splat(v.values));
13319         if(json.label) {
13320           n.setData('stringArray', $.splat(json.label));
13321         }
13322       }
13323     });
13324     this.normalizeDims();
13325     st.compute();
13326     st.select(st.root);
13327     if(animate) {
13328       if(horz) {
13329         st.fx.animate({
13330           modes: ['node-property:width:dimArray'],
13331           duration:1500,
13332           onComplete: function() {
13333             that.busy = false;
13334             onComplete && onComplete.onComplete();
13335           }
13336         });
13337       } else {
13338         st.fx.animate({
13339           modes: ['node-property:height:dimArray'],
13340           duration:1500,
13341           onComplete: function() {
13342             that.busy = false;
13343             onComplete && onComplete.onComplete();
13344           }
13345         });
13346       }
13347     }
13348   },
13349   
13350   //adds the little brown bar when hovering the node
13351   select: function(id, name) {
13352
13353     if(!this.config.hoveredColor) return;
13354     var s = this.selected;
13355     if(s.id != id || s.name != name) {
13356       s.id = id;
13357       s.name = name;
13358       s.color = this.config.hoveredColor;
13359       this.st.graph.eachNode(function(n) {
13360         if(id == n.id) {
13361           n.setData('border', s);
13362         } else {
13363           n.setData('border', false);
13364         }
13365       });
13366       this.st.plot();
13367     }
13368   },
13369   
13370   /*
13371     Method: getLegend
13372    
13373     Returns an object containing as keys the legend names and as values hex strings with color values.
13374     
13375     Example:
13376     
13377     (start code js)
13378     var legend = barChart.getLegend();
13379     (end code)
13380   */  
13381   getLegend: function() {
13382     var legend = new Array();
13383     var name = new Array();
13384     var color = new Array();
13385     var n;
13386     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
13387       n = adj.nodeTo;
13388     });
13389     var colors = n.getData('colorArray'),
13390         len = colors.length;
13391     $.each(n.getData('stringArray'), function(s, i) {
13392       color[i] = colors[i % len];
13393       name[i] = s;
13394     });
13395         legend['name'] = name;
13396         legend['color'] = color;
13397     return legend;
13398   },
13399   
13400   /*
13401     Method: getMaxValue
13402    
13403     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
13404     
13405     Example:
13406     
13407     (start code js)
13408     var ans = barChart.getMaxValue();
13409     (end code)
13410     
13411     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
13412     
13413     Example:
13414     
13415     (start code js)
13416     //will return 100 for all BarChart instances,
13417     //displaying all of them with the same scale
13418     $jit.BarChart.implement({
13419       'getMaxValue': function() {
13420         return 100;
13421       }
13422     });
13423     (end code)
13424     
13425   */  
13426   getMaxValue: function() {
13427     var maxValue = 0, stacked = this.config.type.split(':')[0] == 'stacked';
13428     this.st.graph.eachNode(function(n) {
13429       var valArray = n.getData('valueArray'),
13430           acum = 0;
13431       if(!valArray) return;
13432       if(stacked) {
13433         $.each(valArray, function(v) { 
13434           acum += +v;
13435         });
13436       } else {
13437         acum = Math.max.apply(null, valArray);
13438       }
13439       maxValue = maxValue>acum? maxValue:acum;
13440     });
13441     return maxValue;
13442   },
13443   
13444   setBarType: function(type) {
13445     this.config.type = type;
13446     this.st.config.Node.type = 'barchart-' + type.split(':')[0];
13447   },
13448   
13449   normalizeDims: function() {
13450     //number of elements
13451     var root = this.st.graph.getNode(this.st.root), l=0;
13452     root.eachAdjacency(function() {
13453       l++;
13454     });
13455     var maxValue = this.getMaxValue() || 1,
13456         size = this.st.canvas.getSize(),
13457         config = this.config,
13458         margin = config.Margin,
13459         ticks = config.Ticks,
13460         title = config.Title,
13461         subtitle = config.Subtitle,
13462         grouped = config.type.split(':')[0] == 'grouped',
13463         marginWidth = margin.left + margin.right + (config.Label && grouped ? config.Label.size + config.labelOffset: 0),
13464         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
13465         horz = config.orientation == 'horizontal',
13466         fixedDim = (size[horz? 'height':'width'] - (horz? marginHeight:marginWidth) - (ticks.enable? config.Label.size + config.labelOffset : 0) - (l -1) * config.barsOffset) / l,
13467         animate = config.animate,
13468         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
13469
13470           - ((config.showLabels && !horz) ? (config.Label.size + config.labelOffset) : 0),
13471         dim1 = horz? 'height':'width',
13472         dim2 = horz? 'width':'height',
13473         basic = config.type.split(':')[0] == 'basic';
13474
13475         // Bug #47147 Correct detection of maxTickValue and step size for asix labels
13476         var iDirection = 10; // We need this var for convert value. 10^2 = 100
13477         var zeroCount = 0; // Pow for iDirection for detection of size of human step.
13478         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.
13479         // Tries to get two first digits from maxValue
13480         if (iNumber >= 0)
13481         {
13482             // Tries to calculate zeroCount
13483             // if iNumber = 100 we will get zeroCount = 2, iNumber = 0.1
13484             while (iNumber >= 1)
13485             {
13486                 zeroCount ++;
13487                 iNumber = iNumber / 10;
13488             }
13489             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
13490         }
13491         else
13492         {
13493             iDirection = 0.1; // if iNumber is less than 1 we should change iDirection. 0.1^2 = 0.01
13494             // Tries to calculate zeroCount
13495             // if iNumber = 0.01 we will get zeroCount = 2, iNumber = 1
13496             while (iNumber < 1)
13497             {
13498                 zeroCount ++;
13499                 iNumber = iNumber * 10;
13500             }
13501             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
13502         }
13503         var humanNumber = 0;
13504         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.
13505         // 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.
13506         while (iNumberTemp % 5 != 0)
13507         {
13508             iNumberTemp ++;
13509         }
13510         var isFound = false;
13511         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
13512         // Tries to find humanNumber. Our step is 5. ticks.segments is number of lines = 4 (for example)
13513         // 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
13514         while (isFound == false)
13515         {
13516             if (iNumberTemp % ticks.segments == 0)
13517             {
13518                 humanNumber = iNumberTemp / ticks.segments;
13519                 isFound = true;
13520                 break;
13521             }
13522             iNumberTemp = iNumberTemp + 5;
13523         }
13524         // Getting real values
13525         var maxTickValue = config.Ticks.maxValue = maxTickValue = iNumberTemp * Math.pow(iDirection, zeroCount - 1);
13526         config.Ticks.humanNumber = humanNumber = humanNumber * Math.pow(iDirection, zeroCount - 1);
13527         config.Ticks.segments = Math.floor(maxTickValue / humanNumber);
13528
13529                 fixedDim = fixedDim > 40 ? 40 : fixedDim;
13530
13531                 
13532     this.st.graph.eachNode(function(n) {
13533       var acum = 0, animateValue = [];
13534       $.each(n.getData('valueArray'), function(v) {
13535         acum += +v;
13536         animateValue.push(0);
13537       });
13538       
13539       if(grouped) {
13540         fixedDim = animateValue.length * 40;
13541       }
13542       n.setData(dim1, fixedDim);
13543       
13544       
13545       if(animate) {
13546         n.setData(dim2, acum * height / maxValue, 'end');
13547         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13548           return n * height / maxValue; 
13549         }), 'end');
13550         var dimArray = n.getData('dimArray');
13551         if(!dimArray) {
13552           n.setData('dimArray', animateValue);
13553         }
13554       } else {
13555         
13556
13557                 if(ticks.enable) {
13558                         n.setData(dim2, acum * height / maxTickValue);
13559                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13560                           return n * height / maxTickValue; 
13561                         }));
13562                 } else {
13563                         n.setData(dim2, acum * height / maxValue);
13564                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
13565                           return n * height / maxValue; 
13566                         }));
13567                 }
13568       }
13569     });
13570   }
13571 });
13572
13573 //funnel chart options
13574
13575
13576 Options.FunnelChart = {
13577   $extend: true,
13578   
13579   animate: true,
13580   type: 'stacked', //stacked, grouped, : gradient
13581   labelOffset: 3, //label offset
13582   barsOffset: 0, //distance between bars
13583   hoveredColor: '#9fd4ff',
13584   orientation: 'vertical',
13585   showAggregates: true,
13586   showLabels: true,
13587   Tips: {
13588     enable: false,
13589     onShow: $.empty,
13590     onHide: $.empty
13591   },
13592   Events: {
13593     enable: false,
13594     onClick: $.empty
13595   }
13596 };
13597
13598 $jit.ST.Plot.NodeTypes.implement({
13599   'funnelchart-basic' : {
13600     'render' : function(node, canvas) {
13601       var pos = node.pos.getc(true), 
13602           width  = node.getData('width'),
13603           height = node.getData('height'),
13604           algnPos = this.getAlignedPos(pos, width, height),
13605           x = algnPos.x, y = algnPos.y,
13606           dimArray = node.getData('dimArray'),
13607           valueArray = node.getData('valueArray'),
13608           valuelabelArray = node.getData('valuelabelArray'),
13609           linkArray = node.getData('linkArray'),
13610           colorArray = node.getData('colorArray'),
13611           colorLength = colorArray.length,
13612           stringArray = node.getData('stringArray');
13613       var ctx = canvas.getCtx(),
13614           opt = {},
13615           border = node.getData('border'),
13616           gradient = node.getData('gradient'),
13617           config = node.getData('config'),
13618           horz = config.orientation == 'horizontal',
13619           aggregates = config.showAggregates,
13620           showLabels = config.showLabels,
13621           label = config.Label,
13622           size = canvas.getSize(),
13623           labelOffset = config.labelOffset + 10;
13624           minWidth =  width * .25;
13625           ratio = .65;
13626
13627       if (colorArray && dimArray && stringArray) {
13628           var newDimArray = this.positions(dimArray, label.size);
13629         
13630         // horizontal lines
13631         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13632         ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13633
13634         if(label.type == 'Native') {      
13635        if(showLabels(node.name, valAcum, node)) {
13636                  ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13637                  var stringValue = stringArray[i];
13638                  var valueLabel = String(valuelabelArray[i]);
13639              var mV = ctx.measureText(stringValue);
13640              var mVL = ctx.measureText(valueLabel);
13641            var next_mVL = 0;
13642            var next_mV = 0;
13643            if ((i + 1) < l)
13644            {
13645                next_mV = ctx.measureText(stringArray[i + 1]);
13646                next_mVL = ctx.measureText(String(valuelabelArray[i + 1]));
13647            }
13648            else
13649            {
13650                next_mV = mV;
13651                next_mVL = mVL;
13652            }
13653                  var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13654                          var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13655                          var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13656                  var bottomWidth = minWidth + ((acum) * ratio);
13657            var bottomWidthLabel = minWidth + (newDimArray[i].position * ratio);
13658            var labelOffsetRight = (newDimArray[i].filament) ? (next_mV.width + 25) : 0;
13659            var labelOffsetLeft = (newDimArray[i].filament) ? (next_mVL.width + 25) : 0;
13660 //             ctx.fillRect((-bottomWidth/2) - mVL.width - config.labelOffset , y - acum, bottomWidth + mVL.width + mV.width + (config.labelOffset*2), 1);
13661
13662                         //right lines
13663                         ctx.beginPath();
13664                         ctx.moveTo(bottomWidth/2,y - acum); //
13665            ctx.lineTo(bottomWidthLabel / 2 + (labelOffset - 10), y - newDimArray[i].position);  // top right
13666            ctx.lineTo(bottomWidthLabel / 2 + (labelOffset) + labelOffsetRight + mV.width, y - newDimArray[i].position);  // bottom right
13667                         ctx.stroke();
13668                         //left lines
13669                         ctx.beginPath();
13670                         ctx.moveTo(-bottomWidth/2,y - acum); //
13671            ctx.lineTo( - bottomWidthLabel / 2 - (labelOffset - 10), y - newDimArray[i].position);  // top right
13672            ctx.lineTo( - bottomWidthLabel / 2 - (labelOffset) - labelOffsetLeft - mVL.width, y - newDimArray[i].position);  // bottom right
13673                         ctx.stroke();
13674        }
13675         }
13676
13677                 acum += (dimArray[i] || 0);
13678           valAcum += (valueArray[i] || 0);
13679           
13680           
13681                 }
13682                 
13683  
13684   
13685         //funnel segments and labels
13686         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
13687           ctx.fillStyle = ctx.strokeStyle = colorArray[i % colorLength];
13688                           var colori = colorArray[i % colorLength];
13689                           if(label.type == 'Native') { 
13690                                   var stringValue = stringArray[i];
13691                           var valueLabel = String(valuelabelArray[i]);
13692                               var mV = ctx.measureText(stringValue);
13693                       var mVL = ctx.measureText(valueLabel);
13694                           } else {
13695                                   var mV = 10;
13696                       var mVL = 10;     
13697                           }
13698             if ((i + 1) < l)
13699             {
13700                 next_mV = ctx.measureText(stringArray[i + 1]);
13701                 next_mVL = ctx.measureText(String(valuelabelArray[i + 1]));
13702             }
13703             else
13704             {
13705                 next_mV = mV;
13706                 next_mVL = mVL;
13707             }
13708                       var previousElementHeight = (i > 0) ? dimArray[i - 1] : 100;
13709                       var labelOffsetHeight = (previousElementHeight < label.size && i > 0) ? ((dimArray[i] > label.size) ? (dimArray[i]/2) - (label.size/2) : label.size) : 0;
13710             var labelOffsetRight = (newDimArray[i].filament) ? (next_mV.width + 20) : 0;
13711             var labelOffsetLeft = (newDimArray[i].filament) ? (next_mVL.width + 20) : 0;
13712                       
13713           var topWidth = minWidth + ((acum + dimArray[i]) * ratio);
13714           var bottomWidth = minWidth + ((acum) * ratio);
13715             var bottomWidthLabel = minWidth + (newDimArray[i].position * ratio);
13716           
13717
13718           if(gradient) {
13719             var linear;
13720               linear = ctx.createLinearGradient(-topWidth/2, y - acum - dimArray[i]/2, topWidth/2, y - acum- dimArray[i]/2);
13721                         var colorRgb = $.hexToRgb(colori);
13722             var color = $.map($.hexToRgb(colorArray[i % colorLength].slice(1)), 
13723                 function(v) { return (v * .5) >> 0; });
13724             linear.addColorStop(0, 'rgba('+color+',1)');
13725             linear.addColorStop(0.5,  'rgba('+colorRgb+',1)');
13726             linear.addColorStop(1, 'rgba('+color+',1)');
13727             ctx.fillStyle = linear;
13728           }
13729           
13730                         ctx.beginPath();
13731                         ctx.moveTo(-topWidth/2,y - acum - dimArray[i]); //top left
13732                         ctx.lineTo(topWidth/2,y - acum - dimArray[i]);  // top right
13733                         ctx.lineTo(bottomWidth/2,y - acum);  // bottom right
13734                         ctx.lineTo(-bottomWidth/2,y - acum);  // bottom left
13735                         ctx.closePath(); 
13736                         ctx.fill();
13737                 
13738           
13739           if(border && border.name == stringArray[i]) {
13740             opt.acum = acum;
13741             opt.dimValue = dimArray[i];
13742           }
13743           
13744           
13745         if(border) {
13746           ctx.save();
13747           ctx.lineWidth = 2;
13748           ctx.strokeStyle = border.color;
13749
13750             //ctx.strokeRect(x + 1, y - opt.acum - opt.dimValue + 1, minWidth -2, opt.dimValue -2);
13751          
13752           ctx.restore();
13753         }
13754         if(label.type == 'Native') {
13755           ctx.save();
13756           ctx.fillStyle = ctx.strokeStyle = label.color;
13757           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
13758           ctx.textBaseline = 'middle';
13759
13760                                 acumValueLabel = valAcum;
13761
13762           if(showLabels(node.name, valAcum, node)) {
13763
13764                       
13765               ctx.textAlign = 'left';
13766               ctx.fillText(stringArray[i], (bottomWidthLabel / 2) + labelOffset + labelOffsetRight, y - newDimArray[i].position - label.size / 2);
13767               ctx.textAlign = 'right';
13768               ctx.fillText(valuelabelArray[i], (- bottomWidthLabel / 2) - labelOffset - labelOffsetLeft, y - newDimArray[i].position - label.size / 2);
13769               }
13770           ctx.restore();
13771         }
13772
13773           acum += (dimArray[i] || 0);
13774           valAcum += (valueArray[i] || 0);
13775           
13776         }
13777
13778       }
13779     },
13780     'contains': function(node, mpos) {
13781       var pos = node.pos.getc(true), 
13782           width = node.getData('width'),
13783           height = node.getData('height'),
13784           algnPos = this.getAlignedPos(pos, width, height),
13785           x = algnPos.x, y = algnPos.y,
13786           dimArray = node.getData('dimArray'),
13787           config = node.getData('config'),
13788           st = node.getData('st'),
13789           rx = mpos.x - x,
13790           horz = config.orientation == 'horizontal',
13791            minWidth =  width * .25;
13792           ratio = .65,
13793           canvas = node.getData('canvas'),
13794           size = canvas.getSize(),
13795           offsetY = st.config.offsetY;
13796       //bounding box check
13797
13798         if(mpos.y > y || mpos.y < y - height) {
13799             return false;
13800           }
13801           
13802          var newY = Math.abs(mpos.y + offsetY);
13803         var bound = minWidth + (newY * ratio);
13804         var boundLeft = -bound/2;
13805         var boundRight = bound/2;
13806          if(mpos.x < boundLeft || mpos.x > boundRight ) {
13807             return false;
13808           }
13809
13810       
13811       //deep check
13812       for(var i=0, l=dimArray.length, acum=(horz? x:y); i<l; i++) {
13813         var dimi = dimArray[i];
13814
13815           
13816           
13817                 var url = Url.decode(node.getData('linkArray')[i]);
13818           acum -= dimi;  
13819           var intersec = acum;
13820           if(mpos.y >= intersec) {
13821             return {
13822               'name': node.getData('stringArray')[i],
13823               'color': node.getData('colorArray')[i],
13824               'value': node.getData('valueArray')[i],
13825               'percentage': node.getData('percentageArray')[i],
13826                           'valuelabel': node.getData('valuelabelArray')[i],
13827               'link': url,
13828               'label': node.name
13829             };
13830           }
13831         
13832       }
13833       return false;
13834     }
13835   }
13836 });
13837
13838 /*
13839   Class: FunnelChart
13840   
13841   A visualization that displays funnel charts.
13842   
13843   Constructor Options:
13844   
13845   See <Options.FunnelChart>.
13846
13847 */
13848 $jit.FunnelChart = new Class({
13849   st: null,
13850   colors: ["#004b9c", "#9c0079", "#9c0033", "#28009c", "#9c0000", "#7d009c", "#001a9c","#00809c","#009c80","#009c42","#009c07","#469c00","#799c00","#9c9600","#9c5c00"],
13851   selected: {},
13852   busy: false,
13853   
13854   initialize: function(opt) {
13855     this.controller = this.config = 
13856       $.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
13857         Label: { type: 'Native' }
13858       }, opt);
13859     //set functions for showLabels and showAggregates
13860     var showLabels = this.config.showLabels,
13861         typeLabels = $.type(showLabels),
13862         showAggregates = this.config.showAggregates,
13863         typeAggregates = $.type(showAggregates);
13864     this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
13865     this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
13866     Options.Fx.clearCanvas = false;
13867     this.initializeViz();
13868   },
13869   
13870   initializeViz: function() {
13871     var config = this.config, that = this;
13872     var nodeType = config.type.split(":")[0],
13873         horz = config.orientation == 'horizontal',
13874         nodeLabels = {};
13875     var st = new $jit.ST({
13876       injectInto: config.injectInto,
13877       orientation: horz? 'left' : 'bottom',
13878       levelDistance: 0,
13879       background: config.background,
13880       renderBackground: config.renderBackground,
13881       backgroundColor: config.backgroundColor,
13882       colorStop1: config.colorStop1,
13883       colorStop2: config.colorStop2,
13884       siblingOffset: config.segmentOffset,
13885       subtreeOffset: 0,
13886       withLabels: config.Label.type != 'Native',      
13887       useCanvas: config.useCanvas,
13888       Label: {
13889         type: config.Label.type
13890       },
13891       Node: {
13892         overridable: true,
13893         type: 'funnelchart-' + nodeType,
13894         align: 'left',
13895         width: 1,
13896         height: 1
13897       },
13898       Edge: {
13899         type: 'none'
13900       },
13901       Tips: {
13902         enable: config.Tips.enable,
13903         type: 'Native',
13904         force: true,
13905         onShow: function(tip, node, contains) {
13906           var elem = contains;
13907           config.Tips.onShow(tip, elem, node);
13908                           if(elem.link != 'undefined' && elem.link != '') {
13909                                 document.body.style.cursor = 'pointer';
13910                           }
13911         },
13912                 onHide: function(call) {
13913                         document.body.style.cursor = 'default';
13914
13915         }
13916       },
13917       Events: {
13918         enable: true,
13919         type: 'Native',
13920         onClick: function(node, eventInfo, evt) {
13921           if(!config.Events.enable) return;
13922           var elem = eventInfo.getContains();
13923           config.Events.onClick(elem, eventInfo, evt);
13924         },
13925         onMouseMove: function(node, eventInfo, evt) {
13926           if(!config.hoveredColor) return;
13927           if(node) {
13928             var elem = eventInfo.getContains();
13929             that.select(node.id, elem.name, elem.index);
13930           } else {
13931             that.select(false, false, false);
13932           }
13933         }
13934       },
13935       onCreateLabel: function(domElement, node) {
13936         var labelConf = config.Label,
13937             valueArray = node.getData('valueArray'),
13938             idArray = node.getData('idArray'),
13939             valuelabelArray = node.getData('valuelabelArray'),
13940             stringArray = node.getData('stringArray');
13941             size = st.canvas.getSize()
13942             prefix = $.time();
13943                 
13944                 for(var i=0, l=valueArray.length; i<l; i++) {
13945         var nlbs = {
13946           wrapper: document.createElement('div'),
13947           valueLabel: document.createElement('div'),
13948           label: document.createElement('div')
13949         };
13950         var wrapper = nlbs.wrapper,
13951             label = nlbs.label,
13952             valueLabel = nlbs.valueLabel,
13953             wrapperStyle = wrapper.style,
13954             labelStyle = label.style,
13955             valueLabelStyle = valueLabel.style;
13956         //store node labels
13957         nodeLabels[idArray[i]] = nlbs;
13958         //append labels
13959         wrapper.appendChild(label);
13960         wrapper.appendChild(valueLabel);
13961
13962         wrapperStyle.position = 'relative';
13963         wrapperStyle.overflow = 'visible';
13964         wrapperStyle.fontSize = labelConf.size + 'px';
13965         wrapperStyle.fontFamily = labelConf.family;
13966         wrapperStyle.color = labelConf.color;
13967         wrapperStyle.textAlign = 'center';
13968         wrapperStyle.width = size.width + 'px';
13969         valueLabelStyle.position = labelStyle.position = 'absolute';
13970         valueLabelStyle.left = labelStyle.left =  '0px';
13971                 valueLabelStyle.width = (size.width/3) + 'px';
13972                 valueLabelStyle.textAlign = 'right';
13973         label.innerHTML = stringArray[i];
13974         valueLabel.innerHTML = valuelabelArray[i];
13975         domElement.id = prefix+'funnel';
13976         domElement.style.width = size.width + 'px';
13977         
13978                 domElement.appendChild(wrapper);
13979                 }
13980
13981       },
13982       onPlaceLabel: function(domElement, node) {
13983
13984             var dimArray = node.getData('dimArray'),
13985             idArray = node.getData('idArray'),
13986             valueArray = node.getData('valueArray'),
13987             valuelabelArray = node.getData('valuelabelArray'),
13988             stringArray = node.getData('stringArray');
13989             size = st.canvas.getSize(),
13990             pos = node.pos.getc(true),
13991              domElement.style.left = "0px",
13992              domElement.style.top = "0px",
13993              minWidth = node.getData('width') * .25,
13994              ratio = .65,
13995              pos = node.pos.getc(true),
13996              labelConf = config.Label;
13997              
13998              
13999                 for(var i=0, l=valueArray.length, acum = 0; i<l; i++) {
14000
14001         var labels = nodeLabels[idArray[i]],
14002             wrapperStyle = labels.wrapper.style,
14003             labelStyle = labels.label.style,
14004             valueLabelStyle = labels.valueLabel.style;
14005                 var bottomWidth = minWidth + (acum * ratio); 
14006                 
14007             font = parseInt(wrapperStyle.fontSize, 10),
14008             domStyle = domElement.style;
14009            
14010                 
14011        
14012                         wrapperStyle.top = (pos.y + size.height/2) - acum - labelConf.size + "px";
14013             valueLabelStyle.left = (size.width/2) - (bottomWidth/2) - config.labelOffset - (size.width/3) + 'px';
14014             labelStyle.left =  (size.width/2) + (bottomWidth/2) + config.labelOffset + 'px';;
14015
14016                         acum += (dimArray[i] || 0);
14017
14018                 }
14019
14020       }
14021
14022     });
14023
14024     var size = st.canvas.getSize(),
14025         margin = config.Margin;
14026         title = config.Title;
14027         subtitle = config.Subtitle;
14028         //y offset
14029
14030       st.config.offsetY = -size.height/2 + margin.bottom 
14031         + (config.showLabels && (config.labelOffset + config.Label.size)) + (subtitle.text? subtitle.size+subtitle.offset:0);
14032
14033                 st.config.offsetX = (margin.right - margin.left)/2;
14034           
14035     
14036     this.st = st;
14037     this.canvas = this.st.canvas;
14038   },
14039   
14040   renderTitle: function() {
14041         var canvas = this.canvas,
14042         size = canvas.getSize(),
14043         config = this.config,
14044         margin = config.Margin,
14045         label = config.Label,
14046         title = config.Title;
14047         ctx = canvas.getCtx();
14048         ctx.fillStyle = title.color;
14049         ctx.textAlign = 'left';
14050         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
14051         if(label.type == 'Native') {
14052                 ctx.fillText(title.text, -size.width/2+margin.left, -size.height/2+margin.top);
14053         }
14054   },  
14055   
14056   renderSubtitle: function() {
14057         var canvas = this.canvas,
14058         size = canvas.getSize(),
14059         config = this.config,
14060         margin = config.Margin,
14061         label = config.Label,
14062         subtitle = config.Subtitle;
14063         ctx = canvas.getCtx();
14064         ctx.fillStyle = title.color;
14065         ctx.textAlign = 'left';
14066         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
14067         if(label.type == 'Native') {
14068                 ctx.fillText(subtitle.text, -size.width/2+margin.left, size.height/2-margin.bottom-subtitle.size);
14069         }
14070   },
14071   
14072   
14073   renderDropShadow: function() {
14074         var canvas = this.canvas,
14075         size = canvas.getSize(),
14076         config = this.config,
14077         margin = config.Margin,
14078         horz = config.orientation == 'horizontal',
14079         label = config.Label,
14080         title = config.Title,
14081         shadowThickness = 4,
14082         subtitle = config.Subtitle,
14083         ctx = canvas.getCtx(),
14084         minwidth = (size.width/8) * .25,
14085         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14086         topMargin = (title.text? title.size + title.offset : 0)  + margin.top,
14087     height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14088           - (config.showLabels && (config.Label.size + config.labelOffset)),
14089     ratio = .65,
14090         topWidth = minwidth + ((height + (shadowThickness*4)) * ratio);
14091         topY = (-size.height/2) + topMargin - shadowThickness;
14092         bottomY = (-size.height/2) + topMargin + height + shadowThickness;
14093         bottomWidth = minwidth + shadowThickness;
14094         ctx.beginPath();
14095         ctx.fillStyle = "rgba(0,0,0,.2)";
14096         ctx.moveTo(0,topY);
14097         ctx.lineTo(-topWidth/2,topY); //top left
14098         ctx.lineTo(-bottomWidth/2,bottomY);  // bottom left
14099         ctx.lineTo(bottomWidth/2,bottomY);  // bottom right
14100         ctx.lineTo(topWidth/2,topY);  // top right
14101         ctx.closePath(); 
14102         ctx.fill();
14103                         
14104                         
14105   },
14106
14107    renderBackground: function() {
14108                 var canvas = this.canvas,
14109                 config = this.config,
14110                 backgroundColor = config.backgroundColor,
14111                 size = canvas.getSize(),
14112                 ctx = canvas.getCtx();
14113                 //ctx.globalCompositeOperation = "destination-over";
14114             ctx.fillStyle = backgroundColor;
14115             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
14116   },
14117   clear: function() {
14118         var canvas = this.canvas;
14119         var ctx = canvas.getCtx(),
14120         size = canvas.getSize();
14121         ctx.fillStyle = "rgba(255,255,255,0)";
14122         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
14123         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
14124   },
14125    resizeGraph: function(json,width) {
14126         var canvas = this.canvas,
14127         size = canvas.getSize(),
14128         config = this.config,
14129         orgHeight = size.height;
14130         
14131
14132         canvas.resize(width,orgHeight);
14133
14134         if(typeof FlashCanvas == "undefined") {
14135                 canvas.clear();
14136         } else {
14137                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
14138         }
14139         this.loadJSON(json);
14140
14141         },
14142         
14143   loadJSON: function(json) {
14144     if(this.busy) return;
14145     this.busy = true;
14146     var prefix = $.time(), 
14147         ch = [], 
14148         st = this.st,
14149         name = $.splat(json.label), 
14150         color = $.splat(json.color || this.colors),
14151         config = this.config,
14152         canvas = this.canvas,
14153         gradient = !!config.type.split(":")[1],
14154         animate = config.animate,
14155         title = config.Title,
14156         subtitle = config.Subtitle,
14157         renderBackground = config.renderBackground,
14158         horz = config.orientation == 'horizontal',
14159         that = this,
14160                 colorLength = color.length,
14161                 nameLength = name.length,
14162                 totalValue = 0;
14163     for(var i=0, values=json.values, l=values.length; i<l; i++) {
14164         var val = values[i];
14165         var valArray = $.splat(val.values);
14166         totalValue += parseFloat(valArray.sum());
14167     }
14168     
14169     
14170     var nameArray = new Array();
14171     var idArray = new Array();
14172     var valArray = new Array();
14173     var valuelabelArray = new Array();
14174     var linkArray = new Array();
14175     var titleArray = new Array();
14176     var percentageArray = new Array();
14177     
14178     for(var i=0, values=json.values, l=values.length; i<l; i++) {
14179       var val = values[i];
14180       nameArray[i] = $.splat(val.label);
14181       idArray[i] = $.splat(prefix + val.label);
14182       valArray[i] = $.splat(val.values);
14183       valuelabelArray[i] = $.splat(val.valuelabels);
14184       linkArray[i] = $.splat(val.links);
14185       titleArray[i] = $.splat(val.titles);
14186       percentageArray[i] = (($.splat(val.values).sum()/totalValue) * 100).toFixed(1);
14187       var acum = 0;
14188     }
14189     
14190
14191     nameArray.reverse();
14192     valArray.reverse();
14193     valuelabelArray.reverse();
14194     linkArray.reverse();
14195     titleArray.reverse();
14196     percentageArray.reverse();
14197     
14198       ch.push({
14199         'id': prefix + val.label,
14200         'name': val.label,
14201         
14202         'data': {
14203           'value': valArray,
14204           '$idArray': idArray,
14205           '$linkArray': linkArray,
14206           '$titleArray': titleArray,
14207           '$valueArray': valArray,
14208           '$valuelabelArray': valuelabelArray,
14209           '$colorArray': color,
14210           '$colorMono': $.splat(color[i % colorLength]),
14211           '$stringArray': (typeof FlashCanvas == "undefined") ? nameArray: name.reverse(),
14212           '$gradient': gradient,
14213           '$config': config,
14214           '$percentageArray' : percentageArray,
14215           '$canvas': canvas,
14216           '$st': st
14217         },
14218         'children': []
14219       });
14220     
14221     var root = {
14222       'id': prefix + '$root',
14223       'name': '',
14224       'data': {
14225         '$type': 'none',
14226         '$width': 1,
14227         '$height': 1
14228       },
14229       'children': ch
14230     };
14231     st.loadJSON(root);
14232     
14233     this.normalizeDims();
14234         
14235         if(renderBackground) {
14236                 this.renderBackground();        
14237         }
14238         if(!animate && title.text) {
14239                 this.renderTitle();
14240         }
14241         if(!animate && subtitle.text) {
14242                 this.renderSubtitle();
14243         }
14244         if(typeof FlashCanvas == "undefined") {
14245                 this.renderDropShadow();
14246         }
14247     st.compute();
14248     st.select(st.root);
14249     if(animate) {
14250       if(horz) {
14251         st.fx.animate({
14252           modes: ['node-property:width:dimArray'],
14253           duration:1500,
14254           onComplete: function() {
14255             that.busy = false;
14256           }
14257         });
14258       } else {
14259         st.fx.animate({
14260           modes: ['node-property:height:dimArray'],
14261           duration:1500,
14262           onComplete: function() {
14263             that.busy = false;
14264           }
14265         });
14266       }
14267     } else {
14268       this.busy = false;
14269     }
14270   },
14271   
14272   /*
14273     Method: updateJSON
14274    
14275     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.
14276     
14277     Parameters:
14278     
14279     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
14280     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
14281     
14282     Example:
14283     
14284     (start code js)
14285     barChart.updateJSON(json, {
14286       onComplete: function() {
14287         alert('update complete!');
14288       }
14289     });
14290     (end code)
14291  */  
14292   updateJSON: function(json, onComplete) {
14293     if(this.busy) return;
14294     this.busy = true;
14295     
14296     var st = this.st;
14297     var graph = st.graph;
14298     var values = json.values;
14299     var animate = this.config.animate;
14300     var that = this;
14301     var horz = this.config.orientation == 'horizontal';
14302     $.each(values, function(v) {
14303       var n = graph.getByName(v.label);
14304       if(n) {
14305         n.setData('valueArray', $.splat(v.values));
14306         if(json.label) {
14307           n.setData('stringArray', $.splat(json.label));
14308         }
14309       }
14310     });
14311     this.normalizeDims();
14312     st.compute();
14313     st.select(st.root);
14314     if(animate) {
14315       if(horz) {
14316         st.fx.animate({
14317           modes: ['node-property:width:dimArray'],
14318           duration:1500,
14319           onComplete: function() {
14320             that.busy = false;
14321             onComplete && onComplete.onComplete();
14322           }
14323         });
14324       } else {
14325         st.fx.animate({
14326           modes: ['node-property:height:dimArray'],
14327           duration:1500,
14328           onComplete: function() {
14329             that.busy = false;
14330             onComplete && onComplete.onComplete();
14331           }
14332         });
14333       }
14334     }
14335   },
14336   
14337   //adds the little brown bar when hovering the node
14338   select: function(id, name) {
14339
14340     if(!this.config.hoveredColor) return;
14341     var s = this.selected;
14342     if(s.id != id || s.name != name) {
14343       s.id = id;
14344       s.name = name;
14345       s.color = this.config.hoveredColor;
14346       this.st.graph.eachNode(function(n) {
14347         if(id == n.id) {
14348           n.setData('border', s);
14349         } else {
14350           n.setData('border', false);
14351         }
14352       });
14353       this.st.plot();
14354     }
14355   },
14356   
14357   /*
14358     Method: getLegend
14359    
14360     Returns an object containing as keys the legend names and as values hex strings with color values.
14361     
14362     Example:
14363     
14364     (start code js)
14365     var legend = barChart.getLegend();
14366     (end code)
14367   */  
14368   getLegend: function() {
14369     var legend = new Array();
14370     var name = new Array();
14371     var color = new Array();
14372     var n;
14373     this.st.graph.getNode(this.st.root).eachAdjacency(function(adj) {
14374       n = adj.nodeTo;
14375     });
14376     var colors = n.getData('colorArray'),
14377         len = colors.length;
14378     $.each(n.getData('stringArray'), function(s, i) {
14379       color[i] = colors[i % len];
14380       name[i] = s;
14381     });
14382         legend['name'] = name;
14383         legend['color'] = color;
14384     return legend;
14385   },
14386   
14387   /*
14388     Method: getMaxValue
14389    
14390     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
14391     
14392     Example:
14393     
14394     (start code js)
14395     var ans = barChart.getMaxValue();
14396     (end code)
14397     
14398     In some cases it could be useful to override this method to normalize heights for a group of BarCharts, like when doing small multiples.
14399     
14400     Example:
14401     
14402     (start code js)
14403     //will return 100 for all BarChart instances,
14404     //displaying all of them with the same scale
14405     $jit.BarChart.implement({
14406       'getMaxValue': function() {
14407         return 100;
14408       }
14409     });
14410     (end code)
14411     
14412   */  
14413   getMaxValue: function() {
14414     var maxValue = 0, stacked = true;
14415     this.st.graph.eachNode(function(n) {
14416       var valArray = n.getData('valueArray'),
14417           acum = 0;
14418       if(!valArray) return;
14419       if(stacked) {
14420         $.each(valArray, function(v) { 
14421           acum += +v;
14422         });
14423       } else {
14424         acum = Math.max.apply(null, valArray);
14425       }
14426       maxValue = maxValue>acum? maxValue:acum;
14427     });
14428     return maxValue;
14429   },
14430   
14431   setBarType: function(type) {
14432     this.config.type = type;
14433     this.st.config.Node.type = 'funnelchart-' + type.split(':')[0];
14434   },
14435   
14436   normalizeDims: function() {
14437     //number of elements
14438     var root = this.st.graph.getNode(this.st.root), l=0;
14439     root.eachAdjacency(function() {
14440       l++;
14441     });
14442     var maxValue = this.getMaxValue() || 1,
14443         size = this.st.canvas.getSize(),
14444         config = this.config,
14445         margin = config.Margin,
14446         title = config.Title,
14447         subtitle = config.Subtitle,
14448         marginWidth = margin.left + margin.right,
14449         marginHeight = (title.text? title.size + title.offset : 0) + (subtitle.text? subtitle.size + subtitle.offset : 0) + margin.top + margin.bottom,
14450         horz = config.orientation == 'horizontal',
14451         animate = config.animate,
14452         height = size[horz? 'width':'height'] - (horz? marginWidth:marginHeight) 
14453
14454           - (config.showLabels && (config.Label.size + config.labelOffset)),
14455         dim1 = horz? 'height':'width',
14456         dim2 = horz? 'width':'height';
14457         
14458
14459         minWidth = size.width/8;
14460         
14461
14462
14463     this.st.graph.eachNode(function(n) {
14464       var acum = 0, animateValue = [];
14465       $.each(n.getData('valueArray'), function(v) {
14466         acum += +v;
14467         animateValue.push(0);
14468       });
14469       n.setData(dim1, minWidth);
14470             
14471       if(animate) {
14472         n.setData(dim2, acum * height / maxValue, 'end');
14473         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14474           return n * height / maxValue; 
14475         }), 'end');
14476         var dimArray = n.getData('dimArray');
14477         if(!dimArray) {
14478           n.setData('dimArray', animateValue);
14479         }
14480       } else {
14481                         n.setData(dim2, acum * height / maxValue);
14482                         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
14483                           return n * height / maxValue; 
14484                         }));
14485       }
14486
14487     });
14488   }
14489 });
14490
14491
14492
14493 /*
14494  * File: Options.PieChart.js
14495  *
14496 */
14497 /*
14498   Object: Options.PieChart
14499   
14500   <PieChart> options. 
14501   Other options included in the PieChart are <Options.Canvas>, <Options.Label>, <Options.Tips> and <Options.Events>.
14502   
14503   Syntax:
14504   
14505   (start code js)
14506
14507   Options.PieChart = {
14508     animate: true,
14509     offset: 25,
14510     sliceOffset:0,
14511     labelOffset: 3,
14512     type: 'stacked',
14513     hoveredColor: '#9fd4ff',
14514     showLabels: true,
14515     resizeLabels: false,
14516     updateHeights: false
14517   };  
14518
14519   (end code)
14520   
14521   Example:
14522   
14523   (start code js)
14524
14525   var pie = new $jit.PieChart({
14526     animate: true,
14527     sliceOffset: 5,
14528     type: 'stacked:gradient'
14529   });  
14530
14531   (end code)
14532   
14533   Parameters:
14534   
14535   animate - (boolean) Default's *true*. Whether to add animated transitions when plotting/updating the visualization.
14536   offset - (number) Default's *25*. Adds margin between the visualization and the canvas.
14537   sliceOffset - (number) Default's *0*. Separation between the center of the canvas and each pie slice.
14538   labelOffset - (number) Default's *3*. Adds margin between the label and the default place where it should be drawn.
14539   type - (string) Default's *'stacked'*. Stack style. Posible values are 'stacked', 'stacked:gradient' to add gradients.
14540   hoveredColor - (boolean|string) Default's *'#9fd4ff'*. Sets the selected color for a hovered pie stack.
14541   showLabels - (boolean) Default's *true*. Display the name of the slots.
14542   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.
14543   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.
14544
14545 */
14546 Options.PieChart = {
14547   $extend: true,
14548
14549   animate: true,
14550   offset: 25, // page offset
14551   sliceOffset:0,
14552   labelOffset: 3, // label offset
14553   type: 'stacked', // gradient
14554   labelType: 'name',
14555   hoveredColor: '#9fd4ff',
14556   Events: {
14557     enable: false,
14558     onClick: $.empty
14559   },
14560   Tips: {
14561     enable: false,
14562     onShow: $.empty,
14563     onHide: $.empty
14564   },
14565   showLabels: true,
14566   resizeLabels: false,
14567   
14568   //only valid for mono-valued datasets
14569   updateHeights: false
14570 };
14571
14572 /*
14573  * Class: Layouts.Radial
14574  * 
14575  * Implements a Radial Layout.
14576  * 
14577  * Implemented By:
14578  * 
14579  * <RGraph>, <Hypertree>
14580  * 
14581  */
14582 Layouts.Radial = new Class({
14583
14584   /*
14585    * Method: compute
14586    * 
14587    * Computes nodes' positions.
14588    * 
14589    * Parameters:
14590    * 
14591    * property - _optional_ A <Graph.Node> position property to store the new
14592    * positions. Possible values are 'pos', 'end' or 'start'.
14593    * 
14594    */
14595   compute : function(property) {
14596     var prop = $.splat(property || [ 'current', 'start', 'end' ]);
14597     NodeDim.compute(this.graph, prop, this.config);
14598     this.graph.computeLevels(this.root, 0, "ignore");
14599     var lengthFunc = this.createLevelDistanceFunc(); 
14600     this.computeAngularWidths(prop);
14601     this.computePositions(prop, lengthFunc);
14602   },
14603
14604   /*
14605    * computePositions
14606    * 
14607    * Performs the main algorithm for computing node positions.
14608    */
14609   computePositions : function(property, getLength) {
14610     var propArray = property;
14611     var graph = this.graph;
14612     var root = graph.getNode(this.root);
14613     var parent = this.parent;
14614     var config = this.config;
14615
14616     for ( var i=0, l=propArray.length; i < l; i++) {
14617       var pi = propArray[i];
14618       root.setPos($P(0, 0), pi);
14619       root.setData('span', Math.PI * 2, pi);
14620     }
14621
14622     root.angleSpan = {
14623       begin : 0,
14624       end : 2 * Math.PI
14625     };
14626
14627     graph.eachBFS(this.root, function(elem) {
14628       var angleSpan = elem.angleSpan.end - elem.angleSpan.begin;
14629       var angleInit = elem.angleSpan.begin;
14630       var len = getLength(elem);
14631       //Calculate the sum of all angular widths
14632       var totalAngularWidths = 0, subnodes = [], maxDim = {};
14633       elem.eachSubnode(function(sib) {
14634         totalAngularWidths += sib._treeAngularWidth;
14635         //get max dim
14636         for ( var i=0, l=propArray.length; i < l; i++) {
14637           var pi = propArray[i], dim = sib.getData('dim', pi);
14638           maxDim[pi] = (pi in maxDim)? (dim > maxDim[pi]? dim : maxDim[pi]) : dim;
14639         }
14640         subnodes.push(sib);
14641       }, "ignore");
14642       //Maintain children order
14643       //Second constraint for <http://bailando.sims.berkeley.edu/papers/infovis01.htm>
14644       if (parent && parent.id == elem.id && subnodes.length > 0
14645           && subnodes[0].dist) {
14646         subnodes.sort(function(a, b) {
14647           return (a.dist >= b.dist) - (a.dist <= b.dist);
14648         });
14649       }
14650       //Calculate nodes positions.
14651       for (var k = 0, ls=subnodes.length; k < ls; k++) {
14652         var child = subnodes[k];
14653         if (!child._flag) {
14654           var angleProportion = child._treeAngularWidth / totalAngularWidths * angleSpan;
14655           var theta = angleInit + angleProportion / 2;
14656
14657           for ( var i=0, l=propArray.length; i < l; i++) {
14658             var pi = propArray[i];
14659             child.setPos($P(theta, len), pi);
14660             child.setData('span', angleProportion, pi);
14661             child.setData('dim-quotient', child.getData('dim', pi) / maxDim[pi], pi);
14662           }
14663
14664           child.angleSpan = {
14665             begin : angleInit,
14666             end : angleInit + angleProportion
14667           };
14668           angleInit += angleProportion;
14669         }
14670       }
14671     }, "ignore");
14672   },
14673
14674   /*
14675    * Method: setAngularWidthForNodes
14676    * 
14677    * Sets nodes angular widths.
14678    */
14679   setAngularWidthForNodes : function(prop) {
14680     this.graph.eachBFS(this.root, function(elem, i) {
14681       var diamValue = elem.getData('angularWidth', prop[0]) || 5;
14682       elem._angularWidth = diamValue / i;
14683     }, "ignore");
14684   },
14685
14686   /*
14687    * Method: setSubtreesAngularWidth
14688    * 
14689    * Sets subtrees angular widths.
14690    */
14691   setSubtreesAngularWidth : function() {
14692     var that = this;
14693     this.graph.eachNode(function(elem) {
14694       that.setSubtreeAngularWidth(elem);
14695     }, "ignore");
14696   },
14697
14698   /*
14699    * Method: setSubtreeAngularWidth
14700    * 
14701    * Sets the angular width for a subtree.
14702    */
14703   setSubtreeAngularWidth : function(elem) {
14704     var that = this, nodeAW = elem._angularWidth, sumAW = 0;
14705     elem.eachSubnode(function(child) {
14706       that.setSubtreeAngularWidth(child);
14707       sumAW += child._treeAngularWidth;
14708     }, "ignore");
14709     elem._treeAngularWidth = Math.max(nodeAW, sumAW);
14710   },
14711
14712   /*
14713    * Method: computeAngularWidths
14714    * 
14715    * Computes nodes and subtrees angular widths.
14716    */
14717   computeAngularWidths : function(prop) {
14718     this.setAngularWidthForNodes(prop);
14719     this.setSubtreesAngularWidth();
14720   }
14721
14722 });
14723
14724
14725 /*
14726  * File: Sunburst.js
14727  */
14728
14729 /*
14730    Class: Sunburst
14731       
14732    A radial space filling tree visualization.
14733    
14734    Inspired by:
14735  
14736    Sunburst <http://www.cc.gatech.edu/gvu/ii/sunburst/>.
14737    
14738    Note:
14739    
14740    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.
14741    
14742   Implements:
14743   
14744   All <Loader> methods
14745   
14746    Constructor Options:
14747    
14748    Inherits options from
14749    
14750    - <Options.Canvas>
14751    - <Options.Controller>
14752    - <Options.Node>
14753    - <Options.Edge>
14754    - <Options.Label>
14755    - <Options.Events>
14756    - <Options.Tips>
14757    - <Options.NodeStyles>
14758    - <Options.Navigation>
14759    
14760    Additionally, there are other parameters and some default values changed
14761    
14762    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
14763    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
14764    Node.type - Described in <Options.Node>. Default's to *multipie*.
14765    Node.height - Described in <Options.Node>. Default's *0*.
14766    Edge.type - Described in <Options.Edge>. Default's *none*.
14767    Label.textAlign - Described in <Options.Label>. Default's *start*.
14768    Label.textBaseline - Described in <Options.Label>. Default's *middle*.
14769      
14770    Instance Properties:
14771
14772    canvas - Access a <Canvas> instance.
14773    graph - Access a <Graph> instance.
14774    op - Access a <Sunburst.Op> instance.
14775    fx - Access a <Sunburst.Plot> instance.
14776    labels - Access a <Sunburst.Label> interface implementation.   
14777
14778 */
14779
14780 $jit.Sunburst = new Class({
14781
14782   Implements: [ Loader, Extras, Layouts.Radial ],
14783
14784   initialize: function(controller) {
14785     var $Sunburst = $jit.Sunburst;
14786
14787     var config = {
14788       interpolation: 'linear',
14789       levelDistance: 100,
14790       Node: {
14791         'type': 'multipie',
14792         'height':0
14793       },
14794       Edge: {
14795         'type': 'none'
14796       },
14797       Label: {
14798         textAlign: 'start',
14799         textBaseline: 'middle'
14800       }
14801     };
14802
14803     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
14804         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
14805
14806     var canvasConfig = this.config;
14807     if(canvasConfig.useCanvas) {
14808       this.canvas = canvasConfig.useCanvas;
14809       this.config.labelContainer = this.canvas.id + '-label';
14810     } else {
14811       if(canvasConfig.background) {
14812         canvasConfig.background = $.merge({
14813           type: 'Fade',
14814           colorStop1: this.config.colorStop1,
14815           colorStop2: this.config.colorStop2
14816         }, canvasConfig.background);
14817       }
14818       this.canvas = new Canvas(this, canvasConfig);
14819       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
14820     }
14821
14822     this.graphOptions = {
14823       'complex': false,
14824       'Node': {
14825         'selected': false,
14826         'exist': true,
14827         'drawn': true
14828       }
14829     };
14830     this.graph = new Graph(this.graphOptions, this.config.Node,
14831         this.config.Edge);
14832     this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
14833     this.fx = new $Sunburst.Plot(this, $Sunburst);
14834     this.op = new $Sunburst.Op(this);
14835     this.json = null;
14836     this.root = null;
14837     this.rotated = null;
14838     this.busy = false;
14839     // initialize extras
14840     this.initializeExtras();
14841   },
14842
14843   /* 
14844   
14845     createLevelDistanceFunc 
14846   
14847     Returns the levelDistance function used for calculating a node distance 
14848     to its origin. This function returns a function that is computed 
14849     per level and not per node, such that all nodes with the same depth will have the 
14850     same distance to the origin. The resulting function gets the 
14851     parent node as parameter and returns a float.
14852
14853    */
14854   createLevelDistanceFunc: function() {
14855     var ld = this.config.levelDistance;
14856     return function(elem) {
14857       return (elem._depth + 1) * ld;
14858     };
14859   },
14860
14861   /* 
14862      Method: refresh 
14863      
14864      Computes positions and plots the tree.
14865
14866    */
14867   refresh: function() {
14868     this.compute();
14869     this.plot();
14870   },
14871
14872   /*
14873    reposition
14874   
14875    An alias for computing new positions to _endPos_
14876
14877    See also:
14878
14879    <Sunburst.compute>
14880    
14881   */
14882   reposition: function() {
14883     this.compute('end');
14884   },
14885
14886   /*
14887   Method: rotate
14888   
14889   Rotates the graph so that the selected node is horizontal on the right.
14890
14891   Parameters:
14892   
14893   node - (object) A <Graph.Node>.
14894   method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14895   opt - (object) Configuration options merged with this visualization configuration options.
14896   
14897   See also:
14898
14899   <Sunburst.rotateAngle>
14900   
14901   */
14902   rotate: function(node, method, opt) {
14903     var theta = node.getPos(opt.property || 'current').getp(true).theta;
14904     this.rotated = node;
14905     this.rotateAngle(-theta, method, opt);
14906   },
14907
14908   /*
14909   Method: rotateAngle
14910   
14911   Rotates the graph of an angle theta.
14912   
14913    Parameters:
14914    
14915    node - (object) A <Graph.Node>.
14916    method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
14917    opt - (object) Configuration options merged with this visualization configuration options.
14918    
14919    See also:
14920
14921    <Sunburst.rotate>
14922   
14923   */
14924   rotateAngle: function(theta, method, opt) {
14925     var that = this;
14926     var options = $.merge(this.config, opt || {}, {
14927       modes: [ 'polar' ]
14928     });
14929     var prop = opt.property || (method === "animate" ? 'end' : 'current');
14930     if(method === 'animate') {
14931       this.fx.animation.pause();
14932     }
14933     this.graph.eachNode(function(n) {
14934       var p = n.getPos(prop);
14935       p.theta += theta;
14936       if (p.theta < 0) {
14937         p.theta += Math.PI * 2;
14938       }
14939     });
14940     if (method == 'animate') {
14941       this.fx.animate(options);
14942     } else if (method == 'replot') {
14943       this.fx.plot();
14944       this.busy = false;
14945     }
14946   },
14947
14948   /*
14949    Method: plot
14950   
14951    Plots the Sunburst. This is a shortcut to *fx.plot*.
14952   */
14953   plot: function() {
14954     this.fx.plot();
14955   }
14956 });
14957
14958 $jit.Sunburst.$extend = true;
14959
14960 (function(Sunburst) {
14961
14962   /*
14963      Class: Sunburst.Op
14964
14965      Custom extension of <Graph.Op>.
14966
14967      Extends:
14968
14969      All <Graph.Op> methods
14970      
14971      See also:
14972      
14973      <Graph.Op>
14974
14975   */
14976   Sunburst.Op = new Class( {
14977
14978     Implements: Graph.Op
14979
14980   });
14981
14982   /*
14983      Class: Sunburst.Plot
14984
14985     Custom extension of <Graph.Plot>.
14986   
14987     Extends:
14988   
14989     All <Graph.Plot> methods
14990     
14991     See also:
14992     
14993     <Graph.Plot>
14994   
14995   */
14996   Sunburst.Plot = new Class( {
14997
14998     Implements: Graph.Plot
14999
15000   });
15001
15002   /*
15003     Class: Sunburst.Label
15004
15005     Custom extension of <Graph.Label>. 
15006     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
15007   
15008     Extends:
15009   
15010     All <Graph.Label> methods and subclasses.
15011   
15012     See also:
15013   
15014     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
15015   
15016    */
15017   Sunburst.Label = {};
15018
15019   /*
15020      Sunburst.Label.Native
15021
15022      Custom extension of <Graph.Label.Native>.
15023
15024      Extends:
15025
15026      All <Graph.Label.Native> methods
15027
15028      See also:
15029
15030      <Graph.Label.Native>
15031   */
15032   Sunburst.Label.Native = new Class( {
15033     Implements: Graph.Label.Native,
15034
15035     initialize: function(viz) {
15036       this.viz = viz;
15037       this.label = viz.config.Label;
15038       this.config = viz.config;
15039     },
15040
15041     renderLabel: function(canvas, node, controller) {
15042       var span = node.getData('span');
15043       if(span < Math.PI /2 && Math.tan(span) * 
15044           this.config.levelDistance * node._depth < 10) {
15045         return;
15046       }
15047       var ctx = canvas.getCtx();
15048       var measure = ctx.measureText(node.name);
15049       if (node.id == this.viz.root) {
15050         var x = -measure.width / 2, y = 0, thetap = 0;
15051         var ld = 0;
15052       } else {
15053         var indent = 5;
15054         var ld = controller.levelDistance - indent;
15055         var clone = node.pos.clone();
15056         clone.rho += indent;
15057         var p = clone.getp(true);
15058         var ct = clone.getc(true);
15059         var x = ct.x, y = ct.y;
15060         // get angle in degrees
15061         var pi = Math.PI;
15062         var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
15063         var thetap = cond ? p.theta + pi : p.theta;
15064         if (cond) {
15065           x -= Math.abs(Math.cos(p.theta) * measure.width);
15066           y += Math.sin(p.theta) * measure.width;
15067         } else if (node.id == this.viz.root) {
15068           x -= measure.width / 2;
15069         }
15070       }
15071       ctx.save();
15072       ctx.translate(x, y);
15073       ctx.rotate(thetap);
15074       ctx.fillText(node.name, 0, 0);
15075       ctx.restore();
15076     }
15077   });
15078
15079   /*
15080      Sunburst.Label.SVG
15081
15082     Custom extension of <Graph.Label.SVG>.
15083   
15084     Extends:
15085   
15086     All <Graph.Label.SVG> methods
15087   
15088     See also:
15089   
15090     <Graph.Label.SVG>
15091   
15092   */
15093   Sunburst.Label.SVG = new Class( {
15094     Implements: Graph.Label.SVG,
15095
15096     initialize: function(viz) {
15097       this.viz = viz;
15098     },
15099
15100     /* 
15101        placeLabel
15102
15103        Overrides abstract method placeLabel in <Graph.Plot>.
15104
15105        Parameters:
15106
15107        tag - A DOM label element.
15108        node - A <Graph.Node>.
15109        controller - A configuration/controller object passed to the visualization.
15110       
15111      */
15112     placeLabel: function(tag, node, controller) {
15113       var pos = node.pos.getc(true), viz = this.viz, canvas = this.viz.canvas;
15114       var radius = canvas.getSize();
15115       var labelPos = {
15116         x: Math.round(pos.x + radius.width / 2),
15117         y: Math.round(pos.y + radius.height / 2)
15118       };
15119       tag.setAttribute('x', labelPos.x);
15120       tag.setAttribute('y', labelPos.y);
15121
15122       var bb = tag.getBBox();
15123       if (bb) {
15124         // center the label
15125     var x = tag.getAttribute('x');
15126     var y = tag.getAttribute('y');
15127     // get polar coordinates
15128     var p = node.pos.getp(true);
15129     // get angle in degrees
15130     var pi = Math.PI;
15131     var cond = (p.theta > pi / 2 && p.theta < 3 * pi / 2);
15132     if (cond) {
15133       tag.setAttribute('x', x - bb.width);
15134       tag.setAttribute('y', y - bb.height);
15135     } else if (node.id == viz.root) {
15136       tag.setAttribute('x', x - bb.width / 2);
15137     }
15138
15139     var thetap = cond ? p.theta + pi : p.theta;
15140     if(node._depth)
15141       tag.setAttribute('transform', 'rotate(' + thetap * 360 / (2 * pi) + ' ' + x
15142           + ' ' + y + ')');
15143   }
15144
15145   controller.onPlaceLabel(tag, node);
15146 }
15147   });
15148
15149   /*
15150      Sunburst.Label.HTML
15151
15152      Custom extension of <Graph.Label.HTML>.
15153
15154      Extends:
15155
15156      All <Graph.Label.HTML> methods.
15157
15158      See also:
15159
15160      <Graph.Label.HTML>
15161
15162   */
15163   Sunburst.Label.HTML = new Class( {
15164     Implements: Graph.Label.HTML,
15165
15166     initialize: function(viz) {
15167       this.viz = viz;
15168     },
15169     /* 
15170        placeLabel
15171
15172        Overrides abstract method placeLabel in <Graph.Plot>.
15173
15174        Parameters:
15175
15176        tag - A DOM label element.
15177        node - A <Graph.Node>.
15178        controller - A configuration/controller object passed to the visualization.
15179       
15180      */
15181     placeLabel: function(tag, node, controller) {
15182       var pos = node.pos.clone(), 
15183           canvas = this.viz.canvas,
15184           height = node.getData('height'),
15185           ldist = ((height || node._depth == 0)? height : this.viz.config.levelDistance) /2,
15186           radius = canvas.getSize();
15187       pos.rho += ldist;
15188       pos = pos.getc(true);
15189       
15190       var labelPos = {
15191         x: Math.round(pos.x + radius.width / 2),
15192         y: Math.round(pos.y + radius.height / 2)
15193       };
15194
15195       var style = tag.style;
15196       style.left = labelPos.x + 'px';
15197       style.top = labelPos.y + 'px';
15198       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
15199
15200       controller.onPlaceLabel(tag, node);
15201     }
15202   });
15203
15204   /*
15205     Class: Sunburst.Plot.NodeTypes
15206
15207     This class contains a list of <Graph.Node> built-in types. 
15208     Node types implemented are 'none', 'pie', 'multipie', 'gradient-pie' and 'gradient-multipie'.
15209
15210     You can add your custom node types, customizing your visualization to the extreme.
15211
15212     Example:
15213
15214     (start code js)
15215       Sunburst.Plot.NodeTypes.implement({
15216         'mySpecialType': {
15217           'render': function(node, canvas) {
15218             //print your custom node to canvas
15219           },
15220           //optional
15221           'contains': function(node, pos) {
15222             //return true if pos is inside the node or false otherwise
15223           }
15224         }
15225       });
15226     (end code)
15227
15228   */
15229   Sunburst.Plot.NodeTypes = new Class( {
15230     'none': {
15231       'render': $.empty,
15232       'contains': $.lambda(false),
15233       'anglecontains': function(node, pos) {
15234         var span = node.getData('span') / 2, theta = node.pos.theta;
15235         var begin = theta - span, end = theta + span;
15236         if (begin < 0)
15237           begin += Math.PI * 2;
15238         var atan = Math.atan2(pos.y, pos.x);
15239         if (atan < 0)
15240           atan += Math.PI * 2;
15241         if (begin > end) {
15242           return (atan > begin && atan <= Math.PI * 2) || atan < end;
15243         } else {
15244           return atan > begin && atan < end;
15245         }
15246       },
15247           'anglecontainsgauge': function(node, pos) {
15248         var span = node.getData('span') / 2, theta = node.pos.theta;
15249                 var config = node.getData('config');
15250         var ld = this.config.levelDistance;
15251                 var yOffset = pos.y-(ld/2);
15252                 var begin = ((theta - span)/2)+Math.PI,
15253         end = ((theta + span)/2)+Math.PI;
15254                 
15255         if (begin < 0)
15256           begin += Math.PI * 2;
15257         var atan = Math.atan2(yOffset, pos.x);
15258
15259                 
15260         if (atan < 0)
15261           atan += Math.PI * 2;
15262                   
15263                   
15264         if (begin > end) {
15265           return (atan > begin && atan <= Math.PI * 2) || atan < end;
15266         } else {
15267           return atan > begin && atan < end;
15268         }
15269       }
15270     },
15271
15272     'pie': {
15273       'render': function(node, canvas) {
15274         var span = node.getData('span') / 2, theta = node.pos.theta;
15275         var begin = theta - span, end = theta + span;
15276         var polarNode = node.pos.getp(true);
15277         var polar = new Polar(polarNode.rho, begin);
15278         var p1coord = polar.getc(true);
15279         polar.theta = end;
15280         var p2coord = polar.getc(true);
15281
15282         var ctx = canvas.getCtx();
15283         ctx.beginPath();
15284         ctx.moveTo(0, 0);
15285         ctx.lineTo(p1coord.x, p1coord.y);
15286         ctx.moveTo(0, 0);
15287         ctx.lineTo(p2coord.x, p2coord.y);
15288         ctx.moveTo(0, 0);
15289         ctx.arc(0, 0, polarNode.rho * node.getData('dim-quotient'), begin, end,
15290             false);
15291         ctx.fill();
15292       },
15293       'contains': function(node, pos) {
15294         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15295           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15296           var ld = this.config.levelDistance, d = node._depth;
15297           return (rho <= ld * d);
15298         }
15299         return false;
15300       }
15301     },
15302     'multipie': {
15303       'render': function(node, canvas) {
15304         var height = node.getData('height');
15305         var ldist = height? height : this.config.levelDistance;
15306         var span = node.getData('span') / 2, theta = node.pos.theta;
15307         var begin = theta - span, end = theta + span;
15308         var polarNode = node.pos.getp(true);
15309
15310         var polar = new Polar(polarNode.rho, begin);
15311         var p1coord = polar.getc(true);
15312
15313         polar.theta = end;
15314         var p2coord = polar.getc(true);
15315
15316         polar.rho += ldist;
15317         var p3coord = polar.getc(true);
15318
15319         polar.theta = begin;
15320         var p4coord = polar.getc(true);
15321
15322         var ctx = canvas.getCtx();
15323         ctx.moveTo(0, 0);
15324         ctx.beginPath();
15325         ctx.arc(0, 0, polarNode.rho, begin, end, false);
15326         ctx.arc(0, 0, polarNode.rho + ldist, end, begin, true);
15327         ctx.moveTo(p1coord.x, p1coord.y);
15328         ctx.lineTo(p4coord.x, p4coord.y);
15329         ctx.moveTo(p2coord.x, p2coord.y);
15330         ctx.lineTo(p3coord.x, p3coord.y);
15331         ctx.fill();
15332
15333         if (node.collapsed) {
15334           ctx.save();
15335           ctx.lineWidth = 2;
15336           ctx.moveTo(0, 0);
15337           ctx.beginPath();
15338           ctx.arc(0, 0, polarNode.rho + ldist + 5, end - 0.01, begin + 0.01,
15339               true);
15340           ctx.stroke();
15341           ctx.restore();
15342         }
15343       },
15344       'contains': function(node, pos) {
15345         if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15346           var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15347           var height = node.getData('height');
15348           var ldist = height? height : this.config.levelDistance;
15349           var ld = this.config.levelDistance, d = node._depth;
15350           return (rho >= ld * d) && (rho <= (ld * d + ldist));
15351         }
15352         return false;
15353       }
15354     },
15355
15356     'gradient-multipie': {
15357       'render': function(node, canvas) {
15358         var ctx = canvas.getCtx();
15359         var height = node.getData('height');
15360         var ldist = height? height : this.config.levelDistance;
15361         var radialGradient = ctx.createRadialGradient(0, 0, node.getPos().rho,
15362             0, 0, node.getPos().rho + ldist);
15363
15364         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15365         $.each(colorArray, function(i) {
15366           ans.push(parseInt(i * 0.5, 10));
15367         });
15368         var endColor = $.rgbToHex(ans);
15369         radialGradient.addColorStop(0, endColor);
15370         radialGradient.addColorStop(1, node.getData('color'));
15371         ctx.fillStyle = radialGradient;
15372         this.nodeTypes['multipie'].render.call(this, node, canvas);
15373       },
15374       'contains': function(node, pos) {
15375         return this.nodeTypes['multipie'].contains.call(this, node, pos);
15376       }
15377     },
15378
15379     'gradient-pie': {
15380       'render': function(node, canvas) {
15381         var ctx = canvas.getCtx();
15382         var radialGradient = ctx.createRadialGradient(0, 0, 0, 0, 0, node
15383             .getPos().rho);
15384
15385         var colorArray = $.hexToRgb(node.getData('color')), ans = [];
15386         $.each(colorArray, function(i) {
15387           ans.push(parseInt(i * 0.5, 10));
15388         });
15389         var endColor = $.rgbToHex(ans);
15390         radialGradient.addColorStop(1, endColor);
15391         radialGradient.addColorStop(0, node.getData('color'));
15392         ctx.fillStyle = radialGradient;
15393         this.nodeTypes['pie'].render.call(this, node, canvas);
15394       },
15395       'contains': function(node, pos) {
15396         return this.nodeTypes['pie'].contains.call(this, node, pos);
15397       }
15398     }
15399   });
15400
15401   /*
15402     Class: Sunburst.Plot.EdgeTypes
15403
15404     This class contains a list of <Graph.Adjacence> built-in types. 
15405     Edge types implemented are 'none', 'line' and 'arrow'.
15406   
15407     You can add your custom edge types, customizing your visualization to the extreme.
15408   
15409     Example:
15410   
15411     (start code js)
15412       Sunburst.Plot.EdgeTypes.implement({
15413         'mySpecialType': {
15414           'render': function(adj, canvas) {
15415             //print your custom edge to canvas
15416           },
15417           //optional
15418           'contains': function(adj, pos) {
15419             //return true if pos is inside the arc or false otherwise
15420           }
15421         }
15422       });
15423     (end code)
15424   
15425   */
15426   Sunburst.Plot.EdgeTypes = new Class({
15427     'none': $.empty,
15428     'line': {
15429       'render': function(adj, canvas) {
15430         var from = adj.nodeFrom.pos.getc(true),
15431             to = adj.nodeTo.pos.getc(true);
15432         this.edgeHelper.line.render(from, to, canvas);
15433       },
15434       'contains': function(adj, pos) {
15435         var from = adj.nodeFrom.pos.getc(true),
15436             to = adj.nodeTo.pos.getc(true);
15437         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
15438       }
15439     },
15440     'arrow': {
15441       'render': function(adj, canvas) {
15442         var from = adj.nodeFrom.pos.getc(true),
15443             to = adj.nodeTo.pos.getc(true),
15444             dim = adj.getData('dim'),
15445             direction = adj.data.$direction,
15446             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
15447         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
15448       },
15449       'contains': function(adj, pos) {
15450         var from = adj.nodeFrom.pos.getc(true),
15451             to = adj.nodeTo.pos.getc(true);
15452         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
15453       }
15454     },
15455     'hyperline': {
15456       'render': function(adj, canvas) {
15457         var from = adj.nodeFrom.pos.getc(),
15458             to = adj.nodeTo.pos.getc(),
15459             dim = Math.max(from.norm(), to.norm());
15460         this.edgeHelper.hyperline.render(from.$scale(1/dim), to.$scale(1/dim), dim, canvas);
15461       },
15462       'contains': $.lambda(false) //TODO(nico): Implement this!
15463     }
15464   });
15465
15466 })($jit.Sunburst);
15467
15468
15469 /*
15470  * File: PieChart.js
15471  *
15472 */
15473
15474 $jit.Sunburst.Plot.NodeTypes.implement({
15475   'piechart-stacked' : {
15476     'render' : function(node, canvas) {
15477       var pos = node.pos.getp(true),
15478           dimArray = node.getData('dimArray'),
15479           valueArray = node.getData('valueArray'),
15480           colorArray = node.getData('colorArray'),
15481           colorLength = colorArray.length,
15482           stringArray = node.getData('stringArray'),
15483           span = node.getData('span') / 2,
15484           theta = node.pos.theta,
15485           begin = theta - span,
15486           end = theta + span,
15487           polar = new Polar;
15488     
15489       var ctx = canvas.getCtx(), 
15490           opt = {},
15491           gradient = node.getData('gradient'),
15492           border = node.getData('border'),
15493           config = node.getData('config'),
15494           showLabels = config.showLabels,
15495           resizeLabels = config.resizeLabels,
15496           label = config.Label;
15497
15498       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15499       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15500
15501       if (colorArray && dimArray && stringArray) {
15502         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15503           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15504           if(dimi <= 0) continue;
15505           ctx.fillStyle = ctx.strokeStyle = colori;
15506           if(gradient && dimi) {
15507             var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15508                 xpos, ypos, acum + dimi + config.sliceOffset);
15509             var colorRgb = $.hexToRgb(colori), 
15510                 ans = $.map(colorRgb, function(i) { return (i * 0.8) >> 0; }),
15511                 endColor = $.rgbToHex(ans);
15512
15513             radialGradient.addColorStop(0, colori);
15514             radialGradient.addColorStop(0.5, colori);
15515             radialGradient.addColorStop(1, endColor);
15516             ctx.fillStyle = radialGradient;
15517           }
15518           
15519           polar.rho = acum + config.sliceOffset;
15520           polar.theta = begin;
15521           var p1coord = polar.getc(true);
15522           polar.theta = end;
15523           var p2coord = polar.getc(true);
15524           polar.rho += dimi;
15525           var p3coord = polar.getc(true);
15526           polar.theta = begin;
15527           var p4coord = polar.getc(true);
15528
15529           ctx.beginPath();
15530           //fixing FF arc method + fill
15531           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15532           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15533           ctx.fill();
15534           if(border && border.name == stringArray[i]) {
15535             opt.acum = acum;
15536             opt.dimValue = dimArray[i];
15537             opt.begin = begin;
15538             opt.end = end;
15539           }
15540           acum += (dimi || 0);
15541           valAcum += (valueArray[i] || 0);
15542         }
15543         if(border) {
15544           ctx.save();
15545           ctx.globalCompositeOperation = "source-over";
15546           ctx.lineWidth = 2;
15547           ctx.strokeStyle = border.color;
15548           var s = begin < end? 1 : -1;
15549           ctx.beginPath();
15550           //fixing FF arc method + fill
15551           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15552           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15553           ctx.closePath();
15554           ctx.stroke();
15555           ctx.restore();
15556         }
15557         if(showLabels && label.type == 'Native') {
15558           ctx.save();
15559           ctx.fillStyle = ctx.strokeStyle = label.color;
15560           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15561               fontSize = (label.size * scale) >> 0;
15562           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15563           
15564           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15565           ctx.textBaseline = 'middle';
15566           ctx.textAlign = 'center';
15567           
15568           polar.rho = acum + config.labelOffset + config.sliceOffset;
15569           polar.theta = node.pos.theta;
15570           var cart = polar.getc(true);
15571           
15572           ctx.fillText(node.name, cart.x, cart.y);
15573           ctx.restore();
15574         }
15575       }
15576     },
15577     'contains': function(node, pos) {
15578       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15579         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15580         var ld = this.config.levelDistance, d = node._depth;
15581         var config = node.getData('config');
15582         if(rho <=ld * d + config.sliceOffset) {
15583           var dimArray = node.getData('dimArray');
15584           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15585             var dimi = dimArray[i];
15586             if(rho >= acum && rho <= acum + dimi) {
15587               return {
15588                 name: node.getData('stringArray')[i],
15589                 color: node.getData('colorArray')[i],
15590                 value: node.getData('valueArray')[i],
15591                 label: node.name
15592               };
15593             }
15594             acum += dimi;
15595           }
15596         }
15597         return false;
15598         
15599       }
15600       return false;
15601     }
15602   },
15603     'piechart-basic' : {
15604     'render' : function(node, canvas) {
15605       var pos = node.pos.getp(true),
15606           dimArray = node.getData('dimArray'),
15607           valueArray = node.getData('valueArray'),
15608           colorArray = node.getData('colorMono'),
15609           colorLength = colorArray.length,
15610           stringArray = node.getData('stringArray'),
15611                   percentage = node.getData('percentage'),
15612                   iteration = node.getData('iteration'),
15613           span = node.getData('span') / 2,
15614           theta = node.pos.theta,
15615           begin = theta - span,
15616           end = theta + span,
15617           polar = new Polar;
15618     
15619       var ctx = canvas.getCtx(), 
15620           opt = {},
15621           gradient = node.getData('gradient'),
15622           border = node.getData('border'),
15623           config = node.getData('config'),
15624           renderSubtitle = node.getData('renderSubtitle'),
15625           renderBackground = config.renderBackground,
15626           showLabels = config.showLabels,
15627           resizeLabels = config.resizeLabels,
15628           label = config.Label;
15629
15630       var xpos = config.sliceOffset * Math.cos((begin + end) /2);
15631       var ypos = config.sliceOffset * Math.sin((begin + end) /2);
15632       //background rendering for IE
15633                 if(iteration == 0 && typeof FlashCanvas != "undefined" && renderBackground) {
15634                         backgroundColor = config.backgroundColor,
15635                         size = canvas.getSize();
15636                         ctx.save();
15637                     ctx.fillStyle = backgroundColor;
15638                     ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15639                     
15640                     //subtitle
15641
15642                         var margin = config.Margin,
15643                         title = config.Title,
15644                         subtitle = config.Subtitle;
15645                         ctx.fillStyle = title.color;
15646                         ctx.textAlign = 'left';
15647                         
15648                         if(title.text != "") {
15649                                 ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15650                                 ctx.moveTo(0,0);
15651                                 if(label.type == 'Native') {
15652                                         ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15653                                 }
15654                         }       
15655         
15656                         if(subtitle.text != "") {
15657                                 ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15658                                 if(label.type == 'Native') {
15659                                         ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15660                                 } 
15661                         }
15662                         ctx.restore();          
15663                 }
15664       if (colorArray && dimArray && stringArray) {
15665         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
15666           var dimi = dimArray[i], colori = colorArray[i % colorLength];
15667           if(dimi <= 0) continue;
15668           ctx.fillStyle = ctx.strokeStyle = colori;
15669           
15670           polar.rho = acum + config.sliceOffset;
15671           polar.theta = begin;
15672           var p1coord = polar.getc(true);
15673           polar.theta = end;
15674           var p2coord = polar.getc(true);
15675           polar.rho += dimi;
15676           var p3coord = polar.getc(true);
15677           polar.theta = begin;
15678           var p4coord = polar.getc(true);
15679           
15680           if(typeof FlashCanvas == "undefined") {
15681                   //drop shadow
15682                   ctx.beginPath();
15683                   ctx.fillStyle = "rgba(0,0,0,.2)";
15684                   ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15685                   ctx.arc(xpos, ypos, acum + dimi+4 + .01, end, begin, true);    
15686                   ctx.fill();
15687                   
15688                   if(gradient && dimi) {
15689                     var radialGradient = ctx.createRadialGradient(xpos, ypos, acum + config.sliceOffset,
15690                         xpos, ypos, acum + dimi + config.sliceOffset);
15691                     var colorRgb = $.hexToRgb(colori), 
15692                         endColor = $.map(colorRgb, function(i) { return (i * 0.85) >> 0; }),
15693                         endColor2 = $.map(colorRgb, function(i) { return (i * 0.7) >> 0; });
15694         
15695                     radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
15696                     radialGradient.addColorStop(.7, 'rgba('+colorRgb+',1)');
15697                                 radialGradient.addColorStop(.98, 'rgba('+endColor+',1)');
15698                     radialGradient.addColorStop(1, 'rgba('+endColor2+',1)');
15699                     ctx.fillStyle = radialGradient;
15700                   }
15701           }
15702
15703           
15704           //fixing FF arc method + fill
15705           ctx.beginPath();
15706           ctx.arc(xpos, ypos, acum + .01, begin, end, false);
15707           ctx.arc(xpos, ypos, acum + dimi + .01, end, begin, true);
15708           ctx.fill();
15709           if(border && border.name == stringArray[i]) {
15710             opt.acum = acum;
15711             opt.dimValue = dimArray[i];
15712             opt.begin = begin;
15713             opt.end = end;
15714             opt.sliceValue = valueArray[i];
15715           }
15716           acum += (dimi || 0);
15717           valAcum += (valueArray[i] || 0);
15718         }
15719         if(border) {
15720           ctx.save();
15721           ctx.globalCompositeOperation = "source-over";
15722           ctx.lineWidth = 2;
15723           ctx.strokeStyle = border.color;
15724           var s = begin < end? 1 : -1;
15725           ctx.beginPath();
15726           //fixing FF arc method + fill
15727           ctx.arc(xpos, ypos, opt.acum + .01 + 1, opt.begin, opt.end, false);
15728           ctx.arc(xpos, ypos, opt.acum + opt.dimValue + .01 - 1, opt.end, opt.begin, true);
15729           ctx.closePath();
15730           ctx.stroke();
15731           ctx.restore();
15732         }
15733         if(showLabels && label.type == 'Native') {
15734           ctx.save();
15735           ctx.fillStyle = ctx.strokeStyle = label.color;
15736           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15737               fontSize = (label.size * scale) >> 0;
15738           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15739           
15740           ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
15741           ctx.textBaseline = 'middle';
15742           ctx.textAlign = 'center';
15743           pi = Math.PI;
15744           angle = theta * 360 / (2 * pi);
15745           polar.rho = acum + config.labelOffset + config.sliceOffset;
15746           polar.theta = node.pos.theta;
15747           var cart = polar.getc(true);
15748           if(((angle >= 225 && angle <= 315) || (angle <= 135 && angle >= 45)) && percentage <= 5) {
15749                 
15750                 } else {
15751                   if(config.labelType == 'name') {
15752                                 ctx.fillText(node.name, cart.x, cart.y);
15753                           } else {
15754                                 ctx.fillText(node.data.valuelabel, cart.x, cart.y);
15755                           }
15756           }
15757           ctx.restore();
15758         }
15759       }
15760     },
15761     'contains': function(node, pos) {
15762       if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
15763         var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
15764         var ld = this.config.levelDistance, d = node._depth;
15765         var config = node.getData('config');
15766
15767         if(rho <=ld * d + config.sliceOffset) {
15768           var dimArray = node.getData('dimArray');
15769           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
15770             var dimi = dimArray[i];
15771             if(rho >= acum && rho <= acum + dimi) {
15772                           var url = Url.decode(node.getData('linkArray')[i]);
15773               return {
15774                 name: node.getData('stringArray')[i],
15775                 link: url,
15776                 color: node.getData('colorArray')[i],
15777                 value: node.getData('valueArray')[i],
15778                 percentage: node.getData('percentage'),
15779                 valuelabel: node.getData('valuelabelsArray')[i],
15780                 label: node.name
15781               };
15782             }
15783             acum += dimi;
15784           }
15785         }
15786         return false;
15787         
15788       }
15789       return false;
15790     }
15791   }
15792 });
15793
15794 /*
15795   Class: PieChart
15796   
15797   A visualization that displays stacked bar charts.
15798   
15799   Constructor Options:
15800   
15801   See <Options.PieChart>.
15802
15803 */
15804 $jit.PieChart = new Class({
15805   sb: null,
15806   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
15807   selected: {},
15808   busy: false,
15809   
15810   initialize: function(opt) {
15811     this.controller = this.config = 
15812       $.merge(Options("Canvas", "PieChart", "Label"), {
15813         Label: { type: 'Native' }
15814       }, opt);
15815     this.initializeViz();
15816   },
15817   
15818   initializeViz: function() {
15819     var config = this.config, that = this;
15820     var nodeType = config.type.split(":")[0];
15821     var sb = new $jit.Sunburst({
15822       injectInto: config.injectInto,
15823       useCanvas: config.useCanvas,
15824       withLabels: config.Label.type != 'Native',
15825       background: config.background,
15826       renderBackground: config.renderBackground,
15827       backgroundColor: config.backgroundColor,
15828       colorStop1: config.colorStop1,
15829       colorStop2: config.colorStop2,
15830       Label: {
15831         type: config.Label.type
15832       },
15833       Node: {
15834         overridable: true,
15835         type: 'piechart-' + nodeType,
15836         width: 1,
15837         height: 1
15838       },
15839       Edge: {
15840         type: 'none'
15841       },
15842       Tips: {
15843         enable: config.Tips.enable,
15844         type: 'Native',
15845         force: true,
15846         onShow: function(tip, node, contains) {
15847           var elem = contains;
15848           config.Tips.onShow(tip, elem, node);
15849                           if(elem.link != 'undefined' && elem.link != '') {
15850                                 document.body.style.cursor = 'pointer';
15851                           }
15852         },
15853                 onHide: function() {
15854                                 document.body.style.cursor = 'default';
15855         }
15856       },
15857       Events: {
15858         enable: true,
15859         type: 'Native',
15860         onClick: function(node, eventInfo, evt) {
15861           if(!config.Events.enable) return;
15862           var elem = eventInfo.getContains();
15863           config.Events.onClick(elem, eventInfo, evt);
15864         },
15865         onMouseMove: function(node, eventInfo, evt) {
15866           if(!config.hoveredColor) return;
15867           if(node) {
15868             var elem = eventInfo.getContains();
15869             that.select(node.id, elem.name, elem.index);
15870           } else {
15871             that.select(false, false, false);
15872           }
15873         }
15874       },
15875       onCreateLabel: function(domElement, node) {
15876         var labelConf = config.Label;
15877         if(config.showLabels) {
15878           var style = domElement.style;
15879           style.fontSize = labelConf.size + 'px';
15880           style.fontFamily = labelConf.family;
15881           style.color = labelConf.color;
15882           style.textAlign = 'center';
15883           if(config.labelType == 'name') {
15884                 domElement.innerHTML = node.name;
15885           } else {
15886                 domElement.innerHTML = (node.data.valuelabel != undefined) ? node.data.valuelabel : "";
15887           }
15888           domElement.style.width = '400px';
15889         }
15890       },
15891       onPlaceLabel: function(domElement, node) {
15892         if(!config.showLabels) return;
15893         var pos = node.pos.getp(true),
15894             dimArray = node.getData('dimArray'),
15895             span = node.getData('span') / 2,
15896             theta = node.pos.theta,
15897             begin = theta - span,
15898             end = theta + span,
15899             polar = new Polar;
15900
15901         var showLabels = config.showLabels,
15902             resizeLabels = config.resizeLabels,
15903             label = config.Label;
15904         
15905         if (dimArray) {
15906           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
15907             acum += dimArray[i];
15908           }
15909           var scale = resizeLabels? node.getData('normalizedDim') : 1,
15910               fontSize = (label.size * scale) >> 0;
15911           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
15912           domElement.style.fontSize = fontSize + 'px';
15913           polar.rho = acum + config.labelOffset + config.sliceOffset;
15914           polar.theta = (begin + end) / 2;
15915           var pos = polar.getc(true);
15916           var radius = that.canvas.getSize();
15917           var labelPos = {
15918             x: Math.round(pos.x + radius.width / 2),
15919             y: Math.round(pos.y + radius.height / 2)
15920           };
15921           domElement.style.left = (labelPos.x - 200) + 'px';
15922           domElement.style.top = labelPos.y + 'px';
15923         }
15924       }
15925     });
15926     
15927     var size = sb.canvas.getSize(),
15928         min = Math.min;
15929     sb.config.levelDistance = min(size.width, size.height)/2 
15930       - config.offset - config.sliceOffset;
15931     this.sb = sb;
15932     this.canvas = this.sb.canvas;
15933     this.canvas.getCtx().globalCompositeOperation = 'lighter';
15934   },
15935     renderBackground: function() {
15936                 var canvas = this.canvas,
15937                 config = this.config,
15938                 backgroundColor = config.backgroundColor,
15939                 size = canvas.getSize(),
15940                 ctx = canvas.getCtx();
15941                 ctx.globalCompositeOperation = "destination-over";
15942             ctx.fillStyle = backgroundColor;
15943             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
15944   },
15945    renderTitle: function() {
15946         var canvas = this.canvas,
15947         size = canvas.getSize(),
15948         config = this.config,
15949         margin = config.Margin,
15950         radius = this.sb.config.levelDistance,
15951         title = config.Title,
15952         label = config.Label,
15953         subtitle = config.Subtitle;
15954         ctx = canvas.getCtx();
15955         ctx.fillStyle = title.color;
15956         ctx.textAlign = 'left';
15957         ctx.font = label.style + ' bold ' +' ' + title.size + 'px ' + label.family;
15958         ctx.moveTo(0,0);
15959         if(label.type == 'Native') {
15960                 ctx.fillText(title.text, -size.width/2 + margin.left, -size.height/2 + margin.top); 
15961         }       
15962   },
15963   renderSubtitle: function() {
15964         var canvas = this.canvas,
15965         size = canvas.getSize(),
15966         config = this.config,
15967         margin = config.Margin,
15968         radius = this.sb.config.levelDistance,
15969         title = config.Title,
15970         label = config.Label,
15971         subtitle = config.Subtitle;
15972         ctx = canvas.getCtx();
15973         ctx.fillStyle = title.color;
15974         ctx.textAlign = 'left';
15975         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
15976         ctx.moveTo(0,0);
15977         if(label.type == 'Native') {
15978                 ctx.fillText(subtitle.text, -size.width/2 + margin.left, size.height/2 - margin.bottom); 
15979         }       
15980   },
15981   clear: function() {
15982         var canvas = this.canvas;
15983         var ctx = canvas.getCtx(),
15984         size = canvas.getSize();
15985         ctx.fillStyle = "rgba(255,255,255,0)";
15986         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
15987         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
15988   },
15989   resizeGraph: function(json,width) {
15990         var canvas = this.canvas,
15991         size = canvas.getSize(),
15992         config = this.config,
15993         orgHeight = size.height;
15994         
15995         canvas.resize(width,orgHeight);
15996         if(typeof FlashCanvas == "undefined") {
15997                 canvas.clear();
15998         } else {
15999                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16000         }
16001         this.loadJSON(json);
16002
16003         },
16004   /*
16005     Method: loadJSON
16006    
16007     Loads JSON data into the visualization. 
16008     
16009     Parameters:
16010     
16011     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>.
16012     
16013     Example:
16014     (start code js)
16015     var pieChart = new $jit.PieChart(options);
16016     pieChart.loadJSON(json);
16017     (end code)
16018   */  
16019   loadJSON: function(json) {
16020     var prefix = $.time(), 
16021         ch = [], 
16022         sb = this.sb,
16023         name = $.splat(json.label),
16024         nameLength = name.length,
16025         color = $.splat(json.color || this.colors),
16026         colorLength = color.length,
16027         config = this.config,
16028         renderBackground = config.renderBackground,
16029         title = config.Title,
16030                 subtitle = config.Subtitle,
16031         gradient = !!config.type.split(":")[1],
16032         animate = config.animate,
16033         mono = nameLength == 1;
16034         totalValue = 0;
16035     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16036         var val = values[i];
16037         var valArray = $.splat(val.values);
16038         totalValue += parseFloat(valArray.sum());
16039     }
16040
16041     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16042       var val = values[i];
16043       var valArray = $.splat(val.values);
16044           var percentage = (valArray.sum()/totalValue) * 100;
16045
16046       var linkArray = $.splat(val.links);
16047       var valuelabelsArray = $.splat(val.valuelabels);
16048       
16049  
16050       ch.push({
16051         'id': prefix + val.label,
16052         'name': val.label,
16053         'data': {
16054           'value': valArray,
16055           'valuelabel': valuelabelsArray,
16056           '$linkArray': linkArray,
16057           '$valuelabelsArray': valuelabelsArray,
16058           '$valueArray': valArray,
16059           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16060           '$colorMono': $.splat(color[i % colorLength]),
16061           '$stringArray': name,
16062           '$gradient': gradient,
16063           '$config': config,
16064           '$iteration': i,
16065           '$percentage': percentage.toFixed(1),
16066           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16067         },
16068         'children': []
16069       });
16070     }
16071     var root = {
16072       'id': prefix + '$root',
16073       'name': '',
16074       'data': {
16075         '$type': 'none',
16076         '$width': 1,
16077         '$height': 1
16078       },
16079       'children': ch
16080     };
16081     sb.loadJSON(root);
16082     
16083     
16084     this.normalizeDims();
16085
16086     
16087     sb.refresh();
16088     if(title.text != "") {
16089         this.renderTitle();
16090     }
16091        
16092     if(subtitle.text != "") {
16093         this.renderSubtitle();
16094     }
16095      if(renderBackground && typeof FlashCanvas == "undefined") {
16096         this.renderBackground();        
16097     }
16098     
16099     if(animate) {
16100       sb.fx.animate({
16101         modes: ['node-property:dimArray'],
16102         duration:1500
16103       });
16104     }
16105   },
16106   
16107   /*
16108     Method: updateJSON
16109    
16110     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.
16111     
16112     Parameters:
16113     
16114     json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
16115     onComplete - (object) A callback object to be called when the animation transition when updating the data end.
16116     
16117     Example:
16118     
16119     (start code js)
16120     pieChart.updateJSON(json, {
16121       onComplete: function() {
16122         alert('update complete!');
16123       }
16124     });
16125     (end code)
16126   */  
16127   updateJSON: function(json, onComplete) {
16128     if(this.busy) return;
16129     this.busy = true;
16130     
16131     var sb = this.sb;
16132     var graph = sb.graph;
16133     var values = json.values;
16134     var animate = this.config.animate;
16135     var that = this;
16136     $.each(values, function(v) {
16137       var n = graph.getByName(v.label),
16138           vals = $.splat(v.values);
16139       if(n) {
16140         n.setData('valueArray', vals);
16141         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
16142         if(json.label) {
16143           n.setData('stringArray', $.splat(json.label));
16144         }
16145       }
16146     });
16147     this.normalizeDims();
16148     if(animate) {
16149       sb.compute('end');
16150       sb.fx.animate({
16151         modes: ['node-property:dimArray:span', 'linear'],
16152         duration:1500,
16153         onComplete: function() {
16154           that.busy = false;
16155           onComplete && onComplete.onComplete();
16156         }
16157       });
16158     } else {
16159       sb.refresh();
16160     }
16161   },
16162     
16163   //adds the little brown bar when hovering the node
16164   select: function(id, name) {
16165     if(!this.config.hoveredColor) return;
16166     var s = this.selected;
16167     if(s.id != id || s.name != name) {
16168       s.id = id;
16169       s.name = name;
16170       s.color = this.config.hoveredColor;
16171       this.sb.graph.eachNode(function(n) {
16172         if(id == n.id) {
16173           n.setData('border', s);
16174         } else {
16175           n.setData('border', false);
16176         }
16177       });
16178       this.sb.plot();
16179     }
16180   },
16181   
16182   /*
16183     Method: getLegend
16184    
16185     Returns an object containing as keys the legend names and as values hex strings with color values.
16186     
16187     Example:
16188     
16189     (start code js)
16190     var legend = pieChart.getLegend();
16191     (end code)
16192   */  
16193   getLegend: function() {
16194     var legend = new Array();
16195     var name = new Array();
16196     var color = new Array();
16197     var n;
16198     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
16199       n = adj.nodeTo;
16200     });
16201     var colors = n.getData('colorArray'),
16202         len = colors.length;
16203     $.each(n.getData('stringArray'), function(s, i) {
16204       color[i] = colors[i % len];
16205       name[i] = s;
16206     });
16207         legend['name'] = name;
16208         legend['color'] = color;
16209     return legend;
16210   },
16211   
16212   /*
16213     Method: getMaxValue
16214    
16215     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
16216     
16217     Example:
16218     
16219     (start code js)
16220     var ans = pieChart.getMaxValue();
16221     (end code)
16222     
16223     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
16224     
16225     Example:
16226     
16227     (start code js)
16228     //will return 100 for all PieChart instances,
16229     //displaying all of them with the same scale
16230     $jit.PieChart.implement({
16231       'getMaxValue': function() {
16232         return 100;
16233       }
16234     });
16235     (end code)
16236     
16237   */  
16238   getMaxValue: function() {
16239     var maxValue = 0;
16240     this.sb.graph.eachNode(function(n) {
16241       var valArray = n.getData('valueArray'),
16242           acum = 0;
16243       $.each(valArray, function(v) { 
16244         acum += +v;
16245       });
16246       maxValue = maxValue>acum? maxValue:acum;
16247     });
16248     return maxValue;
16249   },
16250   
16251   normalizeDims: function() {
16252     //number of elements
16253     var root = this.sb.graph.getNode(this.sb.root), l=0;
16254     root.eachAdjacency(function() {
16255       l++;
16256     });
16257     var maxValue = this.getMaxValue() || 1,
16258         config = this.config,
16259         animate = config.animate,
16260         rho = this.sb.config.levelDistance;
16261     this.sb.graph.eachNode(function(n) {
16262       var acum = 0, animateValue = [];
16263       $.each(n.getData('valueArray'), function(v) {
16264         acum += +v;
16265         animateValue.push(1);
16266       });
16267       var stat = (animateValue.length == 1) && !config.updateHeights;
16268       if(animate) {
16269         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16270           return stat? rho: (n * rho / maxValue); 
16271         }), 'end');
16272         var dimArray = n.getData('dimArray');
16273         if(!dimArray) {
16274           n.setData('dimArray', animateValue);
16275         }
16276       } else {
16277         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
16278           return stat? rho : (n * rho / maxValue); 
16279         }));
16280       }
16281       n.setData('normalizedDim', acum / maxValue);
16282     });
16283   }
16284 });
16285
16286
16287 //Gauge Chart
16288
16289 Options.GaugeChart = {
16290   $extend: true,
16291
16292   animate: true,
16293   offset: 25, // page offset
16294   sliceOffset:0,
16295   labelOffset: 3, // label offset
16296   type: 'stacked', // gradient
16297   labelType: 'name',
16298   hoveredColor: '#9fd4ff',
16299   Events: {
16300     enable: false,
16301     onClick: $.empty
16302   },
16303   Tips: {
16304     enable: false,
16305     onShow: $.empty,
16306     onHide: $.empty
16307   },
16308   showLabels: true,
16309   resizeLabels: false,
16310   
16311   //only valid for mono-valued datasets
16312   updateHeights: false
16313 };
16314
16315
16316
16317 $jit.Sunburst.Plot.NodeTypes.implement({
16318     'gaugechart-basic' : {
16319     'render' : function(node, canvas) {
16320       var pos = node.pos.getp(true),
16321           dimArray = node.getData('dimArray'),
16322           valueArray = node.getData('valueArray'),
16323           valuelabelsArray = node.getData('valuelabelsArray'),
16324           gaugeTarget = node.getData('gaugeTarget'),
16325           nodeIteration = node.getData('nodeIteration'),
16326           nodeLength = node.getData('nodeLength'),
16327           colorArray = node.getData('colorMono'),
16328           colorLength = colorArray.length,
16329           stringArray = node.getData('stringArray'),
16330           span = node.getData('span') / 2,
16331           theta = node.pos.theta,
16332           begin = ((theta - span)/2)+Math.PI,
16333           end = ((theta + span)/2)+Math.PI,
16334           polar = new Polar;
16335
16336   
16337       var ctx = canvas.getCtx(), 
16338           opt = {},
16339           gradient = node.getData('gradient'),
16340           border = node.getData('border'),
16341           config = node.getData('config'),
16342           showLabels = config.showLabels,
16343           resizeLabels = config.resizeLabels,
16344           label = config.Label;
16345
16346       var xpos = Math.cos((begin + end) /2);
16347       var ypos = Math.sin((begin + end) /2);
16348
16349       if (colorArray && dimArray && stringArray && gaugeTarget != 0) {
16350         for (var i=0, l=dimArray.length, acum=0, valAcum=0; i<l; i++) {
16351           var dimi = dimArray[i], colori = colorArray[i % colorLength];
16352           if(dimi <= 0) continue;
16353           ctx.fillStyle = ctx.strokeStyle = colori;
16354           if(gradient && dimi) {
16355             var radialGradient = ctx.createRadialGradient(xpos, (ypos + dimi/2), acum,
16356                 xpos, (ypos + dimi/2), acum + dimi);
16357             var colorRgb = $.hexToRgb(colori), 
16358                 ans = $.map(colorRgb, function(i) { return (i * .8) >> 0; }),
16359                 endColor = $.rgbToHex(ans);
16360
16361             radialGradient.addColorStop(0, 'rgba('+colorRgb+',1)');
16362             radialGradient.addColorStop(0.1, 'rgba('+colorRgb+',1)');
16363             radialGradient.addColorStop(0.85, 'rgba('+colorRgb+',1)');
16364             radialGradient.addColorStop(1,  'rgba('+ans+',1)');
16365             ctx.fillStyle = radialGradient;
16366           }
16367           
16368           polar.rho = acum;
16369           polar.theta = begin;
16370           var p1coord = polar.getc(true);
16371           polar.theta = end;
16372           var p2coord = polar.getc(true);
16373           polar.rho += dimi;
16374           var p3coord = polar.getc(true);
16375           polar.theta = begin;
16376           var p4coord = polar.getc(true);
16377
16378                   
16379           ctx.beginPath();
16380           //fixing FF arc method + fill
16381           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01) * .8, begin, end, false);
16382           ctx.arc(xpos, (ypos + dimi/2), (acum + dimi + .01), end, begin, true);
16383           ctx.fill();
16384                   
16385
16386           acum += (dimi || 0);
16387           valAcum += (valueArray[i] || 0);                
16388         }
16389                 
16390                 if(showLabels && label.type == 'Native') {
16391                           ctx.save();
16392                           ctx.fillStyle = ctx.strokeStyle = label.color;
16393
16394                           
16395                           ctx.font = label.style + ' ' + label.size + 'px ' + label.family;
16396                           ctx.textBaseline = 'bottom';
16397                           ctx.textAlign = 'center';
16398
16399                           polar.rho = acum * .65;
16400                           polar.theta = begin;
16401                           var cart = polar.getc(true);
16402                           
16403                           //changes y pos of first label
16404                           if(nodeIteration == 1) {
16405                                 textY = cart.y - (label.size/2) + acum /2;
16406                           } else {
16407                                 textY = cart.y + acum/2;
16408                           }
16409                           
16410                           if(config.labelType == 'name') {
16411                                 ctx.fillText(node.name, cart.x, textY);
16412                           } else {
16413                                 ctx.fillText(valuelabelsArray[0], cart.x, textY);
16414                           }
16415                           
16416                           //adds final label
16417                           if(nodeIteration == nodeLength) {
16418                                 polar.theta = end;
16419                                 var cart = polar.getc(true);
16420                                 if(config.labelType == 'name') {
16421                                         ctx.fillText(node.name, cart.x, cart.x, cart.y - (label.size/2) + acum/2);
16422                                 } else {
16423                                         ctx.fillText(valuelabelsArray[1], cart.x, cart.y - (label.size/2) + acum/2);
16424                                 }
16425                                 
16426                           }
16427                           ctx.restore();
16428                 }
16429
16430       }
16431       },
16432     'contains': function(node, pos) {
16433                 
16434                 
16435                 
16436       if (this.nodeTypes['none'].anglecontainsgauge.call(this, node, pos)) {
16437                 var config = node.getData('config');
16438         var ld = this.config.levelDistance , d = node._depth;
16439                 var yOffset = pos.y - (ld/2);
16440                 var xOffset = pos.x;
16441         var rho = Math.sqrt(xOffset * xOffset + yOffset * yOffset);
16442         if(rho <=parseInt(ld * d)) {
16443           var dimArray = node.getData('dimArray');
16444           for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
16445                         var dimi = dimArray[i];
16446             if(rho >= ld * .8 && rho <= acum + dimi) {
16447                 
16448                           var url = Url.decode(node.getData('linkArray')[i]);
16449               return {
16450                 name: node.getData('stringArray')[i],
16451                 link: url,
16452                 color: node.getData('colorArray')[i],
16453                 value: node.getData('valueArray')[i],
16454                 valuelabel: node.getData('valuelabelsArray')[0] + " - " + node.getData('valuelabelsArray')[1],
16455                 label: node.name
16456               };
16457             }
16458             acum += dimi;
16459                         
16460                         
16461           }
16462         }
16463         return false;
16464         
16465       }
16466       return false;
16467     }
16468   }
16469 });
16470
16471 /*
16472   Class: GaugeChart
16473   
16474   A visualization that displays gauge charts
16475   
16476   Constructor Options:
16477   
16478   See <Options.Gauge>.
16479
16480 */
16481 $jit.GaugeChart = new Class({
16482   sb: null,
16483   colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
16484   selected: {},
16485   busy: false,
16486   
16487   initialize: function(opt) {
16488     this.controller = this.config = 
16489       $.merge(Options("Canvas", "GaugeChart", "Label"), {
16490         Label: { type: 'Native' }
16491       }, opt);
16492     this.initializeViz();
16493   },
16494   
16495   initializeViz: function() {
16496     var config = this.config, that = this;
16497     var nodeType = config.type.split(":")[0];
16498     var sb = new $jit.Sunburst({
16499       injectInto: config.injectInto,
16500       useCanvas: config.useCanvas,
16501       withLabels: config.Label.type != 'Native',
16502       background: config.background,
16503       renderBackground: config.renderBackground,
16504       backgroundColor: config.backgroundColor,
16505       colorStop1: config.colorStop1,
16506       colorStop2: config.colorStop2,
16507       Label: {
16508         type: config.Label.type
16509       },
16510       Node: {
16511         overridable: true,
16512         type: 'gaugechart-' + nodeType,
16513         width: 1,
16514         height: 1
16515       },
16516       Edge: {
16517         type: 'none'
16518       },
16519       Tips: {
16520         enable: config.Tips.enable,
16521         type: 'Native',
16522         force: true,
16523         onShow: function(tip, node, contains) {
16524           var elem = contains;
16525           config.Tips.onShow(tip, elem, node);
16526                           if(elem.link != 'undefined' && elem.link != '') {
16527                                 document.body.style.cursor = 'pointer';
16528                           }
16529         },
16530                 onHide: function() {
16531                                 document.body.style.cursor = 'default';
16532         }
16533       },
16534       Events: {
16535         enable: true,
16536         type: 'Native',
16537         onClick: function(node, eventInfo, evt) {
16538           if(!config.Events.enable) return;
16539           var elem = eventInfo.getContains();
16540           config.Events.onClick(elem, eventInfo, evt);
16541         }
16542         },
16543       onCreateLabel: function(domElement, node) {
16544         var labelConf = config.Label;
16545         if(config.showLabels) {
16546           var style = domElement.style;
16547           style.fontSize = labelConf.size + 'px';
16548           style.fontFamily = labelConf.family;
16549           style.color = labelConf.color;
16550           style.textAlign = 'center';
16551           valuelabelsArray = node.getData('valuelabelsArray'),
16552           nodeIteration = node.getData('nodeIteration'),
16553           nodeLength = node.getData('nodeLength'),
16554           canvas = sb.canvas,
16555           prefix = $.time();
16556           
16557           if(config.labelType == 'name') {
16558                 domElement.innerHTML = node.name;
16559           } else {
16560                 domElement.innerHTML = (valuelabelsArray[0] != undefined) ? valuelabelsArray[0] : "";
16561           }
16562           
16563           domElement.style.width = '400px';
16564           
16565           //adds final label
16566                   if(nodeIteration == nodeLength && nodeLength != 0) {
16567                   idLabel = canvas.id + "-label";
16568                   container = document.getElementById(idLabel);
16569                   finalLabel = document.createElement('div');
16570                   finalLabelStyle = finalLabel.style;
16571                   finalLabel.id = prefix + "finalLabel";
16572                   finalLabelStyle.position = "absolute";
16573                   finalLabelStyle.width = "400px";
16574                   finalLabelStyle.left = "0px";
16575                   container.appendChild(finalLabel);
16576                         if(config.labelType == 'name') {
16577                                 finalLabel.innerHTML = node.name;
16578                         } else {
16579                                 finalLabel.innerHTML = (valuelabelsArray[1] != undefined) ? valuelabelsArray[1] : "";
16580                         }
16581                         
16582                   }
16583         }
16584       },
16585       onPlaceLabel: function(domElement, node) {
16586         if(!config.showLabels) return;
16587         var pos = node.pos.getp(true),
16588             dimArray = node.getData('dimArray'),
16589             nodeIteration = node.getData('nodeIteration'),
16590             nodeLength = node.getData('nodeLength'),
16591             span = node.getData('span') / 2,
16592             theta = node.pos.theta,
16593             begin = ((theta - span)/2)+Math.PI,
16594             end = ((theta + span)/2)+Math.PI,
16595             polar = new Polar;
16596
16597         var showLabels = config.showLabels,
16598             resizeLabels = config.resizeLabels,
16599             label = config.Label,
16600             radiusOffset = sb.config.levelDistance;
16601
16602         if (dimArray) {
16603           for (var i=0, l=dimArray.length, acum=0; i<l; i++) {
16604             acum += dimArray[i];
16605           }
16606           var scale = resizeLabels? node.getData('normalizedDim') : 1,
16607               fontSize = (label.size * scale) >> 0;
16608           fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
16609           domElement.style.fontSize = fontSize + 'px';
16610           polar.rho = acum * .65;
16611           polar.theta = begin;
16612           var pos = polar.getc(true);
16613           var radius = that.canvas.getSize();
16614           var labelPos = {
16615             x: Math.round(pos.x + radius.width / 2),
16616             y: Math.round(pos.y + (radius.height / 2) + radiusOffset/2)
16617           };
16618           
16619
16620           
16621           domElement.style.left = (labelPos.x - 200) + 'px';
16622           domElement.style.top = labelPos.y + 'px';
16623           
16624           //reposition first label
16625           if(nodeIteration == 1) {
16626                  domElement.style.top = labelPos.y - label.size + 'px';
16627           }
16628           
16629           
16630           //position final label
16631                 if(nodeIteration == nodeLength && nodeLength != 0) {
16632                         polar.theta = end;
16633                         var final = polar.getc(true);
16634                         var finalPos = {
16635                                         x: Math.round(final.x + radius.width / 2),
16636                                 y: Math.round(final.y + (radius.height / 2) + radiusOffset/2)
16637                                 };
16638                         finalLabel.style.left = (finalPos.x - 200) + "px";
16639                         finalLabel.style.top = finalPos.y - label.size + "px";
16640                     }
16641           
16642         }
16643       }
16644    
16645     });
16646     this.sb = sb;
16647     this.canvas = this.sb.canvas;
16648     var size = sb.canvas.getSize(),
16649         min = Math.min;
16650         sb.config.levelDistance = min(size.width, size.height)/2 
16651       - config.offset - config.sliceOffset;
16652
16653
16654   },
16655         
16656   renderBackground: function() {
16657         var canvas = this.sb.canvas,
16658         config = this.config,
16659         style = config.gaugeStyle,
16660         ctx = canvas.getCtx(),
16661         size = canvas.getSize(),
16662         radius = this.sb.config.levelDistance,
16663         startAngle = (Math.PI/180)*1,
16664         endAngle = (Math.PI/180)*179;
16665         
16666
16667         //background border
16668         ctx.fillStyle = style.borderColor;      
16669         ctx.beginPath();
16670         ctx.arc(0,radius/2,radius+4,startAngle,endAngle, true); 
16671         ctx.fill(); 
16672         
16673         
16674         var radialGradient = ctx.createRadialGradient(0,radius/2,0,0,radius/2,radius);
16675         radialGradient.addColorStop(0, '#ffffff');  
16676         radialGradient.addColorStop(0.3, style.backgroundColor);  
16677         radialGradient.addColorStop(0.6, style.backgroundColor);  
16678         radialGradient.addColorStop(1, '#FFFFFF'); 
16679         ctx.fillStyle = radialGradient;
16680         
16681         //background
16682         startAngle = (Math.PI/180)*0;
16683         endAngle = (Math.PI/180)*180;
16684         ctx.beginPath();
16685         ctx.arc(0,radius/2,radius,startAngle,endAngle, true); 
16686         ctx.fill();     
16687         
16688         
16689  
16690   },
16691   
16692   
16693   renderNeedle: function(gaugePosition,target) {
16694         var canvas = this.sb.canvas,
16695         config = this.config,
16696         style = config.gaugeStyle,
16697         ctx = canvas.getCtx(),
16698         size = canvas.getSize(),
16699         radius = this.sb.config.levelDistance;
16700         gaugeCenter = (radius/2);
16701         startAngle = 0;
16702         endAngle = (Math.PI/180)*180;
16703         
16704         
16705         // needle
16706         ctx.fillStyle = style.needleColor;
16707         var segments = 180/target;
16708         needleAngle = gaugePosition * segments;
16709         ctx.translate(0, gaugeCenter);
16710         ctx.save();
16711         ctx.rotate(needleAngle * Math.PI / 180);  
16712         ctx.beginPath();
16713         ctx.moveTo(0,0); 
16714         ctx.lineTo(0,-4);  
16715         ctx.lineTo(-radius*.9,-1);  
16716         ctx.lineTo(-radius*.9,1);  
16717         ctx.lineTo(0,4);  
16718         ctx.lineTo(0,0);  
16719         ctx.closePath(); 
16720         ctx.fill();
16721         ctx.restore(); 
16722         
16723         
16724         // stroke needle
16725         ctx.lineWidth = 1;
16726         ctx.strokeStyle = '#aa0000';
16727         ctx.save();
16728         ctx.rotate(needleAngle * Math.PI / 180);  
16729         ctx.beginPath();
16730         ctx.moveTo(0,0); 
16731         ctx.lineTo(0,-4);  
16732         ctx.lineTo(-radius*.8,-1);  
16733         ctx.lineTo(-radius*.8,1);  
16734         ctx.lineTo(0,4);  
16735         ctx.lineTo(0,0);  
16736         ctx.closePath(); 
16737         ctx.stroke();
16738         ctx.restore(); 
16739
16740         //needle cap
16741         ctx.fillStyle = "#000000";
16742         ctx.lineWidth = style.borderSize;
16743     ctx.strokeStyle = style.borderColor;
16744         var radialGradient = ctx.createRadialGradient(0,style.borderSize,0,0,style.borderSize,radius*.2);
16745         radialGradient.addColorStop(0, '#666666');  
16746         radialGradient.addColorStop(0.8, '#444444');  
16747         radialGradient.addColorStop(1, 'rgba(0,0,0,0)'); 
16748         ctx.fillStyle = radialGradient;
16749         ctx.translate(0,5);
16750         ctx.save();
16751         ctx.beginPath();
16752         ctx.arc(0,0,radius*.2,startAngle,endAngle, true); 
16753         ctx.fill();     
16754         ctx.restore();
16755
16756         
16757   },
16758   
16759   renderTicks: function(values) {
16760         var canvas = this.sb.canvas,
16761         config = this.config,
16762         style = config.gaugeStyle,
16763         ctx = canvas.getCtx(),
16764         size = canvas.getSize(),
16765         radius = this.sb.config.levelDistance,
16766         gaugeCenter = (radius/2);
16767         
16768         
16769         ctx.strokeStyle = style.borderColor;
16770         ctx.lineWidth = 5;
16771         ctx.lineCap = "round";
16772                 for(var i=0, total = 0, l=values.length; i<l; i++) {
16773                         var val = values[i];
16774                         if(val.label != 'GaugePosition') {
16775                         total += (parseInt(val.values) || 0);
16776                         }
16777                 }
16778         
16779                 for(var i=0, acum = 0, l=values.length; i<l-1; i++) {
16780                         var val = values[i];
16781                         if(val.label != 'GaugePosition') {
16782                         acum += (parseInt(val.values) || 0);
16783
16784                            var segments = 180/total;
16785                         angle = acum * segments;
16786
16787                           //alert(acum);
16788                                  ctx.save();
16789                                  ctx.translate(0, gaugeCenter);
16790                                  ctx.beginPath();
16791                                 ctx.rotate(angle * (Math.PI/180));
16792                                 ctx.moveTo(-radius,0);
16793                                 ctx.lineTo(-radius*.75,0);
16794                                 ctx.stroke();
16795                                  ctx.restore();
16796                         
16797                         }
16798                 }
16799         },
16800         
16801         renderPositionLabel: function(position) {
16802                 var canvas = this.sb.canvas,
16803                 config = this.config,
16804                 label = config.Label,
16805                 style = config.gaugeStyle,
16806                 ctx = canvas.getCtx(),
16807                 size = canvas.getSize(),
16808                 radius = this.sb.config.levelDistance,
16809                 gaugeCenter = (radius/2);
16810                 ctx.textBaseline = 'middle';
16811                 ctx.textAlign = 'center';
16812                 ctx.font = style.positionFontSize + 'px ' + label.family;
16813                 ctx.fillStyle = "#ffffff";
16814                 ctx.lineWidth = 2;
16815                 height = style.positionFontSize + 10,
16816                 cornerRadius = 8,
16817                 idLabel = canvas.id + "-label";
16818                 container = document.getElementById(idLabel);
16819                 if(label.type == 'Native') {
16820                         var m = ctx.measureText(position),
16821                         width = m.width + 40;
16822                 } else {
16823                         var width = 70;
16824                 }
16825                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"fill");
16826                 $.roundedRect(ctx,-width/2,0,width,height,cornerRadius,"stroke");
16827                 if(label.type == 'Native') {
16828                         ctx.fillStyle = label.color;
16829                         ctx.fillText(position, 0, (height/2) + style.positionOffset);
16830                 } else {
16831                         var labelDiv =  document.createElement('div');
16832                         labelDivStyle = labelDiv.style;
16833                         labelDivStyle.color =  label.color;
16834                         labelDivStyle.fontSize =  style.positionFontSize + "px";
16835                         labelDivStyle.position = "absolute";
16836                         labelDivStyle.width = width + "px";
16837                         labelDivStyle.left = (size.width/2) - (width/2) + "px";
16838                         labelDivStyle.top = (size.height/2) + style.positionOffset + "px";
16839                         labelDiv.innerHTML = position;
16840                         container.appendChild(labelDiv);
16841                 }
16842         
16843     },
16844     
16845    renderSubtitle: function() {
16846         var canvas = this.canvas,
16847         size = canvas.getSize(),
16848         config = this.config,
16849         margin = config.Margin,
16850         radius = this.sb.config.levelDistance,
16851         title = config.Title,
16852         label = config.Label,
16853         subtitle = config.Subtitle;
16854         ctx = canvas.getCtx();
16855         ctx.fillStyle = title.color;
16856         ctx.textAlign = 'left';
16857         ctx.font = label.style + ' ' + subtitle.size + 'px ' + label.family;
16858         ctx.moveTo(0,0);
16859         if(label.type == 'Native') {
16860                 ctx.fillText(subtitle.text, -radius - 4, subtitle.size + subtitle.offset + (radius/2)); 
16861         }
16862   },
16863   
16864   renderChartBackground: function() {
16865                 var canvas = this.canvas,
16866                 config = this.config,
16867                 backgroundColor = config.backgroundColor,
16868                 size = canvas.getSize(),
16869                 ctx = canvas.getCtx();
16870                 //ctx.globalCompositeOperation = "destination-over";
16871             ctx.fillStyle = backgroundColor;
16872             ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);          
16873   },
16874   clear: function() {
16875         var canvas = this.canvas;
16876         var ctx = canvas.getCtx(),
16877         size = canvas.getSize();
16878         ctx.fillStyle = "rgba(255,255,255,0)";
16879         ctx.fillRect(-size.width/2,-size.height/2,size.width,size.height);
16880         ctx.clearRect(-size.width/2,-size.height/2,size.width,size.height);
16881  },
16882   resizeGraph: function(json,width) {
16883         var canvas = this.canvas,
16884         size = canvas.getSize(),
16885             orgHeight = size.height;
16886         
16887         canvas.resize(width,orgHeight);
16888         if(typeof FlashCanvas == "undefined") {
16889                 canvas.clear();
16890         } else {
16891                 this.clear();// hack for flashcanvas bug not properly clearing rectangle
16892         }
16893         this.loadJSON(json);
16894
16895         },
16896   loadJSON: function(json) {
16897   
16898      var prefix = $.time(), 
16899         ch = [], 
16900         sb = this.sb,
16901         name = $.splat(json.label),
16902         nameLength = name.length,
16903         color = $.splat(json.color || this.colors),
16904         colorLength = color.length,
16905         config = this.config,
16906         renderBackground = config.renderBackground,
16907         gradient = !!config.type.split(":")[1],
16908         animate = config.animate,
16909         mono = nameLength == 1;
16910                 var props = $.splat(json.properties)[0];
16911
16912     for(var i=0, values=json.values, l=values.length; i<l; i++) {
16913         
16914       var val = values[i];
16915           if(val.label != 'GaugePosition') {
16916                   var valArray = $.splat(val.values);
16917                   var linkArray = (val.links == "undefined" || val.links == undefined) ? new Array() : $.splat(val.links);
16918                   var valuelabelsArray = $.splat(val.valuelabels);
16919
16920                   ch.push({
16921                         'id': prefix + val.label,
16922                         'name': val.label,
16923                         'data': {
16924                           'value': valArray,
16925                           'valuelabel': valuelabelsArray,
16926                           '$linkArray': linkArray,
16927                           '$valuelabelsArray': valuelabelsArray,
16928                           '$valueArray': valArray,
16929                           '$nodeIteration': i,
16930                           '$nodeLength': l-1,
16931                           '$colorArray': mono? $.splat(color[i % colorLength]) : color,
16932                           '$colorMono': $.splat(color[i % colorLength]),
16933                           '$stringArray': name,
16934                           '$gradient': gradient,
16935                           '$config': config,
16936                           '$gaugeTarget': props['gaugeTarget'],
16937                           '$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
16938                         },
16939                         'children': []
16940                   });
16941           } else {
16942                 var gaugePosition = val.gvalue;
16943                 var gaugePositionLabel = val.gvaluelabel;
16944           }
16945     }
16946     var root = {
16947       'id': prefix + '$root',
16948       'name': '',
16949       'data': {
16950         '$type': 'none',
16951         '$width': 1,
16952         '$height': 1
16953       },
16954       'children': ch
16955     };
16956         
16957         
16958     sb.loadJSON(root);
16959     
16960     if(renderBackground) {
16961         this.renderChartBackground();   
16962     }
16963     
16964     this.renderBackground();
16965     this.renderSubtitle();
16966     
16967     this.normalizeDims();
16968         
16969     sb.refresh();
16970     if(animate) {
16971       sb.fx.animate({
16972         modes: ['node-property:dimArray'],
16973         duration:1500
16974       });
16975     }
16976         
16977
16978         this.renderPositionLabel(gaugePositionLabel);
16979         if (props['gaugeTarget'] != 0) {
16980                 this.renderTicks(json.values);
16981                 this.renderNeedle(gaugePosition,props['gaugeTarget']);
16982         }
16983         
16984         
16985
16986   },
16987   
16988   updateJSON: function(json, onComplete) {
16989     if(this.busy) return;
16990     this.busy = true;
16991     
16992     var sb = this.sb;
16993     var graph = sb.graph;
16994     var values = json.values;
16995     var animate = this.config.animate;
16996     var that = this;
16997     $.each(values, function(v) {
16998       var n = graph.getByName(v.label),
16999           vals = $.splat(v.values);
17000       if(n) {
17001         n.setData('valueArray', vals);
17002         n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
17003         if(json.label) {
17004           n.setData('stringArray', $.splat(json.label));
17005         }
17006       }
17007     });
17008     this.normalizeDims();
17009     if(animate) {
17010       sb.compute('end');
17011       sb.fx.animate({
17012         modes: ['node-property:dimArray:span', 'linear'],
17013         duration:1500,
17014         onComplete: function() {
17015           that.busy = false;
17016           onComplete && onComplete.onComplete();
17017         }
17018       });
17019     } else {
17020       sb.refresh();
17021     }
17022   },
17023     
17024   //adds the little brown bar when hovering the node
17025   select: function(id, name) {
17026     if(!this.config.hoveredColor) return;
17027     var s = this.selected;
17028     if(s.id != id || s.name != name) {
17029       s.id = id;
17030       s.name = name;
17031       s.color = this.config.hoveredColor;
17032       this.sb.graph.eachNode(function(n) {
17033         if(id == n.id) {
17034           n.setData('border', s);
17035         } else {
17036           n.setData('border', false);
17037         }
17038       });
17039       this.sb.plot();
17040     }
17041   },
17042   
17043   /*
17044     Method: getLegend
17045    
17046     Returns an object containing as keys the legend names and as values hex strings with color values.
17047     
17048     Example:
17049     
17050     (start code js)
17051     var legend = pieChart.getLegend();
17052     (end code)
17053   */  
17054   getLegend: function() {
17055     var legend = new Array();
17056     var name = new Array();
17057     var color = new Array();
17058     var n;
17059     this.sb.graph.getNode(this.sb.root).eachAdjacency(function(adj) {
17060       n = adj.nodeTo;
17061     });
17062     var colors = n.getData('colorArray'),
17063         len = colors.length;
17064     $.each(n.getData('stringArray'), function(s, i) {
17065       color[i] = colors[i % len];
17066       name[i] = s;
17067     });
17068         legend['name'] = name;
17069         legend['color'] = color;
17070     return legend;
17071   },
17072   
17073   /*
17074     Method: getMaxValue
17075    
17076     Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
17077     
17078     Example:
17079     
17080     (start code js)
17081     var ans = pieChart.getMaxValue();
17082     (end code)
17083     
17084     In some cases it could be useful to override this method to normalize heights for a group of PieCharts, like when doing small multiples.
17085     
17086     Example:
17087     
17088     (start code js)
17089     //will return 100 for all PieChart instances,
17090     //displaying all of them with the same scale
17091     $jit.PieChart.implement({
17092       'getMaxValue': function() {
17093         return 100;
17094       }
17095     });
17096     (end code)
17097     
17098   */  
17099   getMaxValue: function() {
17100     var maxValue = 0;
17101     this.sb.graph.eachNode(function(n) {
17102       var valArray = n.getData('valueArray'),
17103           acum = 0;
17104       $.each(valArray, function(v) { 
17105         acum += +v;
17106       });
17107       maxValue = maxValue>acum? maxValue:acum;
17108     });
17109     return maxValue;
17110   },
17111   
17112   normalizeDims: function() {
17113     //number of elements
17114     var root = this.sb.graph.getNode(this.sb.root), l=0;
17115     root.eachAdjacency(function() {
17116       l++;
17117     });
17118     var maxValue = this.getMaxValue() || 1,
17119         config = this.config,
17120         animate = config.animate,
17121         rho = this.sb.config.levelDistance;
17122     this.sb.graph.eachNode(function(n) {
17123       var acum = 0, animateValue = [];
17124       $.each(n.getData('valueArray'), function(v) {
17125         acum += +v;
17126         animateValue.push(1);
17127       });
17128       var stat = (animateValue.length == 1) && !config.updateHeights;
17129       if(animate) {
17130         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
17131           return stat? rho: (n * rho / maxValue); 
17132         }), 'end');
17133         var dimArray = n.getData('dimArray');
17134         if(!dimArray) {
17135           n.setData('dimArray', animateValue);
17136         }
17137       } else {
17138         n.setData('dimArray', $.map(n.getData('valueArray'), function(n) { 
17139           return stat? rho : (n * rho / maxValue); 
17140         }));
17141       }
17142       n.setData('normalizedDim', acum / maxValue);
17143     });
17144   }
17145 });
17146
17147
17148 /*
17149  * Class: Layouts.TM
17150  * 
17151  * Implements TreeMaps layouts (SliceAndDice, Squarified, Strip).
17152  * 
17153  * Implemented By:
17154  * 
17155  * <TM>
17156  * 
17157  */
17158 Layouts.TM = {};
17159
17160 Layouts.TM.SliceAndDice = new Class({
17161   compute: function(prop) {
17162     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17163     this.controller.onBeforeCompute(root);
17164     var size = this.canvas.getSize(),
17165         config = this.config,
17166         width = size.width,
17167         height = size.height;
17168     this.graph.computeLevels(this.root, 0, "ignore");
17169     //set root position and dimensions
17170     root.getPos(prop).setc(-width/2, -height/2);
17171     root.setData('width', width, prop);
17172     root.setData('height', height + config.titleHeight, prop);
17173     this.computePositions(root, root, this.layout.orientation, prop);
17174     this.controller.onAfterCompute(root);
17175   },
17176   
17177   computePositions: function(par, ch, orn, prop) {
17178     //compute children areas
17179     var totalArea = 0;
17180     par.eachSubnode(function(n) {
17181       totalArea += n.getData('area', prop);
17182     });
17183     
17184     var config = this.config,
17185         offst = config.offset,
17186         width  = par.getData('width', prop),
17187         height = par.getData('height', prop) - config.titleHeight,
17188         fact = par == ch? 1: (ch.getData('area', prop) / totalArea);
17189     
17190     var otherSize, size, dim, pos, pos2, posth, pos2th;
17191     var horizontal = (orn == "h");
17192     if(horizontal) {
17193       orn = 'v';    
17194       otherSize = height;
17195       size = width * fact;
17196       dim = 'height';
17197       pos = 'y';
17198       pos2 = 'x';
17199       posth = config.titleHeight;
17200       pos2th = 0;
17201     } else {
17202       orn = 'h';    
17203       otherSize = height * fact;
17204       size = width;
17205       dim = 'width';
17206       pos = 'x';
17207       pos2 = 'y';
17208       posth = 0;
17209       pos2th = config.titleHeight;
17210     }
17211     var cpos = ch.getPos(prop);
17212     ch.setData('width', size, prop);
17213     ch.setData('height', otherSize, prop);
17214     var offsetSize = 0, tm = this;
17215     ch.eachSubnode(function(n) {
17216       var p = n.getPos(prop);
17217       p[pos] = offsetSize + cpos[pos] + posth;
17218       p[pos2] = cpos[pos2] + pos2th;
17219       tm.computePositions(ch, n, orn, prop);
17220       offsetSize += n.getData(dim, prop);
17221     });
17222   }
17223
17224 });
17225
17226 Layouts.TM.Area = {
17227  /*
17228     Method: compute
17229  
17230    Called by loadJSON to calculate recursively all node positions and lay out the tree.
17231  
17232     Parameters:
17233
17234        json - A JSON tree. See also <Loader.loadJSON>.
17235        coord - A coordinates object specifying width, height, left and top style properties.
17236  */
17237  compute: function(prop) {
17238     prop = prop || "current";
17239     var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
17240     this.controller.onBeforeCompute(root);
17241     var config = this.config,
17242         size = this.canvas.getSize(),
17243         width = size.width,
17244         height = size.height,
17245         offst = config.offset,
17246         offwdth = width - offst,
17247         offhght = height - offst;
17248     this.graph.computeLevels(this.root, 0, "ignore");
17249     //set root position and dimensions
17250     root.getPos(prop).setc(-width/2, -height/2);
17251     root.setData('width', width, prop);
17252     root.setData('height', height, prop);
17253     //create a coordinates object
17254     var coord = {
17255         'top': -height/2 + config.titleHeight,
17256         'left': -width/2,
17257         'width': offwdth,
17258         'height': offhght - config.titleHeight
17259     };
17260     this.computePositions(root, coord, prop);
17261     this.controller.onAfterCompute(root);
17262  }, 
17263  
17264  /*
17265     Method: computeDim
17266  
17267    Computes dimensions and positions of a group of nodes
17268    according to a custom layout row condition. 
17269  
17270     Parameters:
17271
17272        tail - An array of nodes.  
17273        initElem - An array of nodes (containing the initial node to be laid).
17274        w - A fixed dimension where nodes will be layed out.
17275        coord - A coordinates object specifying width, height, left and top style properties.
17276        comp - A custom comparison function
17277  */
17278  computeDim: function(tail, initElem, w, coord, comp, prop) {
17279    if(tail.length + initElem.length == 1) {
17280      var l = (tail.length == 1)? tail : initElem;
17281      this.layoutLast(l, w, coord, prop);
17282      return;
17283    }
17284    if(tail.length >= 2 && initElem.length == 0) {
17285      initElem = [tail.shift()];
17286    }
17287    if(tail.length == 0) {
17288      if(initElem.length > 0) this.layoutRow(initElem, w, coord, prop);
17289      return;
17290    }
17291    var c = tail[0];
17292    if(comp(initElem, w) >= comp([c].concat(initElem), w)) {
17293      this.computeDim(tail.slice(1), initElem.concat([c]), w, coord, comp, prop);
17294    } else {
17295      var newCoords = this.layoutRow(initElem, w, coord, prop);
17296      this.computeDim(tail, [], newCoords.dim, newCoords, comp, prop);
17297    }
17298  },
17299
17300  
17301  /*
17302     Method: worstAspectRatio
17303  
17304    Calculates the worst aspect ratio of a group of rectangles. 
17305        
17306     See also:
17307        
17308        <http://en.wikipedia.org/wiki/Aspect_ratio>
17309    
17310     Parameters:
17311
17312      ch - An array of nodes.  
17313      w  - The fixed dimension where rectangles are being laid out.
17314
17315     Returns:
17316  
17317         The worst aspect ratio.
17318
17319
17320  */
17321  worstAspectRatio: function(ch, w) {
17322    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17323    var areaSum = 0, maxArea = 0, minArea = Number.MAX_VALUE;
17324    for(var i=0, l=ch.length; i<l; i++) {
17325      var area = ch[i]._area;
17326      areaSum += area; 
17327      minArea = minArea < area? minArea : area;
17328      maxArea = maxArea > area? maxArea : area; 
17329    }
17330    var sqw = w * w, sqAreaSum = areaSum * areaSum;
17331    return Math.max(sqw * maxArea / sqAreaSum,
17332            sqAreaSum / (sqw * minArea));
17333  },
17334  
17335  /*
17336     Method: avgAspectRatio
17337  
17338    Calculates the average aspect ratio of a group of rectangles. 
17339        
17340        See also:
17341        
17342        <http://en.wikipedia.org/wiki/Aspect_ratio>
17343    
17344     Parameters:
17345
17346      ch - An array of nodes.  
17347        w - The fixed dimension where rectangles are being laid out.
17348
17349     Returns:
17350  
17351         The average aspect ratio.
17352
17353
17354  */
17355  avgAspectRatio: function(ch, w) {
17356    if(!ch || ch.length == 0) return Number.MAX_VALUE;
17357    var arSum = 0;
17358    for(var i=0, l=ch.length; i<l; i++) {
17359      var area = ch[i]._area;
17360      var h = area / w;
17361      arSum += w > h? w / h : h / w;
17362    }
17363    return arSum / l;
17364  },
17365
17366  /*
17367     layoutLast
17368  
17369    Performs the layout of the last computed sibling.
17370  
17371     Parameters:
17372
17373        ch - An array of nodes.  
17374        w - A fixed dimension where nodes will be layed out.
17375      coord - A coordinates object specifying width, height, left and top style properties.
17376  */
17377  layoutLast: function(ch, w, coord, prop) {
17378    var child = ch[0];
17379    child.getPos(prop).setc(coord.left, coord.top);
17380    child.setData('width', coord.width, prop);
17381    child.setData('height', coord.height, prop);
17382  }
17383 };
17384
17385
17386 Layouts.TM.Squarified = new Class({
17387  Implements: Layouts.TM.Area,
17388  
17389  computePositions: function(node, coord, prop) {
17390    var config = this.config;
17391    
17392    if (coord.width >= coord.height) 
17393      this.layout.orientation = 'h';
17394    else
17395      this.layout.orientation = 'v';
17396    
17397    var ch = node.getSubnodes([1, 1], "ignore");
17398    if(ch.length > 0) {
17399      this.processChildrenLayout(node, ch, coord, prop);
17400      for(var i=0, l=ch.length; i<l; i++) {
17401        var chi = ch[i]; 
17402        var offst = config.offset,
17403            height = chi.getData('height', prop) - offst - config.titleHeight,
17404            width = chi.getData('width', prop) - offst;
17405        var chipos = chi.getPos(prop);
17406        coord = {
17407          'width': width,
17408          'height': height,
17409          'top': chipos.y + config.titleHeight,
17410          'left': chipos.x
17411        };
17412        this.computePositions(chi, coord, prop);
17413      }
17414    }
17415  },
17416
17417  /*
17418     Method: processChildrenLayout
17419  
17420    Computes children real areas and other useful parameters for performing the Squarified algorithm.
17421  
17422     Parameters:
17423
17424        par - The parent node of the json subtree.  
17425        ch - An Array of nodes
17426      coord - A coordinates object specifying width, height, left and top style properties.
17427  */
17428  processChildrenLayout: function(par, ch, coord, prop) {
17429    //compute children real areas
17430    var parentArea = coord.width * coord.height;
17431    var i, l=ch.length, totalChArea=0, chArea = [];
17432    for(i=0; i<l; i++) {
17433      chArea[i] = parseFloat(ch[i].getData('area', prop));
17434      totalChArea += chArea[i];
17435    }
17436    for(i=0; i<l; i++) {
17437      ch[i]._area = parentArea * chArea[i] / totalChArea;
17438    }
17439    var minimumSideValue = this.layout.horizontal()? coord.height : coord.width;
17440    ch.sort(function(a, b) { 
17441      var diff = b._area - a._area; 
17442      return diff? diff : (b.id == a.id? 0 : (b.id < a.id? 1 : -1)); 
17443    });
17444    var initElem = [ch[0]];
17445    var tail = ch.slice(1);
17446    this.squarify(tail, initElem, minimumSideValue, coord, prop);
17447  },
17448
17449  /*
17450    Method: squarify
17451  
17452    Performs an heuristic method to calculate div elements sizes in order to have a good aspect ratio.
17453  
17454     Parameters:
17455
17456        tail - An array of nodes.  
17457        initElem - An array of nodes, containing the initial node to be laid out.
17458        w - A fixed dimension where nodes will be laid out.
17459        coord - A coordinates object specifying width, height, left and top style properties.
17460  */
17461  squarify: function(tail, initElem, w, coord, prop) {
17462    this.computeDim(tail, initElem, w, coord, this.worstAspectRatio, prop);
17463  },
17464  
17465  /*
17466     Method: layoutRow
17467  
17468    Performs the layout of an array of nodes.
17469  
17470     Parameters:
17471
17472        ch - An array of nodes.  
17473        w - A fixed dimension where nodes will be laid out.
17474        coord - A coordinates object specifying width, height, left and top style properties.
17475  */
17476  layoutRow: function(ch, w, coord, prop) {
17477    if(this.layout.horizontal()) {
17478      return this.layoutV(ch, w, coord, prop);
17479    } else {
17480      return this.layoutH(ch, w, coord, prop);
17481    }
17482  },
17483  
17484  layoutV: function(ch, w, coord, prop) {
17485    var totalArea = 0, rnd = function(x) { return x; }; 
17486    $.each(ch, function(elem) { totalArea += elem._area; });
17487    var width = rnd(totalArea / w), top =  0; 
17488    for(var i=0, l=ch.length; i<l; i++) {
17489      var h = rnd(ch[i]._area / width);
17490      var chi = ch[i];
17491      chi.getPos(prop).setc(coord.left, coord.top + top);
17492      chi.setData('width', width, prop);
17493      chi.setData('height', h, prop);
17494      top += h;
17495    }
17496    var ans = {
17497      'height': coord.height,
17498      'width': coord.width - width,
17499      'top': coord.top,
17500      'left': coord.left + width
17501    };
17502    //take minimum side value.
17503    ans.dim = Math.min(ans.width, ans.height);
17504    if(ans.dim != ans.height) this.layout.change();
17505    return ans;
17506  },
17507  
17508  layoutH: function(ch, w, coord, prop) {
17509    var totalArea = 0; 
17510    $.each(ch, function(elem) { totalArea += elem._area; });
17511    var height = totalArea / w,
17512        top = coord.top, 
17513        left = 0;
17514    
17515    for(var i=0, l=ch.length; i<l; i++) {
17516      var chi = ch[i];
17517      var w = chi._area / height;
17518      chi.getPos(prop).setc(coord.left + left, top);
17519      chi.setData('width', w, prop);
17520      chi.setData('height', height, prop);
17521      left += w;
17522    }
17523    var ans = {
17524      'height': coord.height - height,
17525      'width': coord.width,
17526      'top': coord.top + height,
17527      'left': coord.left
17528    };
17529    ans.dim = Math.min(ans.width, ans.height);
17530    if(ans.dim != ans.width) this.layout.change();
17531    return ans;
17532  }
17533 });
17534
17535 Layouts.TM.Strip = new Class({
17536   Implements: Layouts.TM.Area,
17537
17538     /*
17539       Method: compute
17540     
17541      Called by loadJSON to calculate recursively all node positions and lay out the tree.
17542     
17543       Parameters:
17544     
17545          json - A JSON subtree. See also <Loader.loadJSON>. 
17546        coord - A coordinates object specifying width, height, left and top style properties.
17547     */
17548     computePositions: function(node, coord, prop) {
17549      var ch = node.getSubnodes([1, 1], "ignore"), config = this.config;
17550      if(ch.length > 0) {
17551        this.processChildrenLayout(node, ch, coord, prop);
17552        for(var i=0, l=ch.length; i<l; i++) {
17553          var chi = ch[i];
17554          var offst = config.offset,
17555              height = chi.getData('height', prop) - offst - config.titleHeight,
17556              width  = chi.getData('width', prop)  - offst;
17557          var chipos = chi.getPos(prop);
17558          coord = {
17559            'width': width,
17560            'height': height,
17561            'top': chipos.y + config.titleHeight,
17562            'left': chipos.x
17563          };
17564          this.computePositions(chi, coord, prop);
17565        }
17566      }
17567     },
17568     
17569     /*
17570       Method: processChildrenLayout
17571     
17572      Computes children real areas and other useful parameters for performing the Strip algorithm.
17573     
17574       Parameters:
17575     
17576          par - The parent node of the json subtree.  
17577          ch - An Array of nodes
17578          coord - A coordinates object specifying width, height, left and top style properties.
17579     */
17580     processChildrenLayout: function(par, ch, coord, prop) {
17581      //compute children real areas
17582       var parentArea = coord.width * coord.height;
17583       var i, l=ch.length, totalChArea=0, chArea = [];
17584       for(i=0; i<l; i++) {
17585         chArea[i] = +ch[i].getData('area', prop);
17586         totalChArea += chArea[i];
17587       }
17588       for(i=0; i<l; i++) {
17589         ch[i]._area = parentArea * chArea[i] / totalChArea;
17590       }
17591      var side = this.layout.horizontal()? coord.width : coord.height;
17592      var initElem = [ch[0]];
17593      var tail = ch.slice(1);
17594      this.stripify(tail, initElem, side, coord, prop);
17595     },
17596     
17597     /*
17598       Method: stripify
17599     
17600      Performs an heuristic method to calculate div elements sizes in order to have 
17601      a good compromise between aspect ratio and order.
17602     
17603       Parameters:
17604     
17605          tail - An array of nodes.  
17606          initElem - An array of nodes.
17607          w - A fixed dimension where nodes will be layed out.
17608        coord - A coordinates object specifying width, height, left and top style properties.
17609     */
17610     stripify: function(tail, initElem, w, coord, prop) {
17611      this.computeDim(tail, initElem, w, coord, this.avgAspectRatio, prop);
17612     },
17613     
17614     /*
17615       Method: layoutRow
17616     
17617      Performs the layout of an array of nodes.
17618     
17619       Parameters:
17620     
17621          ch - An array of nodes.  
17622          w - A fixed dimension where nodes will be laid out.
17623          coord - A coordinates object specifying width, height, left and top style properties.
17624     */
17625     layoutRow: function(ch, w, coord, prop) {
17626      if(this.layout.horizontal()) {
17627        return this.layoutH(ch, w, coord, prop);
17628      } else {
17629        return this.layoutV(ch, w, coord, prop);
17630      }
17631     },
17632     
17633     layoutV: function(ch, w, coord, prop) {
17634      var totalArea = 0; 
17635      $.each(ch, function(elem) { totalArea += elem._area; });
17636      var width = totalArea / w, top =  0; 
17637      for(var i=0, l=ch.length; i<l; i++) {
17638        var chi = ch[i];
17639        var h = chi._area / width;
17640        chi.getPos(prop).setc(coord.left, 
17641            coord.top + (w - h - top));
17642        chi.setData('width', width, prop);
17643        chi.setData('height', h, prop);
17644        top += h;
17645      }
17646     
17647      return {
17648        'height': coord.height,
17649        'width': coord.width - width,
17650        'top': coord.top,
17651        'left': coord.left + width,
17652        'dim': w
17653      };
17654     },
17655     
17656     layoutH: function(ch, w, coord, prop) {
17657      var totalArea = 0; 
17658      $.each(ch, function(elem) { totalArea += elem._area; });
17659      var height = totalArea / w,
17660          top = coord.height - height, 
17661          left = 0;
17662      
17663      for(var i=0, l=ch.length; i<l; i++) {
17664        var chi = ch[i];
17665        var s = chi._area / height;
17666        chi.getPos(prop).setc(coord.left + left, coord.top + top);
17667        chi.setData('width', s, prop);
17668        chi.setData('height', height, prop);
17669        left += s;
17670      }
17671      return {
17672        'height': coord.height - height,
17673        'width': coord.width,
17674        'top': coord.top,
17675        'left': coord.left,
17676        'dim': w
17677      };
17678     }
17679  });
17680
17681 /*
17682  * Class: Layouts.Icicle
17683  *
17684  * Implements the icicle tree layout.
17685  *
17686  * Implemented By:
17687  *
17688  * <Icicle>
17689  *
17690  */
17691
17692 Layouts.Icicle = new Class({
17693  /*
17694   * Method: compute
17695   *
17696   * Called by loadJSON to calculate all node positions.
17697   *
17698   * Parameters:
17699   *
17700   * posType - The nodes' position to compute. Either "start", "end" or
17701   *            "current". Defaults to "current".
17702   */
17703   compute: function(posType) {
17704     posType = posType || "current";
17705     var root = this.graph.getNode(this.root),
17706         config = this.config,
17707         size = this.canvas.getSize(),
17708         width = size.width,
17709         height = size.height,
17710         offset = config.offset,
17711         levelsToShow = config.constrained ? config.levelsToShow : Number.MAX_VALUE;
17712
17713     this.controller.onBeforeCompute(root);
17714
17715     Graph.Util.computeLevels(this.graph, root.id, 0, "ignore");
17716
17717     var treeDepth = 0;
17718
17719     Graph.Util.eachLevel(root, 0, false, function (n, d) { if(d > treeDepth) treeDepth = d; });
17720
17721     var startNode = this.graph.getNode(this.clickedNode && this.clickedNode.id || root.id);
17722     var maxDepth = Math.min(treeDepth, levelsToShow-1);
17723     var initialDepth = startNode._depth;
17724     if(this.layout.horizontal()) {
17725       this.computeSubtree(startNode, -width/2, -height/2, width/(maxDepth+1), height, initialDepth, maxDepth, posType);
17726     } else {
17727       this.computeSubtree(startNode, -width/2, -height/2, width, height/(maxDepth+1), initialDepth, maxDepth, posType);
17728     }
17729   },
17730
17731   computeSubtree: function (root, x, y, width, height, initialDepth, maxDepth, posType) {
17732     root.getPos(posType).setc(x, y);
17733     root.setData('width', width, posType);
17734     root.setData('height', height, posType);
17735
17736     var nodeLength, prevNodeLength = 0, totalDim = 0;
17737     var children = Graph.Util.getSubnodes(root, [1, 1]); // next level from this node
17738
17739     if(!children.length)
17740       return;
17741
17742     $.each(children, function(e) { totalDim += e.getData('dim'); });
17743
17744     for(var i=0, l=children.length; i < l; i++) {
17745       if(this.layout.horizontal()) {
17746         nodeLength = height * children[i].getData('dim') / totalDim;
17747         this.computeSubtree(children[i], x+width, y, width, nodeLength, initialDepth, maxDepth, posType);
17748         y += nodeLength;
17749       } else {
17750         nodeLength = width * children[i].getData('dim') / totalDim;
17751         this.computeSubtree(children[i], x, y+height, nodeLength, height, initialDepth, maxDepth, posType);
17752         x += nodeLength;
17753       }
17754     }
17755   }
17756 });
17757
17758
17759
17760 /*
17761  * File: Icicle.js
17762  *
17763 */
17764
17765 /*
17766   Class: Icicle
17767   
17768   Icicle space filling visualization.
17769   
17770   Implements:
17771   
17772   All <Loader> methods
17773   
17774   Constructor Options:
17775   
17776   Inherits options from
17777   
17778   - <Options.Canvas>
17779   - <Options.Controller>
17780   - <Options.Node>
17781   - <Options.Edge>
17782   - <Options.Label>
17783   - <Options.Events>
17784   - <Options.Tips>
17785   - <Options.NodeStyles>
17786   - <Options.Navigation>
17787   
17788   Additionally, there are other parameters and some default values changed
17789
17790   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
17791   offset - (number) Default's *2*. Boxes offset.
17792   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
17793   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
17794   animate - (boolean) Default's *false*. Whether to animate transitions.
17795   Node.type - Described in <Options.Node>. Default's *rectangle*.
17796   Label.type - Described in <Options.Label>. Default's *Native*.
17797   duration - Described in <Options.Fx>. Default's *700*.
17798   fps - Described in <Options.Fx>. Default's *45*.
17799   
17800   Instance Properties:
17801   
17802   canvas - Access a <Canvas> instance.
17803   graph - Access a <Graph> instance.
17804   op - Access a <Icicle.Op> instance.
17805   fx - Access a <Icicle.Plot> instance.
17806   labels - Access a <Icicle.Label> interface implementation.
17807
17808 */
17809
17810 $jit.Icicle = new Class({
17811   Implements: [ Loader, Extras, Layouts.Icicle ],
17812
17813   layout: {
17814     orientation: "h",
17815     vertical: function(){
17816       return this.orientation == "v";
17817     },
17818     horizontal: function(){
17819       return this.orientation == "h";
17820     },
17821     change: function(){
17822       this.orientation = this.vertical()? "h" : "v";
17823     }
17824   },
17825
17826   initialize: function(controller) {
17827     var config = {
17828       animate: false,
17829       orientation: "h",
17830       offset: 2,
17831       levelsToShow: Number.MAX_VALUE,
17832       constrained: false,
17833       Node: {
17834         type: 'rectangle',
17835         overridable: true
17836       },
17837       Edge: {
17838         type: 'none'
17839       },
17840       Label: {
17841         type: 'Native'
17842       },
17843       duration: 700,
17844       fps: 45
17845     };
17846
17847     var opts = Options("Canvas", "Node", "Edge", "Fx", "Tips", "NodeStyles",
17848                        "Events", "Navigation", "Controller", "Label");
17849     this.controller = this.config = $.merge(opts, config, controller);
17850     this.layout.orientation = this.config.orientation;
17851
17852     var canvasConfig = this.config;
17853     if (canvasConfig.useCanvas) {
17854       this.canvas = canvasConfig.useCanvas;
17855       this.config.labelContainer = this.canvas.id + '-label';
17856     } else {
17857       this.canvas = new Canvas(this, canvasConfig);
17858       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
17859     }
17860
17861     this.graphOptions = {
17862       'complex': true,
17863       'Node': {
17864         'selected': false,
17865         'exist': true,
17866         'drawn': true
17867       }
17868     };
17869
17870     this.graph = new Graph(
17871       this.graphOptions, this.config.Node, this.config.Edge, this.config.Label);
17872
17873     this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
17874     this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
17875     this.op = new $jit.Icicle.Op(this);
17876     this.group = new $jit.Icicle.Group(this);
17877     this.clickedNode = null;
17878
17879     this.initializeExtras();
17880   },
17881
17882   /* 
17883     Method: refresh 
17884     
17885     Computes positions and plots the tree.
17886   */
17887   refresh: function(){
17888     var labelType = this.config.Label.type;
17889     if(labelType != 'Native') {
17890       var that = this;
17891       this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
17892     }
17893     this.compute();
17894     this.plot();
17895   },
17896
17897   /* 
17898     Method: plot 
17899     
17900     Plots the Icicle visualization. This is a shortcut to *fx.plot*. 
17901   
17902    */
17903   plot: function(){
17904     this.fx.plot(this.config);
17905   },
17906
17907   /* 
17908     Method: enter 
17909     
17910     Sets the node as root.
17911     
17912      Parameters:
17913      
17914      node - (object) A <Graph.Node>.
17915   
17916    */
17917   enter: function (node) {
17918     if (this.busy)
17919       return;
17920     this.busy = true;
17921
17922     var that = this,
17923         config = this.config;
17924
17925     var callback = {
17926       onComplete: function() {
17927         //compute positions of newly inserted nodes
17928         if(config.request)
17929           that.compute();
17930
17931         if(config.animate) {
17932           that.graph.nodeList.setDataset(['current', 'end'], {
17933             'alpha': [1, 0] //fade nodes
17934           });
17935
17936           Graph.Util.eachSubgraph(node, function(n) {
17937             n.setData('alpha', 1, 'end');
17938           }, "ignore");
17939
17940           that.fx.animate({
17941             duration: 500,
17942             modes:['node-property:alpha'],
17943             onComplete: function() {
17944               that.clickedNode = node;
17945               that.compute('end');
17946
17947               that.fx.animate({
17948                 modes:['linear', 'node-property:width:height'],
17949                 duration: 1000,
17950                 onComplete: function() {
17951                   that.busy = false;
17952                   that.clickedNode = node;
17953                 }
17954               });
17955             }
17956           });
17957         } else {
17958           that.clickedNode = node;
17959           that.busy = false;
17960           that.refresh();
17961         }
17962       }
17963     };
17964
17965     if(config.request) {
17966       this.requestNodes(clickedNode, callback);
17967     } else {
17968       callback.onComplete();
17969     }
17970   },
17971
17972   /* 
17973     Method: out 
17974     
17975     Sets the parent node of the current selected node as root.
17976   
17977    */
17978   out: function(){
17979     if(this.busy)
17980       return;
17981
17982     var that = this,
17983         GUtil = Graph.Util,
17984         config = this.config,
17985         graph = this.graph,
17986         parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
17987         parent = parents[0],
17988         clickedNode = parent,
17989         previousClickedNode = this.clickedNode;
17990
17991     this.busy = true;
17992     this.events.hoveredNode = false;
17993
17994     if(!parent) {
17995       this.busy = false;
17996       return;
17997     }
17998
17999     //final plot callback
18000     callback = {
18001       onComplete: function() {
18002         that.clickedNode = parent;
18003         if(config.request) {
18004           that.requestNodes(parent, {
18005             onComplete: function() {
18006               that.compute();
18007               that.plot();
18008               that.busy = false;
18009             }
18010           });
18011         } else {
18012           that.compute();
18013           that.plot();
18014           that.busy = false;
18015         }
18016       }
18017     };
18018
18019     //animate node positions
18020     if(config.animate) {
18021       this.clickedNode = clickedNode;
18022       this.compute('end');
18023       //animate the visible subtree only
18024       this.clickedNode = previousClickedNode;
18025       this.fx.animate({
18026         modes:['linear', 'node-property:width:height'],
18027         duration: 1000,
18028         onComplete: function() {
18029           //animate the parent subtree
18030           that.clickedNode = clickedNode;
18031           //change nodes alpha
18032           graph.nodeList.setDataset(['current', 'end'], {
18033             'alpha': [0, 1]
18034           });
18035           GUtil.eachSubgraph(previousClickedNode, function(node) {
18036             node.setData('alpha', 1);
18037           }, "ignore");
18038           that.fx.animate({
18039             duration: 500,
18040             modes:['node-property:alpha'],
18041             onComplete: function() {
18042               callback.onComplete();
18043             }
18044           });
18045         }
18046       });
18047     } else {
18048       callback.onComplete();
18049     }
18050   },
18051   requestNodes: function(node, onComplete){
18052     var handler = $.merge(this.controller, onComplete),
18053         levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
18054
18055     if (handler.request) {
18056       var leaves = [], d = node._depth;
18057       Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
18058         if (n.drawn && !Graph.Util.anySubnode(n)) {
18059           leaves.push(n);
18060           n._level = n._depth - d;
18061           if (this.config.constrained)
18062             n._level = levelsToShow - n._level;
18063
18064         }
18065       });
18066       this.group.requestNodes(leaves, handler);
18067     } else {
18068       handler.onComplete();
18069     }
18070   }
18071 });
18072
18073 /*
18074   Class: Icicle.Op
18075   
18076   Custom extension of <Graph.Op>.
18077   
18078   Extends:
18079   
18080   All <Graph.Op> methods
18081   
18082   See also:
18083   
18084   <Graph.Op>
18085   
18086   */
18087 $jit.Icicle.Op = new Class({
18088
18089   Implements: Graph.Op
18090
18091 });
18092
18093 /*
18094  * Performs operations on group of nodes.
18095  */
18096 $jit.Icicle.Group = new Class({
18097
18098   initialize: function(viz){
18099     this.viz = viz;
18100     this.canvas = viz.canvas;
18101     this.config = viz.config;
18102   },
18103
18104   /*
18105    * Calls the request method on the controller to request a subtree for each node.
18106    */
18107   requestNodes: function(nodes, controller){
18108     var counter = 0, len = nodes.length, nodeSelected = {};
18109     var complete = function(){
18110       controller.onComplete();
18111     };
18112     var viz = this.viz;
18113     if (len == 0)
18114       complete();
18115     for(var i = 0; i < len; i++) {
18116       nodeSelected[nodes[i].id] = nodes[i];
18117       controller.request(nodes[i].id, nodes[i]._level, {
18118         onComplete: function(nodeId, data){
18119           if (data && data.children) {
18120             data.id = nodeId;
18121             viz.op.sum(data, {
18122               type: 'nothing'
18123             });
18124           }
18125           if (++counter == len) {
18126             Graph.Util.computeLevels(viz.graph, viz.root, 0);
18127             complete();
18128           }
18129         }
18130       });
18131     }
18132   }
18133 });
18134
18135 /*
18136   Class: Icicle.Plot
18137   
18138   Custom extension of <Graph.Plot>.
18139   
18140   Extends:
18141   
18142   All <Graph.Plot> methods
18143   
18144   See also:
18145   
18146   <Graph.Plot>
18147   
18148   */
18149 $jit.Icicle.Plot = new Class({
18150   Implements: Graph.Plot,
18151
18152   plot: function(opt, animating){
18153     opt = opt || this.viz.controller;
18154     var viz = this.viz,
18155         graph = viz.graph,
18156         root = graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root),
18157         initialDepth = root._depth;
18158
18159     viz.canvas.clear();
18160     this.plotTree(root, $.merge(opt, {
18161       'withLabels': true,
18162       'hideLabels': false,
18163       'plotSubtree': function(root, node) {
18164         return !viz.config.constrained ||
18165                (node._depth - initialDepth < viz.config.levelsToShow);
18166       }
18167     }), animating);
18168   }
18169 });
18170
18171 /*
18172   Class: Icicle.Label
18173   
18174   Custom extension of <Graph.Label>. 
18175   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18176   
18177   Extends:
18178   
18179   All <Graph.Label> methods and subclasses.
18180   
18181   See also:
18182   
18183   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18184   
18185   */
18186 $jit.Icicle.Label = {};
18187
18188 /*
18189   Icicle.Label.Native
18190   
18191   Custom extension of <Graph.Label.Native>.
18192   
18193   Extends:
18194   
18195   All <Graph.Label.Native> methods
18196   
18197   See also:
18198   
18199   <Graph.Label.Native>
18200
18201   */
18202 $jit.Icicle.Label.Native = new Class({
18203   Implements: Graph.Label.Native,
18204
18205   renderLabel: function(canvas, node, controller) {
18206     var ctx = canvas.getCtx(),
18207         width = node.getData('width'),
18208         height = node.getData('height'),
18209         size = node.getLabelData('size'),
18210         m = ctx.measureText(node.name);
18211
18212     // Guess as much as possible if the label will fit in the node
18213     if(height < (size * 1.5) || width < m.width)
18214       return;
18215
18216     var pos = node.pos.getc(true);
18217     ctx.fillText(node.name,
18218                  pos.x + width / 2,
18219                  pos.y + height / 2);
18220   }
18221 });
18222
18223 /*
18224   Icicle.Label.SVG
18225   
18226   Custom extension of <Graph.Label.SVG>.
18227   
18228   Extends:
18229   
18230   All <Graph.Label.SVG> methods
18231   
18232   See also:
18233   
18234   <Graph.Label.SVG>
18235 */
18236 $jit.Icicle.Label.SVG = new Class( {
18237   Implements: Graph.Label.SVG,
18238
18239   initialize: function(viz){
18240     this.viz = viz;
18241   },
18242
18243   /*
18244     placeLabel
18245    
18246     Overrides abstract method placeLabel in <Graph.Plot>.
18247    
18248     Parameters:
18249    
18250     tag - A DOM label element.
18251     node - A <Graph.Node>.
18252     controller - A configuration/controller object passed to the visualization.
18253    */
18254   placeLabel: function(tag, node, controller){
18255     var pos = node.pos.getc(true), canvas = this.viz.canvas;
18256     var radius = canvas.getSize();
18257     var labelPos = {
18258       x: Math.round(pos.x + radius.width / 2),
18259       y: Math.round(pos.y + radius.height / 2)
18260     };
18261     tag.setAttribute('x', labelPos.x);
18262     tag.setAttribute('y', labelPos.y);
18263
18264     controller.onPlaceLabel(tag, node);
18265   }
18266 });
18267
18268 /*
18269   Icicle.Label.HTML
18270   
18271   Custom extension of <Graph.Label.HTML>.
18272   
18273   Extends:
18274   
18275   All <Graph.Label.HTML> methods.
18276   
18277   See also:
18278   
18279   <Graph.Label.HTML>
18280   
18281   */
18282 $jit.Icicle.Label.HTML = new Class( {
18283   Implements: Graph.Label.HTML,
18284
18285   initialize: function(viz){
18286     this.viz = viz;
18287   },
18288
18289   /*
18290     placeLabel
18291    
18292     Overrides abstract method placeLabel in <Graph.Plot>.
18293    
18294     Parameters:
18295    
18296     tag - A DOM label element.
18297     node - A <Graph.Node>.
18298     controller - A configuration/controller object passed to the visualization.
18299    */
18300   placeLabel: function(tag, node, controller){
18301     var pos = node.pos.getc(true), canvas = this.viz.canvas;
18302     var radius = canvas.getSize();
18303     var labelPos = {
18304       x: Math.round(pos.x + radius.width / 2),
18305       y: Math.round(pos.y + radius.height / 2)
18306     };
18307
18308     var style = tag.style;
18309     style.left = labelPos.x + 'px';
18310     style.top = labelPos.y + 'px';
18311     style.display = '';
18312
18313     controller.onPlaceLabel(tag, node);
18314   }
18315 });
18316
18317 /*
18318   Class: Icicle.Plot.NodeTypes
18319   
18320   This class contains a list of <Graph.Node> built-in types. 
18321   Node types implemented are 'none', 'rectangle'.
18322   
18323   You can add your custom node types, customizing your visualization to the extreme.
18324   
18325   Example:
18326   
18327   (start code js)
18328     Icicle.Plot.NodeTypes.implement({
18329       'mySpecialType': {
18330         'render': function(node, canvas) {
18331           //print your custom node to canvas
18332         },
18333         //optional
18334         'contains': function(node, pos) {
18335           //return true if pos is inside the node or false otherwise
18336         }
18337       }
18338     });
18339   (end code)
18340   
18341   */
18342 $jit.Icicle.Plot.NodeTypes = new Class( {
18343   'none': {
18344     'render': $.empty
18345   },
18346
18347   'rectangle': {
18348     'render': function(node, canvas, animating) {
18349       var config = this.viz.config;
18350       var offset = config.offset;
18351       var width = node.getData('width');
18352       var height = node.getData('height');
18353       var border = node.getData('border');
18354       var pos = node.pos.getc(true);
18355       var posx = pos.x + offset / 2, posy = pos.y + offset / 2;
18356       var ctx = canvas.getCtx();
18357       
18358       if(width - offset < 2 || height - offset < 2) return;
18359       
18360       if(config.cushion) {
18361         var color = node.getData('color');
18362         var lg = ctx.createRadialGradient(posx + (width - offset)/2, 
18363                                           posy + (height - offset)/2, 1, 
18364                                           posx + (width-offset)/2, posy + (height-offset)/2, 
18365                                           width < height? height : width);
18366         var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
18367             function(r) { return r * 0.3 >> 0; }));
18368         lg.addColorStop(0, color);
18369         lg.addColorStop(1, colorGrad);
18370         ctx.fillStyle = lg;
18371       }
18372
18373       if (border) {
18374         ctx.strokeStyle = border;
18375         ctx.lineWidth = 3;
18376       }
18377
18378       ctx.fillRect(posx, posy, Math.max(0, width - offset), Math.max(0, height - offset));
18379       border && ctx.strokeRect(pos.x, pos.y, width, height);
18380     },
18381
18382     'contains': function(node, pos) {
18383       if(this.viz.clickedNode && !$jit.Graph.Util.isDescendantOf(node, this.viz.clickedNode.id)) return false;
18384       var npos = node.pos.getc(true),
18385           width = node.getData('width'),
18386           height = node.getData('height');
18387       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
18388     }
18389   }
18390 });
18391
18392 $jit.Icicle.Plot.EdgeTypes = new Class( {
18393   'none': $.empty
18394 });
18395
18396
18397
18398 /*
18399  * File: Layouts.ForceDirected.js
18400  *
18401 */
18402
18403 /*
18404  * Class: Layouts.ForceDirected
18405  * 
18406  * Implements a Force Directed Layout.
18407  * 
18408  * Implemented By:
18409  * 
18410  * <ForceDirected>
18411  * 
18412  * Credits:
18413  * 
18414  * Marcus Cobden <http://marcuscobden.co.uk>
18415  * 
18416  */
18417 Layouts.ForceDirected = new Class({
18418
18419   getOptions: function(random) {
18420     var s = this.canvas.getSize();
18421     var w = s.width, h = s.height;
18422     //count nodes
18423     var count = 0;
18424     this.graph.eachNode(function(n) { 
18425       count++;
18426     });
18427     var k2 = w * h / count, k = Math.sqrt(k2);
18428     var l = this.config.levelDistance;
18429     
18430     return {
18431       width: w,
18432       height: h,
18433       tstart: w * 0.1,
18434       nodef: function(x) { return k2 / (x || 1); },
18435       edgef: function(x) { return /* x * x / k; */ k * (x - l); }
18436     };
18437   },
18438   
18439   compute: function(property, incremental) {
18440     var prop = $.splat(property || ['current', 'start', 'end']);
18441     var opt = this.getOptions();
18442     NodeDim.compute(this.graph, prop, this.config);
18443     this.graph.computeLevels(this.root, 0, "ignore");
18444     this.graph.eachNode(function(n) {
18445       $.each(prop, function(p) {
18446         var pos = n.getPos(p);
18447         if(pos.equals(Complex.KER)) {
18448           pos.x = opt.width/5 * (Math.random() - 0.5);
18449           pos.y = opt.height/5 * (Math.random() - 0.5);
18450         }
18451         //initialize disp vector
18452         n.disp = {};
18453         $.each(prop, function(p) {
18454           n.disp[p] = $C(0, 0);
18455         });
18456       });
18457     });
18458     this.computePositions(prop, opt, incremental);
18459   },
18460   
18461   computePositions: function(property, opt, incremental) {
18462     var times = this.config.iterations, i = 0, that = this;
18463     if(incremental) {
18464       (function iter() {
18465         for(var total=incremental.iter, j=0; j<total; j++) {
18466           opt.t = opt.tstart * (1 - i++/(times -1));
18467           that.computePositionStep(property, opt);
18468           if(i >= times) {
18469             incremental.onComplete();
18470             return;
18471           }
18472         }
18473         incremental.onStep(Math.round(i / (times -1) * 100));
18474         setTimeout(iter, 1);
18475       })();
18476     } else {
18477       for(; i < times; i++) {
18478         opt.t = opt.tstart * (1 - i/(times -1));
18479         this.computePositionStep(property, opt);
18480       }
18481     }
18482   },
18483   
18484   computePositionStep: function(property, opt) {
18485     var graph = this.graph;
18486     var min = Math.min, max = Math.max;
18487     var dpos = $C(0, 0);
18488     //calculate repulsive forces
18489     graph.eachNode(function(v) {
18490       //initialize disp
18491       $.each(property, function(p) {
18492         v.disp[p].x = 0; v.disp[p].y = 0;
18493       });
18494       graph.eachNode(function(u) {
18495         if(u.id != v.id) {
18496           $.each(property, function(p) {
18497             var vp = v.getPos(p), up = u.getPos(p);
18498             dpos.x = vp.x - up.x;
18499             dpos.y = vp.y - up.y;
18500             var norm = dpos.norm() || 1;
18501             v.disp[p].$add(dpos
18502                 .$scale(opt.nodef(norm) / norm));
18503           });
18504         }
18505       });
18506     });
18507     //calculate attractive forces
18508     var T = !!graph.getNode(this.root).visited;
18509     graph.eachNode(function(node) {
18510       node.eachAdjacency(function(adj) {
18511         var nodeTo = adj.nodeTo;
18512         if(!!nodeTo.visited === T) {
18513           $.each(property, function(p) {
18514             var vp = node.getPos(p), up = nodeTo.getPos(p);
18515             dpos.x = vp.x - up.x;
18516             dpos.y = vp.y - up.y;
18517             var norm = dpos.norm() || 1;
18518             node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
18519             nodeTo.disp[p].$add(dpos.$scale(-1));
18520           });
18521         }
18522       });
18523       node.visited = !T;
18524     });
18525     //arrange positions to fit the canvas
18526     var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
18527     graph.eachNode(function(u) {
18528       $.each(property, function(p) {
18529         var disp = u.disp[p];
18530         var norm = disp.norm() || 1;
18531         var p = u.getPos(p);
18532         p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm, 
18533             disp.y * min(Math.abs(disp.y), t) / norm));
18534         p.x = min(w2, max(-w2, p.x));
18535         p.y = min(h2, max(-h2, p.y));
18536       });
18537     });
18538   }
18539 });
18540
18541 /*
18542  * File: ForceDirected.js
18543  */
18544
18545 /*
18546    Class: ForceDirected
18547       
18548    A visualization that lays graphs using a Force-Directed layout algorithm.
18549    
18550    Inspired by:
18551   
18552    Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
18553    
18554   Implements:
18555   
18556   All <Loader> methods
18557   
18558    Constructor Options:
18559    
18560    Inherits options from
18561    
18562    - <Options.Canvas>
18563    - <Options.Controller>
18564    - <Options.Node>
18565    - <Options.Edge>
18566    - <Options.Label>
18567    - <Options.Events>
18568    - <Options.Tips>
18569    - <Options.NodeStyles>
18570    - <Options.Navigation>
18571    
18572    Additionally, there are two parameters
18573    
18574    levelDistance - (number) Default's *50*. The natural length desired for the edges.
18575    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*. 
18576      
18577    Instance Properties:
18578
18579    canvas - Access a <Canvas> instance.
18580    graph - Access a <Graph> instance.
18581    op - Access a <ForceDirected.Op> instance.
18582    fx - Access a <ForceDirected.Plot> instance.
18583    labels - Access a <ForceDirected.Label> interface implementation.
18584
18585 */
18586
18587 $jit.ForceDirected = new Class( {
18588
18589   Implements: [ Loader, Extras, Layouts.ForceDirected ],
18590
18591   initialize: function(controller) {
18592     var $ForceDirected = $jit.ForceDirected;
18593
18594     var config = {
18595       iterations: 50,
18596       levelDistance: 50
18597     };
18598
18599     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
18600         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
18601
18602     var canvasConfig = this.config;
18603     if(canvasConfig.useCanvas) {
18604       this.canvas = canvasConfig.useCanvas;
18605       this.config.labelContainer = this.canvas.id + '-label';
18606     } else {
18607       if(canvasConfig.background) {
18608         canvasConfig.background = $.merge({
18609           type: 'Circles'
18610         }, canvasConfig.background);
18611       }
18612       this.canvas = new Canvas(this, canvasConfig);
18613       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
18614     }
18615
18616     this.graphOptions = {
18617       'complex': true,
18618       'Node': {
18619         'selected': false,
18620         'exist': true,
18621         'drawn': true
18622       }
18623     };
18624     this.graph = new Graph(this.graphOptions, this.config.Node,
18625         this.config.Edge);
18626     this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
18627     this.fx = new $ForceDirected.Plot(this, $ForceDirected);
18628     this.op = new $ForceDirected.Op(this);
18629     this.json = null;
18630     this.busy = false;
18631     // initialize extras
18632     this.initializeExtras();
18633   },
18634
18635   /* 
18636     Method: refresh 
18637     
18638     Computes positions and plots the tree.
18639   */
18640   refresh: function() {
18641     this.compute();
18642     this.plot();
18643   },
18644
18645   reposition: function() {
18646     this.compute('end');
18647   },
18648
18649 /*
18650   Method: computeIncremental
18651   
18652   Performs the Force Directed algorithm incrementally.
18653   
18654   Description:
18655   
18656   ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete. 
18657   This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and 
18658   avoiding browser messages such as "This script is taking too long to complete".
18659   
18660   Parameters:
18661   
18662   opt - (object) The object properties are described below
18663   
18664   iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property 
18665   of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
18666   
18667   property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'. 
18668   You can also set an array of these properties. If you'd like to keep the current node positions but to perform these 
18669   computations for final animation positions then you can just choose 'end'.
18670   
18671   onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal 
18672   parameter a percentage value.
18673   
18674   onComplete - A callback function called when the algorithm completed.
18675   
18676   Example:
18677   
18678   In this example I calculate the end positions and then animate the graph to those positions
18679   
18680   (start code js)
18681   var fd = new $jit.ForceDirected(...);
18682   fd.computeIncremental({
18683     iter: 20,
18684     property: 'end',
18685     onStep: function(perc) {
18686       Log.write("loading " + perc + "%");
18687     },
18688     onComplete: function() {
18689       Log.write("done");
18690       fd.animate();
18691     }
18692   });
18693   (end code)
18694   
18695   In this example I calculate all positions and (re)plot the graph
18696   
18697   (start code js)
18698   var fd = new ForceDirected(...);
18699   fd.computeIncremental({
18700     iter: 20,
18701     property: ['end', 'start', 'current'],
18702     onStep: function(perc) {
18703       Log.write("loading " + perc + "%");
18704     },
18705     onComplete: function() {
18706       Log.write("done");
18707       fd.plot();
18708     }
18709   });
18710   (end code)
18711   
18712   */
18713   computeIncremental: function(opt) {
18714     opt = $.merge( {
18715       iter: 20,
18716       property: 'end',
18717       onStep: $.empty,
18718       onComplete: $.empty
18719     }, opt || {});
18720
18721     this.config.onBeforeCompute(this.graph.getNode(this.root));
18722     this.compute(opt.property, opt);
18723   },
18724
18725   /*
18726     Method: plot
18727    
18728     Plots the ForceDirected graph. This is a shortcut to *fx.plot*.
18729    */
18730   plot: function() {
18731     this.fx.plot();
18732   },
18733
18734   /*
18735      Method: animate
18736     
18737      Animates the graph from the current positions to the 'end' node positions.
18738   */
18739   animate: function(opt) {
18740     this.fx.animate($.merge( {
18741       modes: [ 'linear' ]
18742     }, opt || {}));
18743   }
18744 });
18745
18746 $jit.ForceDirected.$extend = true;
18747
18748 (function(ForceDirected) {
18749
18750   /*
18751      Class: ForceDirected.Op
18752      
18753      Custom extension of <Graph.Op>.
18754
18755      Extends:
18756
18757      All <Graph.Op> methods
18758      
18759      See also:
18760      
18761      <Graph.Op>
18762
18763   */
18764   ForceDirected.Op = new Class( {
18765
18766     Implements: Graph.Op
18767
18768   });
18769
18770   /*
18771     Class: ForceDirected.Plot
18772     
18773     Custom extension of <Graph.Plot>.
18774   
18775     Extends:
18776   
18777     All <Graph.Plot> methods
18778     
18779     See also:
18780     
18781     <Graph.Plot>
18782   
18783   */
18784   ForceDirected.Plot = new Class( {
18785
18786     Implements: Graph.Plot
18787
18788   });
18789
18790   /*
18791     Class: ForceDirected.Label
18792     
18793     Custom extension of <Graph.Label>. 
18794     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
18795   
18796     Extends:
18797   
18798     All <Graph.Label> methods and subclasses.
18799   
18800     See also:
18801   
18802     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
18803   
18804   */
18805   ForceDirected.Label = {};
18806
18807   /*
18808      ForceDirected.Label.Native
18809      
18810      Custom extension of <Graph.Label.Native>.
18811
18812      Extends:
18813
18814      All <Graph.Label.Native> methods
18815
18816      See also:
18817
18818      <Graph.Label.Native>
18819
18820   */
18821   ForceDirected.Label.Native = new Class( {
18822     Implements: Graph.Label.Native
18823   });
18824
18825   /*
18826     ForceDirected.Label.SVG
18827     
18828     Custom extension of <Graph.Label.SVG>.
18829   
18830     Extends:
18831   
18832     All <Graph.Label.SVG> methods
18833   
18834     See also:
18835   
18836     <Graph.Label.SVG>
18837   
18838   */
18839   ForceDirected.Label.SVG = new Class( {
18840     Implements: Graph.Label.SVG,
18841
18842     initialize: function(viz) {
18843       this.viz = viz;
18844     },
18845
18846     /* 
18847        placeLabel
18848
18849        Overrides abstract method placeLabel in <Graph.Label>.
18850
18851        Parameters:
18852
18853        tag - A DOM label element.
18854        node - A <Graph.Node>.
18855        controller - A configuration/controller object passed to the visualization.
18856       
18857      */
18858     placeLabel: function(tag, node, controller) {
18859       var pos = node.pos.getc(true), 
18860           canvas = this.viz.canvas,
18861           ox = canvas.translateOffsetX,
18862           oy = canvas.translateOffsetY,
18863           sx = canvas.scaleOffsetX,
18864           sy = canvas.scaleOffsetY,
18865           radius = canvas.getSize();
18866       var labelPos = {
18867         x: Math.round(pos.x * sx + ox + radius.width / 2),
18868         y: Math.round(pos.y * sy + oy + radius.height / 2)
18869       };
18870       tag.setAttribute('x', labelPos.x);
18871       tag.setAttribute('y', labelPos.y);
18872
18873       controller.onPlaceLabel(tag, node);
18874     }
18875   });
18876
18877   /*
18878      ForceDirected.Label.HTML
18879      
18880      Custom extension of <Graph.Label.HTML>.
18881
18882      Extends:
18883
18884      All <Graph.Label.HTML> methods.
18885
18886      See also:
18887
18888      <Graph.Label.HTML>
18889
18890   */
18891   ForceDirected.Label.HTML = new Class( {
18892     Implements: Graph.Label.HTML,
18893
18894     initialize: function(viz) {
18895       this.viz = viz;
18896     },
18897     /* 
18898        placeLabel
18899
18900        Overrides abstract method placeLabel in <Graph.Plot>.
18901
18902        Parameters:
18903
18904        tag - A DOM label element.
18905        node - A <Graph.Node>.
18906        controller - A configuration/controller object passed to the visualization.
18907       
18908      */
18909     placeLabel: function(tag, node, controller) {
18910       var pos = node.pos.getc(true), 
18911           canvas = this.viz.canvas,
18912           ox = canvas.translateOffsetX,
18913           oy = canvas.translateOffsetY,
18914           sx = canvas.scaleOffsetX,
18915           sy = canvas.scaleOffsetY,
18916           radius = canvas.getSize();
18917       var labelPos = {
18918         x: Math.round(pos.x * sx + ox + radius.width / 2),
18919         y: Math.round(pos.y * sy + oy + radius.height / 2)
18920       };
18921       var style = tag.style;
18922       style.left = labelPos.x + 'px';
18923       style.top = labelPos.y + 'px';
18924       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
18925
18926       controller.onPlaceLabel(tag, node);
18927     }
18928   });
18929
18930   /*
18931     Class: ForceDirected.Plot.NodeTypes
18932
18933     This class contains a list of <Graph.Node> built-in types. 
18934     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
18935
18936     You can add your custom node types, customizing your visualization to the extreme.
18937
18938     Example:
18939
18940     (start code js)
18941       ForceDirected.Plot.NodeTypes.implement({
18942         'mySpecialType': {
18943           'render': function(node, canvas) {
18944             //print your custom node to canvas
18945           },
18946           //optional
18947           'contains': function(node, pos) {
18948             //return true if pos is inside the node or false otherwise
18949           }
18950         }
18951       });
18952     (end code)
18953
18954   */
18955   ForceDirected.Plot.NodeTypes = new Class({
18956     'none': {
18957       'render': $.empty,
18958       'contains': $.lambda(false)
18959     },
18960     'circle': {
18961       'render': function(node, canvas){
18962         var pos = node.pos.getc(true), 
18963             dim = node.getData('dim');
18964         this.nodeHelper.circle.render('fill', pos, dim, canvas);
18965       },
18966       'contains': function(node, pos){
18967         var npos = node.pos.getc(true), 
18968             dim = node.getData('dim');
18969         return this.nodeHelper.circle.contains(npos, pos, dim);
18970       }
18971     },
18972     'ellipse': {
18973       'render': function(node, canvas){
18974         var pos = node.pos.getc(true), 
18975             width = node.getData('width'), 
18976             height = node.getData('height');
18977         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
18978         },
18979       // TODO(nico): be more precise...
18980       'contains': function(node, pos){
18981         var npos = node.pos.getc(true), 
18982             width = node.getData('width'), 
18983             height = node.getData('height');
18984         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
18985       }
18986     },
18987     'square': {
18988       'render': function(node, canvas){
18989         var pos = node.pos.getc(true), 
18990             dim = node.getData('dim');
18991         this.nodeHelper.square.render('fill', pos, dim, canvas);
18992       },
18993       'contains': function(node, pos){
18994         var npos = node.pos.getc(true), 
18995             dim = node.getData('dim');
18996         return this.nodeHelper.square.contains(npos, pos, dim);
18997       }
18998     },
18999     'rectangle': {
19000       'render': function(node, canvas){
19001         var pos = node.pos.getc(true), 
19002             width = node.getData('width'), 
19003             height = node.getData('height');
19004         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
19005       },
19006       'contains': function(node, pos){
19007         var npos = node.pos.getc(true), 
19008             width = node.getData('width'), 
19009             height = node.getData('height');
19010         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
19011       }
19012     },
19013     'triangle': {
19014       'render': function(node, canvas){
19015         var pos = node.pos.getc(true), 
19016             dim = node.getData('dim');
19017         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
19018       },
19019       'contains': function(node, pos) {
19020         var npos = node.pos.getc(true), 
19021             dim = node.getData('dim');
19022         return this.nodeHelper.triangle.contains(npos, pos, dim);
19023       }
19024     },
19025     'star': {
19026       'render': function(node, canvas){
19027         var pos = node.pos.getc(true),
19028             dim = node.getData('dim');
19029         this.nodeHelper.star.render('fill', pos, dim, canvas);
19030       },
19031       'contains': function(node, pos) {
19032         var npos = node.pos.getc(true),
19033             dim = node.getData('dim');
19034         return this.nodeHelper.star.contains(npos, pos, dim);
19035       }
19036     }
19037   });
19038
19039   /*
19040     Class: ForceDirected.Plot.EdgeTypes
19041   
19042     This class contains a list of <Graph.Adjacence> built-in types. 
19043     Edge types implemented are 'none', 'line' and 'arrow'.
19044   
19045     You can add your custom edge types, customizing your visualization to the extreme.
19046   
19047     Example:
19048   
19049     (start code js)
19050       ForceDirected.Plot.EdgeTypes.implement({
19051         'mySpecialType': {
19052           'render': function(adj, canvas) {
19053             //print your custom edge to canvas
19054           },
19055           //optional
19056           'contains': function(adj, pos) {
19057             //return true if pos is inside the arc or false otherwise
19058           }
19059         }
19060       });
19061     (end code)
19062   
19063   */
19064   ForceDirected.Plot.EdgeTypes = new Class({
19065     'none': $.empty,
19066     'line': {
19067       'render': function(adj, canvas) {
19068         var from = adj.nodeFrom.pos.getc(true),
19069             to = adj.nodeTo.pos.getc(true);
19070         this.edgeHelper.line.render(from, to, canvas);
19071       },
19072       'contains': function(adj, pos) {
19073         var from = adj.nodeFrom.pos.getc(true),
19074             to = adj.nodeTo.pos.getc(true);
19075         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
19076       }
19077     },
19078     'arrow': {
19079       'render': function(adj, canvas) {
19080         var from = adj.nodeFrom.pos.getc(true),
19081             to = adj.nodeTo.pos.getc(true),
19082             dim = adj.getData('dim'),
19083             direction = adj.data.$direction,
19084             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
19085         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
19086       },
19087       'contains': function(adj, pos) {
19088         var from = adj.nodeFrom.pos.getc(true),
19089             to = adj.nodeTo.pos.getc(true);
19090         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
19091       }
19092     }
19093   });
19094
19095 })($jit.ForceDirected);
19096
19097
19098 /*
19099  * File: Treemap.js
19100  *
19101 */
19102
19103 $jit.TM = {};
19104
19105 var TM = $jit.TM;
19106
19107 $jit.TM.$extend = true;
19108
19109 /*
19110   Class: TM.Base
19111   
19112   Abstract class providing base functionality for <TM.Squarified>, <TM.Strip> and <TM.SliceAndDice> visualizations.
19113   
19114   Implements:
19115   
19116   All <Loader> methods
19117   
19118   Constructor Options:
19119   
19120   Inherits options from
19121   
19122   - <Options.Canvas>
19123   - <Options.Controller>
19124   - <Options.Node>
19125   - <Options.Edge>
19126   - <Options.Label>
19127   - <Options.Events>
19128   - <Options.Tips>
19129   - <Options.NodeStyles>
19130   - <Options.Navigation>
19131   
19132   Additionally, there are other parameters and some default values changed
19133
19134   orientation - (string) Default's *h*. Whether to set horizontal or vertical layouts. Possible values are 'h' and 'v'.
19135   titleHeight - (number) Default's *13*. The height of the title rectangle for inner (non-leaf) nodes.
19136   offset - (number) Default's *2*. Boxes offset.
19137   constrained - (boolean) Default's *false*. Whether to show the entire tree when loaded or just the number of levels specified by _levelsToShow_.
19138   levelsToShow - (number) Default's *3*. The number of levels to show for a subtree. This number is relative to the selected node.
19139   animate - (boolean) Default's *false*. Whether to animate transitions.
19140   Node.type - Described in <Options.Node>. Default's *rectangle*.
19141   duration - Described in <Options.Fx>. Default's *700*.
19142   fps - Described in <Options.Fx>. Default's *45*.
19143   
19144   Instance Properties:
19145   
19146   canvas - Access a <Canvas> instance.
19147   graph - Access a <Graph> instance.
19148   op - Access a <TM.Op> instance.
19149   fx - Access a <TM.Plot> instance.
19150   labels - Access a <TM.Label> interface implementation.
19151
19152   Inspired by:
19153   
19154   Squarified Treemaps (Mark Bruls, Kees Huizing, and Jarke J. van Wijk) <http://www.win.tue.nl/~vanwijk/stm.pdf>
19155   
19156   Tree visualization with tree-maps: 2-d space-filling approach (Ben Shneiderman) <http://hcil.cs.umd.edu/trs/91-03/91-03.html>
19157   
19158    Note:
19159    
19160    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.
19161
19162 */
19163 TM.Base = {
19164   layout: {
19165     orientation: "h",
19166     vertical: function(){
19167       return this.orientation == "v";
19168     },
19169     horizontal: function(){
19170       return this.orientation == "h";
19171     },
19172     change: function(){
19173       this.orientation = this.vertical()? "h" : "v";
19174     }
19175   },
19176
19177   initialize: function(controller){
19178     var config = {
19179       orientation: "h",
19180       titleHeight: 13,
19181       offset: 2,
19182       levelsToShow: 0,
19183       constrained: false,
19184       animate: false,
19185       Node: {
19186         type: 'rectangle',
19187         overridable: true,
19188         //we all know why this is not zero,
19189         //right, Firefox?
19190         width: 3,
19191         height: 3,
19192         color: '#444'
19193       },
19194       Label: {
19195         textAlign: 'center',
19196         textBaseline: 'top'
19197       },
19198       Edge: {
19199         type: 'none'
19200       },
19201       duration: 700,
19202       fps: 45
19203     };
19204
19205     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19206         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19207     this.layout.orientation = this.config.orientation;
19208
19209     var canvasConfig = this.config;
19210     if (canvasConfig.useCanvas) {
19211       this.canvas = canvasConfig.useCanvas;
19212       this.config.labelContainer = this.canvas.id + '-label';
19213     } else {
19214       if(canvasConfig.background) {
19215         canvasConfig.background = $.merge({
19216           type: 'Circles'
19217         }, canvasConfig.background);
19218       }
19219       this.canvas = new Canvas(this, canvasConfig);
19220       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19221     }
19222
19223     this.graphOptions = {
19224       'complex': true,
19225       'Node': {
19226         'selected': false,
19227         'exist': true,
19228         'drawn': true
19229       }
19230     };
19231     this.graph = new Graph(this.graphOptions, this.config.Node,
19232         this.config.Edge);
19233     this.labels = new TM.Label[canvasConfig.Label.type](this);
19234     this.fx = new TM.Plot(this);
19235     this.op = new TM.Op(this);
19236     this.group = new TM.Group(this);
19237     this.geom = new TM.Geom(this);
19238     this.clickedNode = null;
19239     this.busy = false;
19240     // initialize extras
19241     this.initializeExtras();
19242   },
19243
19244   /* 
19245     Method: refresh 
19246     
19247     Computes positions and plots the tree.
19248   */
19249   refresh: function(){
19250     if(this.busy) return;
19251     this.busy = true;
19252     var that = this;
19253     if(this.config.animate) {
19254       this.compute('end');
19255       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
19256           && this.clickedNode.id || this.root));
19257       this.fx.animate($.merge(this.config, {
19258         modes: ['linear', 'node-property:width:height'],
19259         onComplete: function() {
19260           that.busy = false;
19261         }
19262       }));
19263     } else {
19264       var labelType = this.config.Label.type;
19265       if(labelType != 'Native') {
19266         var that = this;
19267         this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
19268       }
19269       this.busy = false;
19270       this.compute();
19271       this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode 
19272           && this.clickedNode.id || this.root));
19273       this.plot();
19274     }
19275   },
19276
19277   /* 
19278     Method: plot 
19279     
19280     Plots the TreeMap. This is a shortcut to *fx.plot*. 
19281   
19282    */
19283   plot: function(){
19284     this.fx.plot();
19285   },
19286
19287   /* 
19288   Method: leaf 
19289   
19290   Returns whether the node is a leaf.
19291   
19292    Parameters:
19293    
19294    n - (object) A <Graph.Node>.
19295
19296  */
19297   leaf: function(n){
19298     return n.getSubnodes([
19299         1, 1
19300     ], "ignore").length == 0;
19301   },
19302   
19303   /* 
19304   Method: enter 
19305   
19306   Sets the node as root.
19307   
19308    Parameters:
19309    
19310    n - (object) A <Graph.Node>.
19311
19312  */
19313   enter: function(n){
19314     if(this.busy) return;
19315     this.busy = true;
19316     
19317     var that = this,
19318         config = this.config,
19319         graph = this.graph,
19320         clickedNode = n,
19321         previousClickedNode = this.clickedNode;
19322
19323     var callback = {
19324       onComplete: function() {
19325         //ensure that nodes are shown for that level
19326         if(config.levelsToShow > 0) {
19327           that.geom.setRightLevelToShow(n);
19328         }
19329         //compute positions of newly inserted nodes
19330         if(config.levelsToShow > 0 || config.request) that.compute();
19331         if(config.animate) {
19332           //fade nodes
19333           graph.nodeList.setData('alpha', 0, 'end');
19334           n.eachSubgraph(function(n) {
19335             n.setData('alpha', 1, 'end');
19336           }, "ignore");
19337           that.fx.animate({
19338             duration: 500,
19339             modes:['node-property:alpha'],
19340             onComplete: function() {
19341               //compute end positions
19342               that.clickedNode = clickedNode;
19343               that.compute('end');
19344               //animate positions
19345               //TODO(nico) commenting this line didn't seem to throw errors...
19346               that.clickedNode = previousClickedNode;
19347               that.fx.animate({
19348                 modes:['linear', 'node-property:width:height'],
19349                 duration: 1000,
19350                 onComplete: function() { 
19351                   that.busy = false;
19352                   //TODO(nico) check comment above
19353                   that.clickedNode = clickedNode;
19354                 }
19355               });
19356             }
19357           });
19358         } else {
19359           that.busy = false;
19360           that.clickedNode = n;
19361           that.refresh();
19362         }
19363       }
19364     };
19365     if(config.request) {
19366       this.requestNodes(clickedNode, callback);
19367     } else {
19368       callback.onComplete();
19369     }
19370   },
19371
19372   /* 
19373   Method: out 
19374   
19375   Sets the parent node of the current selected node as root.
19376
19377  */
19378   out: function(){
19379     if(this.busy) return;
19380     this.busy = true;
19381     this.events.hoveredNode = false;
19382     var that = this,
19383         config = this.config,
19384         graph = this.graph,
19385         parents = graph.getNode(this.clickedNode 
19386             && this.clickedNode.id || this.root).getParents(),
19387         parent = parents[0],
19388         clickedNode = parent,
19389         previousClickedNode = this.clickedNode;
19390     
19391     //if no parents return
19392     if(!parent) {
19393       this.busy = false;
19394       return;
19395     }
19396     //final plot callback
19397     callback = {
19398       onComplete: function() {
19399         that.clickedNode = parent;
19400         if(config.request) {
19401           that.requestNodes(parent, {
19402             onComplete: function() {
19403               that.compute();
19404               that.plot();
19405               that.busy = false;
19406             }
19407           });
19408         } else {
19409           that.compute();
19410           that.plot();
19411           that.busy = false;
19412         }
19413       }
19414     };
19415     //prune tree
19416     if (config.levelsToShow > 0)
19417       this.geom.setRightLevelToShow(parent);
19418     //animate node positions
19419     if(config.animate) {
19420       this.clickedNode = clickedNode;
19421       this.compute('end');
19422       //animate the visible subtree only
19423       this.clickedNode = previousClickedNode;
19424       this.fx.animate({
19425         modes:['linear', 'node-property:width:height'],
19426         duration: 1000,
19427         onComplete: function() {
19428           //animate the parent subtree
19429           that.clickedNode = clickedNode;
19430           //change nodes alpha
19431           graph.eachNode(function(n) {
19432             n.setDataset(['current', 'end'], {
19433               'alpha': [0, 1]
19434             });
19435           }, "ignore");
19436           previousClickedNode.eachSubgraph(function(node) {
19437             node.setData('alpha', 1);
19438           }, "ignore");
19439           that.fx.animate({
19440             duration: 500,
19441             modes:['node-property:alpha'],
19442             onComplete: function() {
19443               callback.onComplete();
19444             }
19445           });
19446         }
19447       });
19448     } else {
19449       callback.onComplete();
19450     }
19451   },
19452
19453   requestNodes: function(node, onComplete){
19454     var handler = $.merge(this.controller, onComplete), 
19455         lev = this.config.levelsToShow;
19456     if (handler.request) {
19457       var leaves = [], d = node._depth;
19458       node.eachLevel(0, lev, function(n){
19459         var nodeLevel = lev - (n._depth - d);
19460         if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
19461           leaves.push(n);
19462           n._level = nodeLevel;
19463         }
19464       });
19465       this.group.requestNodes(leaves, handler);
19466     } else {
19467       handler.onComplete();
19468     }
19469   }
19470 };
19471
19472 /*
19473   Class: TM.Op
19474   
19475   Custom extension of <Graph.Op>.
19476   
19477   Extends:
19478   
19479   All <Graph.Op> methods
19480   
19481   See also:
19482   
19483   <Graph.Op>
19484   
19485   */
19486 TM.Op = new Class({
19487   Implements: Graph.Op,
19488
19489   initialize: function(viz){
19490     this.viz = viz;
19491   }
19492 });
19493
19494 //extend level methods of Graph.Geom
19495 TM.Geom = new Class({
19496   Implements: Graph.Geom,
19497   
19498   getRightLevelToShow: function() {
19499     return this.viz.config.levelsToShow;
19500   },
19501   
19502   setRightLevelToShow: function(node) {
19503     var level = this.getRightLevelToShow(), 
19504         fx = this.viz.labels;
19505     node.eachLevel(0, level+1, function(n) {
19506       var d = n._depth - node._depth;
19507       if(d > level) {
19508         n.drawn = false; 
19509         n.exist = false;
19510         n.ignore = true;
19511         fx.hideLabel(n, false);
19512       } else {
19513         n.drawn = true;
19514         n.exist = true;
19515         delete n.ignore;
19516       }
19517     });
19518     node.drawn = true;
19519     delete node.ignore;
19520   }
19521 });
19522
19523 /*
19524
19525 Performs operations on group of nodes.
19526
19527 */
19528 TM.Group = new Class( {
19529
19530   initialize: function(viz){
19531     this.viz = viz;
19532     this.canvas = viz.canvas;
19533     this.config = viz.config;
19534   },
19535
19536   /*
19537   
19538     Calls the request method on the controller to request a subtree for each node. 
19539   */
19540   requestNodes: function(nodes, controller){
19541     var counter = 0, len = nodes.length, nodeSelected = {};
19542     var complete = function(){
19543       controller.onComplete();
19544     };
19545     var viz = this.viz;
19546     if (len == 0)
19547       complete();
19548     for ( var i = 0; i < len; i++) {
19549       nodeSelected[nodes[i].id] = nodes[i];
19550       controller.request(nodes[i].id, nodes[i]._level, {
19551         onComplete: function(nodeId, data){
19552           if (data && data.children) {
19553             data.id = nodeId;
19554             viz.op.sum(data, {
19555               type: 'nothing'
19556             });
19557           }
19558           if (++counter == len) {
19559             viz.graph.computeLevels(viz.root, 0);
19560             complete();
19561           }
19562         }
19563       });
19564     }
19565   }
19566 });
19567
19568 /*
19569   Class: TM.Plot
19570   
19571   Custom extension of <Graph.Plot>.
19572   
19573   Extends:
19574   
19575   All <Graph.Plot> methods
19576   
19577   See also:
19578   
19579   <Graph.Plot>
19580   
19581   */
19582 TM.Plot = new Class({
19583
19584   Implements: Graph.Plot,
19585
19586   initialize: function(viz){
19587     this.viz = viz;
19588     this.config = viz.config;
19589     this.node = this.config.Node;
19590     this.edge = this.config.Edge;
19591     this.animation = new Animation;
19592     this.nodeTypes = new TM.Plot.NodeTypes;
19593     this.edgeTypes = new TM.Plot.EdgeTypes;
19594     this.labels = viz.labels;
19595   },
19596
19597   plot: function(opt, animating){
19598     var viz = this.viz, 
19599         graph = viz.graph;
19600     viz.canvas.clear();
19601     this.plotTree(graph.getNode(viz.clickedNode && viz.clickedNode.id || viz.root), $.merge(viz.config, opt || {}, {
19602       'withLabels': true,
19603       'hideLabels': false,
19604       'plotSubtree': function(n, ch){
19605         return n.anySubnode("exist");
19606       }
19607     }), animating);
19608   }
19609 });
19610
19611 /*
19612   Class: TM.Label
19613   
19614   Custom extension of <Graph.Label>. 
19615   Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
19616
19617   Extends:
19618
19619   All <Graph.Label> methods and subclasses.
19620
19621   See also:
19622
19623   <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
19624   
19625 */
19626 TM.Label = {};
19627
19628 /*
19629  TM.Label.Native
19630
19631  Custom extension of <Graph.Label.Native>.
19632
19633  Extends:
19634
19635  All <Graph.Label.Native> methods
19636
19637  See also:
19638
19639  <Graph.Label.Native>
19640 */
19641 TM.Label.Native = new Class({
19642   Implements: Graph.Label.Native,
19643
19644   initialize: function(viz) {
19645     this.config = viz.config;
19646     this.leaf = viz.leaf;
19647   },
19648   
19649   renderLabel: function(canvas, node, controller){
19650     if(!this.leaf(node) && !this.config.titleHeight) return;
19651     var pos = node.pos.getc(true), 
19652         ctx = canvas.getCtx(),
19653         width = node.getData('width'),
19654         height = node.getData('height'),
19655         x = pos.x + width/2,
19656         y = pos.y;
19657         
19658     ctx.fillText(node.name, x, y, width);
19659   }
19660 });
19661
19662 /*
19663  TM.Label.SVG
19664
19665   Custom extension of <Graph.Label.SVG>.
19666
19667   Extends:
19668
19669   All <Graph.Label.SVG> methods
19670
19671   See also:
19672
19673   <Graph.Label.SVG>
19674 */
19675 TM.Label.SVG = new Class( {
19676   Implements: Graph.Label.SVG,
19677
19678   initialize: function(viz){
19679     this.viz = viz;
19680     this.leaf = viz.leaf;
19681     this.config = viz.config;
19682   },
19683
19684   /* 
19685   placeLabel
19686
19687   Overrides abstract method placeLabel in <Graph.Plot>.
19688
19689   Parameters:
19690
19691   tag - A DOM label element.
19692   node - A <Graph.Node>.
19693   controller - A configuration/controller object passed to the visualization.
19694   
19695   */
19696   placeLabel: function(tag, node, controller){
19697     var pos = node.pos.getc(true), 
19698         canvas = this.viz.canvas,
19699         ox = canvas.translateOffsetX,
19700         oy = canvas.translateOffsetY,
19701         sx = canvas.scaleOffsetX,
19702         sy = canvas.scaleOffsetY,
19703         radius = canvas.getSize();
19704     var labelPos = {
19705       x: Math.round(pos.x * sx + ox + radius.width / 2),
19706       y: Math.round(pos.y * sy + oy + radius.height / 2)
19707     };
19708     tag.setAttribute('x', labelPos.x);
19709     tag.setAttribute('y', labelPos.y);
19710
19711     if(!this.leaf(node) && !this.config.titleHeight) {
19712       tag.style.display = 'none';
19713     }
19714     controller.onPlaceLabel(tag, node);
19715   }
19716 });
19717
19718 /*
19719  TM.Label.HTML
19720
19721  Custom extension of <Graph.Label.HTML>.
19722
19723  Extends:
19724
19725  All <Graph.Label.HTML> methods.
19726
19727  See also:
19728
19729  <Graph.Label.HTML>
19730
19731 */
19732 TM.Label.HTML = new Class( {
19733   Implements: Graph.Label.HTML,
19734
19735   initialize: function(viz){
19736     this.viz = viz;
19737     this.leaf = viz.leaf;
19738     this.config = viz.config;
19739   },
19740
19741   /* 
19742     placeLabel
19743   
19744     Overrides abstract method placeLabel in <Graph.Plot>.
19745   
19746     Parameters:
19747   
19748     tag - A DOM label element.
19749     node - A <Graph.Node>.
19750     controller - A configuration/controller object passed to the visualization.
19751   
19752   */
19753   placeLabel: function(tag, node, controller){
19754     var pos = node.pos.getc(true), 
19755         canvas = this.viz.canvas,
19756         ox = canvas.translateOffsetX,
19757         oy = canvas.translateOffsetY,
19758         sx = canvas.scaleOffsetX,
19759         sy = canvas.scaleOffsetY,
19760         radius = canvas.getSize();
19761     var labelPos = {
19762       x: Math.round(pos.x * sx + ox + radius.width / 2),
19763       y: Math.round(pos.y * sy + oy + radius.height / 2)
19764     };
19765
19766     var style = tag.style;
19767     style.left = labelPos.x + 'px';
19768     style.top = labelPos.y + 'px';
19769     style.width = node.getData('width') * sx + 'px';
19770     style.height = node.getData('height') * sy + 'px';
19771     style.zIndex = node._depth * 100;
19772     style.display = '';
19773
19774     if(!this.leaf(node) && !this.config.titleHeight) {
19775       tag.style.display = 'none';
19776     }
19777     controller.onPlaceLabel(tag, node);
19778   }
19779 });
19780
19781 /*
19782   Class: TM.Plot.NodeTypes
19783
19784   This class contains a list of <Graph.Node> built-in types. 
19785   Node types implemented are 'none', 'rectangle'.
19786
19787   You can add your custom node types, customizing your visualization to the extreme.
19788
19789   Example:
19790
19791   (start code js)
19792     TM.Plot.NodeTypes.implement({
19793       'mySpecialType': {
19794         'render': function(node, canvas) {
19795           //print your custom node to canvas
19796         },
19797         //optional
19798         'contains': function(node, pos) {
19799           //return true if pos is inside the node or false otherwise
19800         }
19801       }
19802     });
19803   (end code)
19804
19805 */
19806 TM.Plot.NodeTypes = new Class( {
19807   'none': {
19808     'render': $.empty
19809   },
19810
19811   'rectangle': {
19812     'render': function(node, canvas, animating){
19813       var leaf = this.viz.leaf(node),
19814           config = this.config,
19815           offst = config.offset,
19816           titleHeight = config.titleHeight,
19817           pos = node.pos.getc(true),
19818           width = node.getData('width'),
19819           height = node.getData('height'),
19820           border = node.getData('border'),
19821           ctx = canvas.getCtx(),
19822           posx = pos.x + offst / 2, 
19823           posy = pos.y + offst / 2;
19824       if(width <= offst || height <= offst) return;
19825       if (leaf) {
19826         if(config.cushion) {
19827           var lg = ctx.createRadialGradient(posx + (width-offst)/2, posy + (height-offst)/2, 1, 
19828               posx + (width-offst)/2, posy + (height-offst)/2, width < height? height : width);
19829           var color = node.getData('color');
19830           var colorGrad = $.rgbToHex($.map($.hexToRgb(color), 
19831               function(r) { return r * 0.2 >> 0; }));
19832           lg.addColorStop(0, color);
19833           lg.addColorStop(1, colorGrad);
19834           ctx.fillStyle = lg;
19835         }
19836         ctx.fillRect(posx, posy, width - offst, height - offst);
19837         if(border) {
19838           ctx.save();
19839           ctx.strokeStyle = border;
19840           ctx.strokeRect(posx, posy, width - offst, height - offst);
19841           ctx.restore();
19842         }
19843       } else if(titleHeight > 0){
19844         ctx.fillRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19845             titleHeight - offst);
19846         if(border) {
19847           ctx.save();
19848           ctx.strokeStyle = border;
19849           ctx.strokeRect(pos.x + offst / 2, pos.y + offst / 2, width - offst,
19850               height - offst);
19851           ctx.restore();
19852         }
19853       }
19854     },
19855     'contains': function(node, pos) {
19856       if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
19857       var npos = node.pos.getc(true),
19858           width = node.getData('width'), 
19859           leaf = this.viz.leaf(node),
19860           height = leaf? node.getData('height') : this.config.titleHeight;
19861       return this.nodeHelper.rectangle.contains({x: npos.x + width/2, y: npos.y + height/2}, pos, width, height);
19862     }
19863   }
19864 });
19865
19866 TM.Plot.EdgeTypes = new Class( {
19867   'none': $.empty
19868 });
19869
19870 /*
19871   Class: TM.SliceAndDice
19872   
19873   A slice and dice TreeMap visualization.
19874   
19875   Implements:
19876   
19877   All <TM.Base> methods and properties.
19878 */
19879 TM.SliceAndDice = new Class( {
19880   Implements: [
19881       Loader, Extras, TM.Base, Layouts.TM.SliceAndDice
19882   ]
19883 });
19884
19885 /*
19886   Class: TM.Squarified
19887   
19888   A squarified TreeMap visualization.
19889
19890   Implements:
19891   
19892   All <TM.Base> methods and properties.
19893 */
19894 TM.Squarified = new Class( {
19895   Implements: [
19896       Loader, Extras, TM.Base, Layouts.TM.Squarified
19897   ]
19898 });
19899
19900 /*
19901   Class: TM.Strip
19902   
19903   A strip TreeMap visualization.
19904
19905   Implements:
19906   
19907   All <TM.Base> methods and properties.
19908 */
19909 TM.Strip = new Class( {
19910   Implements: [
19911       Loader, Extras, TM.Base, Layouts.TM.Strip
19912   ]
19913 });
19914
19915
19916 /*
19917  * File: RGraph.js
19918  *
19919  */
19920
19921 /*
19922    Class: RGraph
19923    
19924    A radial graph visualization with advanced animations.
19925    
19926    Inspired by:
19927  
19928    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>
19929    
19930    Note:
19931    
19932    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.
19933    
19934   Implements:
19935   
19936   All <Loader> methods
19937   
19938    Constructor Options:
19939    
19940    Inherits options from
19941    
19942    - <Options.Canvas>
19943    - <Options.Controller>
19944    - <Options.Node>
19945    - <Options.Edge>
19946    - <Options.Label>
19947    - <Options.Events>
19948    - <Options.Tips>
19949    - <Options.NodeStyles>
19950    - <Options.Navigation>
19951    
19952    Additionally, there are other parameters and some default values changed
19953    
19954    interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
19955    levelDistance - (number) Default's *100*. The distance between levels of the tree. 
19956      
19957    Instance Properties:
19958
19959    canvas - Access a <Canvas> instance.
19960    graph - Access a <Graph> instance.
19961    op - Access a <RGraph.Op> instance.
19962    fx - Access a <RGraph.Plot> instance.
19963    labels - Access a <RGraph.Label> interface implementation.   
19964 */
19965
19966 $jit.RGraph = new Class( {
19967
19968   Implements: [
19969       Loader, Extras, Layouts.Radial
19970   ],
19971
19972   initialize: function(controller){
19973     var $RGraph = $jit.RGraph;
19974
19975     var config = {
19976       interpolation: 'linear',
19977       levelDistance: 100
19978     };
19979
19980     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
19981         "Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
19982
19983     var canvasConfig = this.config;
19984     if(canvasConfig.useCanvas) {
19985       this.canvas = canvasConfig.useCanvas;
19986       this.config.labelContainer = this.canvas.id + '-label';
19987     } else {
19988       if(canvasConfig.background) {
19989         canvasConfig.background = $.merge({
19990           type: 'Circles'
19991         }, canvasConfig.background);
19992       }
19993       this.canvas = new Canvas(this, canvasConfig);
19994       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
19995     }
19996
19997     this.graphOptions = {
19998       'complex': false,
19999       'Node': {
20000         'selected': false,
20001         'exist': true,
20002         'drawn': true
20003       }
20004     };
20005     this.graph = new Graph(this.graphOptions, this.config.Node,
20006         this.config.Edge);
20007     this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
20008     this.fx = new $RGraph.Plot(this, $RGraph);
20009     this.op = new $RGraph.Op(this);
20010     this.json = null;
20011     this.root = null;
20012     this.busy = false;
20013     this.parent = false;
20014     // initialize extras
20015     this.initializeExtras();
20016   },
20017
20018   /* 
20019   
20020     createLevelDistanceFunc 
20021   
20022     Returns the levelDistance function used for calculating a node distance 
20023     to its origin. This function returns a function that is computed 
20024     per level and not per node, such that all nodes with the same depth will have the 
20025     same distance to the origin. The resulting function gets the 
20026     parent node as parameter and returns a float.
20027
20028    */
20029   createLevelDistanceFunc: function(){
20030     var ld = this.config.levelDistance;
20031     return function(elem){
20032       return (elem._depth + 1) * ld;
20033     };
20034   },
20035
20036   /* 
20037      Method: refresh 
20038      
20039      Computes positions and plots the tree.
20040
20041    */
20042   refresh: function(){
20043     this.compute();
20044     this.plot();
20045   },
20046
20047   reposition: function(){
20048     this.compute('end');
20049   },
20050
20051   /*
20052    Method: plot
20053   
20054    Plots the RGraph. This is a shortcut to *fx.plot*.
20055   */
20056   plot: function(){
20057     this.fx.plot();
20058   },
20059   /*
20060    getNodeAndParentAngle
20061   
20062    Returns the _parent_ of the given node, also calculating its angle span.
20063   */
20064   getNodeAndParentAngle: function(id){
20065     var theta = false;
20066     var n = this.graph.getNode(id);
20067     var ps = n.getParents();
20068     var p = (ps.length > 0)? ps[0] : false;
20069     if (p) {
20070       var posParent = p.pos.getc(), posChild = n.pos.getc();
20071       var newPos = posParent.add(posChild.scale(-1));
20072       theta = Math.atan2(newPos.y, newPos.x);
20073       if (theta < 0)
20074         theta += 2 * Math.PI;
20075     }
20076     return {
20077       parent: p,
20078       theta: theta
20079     };
20080   },
20081   /*
20082    tagChildren
20083   
20084    Enumerates the children in order to maintain child ordering (second constraint of the paper).
20085   */
20086   tagChildren: function(par, id){
20087     if (par.angleSpan) {
20088       var adjs = [];
20089       par.eachAdjacency(function(elem){
20090         adjs.push(elem.nodeTo);
20091       }, "ignore");
20092       var len = adjs.length;
20093       for ( var i = 0; i < len && id != adjs[i].id; i++)
20094         ;
20095       for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
20096         adjs[j].dist = k++;
20097       }
20098     }
20099   },
20100   /* 
20101   Method: onClick 
20102   
20103   Animates the <RGraph> to center the node specified by *id*.
20104
20105    Parameters:
20106
20107    id - A <Graph.Node> id.
20108    opt - (optional|object) An object containing some extra properties described below
20109    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20110
20111    Example:
20112
20113    (start code js)
20114      rgraph.onClick('someid');
20115      //or also...
20116      rgraph.onClick('someid', {
20117       hideLabels: false
20118      });
20119     (end code)
20120     
20121   */
20122   onClick: function(id, opt){
20123     if (this.root != id && !this.busy) {
20124       this.busy = true;
20125       this.root = id;
20126       that = this;
20127       this.controller.onBeforeCompute(this.graph.getNode(id));
20128       var obj = this.getNodeAndParentAngle(id);
20129
20130       // second constraint
20131       this.tagChildren(obj.parent, id);
20132       this.parent = obj.parent;
20133       this.compute('end');
20134
20135       // first constraint
20136       var thetaDiff = obj.theta - obj.parent.endPos.theta;
20137       this.graph.eachNode(function(elem){
20138         elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
20139       });
20140
20141       var mode = this.config.interpolation;
20142       opt = $.merge( {
20143         onComplete: $.empty
20144       }, opt || {});
20145
20146       this.fx.animate($.merge( {
20147         hideLabels: true,
20148         modes: [
20149           mode
20150         ]
20151       }, opt, {
20152         onComplete: function(){
20153           that.busy = false;
20154           opt.onComplete();
20155         }
20156       }));
20157     }
20158   }
20159 });
20160
20161 $jit.RGraph.$extend = true;
20162
20163 (function(RGraph){
20164
20165   /*
20166      Class: RGraph.Op
20167      
20168      Custom extension of <Graph.Op>.
20169
20170      Extends:
20171
20172      All <Graph.Op> methods
20173      
20174      See also:
20175      
20176      <Graph.Op>
20177
20178   */
20179   RGraph.Op = new Class( {
20180
20181     Implements: Graph.Op
20182
20183   });
20184
20185   /*
20186      Class: RGraph.Plot
20187     
20188     Custom extension of <Graph.Plot>.
20189   
20190     Extends:
20191   
20192     All <Graph.Plot> methods
20193     
20194     See also:
20195     
20196     <Graph.Plot>
20197   
20198   */
20199   RGraph.Plot = new Class( {
20200
20201     Implements: Graph.Plot
20202
20203   });
20204
20205   /*
20206     Object: RGraph.Label
20207
20208     Custom extension of <Graph.Label>. 
20209     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20210   
20211     Extends:
20212   
20213     All <Graph.Label> methods and subclasses.
20214   
20215     See also:
20216   
20217     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20218   
20219    */
20220   RGraph.Label = {};
20221
20222   /*
20223      RGraph.Label.Native
20224
20225      Custom extension of <Graph.Label.Native>.
20226
20227      Extends:
20228
20229      All <Graph.Label.Native> methods
20230
20231      See also:
20232
20233      <Graph.Label.Native>
20234
20235   */
20236   RGraph.Label.Native = new Class( {
20237     Implements: Graph.Label.Native
20238   });
20239
20240   /*
20241      RGraph.Label.SVG
20242     
20243     Custom extension of <Graph.Label.SVG>.
20244   
20245     Extends:
20246   
20247     All <Graph.Label.SVG> methods
20248   
20249     See also:
20250   
20251     <Graph.Label.SVG>
20252   
20253   */
20254   RGraph.Label.SVG = new Class( {
20255     Implements: Graph.Label.SVG,
20256
20257     initialize: function(viz){
20258       this.viz = viz;
20259     },
20260
20261     /* 
20262        placeLabel
20263
20264        Overrides abstract method placeLabel in <Graph.Plot>.
20265
20266        Parameters:
20267
20268        tag - A DOM label element.
20269        node - A <Graph.Node>.
20270        controller - A configuration/controller object passed to the visualization.
20271       
20272      */
20273     placeLabel: function(tag, node, controller){
20274       var pos = node.pos.getc(true), 
20275           canvas = this.viz.canvas,
20276           ox = canvas.translateOffsetX,
20277           oy = canvas.translateOffsetY,
20278           sx = canvas.scaleOffsetX,
20279           sy = canvas.scaleOffsetY,
20280           radius = canvas.getSize();
20281       var labelPos = {
20282         x: Math.round(pos.x * sx + ox + radius.width / 2),
20283         y: Math.round(pos.y * sy + oy + radius.height / 2)
20284       };
20285       tag.setAttribute('x', labelPos.x);
20286       tag.setAttribute('y', labelPos.y);
20287
20288       controller.onPlaceLabel(tag, node);
20289     }
20290   });
20291
20292   /*
20293      RGraph.Label.HTML
20294
20295      Custom extension of <Graph.Label.HTML>.
20296
20297      Extends:
20298
20299      All <Graph.Label.HTML> methods.
20300
20301      See also:
20302
20303      <Graph.Label.HTML>
20304
20305   */
20306   RGraph.Label.HTML = new Class( {
20307     Implements: Graph.Label.HTML,
20308
20309     initialize: function(viz){
20310       this.viz = viz;
20311     },
20312     /* 
20313        placeLabel
20314
20315        Overrides abstract method placeLabel in <Graph.Plot>.
20316
20317        Parameters:
20318
20319        tag - A DOM label element.
20320        node - A <Graph.Node>.
20321        controller - A configuration/controller object passed to the visualization.
20322       
20323      */
20324     placeLabel: function(tag, node, controller){
20325       var pos = node.pos.getc(true), 
20326           canvas = this.viz.canvas,
20327           ox = canvas.translateOffsetX,
20328           oy = canvas.translateOffsetY,
20329           sx = canvas.scaleOffsetX,
20330           sy = canvas.scaleOffsetY,
20331           radius = canvas.getSize();
20332       var labelPos = {
20333         x: Math.round(pos.x * sx + ox + radius.width / 2),
20334         y: Math.round(pos.y * sy + oy + radius.height / 2)
20335       };
20336
20337       var style = tag.style;
20338       style.left = labelPos.x + 'px';
20339       style.top = labelPos.y + 'px';
20340       style.display = this.fitsInCanvas(labelPos, canvas)? '' : 'none';
20341
20342       controller.onPlaceLabel(tag, node);
20343     }
20344   });
20345
20346   /*
20347     Class: RGraph.Plot.NodeTypes
20348
20349     This class contains a list of <Graph.Node> built-in types. 
20350     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
20351
20352     You can add your custom node types, customizing your visualization to the extreme.
20353
20354     Example:
20355
20356     (start code js)
20357       RGraph.Plot.NodeTypes.implement({
20358         'mySpecialType': {
20359           'render': function(node, canvas) {
20360             //print your custom node to canvas
20361           },
20362           //optional
20363           'contains': function(node, pos) {
20364             //return true if pos is inside the node or false otherwise
20365           }
20366         }
20367       });
20368     (end code)
20369
20370   */
20371   RGraph.Plot.NodeTypes = new Class({
20372     'none': {
20373       'render': $.empty,
20374       'contains': $.lambda(false)
20375     },
20376     'circle': {
20377       'render': function(node, canvas){
20378         var pos = node.pos.getc(true), 
20379             dim = node.getData('dim');
20380         this.nodeHelper.circle.render('fill', pos, dim, canvas);
20381       },
20382       'contains': function(node, pos){
20383         var npos = node.pos.getc(true), 
20384             dim = node.getData('dim');
20385         return this.nodeHelper.circle.contains(npos, pos, dim);
20386       }
20387     },
20388     'ellipse': {
20389       'render': function(node, canvas){
20390         var pos = node.pos.getc(true), 
20391             width = node.getData('width'), 
20392             height = node.getData('height');
20393         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
20394         },
20395       // TODO(nico): be more precise...
20396       'contains': function(node, pos){
20397         var npos = node.pos.getc(true), 
20398             width = node.getData('width'), 
20399             height = node.getData('height');
20400         return this.nodeHelper.ellipse.contains(npos, pos, width, height);
20401       }
20402     },
20403     'square': {
20404       'render': function(node, canvas){
20405         var pos = node.pos.getc(true), 
20406             dim = node.getData('dim');
20407         this.nodeHelper.square.render('fill', pos, dim, canvas);
20408       },
20409       'contains': function(node, pos){
20410         var npos = node.pos.getc(true), 
20411             dim = node.getData('dim');
20412         return this.nodeHelper.square.contains(npos, pos, dim);
20413       }
20414     },
20415     'rectangle': {
20416       'render': function(node, canvas){
20417         var pos = node.pos.getc(true), 
20418             width = node.getData('width'), 
20419             height = node.getData('height');
20420         this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
20421       },
20422       'contains': function(node, pos){
20423         var npos = node.pos.getc(true), 
20424             width = node.getData('width'), 
20425             height = node.getData('height');
20426         return this.nodeHelper.rectangle.contains(npos, pos, width, height);
20427       }
20428     },
20429     'triangle': {
20430       'render': function(node, canvas){
20431         var pos = node.pos.getc(true), 
20432             dim = node.getData('dim');
20433         this.nodeHelper.triangle.render('fill', pos, dim, canvas);
20434       },
20435       'contains': function(node, pos) {
20436         var npos = node.pos.getc(true), 
20437             dim = node.getData('dim');
20438         return this.nodeHelper.triangle.contains(npos, pos, dim);
20439       }
20440     },
20441     'star': {
20442       'render': function(node, canvas){
20443         var pos = node.pos.getc(true),
20444             dim = node.getData('dim');
20445         this.nodeHelper.star.render('fill', pos, dim, canvas);
20446       },
20447       'contains': function(node, pos) {
20448         var npos = node.pos.getc(true),
20449             dim = node.getData('dim');
20450         return this.nodeHelper.star.contains(npos, pos, dim);
20451       }
20452     }
20453   });
20454
20455   /*
20456     Class: RGraph.Plot.EdgeTypes
20457
20458     This class contains a list of <Graph.Adjacence> built-in types. 
20459     Edge types implemented are 'none', 'line' and 'arrow'.
20460   
20461     You can add your custom edge types, customizing your visualization to the extreme.
20462   
20463     Example:
20464   
20465     (start code js)
20466       RGraph.Plot.EdgeTypes.implement({
20467         'mySpecialType': {
20468           'render': function(adj, canvas) {
20469             //print your custom edge to canvas
20470           },
20471           //optional
20472           'contains': function(adj, pos) {
20473             //return true if pos is inside the arc or false otherwise
20474           }
20475         }
20476       });
20477     (end code)
20478   
20479   */
20480   RGraph.Plot.EdgeTypes = new Class({
20481     'none': $.empty,
20482     'line': {
20483       'render': function(adj, canvas) {
20484         var from = adj.nodeFrom.pos.getc(true),
20485             to = adj.nodeTo.pos.getc(true);
20486         this.edgeHelper.line.render(from, to, canvas);
20487       },
20488       'contains': function(adj, pos) {
20489         var from = adj.nodeFrom.pos.getc(true),
20490             to = adj.nodeTo.pos.getc(true);
20491         return this.edgeHelper.line.contains(from, to, pos, this.edge.epsilon);
20492       }
20493     },
20494     'arrow': {
20495       'render': function(adj, canvas) {
20496         var from = adj.nodeFrom.pos.getc(true),
20497             to = adj.nodeTo.pos.getc(true),
20498             dim = adj.getData('dim'),
20499             direction = adj.data.$direction,
20500             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
20501         this.edgeHelper.arrow.render(from, to, dim, inv, canvas);
20502       },
20503       'contains': function(adj, pos) {
20504         var from = adj.nodeFrom.pos.getc(true),
20505             to = adj.nodeTo.pos.getc(true);
20506         return this.edgeHelper.arrow.contains(from, to, pos, this.edge.epsilon);
20507       }
20508     }
20509   });
20510
20511 })($jit.RGraph);
20512
20513
20514 /*
20515  * File: Hypertree.js
20516  * 
20517 */
20518
20519 /* 
20520      Complex 
20521      
20522      A multi-purpose Complex Class with common methods. Extended for the Hypertree. 
20523  
20524 */
20525 /* 
20526    moebiusTransformation 
20527  
20528    Calculates a moebius transformation for this point / complex. 
20529     For more information go to: 
20530         http://en.wikipedia.org/wiki/Moebius_transformation. 
20531  
20532    Parameters: 
20533  
20534       c - An initialized Complex instance representing a translation Vector. 
20535 */
20536
20537 Complex.prototype.moebiusTransformation = function(c) {
20538   var num = this.add(c);
20539   var den = c.$conjugate().$prod(this);
20540   den.x++;
20541   return num.$div(den);
20542 };
20543
20544 /* 
20545     moebiusTransformation 
20546      
20547     Calculates a moebius transformation for the hyperbolic tree. 
20548      
20549     <http://en.wikipedia.org/wiki/Moebius_transformation> 
20550       
20551      Parameters: 
20552      
20553         graph - A <Graph> instance.
20554         pos - A <Complex>.
20555         prop - A property array.
20556         theta - Rotation angle. 
20557         startPos - _optional_ start position. 
20558 */
20559 Graph.Util.moebiusTransformation = function(graph, pos, prop, startPos, flags) {
20560   this.eachNode(graph, function(elem) {
20561     for ( var i = 0; i < prop.length; i++) {
20562       var p = pos[i].scale(-1), property = startPos ? startPos : prop[i];
20563       elem.getPos(prop[i]).set(elem.getPos(property).getc().moebiusTransformation(p));
20564     }
20565   }, flags);
20566 };
20567
20568 /* 
20569    Class: Hypertree 
20570    
20571    A Hyperbolic Tree/Graph visualization.
20572    
20573    Inspired by:
20574  
20575    A Focus+Context Technique Based on Hyperbolic Geometry for Visualizing Large Hierarchies (John Lamping, Ramana Rao, and Peter Pirolli). 
20576    <http://www.cs.tau.ac.il/~asharf/shrek/Projects/HypBrowser/startree-chi95.pdf>
20577  
20578   Note:
20579  
20580   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.
20581
20582   Implements:
20583   
20584   All <Loader> methods
20585   
20586   Constructor Options:
20587   
20588   Inherits options from
20589   
20590   - <Options.Canvas>
20591   - <Options.Controller>
20592   - <Options.Node>
20593   - <Options.Edge>
20594   - <Options.Label>
20595   - <Options.Events>
20596   - <Options.Tips>
20597   - <Options.NodeStyles>
20598   - <Options.Navigation>
20599   
20600   Additionally, there are other parameters and some default values changed
20601   
20602   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*.
20603   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.
20604   fps - Described in <Options.Fx>. It's default value has been changed to *35*.
20605   duration - Described in <Options.Fx>. It's default value has been changed to *1500*.
20606   Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*. 
20607   
20608   Instance Properties:
20609   
20610   canvas - Access a <Canvas> instance.
20611   graph - Access a <Graph> instance.
20612   op - Access a <Hypertree.Op> instance.
20613   fx - Access a <Hypertree.Plot> instance.
20614   labels - Access a <Hypertree.Label> interface implementation.
20615
20616 */
20617
20618 $jit.Hypertree = new Class( {
20619
20620   Implements: [ Loader, Extras, Layouts.Radial ],
20621
20622   initialize: function(controller) {
20623     var $Hypertree = $jit.Hypertree;
20624
20625     var config = {
20626       radius: "auto",
20627       offset: 0,
20628       Edge: {
20629         type: 'hyperline'
20630       },
20631       duration: 1500,
20632       fps: 35
20633     };
20634     this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
20635         "Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
20636
20637     var canvasConfig = this.config;
20638     if(canvasConfig.useCanvas) {
20639       this.canvas = canvasConfig.useCanvas;
20640       this.config.labelContainer = this.canvas.id + '-label';
20641     } else {
20642       if(canvasConfig.background) {
20643         canvasConfig.background = $.merge({
20644           type: 'Circles'
20645         }, canvasConfig.background);
20646       }
20647       this.canvas = new Canvas(this, canvasConfig);
20648       this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
20649     }
20650
20651     this.graphOptions = {
20652       'complex': false,
20653       'Node': {
20654         'selected': false,
20655         'exist': true,
20656         'drawn': true
20657       }
20658     };
20659     this.graph = new Graph(this.graphOptions, this.config.Node,
20660         this.config.Edge);
20661     this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
20662     this.fx = new $Hypertree.Plot(this, $Hypertree);
20663     this.op = new $Hypertree.Op(this);
20664     this.json = null;
20665     this.root = null;
20666     this.busy = false;
20667     // initialize extras
20668     this.initializeExtras();
20669   },
20670
20671   /* 
20672   
20673   createLevelDistanceFunc 
20674
20675   Returns the levelDistance function used for calculating a node distance 
20676   to its origin. This function returns a function that is computed 
20677   per level and not per node, such that all nodes with the same depth will have the 
20678   same distance to the origin. The resulting function gets the 
20679   parent node as parameter and returns a float.
20680
20681   */
20682   createLevelDistanceFunc: function() {
20683     // get max viz. length.
20684     var r = this.getRadius();
20685     // get max depth.
20686     var depth = 0, max = Math.max, config = this.config;
20687     this.graph.eachNode(function(node) {
20688       depth = max(node._depth, depth);
20689     }, "ignore");
20690     depth++;
20691     // node distance generator
20692     var genDistFunc = function(a) {
20693       return function(node) {
20694         node.scale = r;
20695         var d = node._depth + 1;
20696         var acum = 0, pow = Math.pow;
20697         while (d) {
20698           acum += pow(a, d--);
20699         }
20700         return acum - config.offset;
20701       };
20702     };
20703     // estimate better edge length.
20704     for ( var i = 0.51; i <= 1; i += 0.01) {
20705       var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
20706       if (valSeries >= 2) { return genDistFunc(i - 0.01); }
20707     }
20708     return genDistFunc(0.75);
20709   },
20710
20711   /* 
20712     Method: getRadius 
20713     
20714     Returns the current radius of the visualization. If *config.radius* is *auto* then it 
20715     calculates the radius by taking the smaller size of the <Canvas> widget.
20716     
20717     See also:
20718     
20719     <Canvas.getSize>
20720    
20721   */
20722   getRadius: function() {
20723     var rad = this.config.radius;
20724     if (rad !== "auto") { return rad; }
20725     var s = this.canvas.getSize();
20726     return Math.min(s.width, s.height) / 2;
20727   },
20728
20729   /* 
20730     Method: refresh 
20731     
20732     Computes positions and plots the tree.
20733
20734     Parameters:
20735
20736     reposition - (optional|boolean) Set this to *true* to force all positions (current, start, end) to match.
20737
20738    */
20739   refresh: function(reposition) {
20740     if (reposition) {
20741       this.reposition();
20742       this.graph.eachNode(function(node) {
20743         node.startPos.rho = node.pos.rho = node.endPos.rho;
20744         node.startPos.theta = node.pos.theta = node.endPos.theta;
20745       });
20746     } else {
20747       this.compute();
20748     }
20749     this.plot();
20750   },
20751
20752   /* 
20753    reposition 
20754    
20755    Computes nodes' positions and restores the tree to its previous position.
20756
20757    For calculating nodes' positions the root must be placed on its origin. This method does this 
20758      and then attemps to restore the hypertree to its previous position.
20759     
20760   */
20761   reposition: function() {
20762     this.compute('end');
20763     var vector = this.graph.getNode(this.root).pos.getc().scale(-1);
20764     Graph.Util.moebiusTransformation(this.graph, [ vector ], [ 'end' ],
20765         'end', "ignore");
20766     this.graph.eachNode(function(node) {
20767       if (node.ignore) {
20768         node.endPos.rho = node.pos.rho;
20769         node.endPos.theta = node.pos.theta;
20770       }
20771     });
20772   },
20773
20774   /* 
20775    Method: plot 
20776    
20777    Plots the <Hypertree>. This is a shortcut to *fx.plot*. 
20778
20779   */
20780   plot: function() {
20781     this.fx.plot();
20782   },
20783
20784   /* 
20785    Method: onClick 
20786    
20787    Animates the <Hypertree> to center the node specified by *id*.
20788
20789    Parameters:
20790
20791    id - A <Graph.Node> id.
20792    opt - (optional|object) An object containing some extra properties described below
20793    hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
20794
20795    Example:
20796
20797    (start code js)
20798      ht.onClick('someid');
20799      //or also...
20800      ht.onClick('someid', {
20801       hideLabels: false
20802      });
20803     (end code)
20804     
20805   */
20806   onClick: function(id, opt) {
20807     var pos = this.graph.getNode(id).pos.getc(true);
20808     this.move(pos, opt);
20809   },
20810
20811   /* 
20812    Method: move 
20813
20814    Translates the tree to the given position. 
20815
20816    Parameters:
20817
20818    pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
20819    opt - This object has been defined in <Hypertree.onClick>
20820    
20821    Example:
20822    
20823    (start code js)
20824      ht.move({ x: 0, y: 0.7 }, {
20825        hideLabels: false
20826      });
20827    (end code)
20828
20829   */
20830   move: function(pos, opt) {
20831     var versor = $C(pos.x, pos.y);
20832     if (this.busy === false && versor.norm() < 1) {
20833       this.busy = true;
20834       var root = this.graph.getClosestNodeToPos(versor), that = this;
20835       this.graph.computeLevels(root.id, 0);
20836       this.controller.onBeforeCompute(root);
20837       opt = $.merge( {
20838         onComplete: $.empty
20839       }, opt || {});
20840       this.fx.animate($.merge( {
20841         modes: [ 'moebius' ],
20842         hideLabels: true
20843       }, opt, {
20844         onComplete: function() {
20845           that.busy = false;
20846           opt.onComplete();
20847         }
20848       }), versor);
20849     }
20850   }
20851 });
20852
20853 $jit.Hypertree.$extend = true;
20854
20855 (function(Hypertree) {
20856
20857   /* 
20858      Class: Hypertree.Op 
20859    
20860      Custom extension of <Graph.Op>.
20861
20862      Extends:
20863
20864      All <Graph.Op> methods
20865      
20866      See also:
20867      
20868      <Graph.Op>
20869
20870   */
20871   Hypertree.Op = new Class( {
20872
20873     Implements: Graph.Op
20874
20875   });
20876
20877   /* 
20878      Class: Hypertree.Plot 
20879    
20880     Custom extension of <Graph.Plot>.
20881   
20882     Extends:
20883   
20884     All <Graph.Plot> methods
20885     
20886     See also:
20887     
20888     <Graph.Plot>
20889   
20890   */
20891   Hypertree.Plot = new Class( {
20892
20893     Implements: Graph.Plot
20894
20895   });
20896
20897   /*
20898     Object: Hypertree.Label
20899
20900     Custom extension of <Graph.Label>. 
20901     Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
20902   
20903     Extends:
20904   
20905     All <Graph.Label> methods and subclasses.
20906   
20907     See also:
20908   
20909     <Graph.Label>, <Graph.Label.Native>, <Graph.Label.HTML>, <Graph.Label.SVG>.
20910
20911    */
20912   Hypertree.Label = {};
20913
20914   /*
20915      Hypertree.Label.Native
20916
20917      Custom extension of <Graph.Label.Native>.
20918
20919      Extends:
20920
20921      All <Graph.Label.Native> methods
20922
20923      See also:
20924
20925      <Graph.Label.Native>
20926
20927   */
20928   Hypertree.Label.Native = new Class( {
20929     Implements: Graph.Label.Native,
20930
20931     initialize: function(viz) {
20932       this.viz = viz;
20933     },
20934
20935     renderLabel: function(canvas, node, controller) {
20936       var ctx = canvas.getCtx();
20937       var coord = node.pos.getc(true);
20938       var s = this.viz.getRadius();
20939       ctx.fillText(node.name, coord.x * s, coord.y * s);
20940     }
20941   });
20942
20943   /*
20944      Hypertree.Label.SVG
20945
20946     Custom extension of <Graph.Label.SVG>.
20947   
20948     Extends:
20949   
20950     All <Graph.Label.SVG> methods
20951   
20952     See also:
20953   
20954     <Graph.Label.SVG>
20955   
20956   */
20957   Hypertree.Label.SVG = new Class( {
20958     Implements: Graph.Label.SVG,
20959
20960     initialize: function(viz) {
20961       this.viz = viz;
20962     },
20963
20964     /* 
20965        placeLabel
20966
20967        Overrides abstract method placeLabel in <Graph.Plot>.
20968
20969        Parameters:
20970
20971        tag - A DOM label element.
20972        node - A <Graph.Node>.
20973        controller - A configuration/controller object passed to the visualization.
20974       
20975      */
20976     placeLabel: function(tag, node, controller) {
20977       var pos = node.pos.getc(true), 
20978           canvas = this.viz.canvas,
20979           ox = canvas.translateOffsetX,
20980           oy = canvas.translateOffsetY,
20981           sx = canvas.scaleOffsetX,
20982           sy = canvas.scaleOffsetY,
20983           radius = canvas.getSize(),
20984           r = this.viz.getRadius();
20985       var labelPos = {
20986         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
20987         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
20988       };
20989       tag.setAttribute('x', labelPos.x);
20990       tag.setAttribute('y', labelPos.y);
20991       controller.onPlaceLabel(tag, node);
20992     }
20993   });
20994
20995   /*
20996      Hypertree.Label.HTML
20997
20998      Custom extension of <Graph.Label.HTML>.
20999
21000      Extends:
21001
21002      All <Graph.Label.HTML> methods.
21003
21004      See also:
21005
21006      <Graph.Label.HTML>
21007
21008   */
21009   Hypertree.Label.HTML = new Class( {
21010     Implements: Graph.Label.HTML,
21011
21012     initialize: function(viz) {
21013       this.viz = viz;
21014     },
21015     /* 
21016        placeLabel
21017
21018        Overrides abstract method placeLabel in <Graph.Plot>.
21019
21020        Parameters:
21021
21022        tag - A DOM label element.
21023        node - A <Graph.Node>.
21024        controller - A configuration/controller object passed to the visualization.
21025       
21026      */
21027     placeLabel: function(tag, node, controller) {
21028       var pos = node.pos.getc(true), 
21029           canvas = this.viz.canvas,
21030           ox = canvas.translateOffsetX,
21031           oy = canvas.translateOffsetY,
21032           sx = canvas.scaleOffsetX,
21033           sy = canvas.scaleOffsetY,
21034           radius = canvas.getSize(),
21035           r = this.viz.getRadius();
21036       var labelPos = {
21037         x: Math.round((pos.x * sx) * r + ox + radius.width / 2),
21038         y: Math.round((pos.y * sy) * r + oy + radius.height / 2)
21039       };
21040       var style = tag.style;
21041       style.left = labelPos.x + 'px';
21042       style.top = labelPos.y + 'px';
21043       style.display = this.fitsInCanvas(labelPos, canvas) ? '' : 'none';
21044
21045       controller.onPlaceLabel(tag, node);
21046     }
21047   });
21048
21049   /*
21050     Class: Hypertree.Plot.NodeTypes
21051
21052     This class contains a list of <Graph.Node> built-in types. 
21053     Node types implemented are 'none', 'circle', 'triangle', 'rectangle', 'star', 'ellipse' and 'square'.
21054
21055     You can add your custom node types, customizing your visualization to the extreme.
21056
21057     Example:
21058
21059     (start code js)
21060       Hypertree.Plot.NodeTypes.implement({
21061         'mySpecialType': {
21062           'render': function(node, canvas) {
21063             //print your custom node to canvas
21064           },
21065           //optional
21066           'contains': function(node, pos) {
21067             //return true if pos is inside the node or false otherwise
21068           }
21069         }
21070       });
21071     (end code)
21072
21073   */
21074   Hypertree.Plot.NodeTypes = new Class({
21075     'none': {
21076       'render': $.empty,
21077       'contains': $.lambda(false)
21078     },
21079     'circle': {
21080       'render': function(node, canvas) {
21081         var nconfig = this.node,
21082             dim = node.getData('dim'),
21083             p = node.pos.getc();
21084         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21085         p.$scale(node.scale);
21086         if (dim > 0.2) {
21087           this.nodeHelper.circle.render('fill', p, dim, canvas);
21088         }
21089       },
21090       'contains': function(node, pos) {
21091         var dim = node.getData('dim'),
21092             npos = node.pos.getc().$scale(node.scale);
21093         return this.nodeHelper.circle.contains(npos, pos, dim);
21094       }
21095     },
21096     'ellipse': {
21097       'render': function(node, canvas) {
21098         var pos = node.pos.getc().$scale(node.scale),
21099             width = node.getData('width'),
21100             height = node.getData('height');
21101         this.nodeHelper.ellipse.render('fill', pos, width, height, canvas);
21102       },
21103       'contains': function(node, pos) {
21104         var width = node.getData('width'),
21105             height = node.getData('height'),
21106             npos = node.pos.getc().$scale(node.scale);
21107         return this.nodeHelper.circle.contains(npos, pos, width, height);
21108       }
21109     },
21110     'square': {
21111       'render': function(node, canvas) {
21112         var nconfig = this.node,
21113             dim = node.getData('dim'),
21114             p = node.pos.getc();
21115         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21116         p.$scale(node.scale);
21117         if (dim > 0.2) {
21118           this.nodeHelper.square.render('fill', p, dim, canvas);
21119         }
21120       },
21121       'contains': function(node, pos) {
21122         var dim = node.getData('dim'),
21123             npos = node.pos.getc().$scale(node.scale);
21124         return this.nodeHelper.square.contains(npos, pos, dim);
21125       }
21126     },
21127     'rectangle': {
21128       'render': function(node, canvas) {
21129         var nconfig = this.node,
21130             width = node.getData('width'),
21131             height = node.getData('height'),
21132             pos = node.pos.getc();
21133         width = nconfig.transform? width * (1 - pos.squaredNorm()) : width;
21134         height = nconfig.transform? height * (1 - pos.squaredNorm()) : height;
21135         pos.$scale(node.scale);
21136         if (width > 0.2 && height > 0.2) {
21137           this.nodeHelper.rectangle.render('fill', pos, width, height, canvas);
21138         }
21139       },
21140       'contains': function(node, pos) {
21141         var width = node.getData('width'),
21142             height = node.getData('height'),
21143             npos = node.pos.getc().$scale(node.scale);
21144         return this.nodeHelper.square.contains(npos, pos, width, height);
21145       }
21146     },
21147     'triangle': {
21148       'render': function(node, canvas) {
21149         var nconfig = this.node,
21150             dim = node.getData('dim'),
21151             p = node.pos.getc();
21152         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21153         p.$scale(node.scale);
21154         if (dim > 0.2) {
21155           this.nodeHelper.triangle.render('fill', p, dim, canvas);
21156         }
21157       },
21158       'contains': function(node, pos) {
21159         var dim = node.getData('dim'),
21160             npos = node.pos.getc().$scale(node.scale);
21161         return this.nodeHelper.triangle.contains(npos, pos, dim);
21162       }
21163     },
21164     'star': {
21165       'render': function(node, canvas) {
21166         var nconfig = this.node,
21167             dim = node.getData('dim'),
21168             p = node.pos.getc();
21169         dim = nconfig.transform? dim * (1 - p.squaredNorm()) : dim;
21170         p.$scale(node.scale);
21171         if (dim > 0.2) {
21172           this.nodeHelper.star.render('fill', p, dim, canvas);
21173         }
21174       },
21175       'contains': function(node, pos) {
21176         var dim = node.getData('dim'),
21177             npos = node.pos.getc().$scale(node.scale);
21178         return this.nodeHelper.star.contains(npos, pos, dim);
21179       }
21180     }
21181   });
21182
21183   /*
21184    Class: Hypertree.Plot.EdgeTypes
21185
21186     This class contains a list of <Graph.Adjacence> built-in types. 
21187     Edge types implemented are 'none', 'line', 'arrow' and 'hyperline'.
21188   
21189     You can add your custom edge types, customizing your visualization to the extreme.
21190   
21191     Example:
21192   
21193     (start code js)
21194       Hypertree.Plot.EdgeTypes.implement({
21195         'mySpecialType': {
21196           'render': function(adj, canvas) {
21197             //print your custom edge to canvas
21198           },
21199           //optional
21200           'contains': function(adj, pos) {
21201             //return true if pos is inside the arc or false otherwise
21202           }
21203         }
21204       });
21205     (end code)
21206   
21207   */
21208   Hypertree.Plot.EdgeTypes = new Class({
21209     'none': $.empty,
21210     'line': {
21211       'render': function(adj, canvas) {
21212         var from = adj.nodeFrom.pos.getc(true),
21213           to = adj.nodeTo.pos.getc(true),
21214           r = adj.nodeFrom.scale;
21215           this.edgeHelper.line.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, canvas);
21216       },
21217       'contains': function(adj, pos) {
21218         var from = adj.nodeFrom.pos.getc(true),
21219             to = adj.nodeTo.pos.getc(true),
21220             r = adj.nodeFrom.scale;
21221             this.edgeHelper.line.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21222       }
21223     },
21224     'arrow': {
21225       'render': function(adj, canvas) {
21226         var from = adj.nodeFrom.pos.getc(true),
21227             to = adj.nodeTo.pos.getc(true),
21228             r = adj.nodeFrom.scale,
21229             dim = adj.getData('dim'),
21230             direction = adj.data.$direction,
21231             inv = (direction && direction.length>1 && direction[0] != adj.nodeFrom.id);
21232         this.edgeHelper.arrow.render({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, dim, inv, canvas);
21233       },
21234       'contains': function(adj, pos) {
21235         var from = adj.nodeFrom.pos.getc(true),
21236             to = adj.nodeTo.pos.getc(true),
21237             r = adj.nodeFrom.scale;
21238         this.edgeHelper.arrow.contains({x:from.x*r, y:from.y*r}, {x:to.x*r, y:to.y*r}, pos, this.edge.epsilon);
21239       }
21240     },
21241     'hyperline': {
21242       'render': function(adj, canvas) {
21243         var from = adj.nodeFrom.pos.getc(),
21244             to = adj.nodeTo.pos.getc(),
21245             dim = this.viz.getRadius();
21246         this.edgeHelper.hyperline.render(from, to, dim, canvas);
21247       },
21248       'contains': $.lambda(false)
21249     }
21250   });
21251
21252 })($jit.Hypertree);
21253
21254
21255
21256
21257  })();